Pattern as parameter (#854)

This commit is contained in:
Marmare314 2023-04-26 11:32:11 +02:00 committed by GitHub
parent 6134e3f4ee
commit 59957746e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 36 deletions

View File

@ -276,13 +276,11 @@ pub(super) struct Closure {
#[derive(Hash)]
pub enum Param {
/// A positional parameter: `x`.
Pos(Ident),
Pos(ast::Pattern),
/// A named parameter with a default value: `draw: false`.
Named(Ident, Value),
/// An argument sink: `..args`.
Sink(Option<Ident>),
/// A placeholder: `_`.
Placeholder,
}
impl Closure {
@ -330,9 +328,18 @@ impl Closure {
let mut sink_pos_values = None;
for p in &closure.params {
match p {
Param::Pos(ident) => {
vm.define(ident.clone(), args.expect::<Value>(ident)?);
}
Param::Pos(pattern) => match pattern {
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
vm.define(ident.clone(), args.expect::<Value>(ident)?)
}
ast::Pattern::Normal(_) => unreachable!(),
_ => {
pattern.define(
&mut vm,
args.expect::<Value>("pattern parameter")?,
)?;
}
},
Param::Sink(ident) => {
sink = ident.clone();
if let Some(sink_size) = sink_size {
@ -344,9 +351,6 @@ impl Closure {
args.named::<Value>(ident)?.unwrap_or_else(|| default.clone());
vm.define(ident.clone(), value);
}
Param::Placeholder => {
args.eat::<Value>()?;
}
}
}
@ -443,12 +447,15 @@ impl<'a> CapturesVisitor<'a> {
for param in expr.params().children() {
match param {
ast::Param::Pos(ident) => self.bind(ident),
ast::Param::Pos(pattern) => {
for ident in pattern.idents() {
self.bind(ident);
}
}
ast::Param::Named(named) => self.bind(named.name()),
ast::Param::Sink(spread) => {
self.bind(spread.name().unwrap_or_default())
}
_ => {}
}
}

View File

@ -1207,14 +1207,11 @@ impl Eval for ast::Closure {
let mut params = Vec::new();
for param in self.params().children() {
match param {
ast::Param::Pos(name) => {
params.push(Param::Pos(name));
}
ast::Param::Pos(pattern) => params.push(Param::Pos(pattern)),
ast::Param::Named(named) => {
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
}
ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())),
ast::Param::Placeholder(_) => params.push(Param::Placeholder),
}
}

View File

@ -1594,23 +1594,19 @@ node! {
#[derive(Debug, Clone, Hash)]
pub enum Param {
/// A positional parameter: `x`.
Pos(Ident),
Pos(Pattern),
/// A named parameter with a default value: `draw: false`.
Named(Named),
/// An argument sink: `..args`.
Sink(Spread),
/// A placeholder: `_`.
Placeholder(Underscore),
}
impl AstNode for Param {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
SyntaxKind::Ident => node.cast().map(Self::Pos),
SyntaxKind::Named => node.cast().map(Self::Named),
SyntaxKind::Spread => node.cast().map(Self::Sink),
SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
_ => Option::None,
_ => node.cast().map(Self::Pos),
}
}
@ -1619,7 +1615,6 @@ impl AstNode for Param {
Self::Pos(v) => v.as_untyped(),
Self::Named(v) => v.as_untyped(),
Self::Sink(v) => v.as_untyped(),
Self::Placeholder(v) => v.as_untyped(),
}
}
}

View File

@ -523,7 +523,11 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
}
fn code_expr(p: &mut Parser) {
code_expr_prec(p, false, 0)
code_expr_prec(p, false, 0, false)
}
fn code_expr_or_pattern(p: &mut Parser) {
code_expr_prec(p, false, 0, true)
}
fn embedded_code_expr(p: &mut Parser) {
@ -542,7 +546,7 @@ fn embedded_code_expr(p: &mut Parser) {
);
let prev = p.prev_end();
code_expr_prec(p, true, 0);
code_expr_prec(p, true, 0, false);
// Consume error for things like `#12p` or `#"abc\"`.
if !p.progress(prev) {
@ -560,14 +564,19 @@ fn embedded_code_expr(p: &mut Parser) {
p.unstop();
}
fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
fn code_expr_prec(
p: &mut Parser,
atomic: bool,
min_prec: usize,
allow_destructuring: bool,
) {
let m = p.marker();
if let (false, Some(op)) = (atomic, ast::UnOp::from_kind(p.current())) {
p.eat();
code_expr_prec(p, atomic, op.precedence());
code_expr_prec(p, atomic, op.precedence(), false);
p.wrap(m, SyntaxKind::Unary);
} else {
code_primary(p, atomic);
code_primary(p, atomic, allow_destructuring);
}
loop {
@ -615,7 +624,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
}
p.eat();
code_expr_prec(p, false, prec);
code_expr_prec(p, false, prec, false);
p.wrap(m, SyntaxKind::Binary);
continue;
}
@ -624,7 +633,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
}
}
fn code_primary(p: &mut Parser, atomic: bool) {
fn code_primary(p: &mut Parser, atomic: bool, allow_destructuring: bool) {
let m = p.marker();
match p.current() {
SyntaxKind::Ident => {
@ -650,7 +659,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
SyntaxKind::LeftBrace => code_block(p),
SyntaxKind::LeftBracket => content_block(p),
SyntaxKind::LeftParen => with_paren(p),
SyntaxKind::LeftParen => with_paren(p, allow_destructuring),
SyntaxKind::Dollar => equation(p),
SyntaxKind::Let => let_binding(p),
SyntaxKind::Set => set_rule(p),
@ -716,7 +725,7 @@ fn content_block(p: &mut Parser) {
p.wrap(m, SyntaxKind::ContentBlock);
}
fn with_paren(p: &mut Parser) {
fn with_paren(p: &mut Parser, allow_destructuring: bool) {
let m = p.marker();
let mut kind = collection(p, true);
if p.at(SyntaxKind::Arrow) {
@ -739,11 +748,37 @@ fn with_paren(p: &mut Parser) {
SyntaxKind::Array => validate_array(p, m),
SyntaxKind::Dict => validate_dict(p, m),
SyntaxKind::Parenthesized => validate_parenthesized(p, m),
SyntaxKind::Destructuring if !allow_destructuring => {
invalidate_destructuring(p, m)
}
_ => {}
}
p.wrap(m, kind);
}
fn invalidate_destructuring(p: &mut Parser, m: Marker) {
let mut collection_kind = Option::None;
for child in p.post_process(m) {
match child.kind() {
SyntaxKind::Named | SyntaxKind::Keyed => match collection_kind {
Some(SyntaxKind::Array) => child.convert_to_error(eco_format!(
"expected expression, found {}",
child.kind().name()
)),
_ => collection_kind = Some(SyntaxKind::Dict),
},
SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}
kind => match collection_kind {
Some(SyntaxKind::Dict) => child.convert_to_error(eco_format!(
"expected named or keyed pair, found {}",
kind.name()
)),
_ => collection_kind = Some(SyntaxKind::Array),
},
}
}
}
fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind {
p.stop_at_newline(false);
p.assert(SyntaxKind::LeftParen);
@ -760,10 +795,17 @@ fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind {
let prev = p.prev_end();
match item(p, keyed) {
SyntaxKind::Spread => parenthesized = false,
SyntaxKind::Named | SyntaxKind::Keyed if kind.is_none() => {
kind = Some(SyntaxKind::Dict);
SyntaxKind::Named | SyntaxKind::Keyed => {
match kind {
Some(SyntaxKind::Array) => kind = Some(SyntaxKind::Destructuring),
_ => kind = Some(SyntaxKind::Dict),
}
parenthesized = false;
}
SyntaxKind::Int => match kind {
Some(SyntaxKind::Array) | None => kind = Some(SyntaxKind::Array),
Some(_) => kind = Some(SyntaxKind::Destructuring),
},
_ if kind.is_none() => kind = Some(SyntaxKind::Array),
_ => {}
}
@ -809,7 +851,7 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind {
}
if !p.eat_if(SyntaxKind::Underscore) {
code_expr(p);
code_expr_or_pattern(p);
} else {
return SyntaxKind::Underscore;
}
@ -1161,6 +1203,10 @@ fn validate_params(p: &mut Parser, m: Marker) {
child.make_erroneous();
}
}
SyntaxKind::Array | SyntaxKind::Dict | SyntaxKind::Destructuring => {
validate_pattern(child.children_mut().iter_mut(), &mut used, false);
child.convert_to_kind(SyntaxKind::Destructuring);
}
SyntaxKind::LeftParen
| SyntaxKind::RightParen
| SyntaxKind::Comma
@ -1194,9 +1240,17 @@ fn validate_args(p: &mut Parser, m: Marker) {
}
fn validate_destruct_pattern(p: &mut Parser, m: Marker, forbid_expressions: bool) {
let mut used_spread = false;
let mut used = HashSet::new();
for child in p.post_process(m) {
validate_pattern(p.post_process(m), &mut used, forbid_expressions);
}
fn validate_pattern<'a>(
children: impl Iterator<Item = &'a mut SyntaxNode>,
used: &mut HashSet<EcoString>,
forbid_expressions: bool,
) {
let mut used_spread = false;
for child in children {
match child.kind() {
SyntaxKind::Ident => {
if !used.insert(child.text().clone()) {

View File

@ -145,6 +145,29 @@
test(greet("Typst", whatever: 10))
}
---
// Parameter unpacking.
#let f((a, b), ..c) = (a, b, c)
#test(f((1, 2), 3, 4), (1, 2, (3, 4)))
#let f((k: a, b), c: 3, (d,)) = (a, b, c, d)
#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4))
// Error: 22-23 duplicate parameter: a
#let f((a: b), (c,), a) = none
// Error: 8-14 expected identifier, found array
#let f((a, b): 0) = none
// Error: 10-19 expected identifier, found destructuring pattern
#let f(..(a, b: c)) = none
// Error: 10-16 expected identifier, found array
#let f(..(a, b)) = none
// Error: 10-19 expected identifier, found destructuring pattern
#let f(..(a, b: c)) = none
---
// Error: 11-12 duplicate parameter: x
#let f(x, x) = none