summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-06 15:14:14 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-06 15:16:41 +0200
commitadcc6e5506eb1888c8eaeea167f0058d70220cab (patch)
tree6da5476f83b48edf9abb03d4cfb8a43224ed2e59
parent46a6f92bf341268a1369a6034f1bc32408aedd9e (diff)
Better error messages for keywords in place of identifiers
Fixes #1123
-rw-r--r--crates/typst/src/syntax/kind.rs26
-rw-r--r--crates/typst/src/syntax/parser.rs66
-rw-r--r--tests/typ/compiler/embedded-expr.typ12
3 files changed, 84 insertions, 20 deletions
diff --git a/crates/typst/src/syntax/kind.rs b/crates/typst/src/syntax/kind.rs
index 26e949ca..0c24c667 100644
--- a/crates/typst/src/syntax/kind.rs
+++ b/crates/typst/src/syntax/kind.rs
@@ -304,6 +304,32 @@ impl SyntaxKind {
)
}
+ /// Is this node is a keyword.
+ pub fn is_keyword(self) -> bool {
+ matches!(
+ self,
+ Self::Not
+ | Self::And
+ | Self::Or
+ | Self::None
+ | Self::Auto
+ | Self::Let
+ | Self::Set
+ | Self::Show
+ | Self::If
+ | Self::Else
+ | Self::For
+ | Self::In
+ | Self::While
+ | Self::Break
+ | Self::Continue
+ | Self::Return
+ | Self::Import
+ | Self::Include
+ | Self::As
+ )
+ }
+
/// Whether this kind of node is automatically skipped by the parser in
/// code and math mode.
pub fn is_trivia(self) -> bool {
diff --git a/crates/typst/src/syntax/parser.rs b/crates/typst/src/syntax/parser.rs
index 4849ff79..be09be01 100644
--- a/crates/typst/src/syntax/parser.rs
+++ b/crates/typst/src/syntax/parser.rs
@@ -1583,11 +1583,16 @@ impl<'s> Parser<'s> {
self.current = SyntaxKind::Eof;
}
}
+}
+impl<'s> Parser<'s> {
+ /// Consume the given syntax `kind` or produce an error.
fn expect(&mut self, kind: SyntaxKind) -> bool {
let at = self.at(kind);
if at {
self.eat();
+ } else if kind == SyntaxKind::Ident && self.current.is_keyword() {
+ self.expected_found(kind.name(), self.current.name());
} else {
self.balanced &= !kind.is_grouping();
self.expected(kind.name());
@@ -1595,59 +1600,80 @@ impl<'s> Parser<'s> {
at
}
- fn expect_closing_delimiter(&mut self, open: Marker, kind: SyntaxKind) {
- if !self.eat_if(kind) {
- self.nodes[open.0].convert_to_error("unclosed delimiter");
- }
- }
-
+ /// Produce an error that the given `thing` was expected.
fn expected(&mut self, thing: &str) {
self.unskip();
- if self
- .nodes
- .last()
- .map_or(true, |child| child.kind() != SyntaxKind::Error)
- {
- let message = eco_format!("expected {}", thing);
+ if !self.after_error() {
+ let message = eco_format!("expected {thing}");
self.nodes.push(SyntaxNode::error(message, ""));
}
self.skip();
}
- // Adds a hint to the last node, if the last node is an error.
- fn hint(&mut self, hint: impl Into<EcoString>) {
+ /// Produce an error that the given `thing` was expected but another
+ /// thing was `found` and consumethe next token.
+ fn expected_found(&mut self, thing: &str, found: &str) {
self.unskip();
- if let Some(last) = self.nodes.last_mut() {
- last.hint(hint);
+ if !self.after_error() {
+ self.skip();
+ self.convert_to_error(eco_format!("expected {thing}, found {found}"));
}
self.skip();
}
+ /// Produce an error that the given `thing` was expected at the position
+ /// of the marker `m`.
fn expected_at(&mut self, m: Marker, thing: &str) {
let message = eco_format!("expected {}", thing);
let error = SyntaxNode::error(message, "");
self.nodes.insert(m.0, error);
}
+ /// Produce an error for the unclosed delimiter `kind` at the position
+ /// `open`.
+ fn expect_closing_delimiter(&mut self, open: Marker, kind: SyntaxKind) {
+ if !self.eat_if(kind) {
+ self.nodes[open.0].convert_to_error("unclosed delimiter");
+ }
+ }
+
+ /// Consume the next token and produce an error stating that it was
+ /// unexpected.
fn unexpected(&mut self) {
self.unskip();
while self
.nodes
.last()
- .map_or(false, |child| child.kind() == SyntaxKind::Error && child.is_empty())
+ .map_or(false, |child| child.kind().is_error() && child.is_empty())
{
self.nodes.pop();
}
self.skip();
+ self.convert_to_error(eco_format!("unexpected {}", self.current.name()));
+ }
+
+ /// Whether the last node is an error.
+ fn after_error(&self) -> bool {
+ self.nodes.last().map_or(false, |child| child.kind().is_error())
+ }
+ /// Consume the next token and turn it into an error.
+ fn convert_to_error(&mut self, message: EcoString) {
let kind = self.current;
let offset = self.nodes.len();
self.eat();
self.balanced &= !kind.is_grouping();
-
if !kind.is_error() {
- self.nodes[offset]
- .convert_to_error(eco_format!("unexpected {}", kind.name()));
+ self.nodes[offset].convert_to_error(message);
}
}
+
+ /// Adds a hint to the last node, if the last node is an error.
+ fn hint(&mut self, hint: impl Into<EcoString>) {
+ self.unskip();
+ if let Some(last) = self.nodes.last_mut() {
+ last.hint(hint);
+ }
+ self.skip();
+ }
}
diff --git a/tests/typ/compiler/embedded-expr.typ b/tests/typ/compiler/embedded-expr.typ
new file mode 100644
index 00000000..ab426f6a
--- /dev/null
+++ b/tests/typ/compiler/embedded-expr.typ
@@ -0,0 +1,12 @@
+// Test embedded expressions.
+// Ref: false
+
+---
+// Error: 6-8 expected identifier, found keyword `as`
+#let as = 1 + 2
+
+---
+#{
+ // Error: 7-9 expected identifier, found keyword `as`
+ let as = 10
+}