diff options
| -rw-r--r-- | crates/typst/src/syntax/kind.rs | 26 | ||||
| -rw-r--r-- | crates/typst/src/syntax/parser.rs | 66 | ||||
| -rw-r--r-- | tests/typ/compiler/embedded-expr.typ | 12 |
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 +} |
