From 0f8219b392e96d3cf7d784ee5d474274169d9918 Mon Sep 17 00:00:00 2001 From: Marmare314 <49279081+Marmare314@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:26:09 +0200 Subject: Unpacking syntax (#532) Closes #341 --- src/syntax/ast.rs | 147 ++++++++++++++++++++++++++++++++++++++------------- src/syntax/kind.rs | 3 ++ src/syntax/parser.rs | 136 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 229 insertions(+), 57 deletions(-) (limited to 'src/syntax') diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 780c6164..94114958 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1533,7 +1533,10 @@ impl Closure { /// /// This only exists if you use the function syntax sugar: `let f(x) = y`. pub fn name(&self) -> Option { - self.0.children().next()?.cast() + match self.0.cast_first_match::()?.kind() { + PatternKind::Ident(ident) => Some(ident), + _ => Option::None, + } } /// The parameter bindings. @@ -1589,29 +1592,122 @@ impl AstNode for Param { } } +node! { + /// A destructuring pattern: `x` or `(x, _, ..y)`. + Pattern +} + +/// The kind of a pattern. +#[derive(Debug, Clone, Hash)] +pub enum PatternKind { + /// A single identifier: `x`. + Ident(Ident), + /// A destructuring pattern: `(x, _, ..y)`. + Destructure(Vec), +} + +/// The kind of an element in a destructuring pattern. +#[derive(Debug, Clone, Hash)] +pub enum DestructuringKind { + /// An identifier: `x`. + Ident(Ident), + /// An argument sink: `..y`. + Sink(Option), + /// Named arguments: `x: 1`. + Named(Ident, Ident), +} + +impl Pattern { + /// The kind of the pattern. + pub fn kind(&self) -> PatternKind { + if self.0.children().len() <= 1 { + return PatternKind::Ident(self.0.cast_first_match().unwrap_or_default()); + } + + let mut bindings = Vec::new(); + for child in self.0.children() { + match child.kind() { + SyntaxKind::Ident => { + bindings + .push(DestructuringKind::Ident(child.cast().unwrap_or_default())); + } + SyntaxKind::Spread => { + bindings.push(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(); + bindings.push(DestructuringKind::Named(key, ident)); + } + _ => (), + } + } + + PatternKind::Destructure(bindings) + } + + // Returns a list of all identifiers in the pattern. + pub fn idents(&self) -> Vec { + match self.kind() { + PatternKind::Ident(ident) => vec![ident], + PatternKind::Destructure(bindings) => bindings + .into_iter() + .filter_map(|binding| match binding { + DestructuringKind::Ident(ident) => Some(ident), + DestructuringKind::Sink(ident) => ident, + DestructuringKind::Named(_, ident) => Some(ident), + }) + .collect(), + } + } +} + node! { /// A let binding: `let x = 1`. LetBinding } +pub enum LetBindingKind { + /// A normal binding: `let x = 1`. + Normal(Pattern), + /// A closure binding: `let f(x) = 1`. + Closure(Ident), +} + +impl LetBindingKind { + // Returns a list of all identifiers in the pattern. + pub fn idents(&self) -> Vec { + match self { + LetBindingKind::Normal(pattern) => pattern.idents(), + LetBindingKind::Closure(ident) => { + vec![ident.clone()] + } + } + } +} + impl LetBinding { - /// The binding to assign to. - pub fn binding(&self) -> Ident { - match self.0.cast_first_match() { - Some(Expr::Ident(binding)) => binding, - Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(), - _ => Ident::default(), + /// The kind of the let binding. + pub fn kind(&self) -> LetBindingKind { + if let Some(pattern) = self.0.cast_first_match::() { + LetBindingKind::Normal(pattern) + } else { + LetBindingKind::Closure( + self.0 + .cast_first_match::() + .unwrap_or_default() + .name() + .unwrap_or_default(), + ) } } /// The expression the binding is initialized with. pub fn init(&self) -> Option { - if self.0.cast_first_match::().is_some() { - // This is a normal binding like `let x = 1`. - self.0.children().filter_map(SyntaxNode::cast).nth(1) - } else { - // This is a closure binding like `let f(x) = 1`. - self.0.cast_first_match() + match self.kind() { + LetBindingKind::Normal(_) => self.0.cast_last_match(), + LetBindingKind::Closure(_) => self.0.cast_first_match(), } } } @@ -1712,7 +1808,7 @@ node! { impl ForLoop { /// The pattern to assign to. - pub fn pattern(&self) -> ForPattern { + pub fn pattern(&self) -> Pattern { self.0.cast_first_match().unwrap_or_default() } @@ -1727,29 +1823,6 @@ impl ForLoop { } } -node! { - /// A for loop's destructuring pattern: `x` or `x, y`. - ForPattern -} - -impl ForPattern { - /// The key part of the pattern: index for arrays, name for dictionaries. - pub fn key(&self) -> Option { - let mut children = self.0.children().filter_map(SyntaxNode::cast); - let key = children.next(); - if children.next().is_some() { - key - } else { - Option::None - } - } - - /// The value part of the pattern. - pub fn value(&self) -> Ident { - self.0.cast_last_match().unwrap_or_default() - } -} - node! { /// A module import: `import "utils.typ": a, b, c`. ModuleImport diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index b0d934d1..fcde2bb4 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -244,6 +244,8 @@ pub enum SyntaxKind { LoopContinue, /// A return from a function: `return`, `return x + 1`. FuncReturn, + /// A destructuring pattern: `x`, `(x, _, ..y)`. + Pattern, /// A line comment: `// ...`. LineComment, @@ -430,6 +432,7 @@ impl SyntaxKind { Self::LoopBreak => "`break` expression", Self::LoopContinue => "`continue` expression", Self::FuncReturn => "`return` expression", + Self::Pattern => "destructuring pattern", 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 e95da4af..83d9ae46 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -777,6 +777,11 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind { let m = p.marker(); if p.eat_if(SyntaxKind::Dots) { + if p.at(SyntaxKind::Comma) || p.at(SyntaxKind::RightParen) { + p.wrap(m, SyntaxKind::Spread); + return SyntaxKind::Spread; + } + code_expr(p); p.wrap(m, SyntaxKind::Spread); return SyntaxKind::Spread; @@ -833,22 +838,56 @@ fn args(p: &mut Parser) { p.wrap(m, SyntaxKind::Args); } +enum PatternKind { + Normal, + Destructuring, +} + +fn pattern(p: &mut Parser) -> PatternKind { + let m = p.marker(); + + if p.at(SyntaxKind::LeftParen) { + collection(p, false); + validate_destruct_pattern(p, m); + p.wrap(m, SyntaxKind::Pattern); + + PatternKind::Destructuring + } else { + let success = p.expect(SyntaxKind::Ident); + if p.at(SyntaxKind::Comma) { + // TODO: this should only be a warning instead + p.expected("keyword `in`. did you mean to use a destructuring pattern?"); + } + + if success { + p.wrap(m, SyntaxKind::Pattern); + } + + PatternKind::Normal + } +} + fn let_binding(p: &mut Parser) { let m = p.marker(); p.assert(SyntaxKind::Let); let m2 = p.marker(); - p.expect(SyntaxKind::Ident); - - let closure = p.directly_at(SyntaxKind::LeftParen); - if closure { - let m3 = p.marker(); - collection(p, false); - validate_params(p, m3); - p.wrap(m3, SyntaxKind::Params); + let mut closure = false; + let mut destructuring = false; + match pattern(p) { + PatternKind::Normal => { + closure = p.directly_at(SyntaxKind::LeftParen); + if closure { + let m3 = p.marker(); + collection(p, false); + validate_params(p, m3); + p.wrap(m3, SyntaxKind::Params); + } + } + PatternKind::Destructuring => destructuring = true, } - let f = if closure { Parser::expect } else { Parser::eat_if }; + let f = if closure || destructuring { Parser::expect } else { Parser::eat_if }; if f(p, SyntaxKind::Eq) { code_expr(p); } @@ -924,23 +963,13 @@ fn while_loop(p: &mut Parser) { fn for_loop(p: &mut Parser) { let m = p.marker(); p.assert(SyntaxKind::For); - for_pattern(p); + pattern(p); p.expect(SyntaxKind::In); code_expr(p); block(p); p.wrap(m, SyntaxKind::ForLoop); } -fn for_pattern(p: &mut Parser) { - let m = p.marker(); - if p.expect(SyntaxKind::Ident) { - if p.eat_if(SyntaxKind::Comma) { - p.expect(SyntaxKind::Ident); - } - p.wrap(m, SyntaxKind::ForPattern); - } -} - fn module_import(p: &mut Parser) { let m = p.marker(); p.assert(SyntaxKind::Import); @@ -1086,6 +1115,73 @@ fn validate_args(p: &mut Parser, m: Marker) { } } +fn validate_destruct_pattern(p: &mut Parser, m: Marker) { + let mut used_spread = false; + let mut used = HashSet::new(); + for child in p.post_process(m) { + match child.kind() { + SyntaxKind::Ident => { + if child.text() != "_" && !used.insert(child.text().clone()) { + child.convert_to_error( + "at most one binding per identifier is allowed", + ); + } + } + SyntaxKind::Spread => { + let Some(within) = child.children_mut().last_mut() else { continue }; + if used_spread { + child.convert_to_error("at most one destructuring sink is allowed"); + continue; + } + used_spread = true; + + if within.kind() == SyntaxKind::Dots { + continue; + } else if within.kind() != SyntaxKind::Ident { + within.convert_to_error(eco_format!( + "expected identifier, found {}", + within.kind().name(), + )); + child.make_erroneous(); + continue; + } + + if within.text() != "_" && !used.insert(within.text().clone()) { + within.convert_to_error( + "at most one binding per identifier is allowed", + ); + child.make_erroneous(); + } + } + SyntaxKind::Named => { + let Some(within) = child.children_mut().first_mut() else { return }; + if !used.insert(within.text().clone()) { + within.convert_to_error( + "at most one binding per identifier is allowed", + ); + child.make_erroneous(); + } + + let Some(within) = child.children_mut().last_mut() else { return }; + if within.kind() != SyntaxKind::Ident { + within.convert_to_error(eco_format!( + "expected identifier, found {}", + within.kind().name(), + )); + child.make_erroneous(); + } + } + SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {} + kind => { + child.convert_to_error(eco_format!( + "expected identifier or destructuring sink, found {}", + kind.name() + )); + } + } + } +} + /// Manages parsing of a stream of tokens. struct Parser<'s> { text: &'s str, -- cgit v1.2.3