summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-02-27 11:43:02 +0100
committerGitHub <noreply@github.com>2024-02-27 10:43:02 +0000
commit9646a132a80d11b37649b82c419833003ac7f455 (patch)
tree82324da4b63921be724969998c0249eca20cff34
parent145723b1ef4fa23f1f6665b8907dfe79d0bf83cf (diff)
Fix parser bug with space before colon (#3504)
-rw-r--r--crates/typst-syntax/src/parser.rs111
-rw-r--r--tests/typ/bugs/3502-colon-space.typ14
2 files changed, 71 insertions, 54 deletions
diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs
index 567bcbd1..d37b552f 100644
--- a/crates/typst-syntax/src/parser.rs
+++ b/crates/typst-syntax/src/parser.rs
@@ -183,7 +183,8 @@ fn heading(p: &mut Parser) {
p.assert(SyntaxKind::HeadingMarker);
whitespace_line(p);
markup(p, false, usize::MAX, |p| {
- p.at_set(END) && (!p.at(SyntaxKind::Space) || p.peek() == SyntaxKind::Label)
+ p.at_set(END)
+ && (!p.at(SyntaxKind::Space) || p.lexer.clone().next() == SyntaxKind::Label)
});
p.wrap(m, SyntaxKind::Heading);
}
@@ -666,7 +667,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
}
let at_field_or_method =
- p.directly_at(SyntaxKind::Dot) && p.peek() == SyntaxKind::Ident;
+ p.directly_at(SyntaxKind::Dot) && p.lexer.clone().next() == SyntaxKind::Ident;
if atomic && !at_field_or_method {
break;
@@ -1225,30 +1226,32 @@ fn args(p: &mut Parser) {
/// Parses a single argument in an argument list.
fn arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) {
let m = p.marker();
+
+ // Parses a spreaded argument: `..args`.
if p.eat_if(SyntaxKind::Dots) {
- // Parses a spreaded argument: `..args`.
code_expr(p);
p.wrap(m, SyntaxKind::Spread);
- } else if p.at(SyntaxKind::Ident) && p.peek() == SyntaxKind::Colon {
- // Parses a named argument: `thickness: 12pt`.
- let text = p.current_text();
- p.assert(SyntaxKind::Ident);
- if !seen.insert(text) {
- p[m].convert_to_error(eco_format!("duplicate argument: {text}"));
- }
- p.assert(SyntaxKind::Colon);
- code_expr(p);
- p.wrap(m, SyntaxKind::Named);
- } else {
- // Parses a normal positional argument.
- let at_expr = p.at_set(set::CODE_EXPR);
- code_expr(p);
+ return;
+ }
- // Recover from bad named pair.
- if at_expr && p.eat_if(SyntaxKind::Colon) {
- p[m].expected("identifier");
- code_expr(p);
+ // Parses a normal positional argument or an argument name.
+ let was_at_expr = p.at_set(set::CODE_EXPR);
+ let text = p.current_text();
+ code_expr(p);
+
+ // Parses a named argument: `thickness: 12pt`.
+ if p.eat_if(SyntaxKind::Colon) {
+ // Recover from bad argument name.
+ if was_at_expr {
+ if p[m].kind() != SyntaxKind::Ident {
+ p[m].expected("identifier");
+ } else if !seen.insert(text) {
+ p[m].convert_to_error(eco_format!("duplicate argument: {text}"));
+ }
}
+
+ code_expr(p);
+ p.wrap(m, SyntaxKind::Named);
}
}
@@ -1282,8 +1285,9 @@ fn params(p: &mut Parser) {
/// Parses a single parameter in a parameter list.
fn param<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>, sink: &mut bool) {
let m = p.marker();
+
+ // Parses argument sink: `..sink`.
if p.eat_if(SyntaxKind::Dots) {
- // Parses argument sink: `..sink`.
if p.at_set(set::PATTERN_LEAF) {
pattern_leaf(p, false, seen, Some("parameter"));
}
@@ -1291,24 +1295,22 @@ fn param<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>, sink: &mut bool) {
if mem::replace(sink, true) {
p[m].convert_to_error("only one argument sink is allowed");
}
- } else if p.at(SyntaxKind::Ident) && p.peek() == SyntaxKind::Colon {
- // Parses named parameter: `thickness: 3pt`.
- // We still use `pattern` even though we know it's just an identifier
- // because it gives us duplicate parameter detection for free.
- pattern(p, false, seen, Some("parameter"));
- p.assert(SyntaxKind::Colon);
- code_expr(p);
- p.wrap(m, SyntaxKind::Named);
- } else {
- // Parses a normal position parameter.
- let at_pat = p.at_set(set::PATTERN);
- pattern(p, false, seen, Some("parameter"));
+ return;
+ }
- // Recover from bad named pair.
- if at_pat && p.eat_if(SyntaxKind::Colon) {
+ // Parses a normal positional parameter or a parameter name.
+ let was_at_pat = p.at_set(set::PATTERN);
+ pattern(p, false, seen, Some("parameter"));
+
+ // Parses a named parameter: `thickness: 12pt`.
+ if p.eat_if(SyntaxKind::Colon) {
+ // Recover from bad parameter name.
+ if was_at_pat && p[m].kind() != SyntaxKind::Ident {
p[m].expected("identifier");
- code_expr(p);
}
+
+ code_expr(p);
+ p.wrap(m, SyntaxKind::Named);
}
}
@@ -1373,8 +1375,9 @@ fn destructuring_item<'s>(
sink: &mut bool,
) {
let m = p.marker();
+
+ // Parse destructuring sink: `..rest`.
if p.eat_if(SyntaxKind::Dots) {
- // Parse destructuring sink: `..rest`.
if p.at_set(set::PATTERN_LEAF) {
pattern_leaf(p, reassignment, seen, None);
}
@@ -1382,23 +1385,27 @@ fn destructuring_item<'s>(
if mem::replace(sink, true) {
p[m].convert_to_error("only one destructuring sink is allowed");
}
- } else if p.at(SyntaxKind::Ident) && p.peek() == SyntaxKind::Colon {
- // Parse named destructuring item.
- p.assert(SyntaxKind::Ident);
- p.assert(SyntaxKind::Colon);
- pattern(p, reassignment, seen, None);
- p.wrap(m, SyntaxKind::Named);
- *maybe_just_parens = false;
- } else {
- // Parse positional destructuring item.
- let at_pat = p.at_set(set::PATTERN);
+ return;
+ }
+
+ // Parse a normal positional pattern or a destructuring key.
+ let was_at_pat = p.at_set(set::PATTERN);
+ let checkpoint = p.checkpoint();
+ if !(p.eat_if(SyntaxKind::Ident) && p.at(SyntaxKind::Colon)) {
+ p.restore(checkpoint);
pattern(p, reassignment, seen, None);
+ }
+ // Parse named destructuring item.
+ if p.eat_if(SyntaxKind::Colon) {
// Recover from bad named destructuring.
- if at_pat && p.eat_if(SyntaxKind::Colon) {
+ if was_at_pat && p[m].kind() != SyntaxKind::Ident {
p[m].expected("identifier");
- pattern(p, reassignment, seen, None);
}
+
+ pattern(p, reassignment, seen, None);
+ p.wrap(m, SyntaxKind::Named);
+ *maybe_just_parens = false;
}
}
@@ -1532,10 +1539,6 @@ impl<'s> Parser<'s> {
set.contains(self.current)
}
- fn peek(&self) -> SyntaxKind {
- self.lexer.clone().next()
- }
-
fn eof(&self) -> bool {
self.at(SyntaxKind::Eof)
}
diff --git a/tests/typ/bugs/3502-colon-space.typ b/tests/typ/bugs/3502-colon-space.typ
new file mode 100644
index 00000000..35f38a9b
--- /dev/null
+++ b/tests/typ/bugs/3502-colon-space.typ
@@ -0,0 +1,14 @@
+// Test that a space after a named parameter is permissible.
+// https://github.com/typst/typst/issues/3502
+// Ref: false
+
+---
+#let f( param : v ) = param
+#test(f( param /* ok */ : 2 ), 2)
+
+---
+#let ( key : /* hi */ binding ) = ( key: "ok" )
+#test(binding, "ok")
+
+---
+#test(( key : "value" ).key, "value")