summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarmare314 <49279081+Marmare314@users.noreply.github.com>2023-04-20 11:05:11 +0200
committerGitHub <noreply@github.com>2023-04-20 11:05:11 +0200
commit4524539c2bc5f3a9f53bc57a1902264fc894969b (patch)
tree578dcc6b82b0d40d88eb5d6a6f07cbdc38ef10d0
parentc505a0f5dccd120d97926f6ff5bbe0becf783aeb (diff)
forbid underscore as identifier closes #513 (#837)
-rw-r--r--src/eval/func.rs5
-rw-r--r--src/eval/mod.rs4
-rw-r--r--src/syntax/ast.rs17
-rw-r--r--src/syntax/lexer.rs6
-rw-r--r--src/syntax/parser.rs72
-rw-r--r--tests/typ/compiler/closure.typ4
-rw-r--r--tests/typ/compiler/let.typ20
7 files changed, 114 insertions, 14 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs
index faf2d696..29b85f7a 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -274,6 +274,8 @@ pub enum Param {
Named(Ident, Value),
/// An argument sink: `..args`.
Sink(Option<Ident>),
+ /// A placeholder: `_`.
+ Placeholder,
}
impl Closure {
@@ -334,6 +336,9 @@ impl Closure {
args.named::<Value>(ident)?.unwrap_or_else(|| default.clone());
vm.define(ident.clone(), value);
}
+ Param::Placeholder => {
+ args.eat::<Value>()?;
+ }
}
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 797e1fef..0a963faf 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -1160,6 +1160,7 @@ impl Eval for ast::Closure {
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),
}
}
@@ -1184,6 +1185,7 @@ impl ast::Pattern {
vm.define(ident.clone(), value);
Ok(Value::None)
}
+ ast::Pattern::Placeholder => Ok(Value::None),
ast::Pattern::Destructuring(destruct) => {
match value {
Value::Array(value) => {
@@ -1215,6 +1217,7 @@ impl ast::Pattern {
"cannot destructure named elements from an array"
)
}
+ ast::DestructuringKind::Placeholder => i += 1,
}
}
if i < value.len() {
@@ -1243,6 +1246,7 @@ impl ast::Pattern {
vm.define(ident.clone(), v.clone());
used.insert(key.clone().take());
}
+ ast::DestructuringKind::Placeholder => {}
}
}
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index caf5319f..f22508e8 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -1568,6 +1568,8 @@ pub enum Param {
Named(Named),
/// An argument sink: `..args`.
Sink(Option<Ident>),
+ /// A placeholder: `_`.
+ Placeholder,
}
impl AstNode for Param {
@@ -1576,6 +1578,7 @@ impl AstNode for Param {
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),
_ => Option::None,
}
}
@@ -1585,6 +1588,7 @@ impl AstNode for Param {
Self::Pos(v) => v.as_untyped(),
Self::Named(v) => v.as_untyped(),
Self::Sink(_) => self.as_untyped(),
+ Self::Placeholder => self.as_untyped(),
}
}
}
@@ -1603,6 +1607,8 @@ pub enum DestructuringKind {
Sink(Option<Ident>),
/// Named arguments: `x: 1`.
Named(Ident, Ident),
+ /// A placeholder: `_`.
+ Placeholder,
}
impl Destructuring {
@@ -1619,6 +1625,7 @@ impl Destructuring {
let ident = filtered.next().unwrap_or_default();
Some(DestructuringKind::Named(key, ident))
}
+ SyntaxKind::Underscore => Some(DestructuringKind::Placeholder),
_ => Option::None,
})
}
@@ -1629,6 +1636,7 @@ impl Destructuring {
DestructuringKind::Ident(ident) => Some(ident),
DestructuringKind::Sink(ident) => ident,
DestructuringKind::Named(_, ident) => Some(ident),
+ DestructuringKind::Placeholder => Option::None,
})
}
}
@@ -1638,6 +1646,8 @@ impl Destructuring {
pub enum Pattern {
/// A single identifier: `x`.
Ident(Ident),
+ /// A placeholder: `_`.
+ Placeholder,
/// A destructuring pattern: `(x, _, ..y)`.
Destructuring(Destructuring),
}
@@ -1647,6 +1657,7 @@ impl AstNode for Pattern {
match node.kind() {
SyntaxKind::Ident => node.cast().map(Self::Ident),
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
+ SyntaxKind::Underscore => Some(Self::Placeholder),
_ => Option::None,
}
}
@@ -1655,6 +1666,7 @@ impl AstNode for Pattern {
match self {
Self::Ident(v) => v.as_untyped(),
Self::Destructuring(v) => v.as_untyped(),
+ Self::Placeholder => self.as_untyped(),
}
}
}
@@ -1665,6 +1677,7 @@ impl Pattern {
match self {
Pattern::Ident(ident) => vec![ident.clone()],
Pattern::Destructuring(destruct) => destruct.idents().collect(),
+ Pattern::Placeholder => vec![],
}
}
}
@@ -1722,9 +1735,7 @@ impl LetBinding {
LetBindingKind::Normal(Pattern::Ident(_)) => {
self.0.children().filter_map(SyntaxNode::cast).nth(1)
}
- LetBindingKind::Normal(Pattern::Destructuring(_)) => {
- self.0.cast_first_match()
- }
+ LetBindingKind::Normal(_) => self.0.cast_first_match(),
LetBindingKind::Closure(_) => self.0.cast_first_match(),
}
}
diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs
index dcd2509a..dc75a902 100644
--- a/src/syntax/lexer.rs
+++ b/src/syntax/lexer.rs
@@ -527,7 +527,11 @@ impl Lexer<'_> {
}
}
- SyntaxKind::Ident
+ if ident == "_" {
+ SyntaxKind::Underscore
+ } else {
+ SyntaxKind::Ident
+ }
}
fn number(&mut self, mut start: usize, c: char) -> SyntaxKind {
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 801fdfc8..389cf026 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -636,6 +636,17 @@ fn code_primary(p: &mut Parser, atomic: bool) {
p.wrap(m, SyntaxKind::Closure);
}
}
+ SyntaxKind::Underscore if !atomic => {
+ p.eat();
+ if p.at(SyntaxKind::Arrow) {
+ p.wrap(m, SyntaxKind::Params);
+ p.eat();
+ code_expr(p);
+ p.wrap(m, SyntaxKind::Closure);
+ } else if let Some(underscore) = p.node_mut(m) {
+ underscore.convert_to_error("expected expression, found underscore");
+ }
+ }
SyntaxKind::LeftBrace => code_block(p),
SyntaxKind::LeftBracket => content_block(p),
@@ -718,6 +729,7 @@ fn with_paren(p: &mut Parser) {
match kind {
SyntaxKind::Array => validate_array(p, m),
SyntaxKind::Dict => validate_dict(p, m),
+ SyntaxKind::Parenthesized => validate_parenthesized(p, m),
_ => {}
}
p.wrap(m, kind);
@@ -787,13 +799,19 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind {
return SyntaxKind::Spread;
}
- code_expr(p);
+ if !p.eat_if(SyntaxKind::Underscore) {
+ code_expr(p);
+ } else {
+ return SyntaxKind::Underscore;
+ }
if !p.eat_if(SyntaxKind::Colon) {
return SyntaxKind::Int;
}
- code_expr(p);
+ if !p.eat_if(SyntaxKind::Underscore) {
+ code_expr(p);
+ }
let kind = match p.node(m).map(SyntaxNode::kind) {
Some(SyntaxKind::Ident) => SyntaxKind::Named,
@@ -840,12 +858,12 @@ fn args(p: &mut Parser) {
enum PatternKind {
Ident,
+ Placeholder,
Destructuring,
}
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);
@@ -856,6 +874,8 @@ fn pattern(p: &mut Parser) -> PatternKind {
p.wrap(m, SyntaxKind::Destructuring);
PatternKind::Destructuring
}
+ } else if p.eat_if(SyntaxKind::Underscore) {
+ PatternKind::Placeholder
} else {
p.expect(SyntaxKind::Ident);
PatternKind::Ident
@@ -879,6 +899,7 @@ fn let_binding(p: &mut Parser) {
p.wrap(m3, SyntaxKind::Params);
}
}
+ PatternKind::Placeholder => {}
PatternKind::Destructuring => destructuring = true,
}
@@ -961,7 +982,9 @@ fn for_loop(p: &mut Parser) {
pattern(p);
if p.at(SyntaxKind::Comma) {
p.expected("keyword `in`. did you mean to use a destructuring pattern?");
- p.eat_if(SyntaxKind::Ident);
+ if !p.eat_if(SyntaxKind::Ident) {
+ p.eat_if(SyntaxKind::Underscore);
+ }
p.eat_if(SyntaxKind::In);
} else {
p.expect(SyntaxKind::In);
@@ -1023,10 +1046,25 @@ fn return_stmt(p: &mut Parser) {
p.wrap(m, SyntaxKind::FuncReturn);
}
+fn validate_parenthesized(p: &mut Parser, m: Marker) {
+ for child in p.post_process(m) {
+ let kind = child.kind();
+ if kind == SyntaxKind::Underscore {
+ child.convert_to_error(eco_format!(
+ "expected expression, found {}",
+ kind.name()
+ ));
+ }
+ }
+}
+
fn validate_array(p: &mut Parser, m: Marker) {
for child in p.post_process(m) {
let kind = child.kind();
- if kind == SyntaxKind::Named || kind == SyntaxKind::Keyed {
+ if kind == SyntaxKind::Named
+ || kind == SyntaxKind::Keyed
+ || kind == SyntaxKind::Underscore
+ {
child.convert_to_error(eco_format!(
"expected expression, found {}",
kind.name()
@@ -1114,7 +1152,10 @@ fn validate_params(p: &mut Parser, m: Marker) {
child.make_erroneous();
}
}
- SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}
+ SyntaxKind::LeftParen
+ | SyntaxKind::RightParen
+ | SyntaxKind::Comma
+ | SyntaxKind::Underscore => {}
kind => {
child.convert_to_error(eco_format!(
"expected identifier, named pair or argument sink, found {}",
@@ -1137,6 +1178,8 @@ fn validate_args(p: &mut Parser, m: Marker) {
));
child.make_erroneous();
}
+ } else if child.kind() == SyntaxKind::Underscore {
+ child.convert_to_error("unexpected underscore");
}
}
}
@@ -1147,7 +1190,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
for child in p.post_process(m) {
match child.kind() {
SyntaxKind::Ident => {
- if child.text() != "_" && !used.insert(child.text().clone()) {
+ if !used.insert(child.text().clone()) {
child.convert_to_error(
"at most one binding per identifier is allowed",
);
@@ -1172,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
continue;
}
- if within.text() != "_" && !used.insert(within.text().clone()) {
+ if !used.insert(within.text().clone()) {
within.convert_to_error(
"at most one binding per identifier is allowed",
);
@@ -1189,7 +1232,9 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
}
let Some(within) = child.children_mut().last_mut() else { return };
- if within.kind() != SyntaxKind::Ident {
+ if within.kind() != SyntaxKind::Ident
+ && within.kind() != SyntaxKind::Underscore
+ {
within.convert_to_error(eco_format!(
"expected identifier, found {}",
within.kind().name(),
@@ -1197,7 +1242,10 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
child.make_erroneous();
}
}
- SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}
+ SyntaxKind::LeftParen
+ | SyntaxKind::RightParen
+ | SyntaxKind::Comma
+ | SyntaxKind::Underscore => {}
kind => {
child.convert_to_error(eco_format!(
"expected identifier or destructuring sink, found {}",
@@ -1313,6 +1361,10 @@ impl<'s> Parser<'s> {
self.nodes.get(m.0)
}
+ fn node_mut(&mut self, m: Marker) -> Option<&mut SyntaxNode> {
+ self.nodes.get_mut(m.0)
+ }
+
fn post_process(&mut self, m: Marker) -> impl Iterator<Item = &mut SyntaxNode> {
self.nodes[m.0..]
.iter_mut()
diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ
index 915de850..2d12a2bc 100644
--- a/tests/typ/compiler/closure.typ
+++ b/tests/typ/compiler/closure.typ
@@ -170,3 +170,7 @@
---
// Error: 10-14 expected identifier, found `none`
#let foo(none: b) = key
+
+---
+// Error: 11 expected comma
+#let foo(_: 3) = none
diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ
index 70657617..160d2147 100644
--- a/tests/typ/compiler/let.typ
+++ b/tests/typ/compiler/let.typ
@@ -222,6 +222,26 @@ Three
#let (..true) = false
---
+#let _ = 4
+
+#for _ in range(2) []
+
+// Error: 2-3 unexpected underscore
+#_
+
+// Error: 8-9 unexpected underscore
+#lorem(_)
+
+// Error: 3-4 expected expression, found underscore
+#(_,)
+
+// Error: 3-4 expected expression, found underscore
+#{_}
+
+// Error: 8-9 expected expression, found underscore
+#{ 1 + _ }
+
+---
// Error: 13 expected equals sign
#let func(x)