summaryrefslogtreecommitdiff
path: root/src/syntax/parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax/parser.rs')
-rw-r--r--src/syntax/parser.rs136
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,