diff options
Diffstat (limited to 'src/syntax/parser.rs')
| -rw-r--r-- | src/syntax/parser.rs | 136 |
1 files changed, 116 insertions, 20 deletions
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, |
