summaryrefslogtreecommitdiff
path: root/src/syntax
diff options
context:
space:
mode:
authorMarmare314 <49279081+Marmare314@users.noreply.github.com>2023-04-06 15:26:09 +0200
committerGitHub <noreply@github.com>2023-04-06 15:26:09 +0200
commit0f8219b392e96d3cf7d784ee5d474274169d9918 (patch)
tree60ce53bb076b61b09184f4f1aefa2fa63ebb3ed2 /src/syntax
parenta73149767c82509b77ccf6996ab0b1125cc9c249 (diff)
Unpacking syntax (#532)
Closes #341
Diffstat (limited to 'src/syntax')
-rw-r--r--src/syntax/ast.rs147
-rw-r--r--src/syntax/kind.rs3
-rw-r--r--src/syntax/parser.rs136
3 files changed, 229 insertions, 57 deletions
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<Ident> {
- self.0.children().next()?.cast()
+ match self.0.cast_first_match::<Pattern>()?.kind() {
+ PatternKind::Ident(ident) => Some(ident),
+ _ => Option::None,
+ }
}
/// The parameter bindings.
@@ -1590,28 +1593,121 @@ 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<DestructuringKind>),
+}
+
+/// 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<Ident>),
+ /// 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<Ident> {
+ 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<Ident> {
+ 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::<Pattern>() {
+ LetBindingKind::Normal(pattern)
+ } else {
+ LetBindingKind::Closure(
+ self.0
+ .cast_first_match::<Closure>()
+ .unwrap_or_default()
+ .name()
+ .unwrap_or_default(),
+ )
}
}
/// The expression the binding is initialized with.
pub fn init(&self) -> Option<Expr> {
- if self.0.cast_first_match::<Ident>().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()
}
@@ -1728,29 +1824,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<Ident> {
- 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,