diff options
| author | Marmare314 <49279081+Marmare314@users.noreply.github.com> | 2023-04-25 11:22:12 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-25 11:22:12 +0200 |
| commit | d5d98b67a83944d72a5c0f8e8e2f43aeee667122 (patch) | |
| tree | 11580441926d1c24c6243868232b44b844663c4b /src | |
| parent | efad1e71fa699e0d2413d3a6a3ce5a4163e38112 (diff) | |
Destructuring assign (#703)
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/func.rs | 4 | ||||
| -rw-r--r-- | src/eval/mod.rs | 223 | ||||
| -rw-r--r-- | src/ide/highlight.rs | 1 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 165 | ||||
| -rw-r--r-- | src/syntax/kind.rs | 3 | ||||
| -rw-r--r-- | src/syntax/parser.rs | 45 |
6 files changed, 282 insertions, 159 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs index 489527ef..e3b6a99c 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -445,7 +445,9 @@ impl<'a> CapturesVisitor<'a> { match param { ast::Param::Pos(ident) => self.bind(ident), ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(Some(ident)) => self.bind(ident), + ast::Param::Sink(spread) => { + self.bind(spread.name().unwrap_or_default()) + } _ => {} } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 850a3d32..d8f49d66 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -456,6 +456,7 @@ impl Eval for ast::Expr { Self::Unary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm), Self::Let(v) => v.eval(vm), + Self::DestructAssign(v) => v.eval(vm), Self::Set(_) => bail!(forbidden("set")), Self::Show(_) => bail!(forbidden("show")), Self::Conditional(v) => v.eval(vm), @@ -1212,8 +1213,8 @@ impl Eval for ast::Closure { ast::Param::Named(named) => { params.push(Param::Named(named.name(), named.expr().eval(vm)?)); } - ast::Param::Sink(name) => params.push(Param::Sink(name)), - ast::Param::Placeholder => params.push(Param::Placeholder), + ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())), + ast::Param::Placeholder(_) => params.push(Param::Placeholder), } } @@ -1231,98 +1232,142 @@ impl Eval for ast::Closure { } impl ast::Pattern { - // Destruct the given value into the pattern. - #[tracing::instrument(skip_all)] - pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { - match self { - ast::Pattern::Ident(ident) => { - vm.define(ident.clone(), value); - Ok(Value::None) - } - ast::Pattern::Placeholder => Ok(Value::None), - ast::Pattern::Destructuring(destruct) => { - match value { - Value::Array(value) => { - let mut i = 0; - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Ident(ident) => { - let Ok(v) = value.at(i) else { - bail!(ident.span(), "not enough elements to destructure"); - }; - vm.define(ident.clone(), v.clone()); - i += 1; - } - ast::DestructuringKind::Sink(ident) => { - (1 + value.len() as usize).checked_sub(destruct.bindings().count()).and_then(|sink_size| { - let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else { - return None; - }; - if let Some(ident) = ident { - vm.define(ident, sink); - } - i += sink_size as i64; - Some(()) - }).ok_or("not enough elements to destructure").at(self.span())?; - } - ast::DestructuringKind::Named(key, _) => { - bail!( - key.span(), - "cannot destructure named elements from an array" - ) - } - ast::DestructuringKind::Placeholder => i += 1, - } - } - if i < value.len() { - bail!(self.span(), "too many elements to destructure"); + fn destruct_array<T>( + &self, + vm: &mut Vm, + value: Array, + f: T, + destruct: &ast::Destructuring, + ) -> SourceResult<Value> + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, + { + let mut i = 0; + for p in destruct.bindings() { + match p { + ast::DestructuringKind::Normal(expr) => { + let Ok(v) = value.at(i) else { + bail!(expr.span(), "not enough elements to destructure"); + }; + f(vm, expr, v.clone())?; + i += 1; + } + ast::DestructuringKind::Sink(spread) => { + let sink_size = (1 + value.len() as usize) + .checked_sub(destruct.bindings().count()); + let sink = + sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok()); + + if let (Some(sink_size), Some(sink)) = (sink_size, sink) { + if let Some(expr) = spread.expr() { + f(vm, expr, Value::Array(sink.clone()))?; } + i += sink_size as i64; + } else { + bail!(self.span(), "not enough elements to destructure") } - Value::Dict(value) => { - let mut sink = None; - let mut used = HashSet::new(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Ident(ident) => { - let Ok(v) = value.at(&ident) else { + } + ast::DestructuringKind::Named(named) => { + bail!(named.span(), "cannot destructure named elements from an array") + } + ast::DestructuringKind::Placeholder(_) => i += 1, + } + } + if i < value.len() { + bail!(self.span(), "too many elements to destructure"); + } + + Ok(Value::None) + } + + fn destruct_dict<T>( + &self, + vm: &mut Vm, + value: Dict, + f: T, + destruct: &ast::Destructuring, + ) -> SourceResult<Value> + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, + { + let mut sink = None; + let mut used = HashSet::new(); + for p in destruct.bindings() { + match p { + ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => { + let Ok(v) = value.at(&ident) else { bail!(ident.span(), "destructuring key not found in dictionary"); }; - vm.define(ident.clone(), v.clone()); - used.insert(ident.clone().take()); - } - ast::DestructuringKind::Sink(ident) => { - sink = ident.clone() - } - ast::DestructuringKind::Named(key, ident) => { - let Ok(v) = value.at(&key) else { - bail!(ident.span(), "destructuring key not found in dictionary"); + f(vm, ast::Expr::Ident(ident.clone()), v.clone())?; + used.insert(ident.take()); + } + ast::DestructuringKind::Sink(spread) => sink = spread.expr(), + ast::DestructuringKind::Named(named) => { + let Ok(v) = value.at(named.name().as_str()) else { + bail!(named.name().span(), "destructuring key not found in dictionary"); }; - vm.define(ident.clone(), v.clone()); - used.insert(key.clone().take()); - } - ast::DestructuringKind::Placeholder => {} - } - } + f(vm, named.expr(), v.clone())?; + used.insert(named.name().take()); + } + ast::DestructuringKind::Placeholder(_) => {} + ast::DestructuringKind::Normal(expr) => { + bail!(expr.span(), "expected key, found expression"); + } + } + } - if let Some(ident) = sink { - let mut sink = Dict::new(); - for (key, value) in value { - if !used.contains(key.as_str()) { - sink.insert(key, value); - } - } - vm.define(ident, Value::Dict(sink)); - } - } - _ => { - bail!(self.span(), "cannot destructure {}", value.type_name()); - } + if let Some(expr) = sink { + let mut sink = Dict::new(); + for (key, value) in value { + if !used.contains(key.as_str()) { + sink.insert(key, value); } + } + f(vm, expr, Value::Dict(sink))?; + } + + Ok(Value::None) + } + /// Destruct the given value into the pattern and apply the function to each binding. + #[tracing::instrument(skip_all)] + fn apply<T>(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult<Value> + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>, + { + match self { + ast::Pattern::Normal(expr) => { + f(vm, expr.clone(), value)?; Ok(Value::None) } + ast::Pattern::Placeholder(_) => Ok(Value::None), + ast::Pattern::Destructuring(destruct) => match value { + Value::Array(value) => self.destruct_array(vm, value, f, destruct), + Value::Dict(value) => self.destruct_dict(vm, value, f, destruct), + _ => bail!(self.span(), "cannot destructure {}", value.type_name()), + }, } } + + /// Destruct the value into the pattern by binding. + pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { + self.apply(vm, value, |vm, expr, value| match expr { + ast::Expr::Ident(ident) => { + vm.define(ident, value); + Ok(Value::None) + } + _ => unreachable!(), + }) + } + + /// Destruct the value into the pattern by assignment. + pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> { + self.apply(vm, value, |vm, expr, value| { + let location = expr.access(vm)?; + *location = value; + Ok(Value::None) + }) + } } impl Eval for ast::LetBinding { @@ -1345,6 +1390,16 @@ impl Eval for ast::LetBinding { } } +impl Eval for ast::DestructAssignment { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + let value = self.value().eval(vm)?; + self.pattern().assign(vm, value)?; + Ok(Value::None) + } +} + impl Eval for ast::SetRule { type Output = Styles; @@ -1514,7 +1569,7 @@ impl Eval for ast::ForLoop { let pattern = self.pattern(); match (&pattern, iter.clone()) { - (ast::Pattern::Ident(_), Value::Str(string)) => { + (ast::Pattern::Normal(_), Value::Str(string)) => { // Iterate over graphemes of string. iter!(for pattern in string.as_str().graphemes(true)); } @@ -1526,7 +1581,7 @@ impl Eval for ast::ForLoop { // Iterate over values of array. iter!(for pattern in array); } - (ast::Pattern::Ident(_), _) => { + (ast::Pattern::Normal(_), _) => { bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); } (_, _) => { diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index 259d34c3..b7b063a6 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -246,6 +246,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> { SyntaxKind::LoopContinue => None, SyntaxKind::FuncReturn => None, SyntaxKind::Destructuring => None, + SyntaxKind::DestructAssignment => None, SyntaxKind::LineComment => Some(Tag::Comment), SyntaxKind::BlockComment => Some(Tag::Comment), diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index f22508e8..1fa6c89f 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -164,6 +164,8 @@ pub enum Expr { Closure(Closure), /// A let binding: `let x = 1`. Let(LetBinding), + //// A destructuring assignment: `(x, y) = (1, 2)`. + DestructAssign(DestructAssignment), /// A set rule: `set text(...)`. Set(SetRule), /// A show rule: `show heading: it => emph(it.body)`. @@ -240,6 +242,7 @@ impl AstNode for Expr { SyntaxKind::FuncCall => node.cast().map(Self::FuncCall), SyntaxKind::Closure => node.cast().map(Self::Closure), SyntaxKind::LetBinding => node.cast().map(Self::Let), + SyntaxKind::DestructAssignment => node.cast().map(Self::DestructAssign), SyntaxKind::SetRule => node.cast().map(Self::Set), SyntaxKind::ShowRule => node.cast().map(Self::Show), SyntaxKind::Conditional => node.cast().map(Self::Conditional), @@ -299,6 +302,7 @@ impl AstNode for Expr { Self::FuncCall(v) => v.as_untyped(), Self::Closure(v) => v.as_untyped(), Self::Let(v) => v.as_untyped(), + Self::DestructAssign(v) => v.as_untyped(), Self::Set(v) => v.as_untyped(), Self::Show(v) => v.as_untyped(), Self::Conditional(v) => v.as_untyped(), @@ -1179,6 +1183,11 @@ impl Named { pub fn expr(&self) -> Expr { self.0.cast_last_match().unwrap_or_default() } + + /// The right-hand side of the pair as an identifier. + pub fn expr_ident(&self) -> Option<Ident> { + self.0.cast_last_match() + } } node! { @@ -1559,6 +1568,28 @@ impl Params { } } +node! { + /// A spread: `..x` or `..x.at(0)`. + Spread +} + +impl Spread { + /// Try to get an identifier. + pub fn name(&self) -> Option<Ident> { + self.0.cast_first_match() + } + + /// Try to get an expression. + pub fn expr(&self) -> Option<Expr> { + self.0.cast_first_match() + } +} + +node! { + /// An underscore: `_` + Underscore +} + /// A parameter to a closure. #[derive(Debug, Clone, Hash)] pub enum Param { @@ -1567,9 +1598,9 @@ pub enum Param { /// A named parameter with a default value: `draw: false`. Named(Named), /// An argument sink: `..args`. - Sink(Option<Ident>), + Sink(Spread), /// A placeholder: `_`. - Placeholder, + Placeholder(Underscore), } impl AstNode for Param { @@ -1577,8 +1608,8 @@ impl AstNode for Param { match node.kind() { SyntaxKind::Ident => node.cast().map(Self::Pos), SyntaxKind::Named => node.cast().map(Self::Named), - SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())), - SyntaxKind::Underscore => Some(Self::Placeholder), + SyntaxKind::Spread => node.cast().map(Self::Sink), + SyntaxKind::Underscore => node.cast().map(Self::Placeholder), _ => Option::None, } } @@ -1587,8 +1618,8 @@ impl AstNode for Param { match self { Self::Pos(v) => v.as_untyped(), Self::Named(v) => v.as_untyped(), - Self::Sink(_) => self.as_untyped(), - Self::Placeholder => self.as_untyped(), + Self::Sink(v) => v.as_untyped(), + Self::Placeholder(v) => v.as_untyped(), } } } @@ -1598,56 +1629,63 @@ node! { Destructuring } +impl Destructuring { + /// The bindings of the destructuring. + pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ { + self.0.children().filter_map(SyntaxNode::cast) + } + + // Returns a list of all identifiers in the pattern. + pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ { + self.bindings().filter_map(|binding| match binding { + DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident), + DestructuringKind::Sink(spread) => spread.name(), + DestructuringKind::Named(named) => named.expr_ident(), + _ => Option::None, + }) + } +} + /// The kind of an element in a destructuring pattern. #[derive(Debug, Clone, Hash)] pub enum DestructuringKind { - /// An identifier: `x`. - Ident(Ident), + /// An expression: `x`. + Normal(Expr), /// An argument sink: `..y`. - Sink(Option<Ident>), + Sink(Spread), /// Named arguments: `x: 1`. - Named(Ident, Ident), + Named(Named), /// A placeholder: `_`. - Placeholder, + Placeholder(Underscore), } -impl Destructuring { - /// The bindings of the destructuring. - pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ { - self.0.children().filter_map(|child| match child.kind() { - SyntaxKind::Ident => { - Some(DestructuringKind::Ident(child.cast().unwrap_or_default())) - } - SyntaxKind::Spread => Some(DestructuringKind::Sink(child.cast_first_match())), - SyntaxKind::Named => { - let mut filtered = child.children().filter_map(SyntaxNode::cast); - let key = filtered.next().unwrap_or_default(); - let ident = filtered.next().unwrap_or_default(); - Some(DestructuringKind::Named(key, ident)) - } - SyntaxKind::Underscore => Some(DestructuringKind::Placeholder), - _ => Option::None, - }) +impl AstNode for DestructuringKind { + fn from_untyped(node: &SyntaxNode) -> Option<Self> { + match node.kind() { + SyntaxKind::Named => node.cast().map(Self::Named), + SyntaxKind::Spread => node.cast().map(Self::Sink), + SyntaxKind::Underscore => node.cast().map(Self::Placeholder), + _ => node.cast().map(Self::Normal), + } } - // Returns a list of all identifiers in the pattern. - pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ { - self.bindings().filter_map(|binding| match binding { - DestructuringKind::Ident(ident) => Some(ident), - DestructuringKind::Sink(ident) => ident, - DestructuringKind::Named(_, ident) => Some(ident), - DestructuringKind::Placeholder => Option::None, - }) + fn as_untyped(&self) -> &SyntaxNode { + match self { + Self::Normal(v) => v.as_untyped(), + Self::Named(v) => v.as_untyped(), + Self::Sink(v) => v.as_untyped(), + Self::Placeholder(v) => v.as_untyped(), + } } } /// The kind of a pattern. #[derive(Debug, Clone, Hash)] pub enum Pattern { - /// A single identifier: `x`. - Ident(Ident), + /// A single expression: `x`. + Normal(Expr), /// A placeholder: `_`. - Placeholder, + Placeholder(Underscore), /// A destructuring pattern: `(x, _, ..y)`. Destructuring(Destructuring), } @@ -1655,18 +1693,17 @@ pub enum Pattern { impl AstNode for Pattern { fn from_untyped(node: &SyntaxNode) -> Option<Self> { match node.kind() { - SyntaxKind::Ident => node.cast().map(Self::Ident), SyntaxKind::Destructuring => node.cast().map(Self::Destructuring), - SyntaxKind::Underscore => Some(Self::Placeholder), - _ => Option::None, + SyntaxKind::Underscore => node.cast().map(Self::Placeholder), + _ => node.cast().map(Self::Normal), } } fn as_untyped(&self) -> &SyntaxNode { match self { - Self::Ident(v) => v.as_untyped(), + Self::Normal(v) => v.as_untyped(), Self::Destructuring(v) => v.as_untyped(), - Self::Placeholder => self.as_untyped(), + Self::Placeholder(v) => v.as_untyped(), } } } @@ -1675,16 +1712,16 @@ impl Pattern { // Returns a list of all identifiers in the pattern. pub fn idents(&self) -> Vec<Ident> { match self { - Pattern::Ident(ident) => vec![ident.clone()], + Pattern::Normal(Expr::Ident(ident)) => vec![ident.clone()], Pattern::Destructuring(destruct) => destruct.idents().collect(), - Pattern::Placeholder => vec![], + _ => vec![], } } } impl Default for Pattern { fn default() -> Self { - Self::Ident(Ident::default()) + Self::Normal(Expr::default()) } } @@ -1716,23 +1753,18 @@ impl LetBindingKind { impl LetBinding { /// The kind of the let binding. pub fn kind(&self) -> LetBindingKind { - if let Some(pattern) = self.0.cast_first_match::<Pattern>() { - LetBindingKind::Normal(pattern) - } else { - LetBindingKind::Closure( - self.0 - .cast_first_match::<Closure>() - .unwrap_or_default() - .name() - .unwrap_or_default(), - ) + match self.0.cast_first_match::<Pattern>() { + Some(Pattern::Normal(Expr::Closure(closure))) => { + LetBindingKind::Closure(closure.name().unwrap_or_default()) + } + pattern => LetBindingKind::Normal(pattern.unwrap_or_default()), } } /// The expression the binding is initialized with. pub fn init(&self) -> Option<Expr> { match self.kind() { - LetBindingKind::Normal(Pattern::Ident(_)) => { + LetBindingKind::Normal(Pattern::Normal(_)) => { self.0.children().filter_map(SyntaxNode::cast).nth(1) } LetBindingKind::Normal(_) => self.0.cast_first_match(), @@ -1742,6 +1774,23 @@ impl LetBinding { } node! { + /// An assignment expression `(x, y) = (1, 2)`. + DestructAssignment +} + +impl DestructAssignment { + /// The pattern of the assignment. + pub fn pattern(&self) -> Pattern { + self.0.cast_first_match::<Pattern>().unwrap_or_default() + } + + /// The expression that is assigned. + pub fn value(&self) -> Expr { + self.0.cast_last_match().unwrap_or_default() + } +} + +node! { /// A set rule: `set text(...)`. SetRule } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index d35901b0..0717e16c 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -244,6 +244,8 @@ pub enum SyntaxKind { FuncReturn, /// A destructuring pattern: `(x, _, ..y)`. Destructuring, + /// A destructuring assignment expression: `(x, y) = (1, 2)`. + DestructAssignment, /// A line comment: `// ...`. LineComment, @@ -430,6 +432,7 @@ impl SyntaxKind { Self::LoopContinue => "`continue` expression", Self::FuncReturn => "`return` expression", Self::Destructuring => "destructuring pattern", + Self::DestructAssignment => "destructuring assignment expression", Self::LineComment => "line comment", Self::BlockComment => "block comment", Self::Error => "syntax error", diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 389cf026..0bbf77e2 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -725,7 +725,16 @@ fn with_paren(p: &mut Parser) { p.assert(SyntaxKind::Arrow); code_expr(p); kind = SyntaxKind::Closure; + } else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized { + // TODO: add warning if p.at(SyntaxKind::Eq) && kind == SyntaxKind::Parenthesized + + validate_destruct_pattern(p, m, false); + p.wrap(m, SyntaxKind::Destructuring); + p.assert(SyntaxKind::Eq); + code_expr(p); + kind = SyntaxKind::DestructAssignment; } + match kind { SyntaxKind::Array => validate_array(p, m), SyntaxKind::Dict => validate_dict(p, m), @@ -866,7 +875,7 @@ fn pattern(p: &mut Parser) -> PatternKind { let m = p.marker(); if p.at(SyntaxKind::LeftParen) { let kind = collection(p, false); - validate_destruct_pattern(p, m); + validate_destruct_pattern(p, m, true); if kind == SyntaxKind::Parenthesized { PatternKind::Ident @@ -1184,7 +1193,7 @@ fn validate_args(p: &mut Parser, m: Marker) { } } -fn validate_destruct_pattern(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) { @@ -1206,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { if within.kind() == SyntaxKind::Dots { continue; - } else if within.kind() != SyntaxKind::Ident { + } else if forbid_expressions && within.kind() != SyntaxKind::Ident { within.convert_to_error(eco_format!( "expected identifier, found {}", within.kind().name(), @@ -1231,15 +1240,17 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { child.make_erroneous(); } - let Some(within) = child.children_mut().last_mut() else { return }; - if within.kind() != SyntaxKind::Ident - && within.kind() != SyntaxKind::Underscore - { - within.convert_to_error(eco_format!( - "expected identifier, found {}", - within.kind().name(), - )); - child.make_erroneous(); + if forbid_expressions { + let Some(within) = child.children_mut().last_mut() else { return }; + if within.kind() != SyntaxKind::Ident + && within.kind() != SyntaxKind::Underscore + { + within.convert_to_error(eco_format!( + "expected identifier, found {}", + within.kind().name(), + )); + child.make_erroneous(); + } } } SyntaxKind::LeftParen @@ -1247,10 +1258,12 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { | SyntaxKind::Comma | SyntaxKind::Underscore => {} kind => { - child.convert_to_error(eco_format!( - "expected identifier or destructuring sink, found {}", - kind.name() - )); + if forbid_expressions { + child.convert_to_error(eco_format!( + "expected identifier or destructuring sink, found {}", + kind.name() + )); + } } } } |
