diff --git a/crates/typst/src/syntax/kind.rs b/crates/typst/src/syntax/kind.rs index 26e949ca6..0c24c6674 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 4849ff791..be09be013 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,26 +1600,75 @@ impl<'s> Parser<'s> { at } + /// Produce an error that the given `thing` was expected. + fn expected(&mut self, thing: &str) { + self.unskip(); + if !self.after_error() { + let message = eco_format!("expected {thing}"); + self.nodes.push(SyntaxNode::error(message, "")); + } + self.skip(); + } + + /// 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 !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"); } } - fn expected(&mut self, thing: &str) { + /// Consume the next token and produce an error stating that it was + /// unexpected. + fn unexpected(&mut self) { self.unskip(); - if self + while self .nodes .last() - .map_or(true, |child| child.kind() != SyntaxKind::Error) + .map_or(false, |child| child.kind().is_error() && child.is_empty()) { - let message = eco_format!("expected {}", thing); - self.nodes.push(SyntaxNode::error(message, "")); + self.nodes.pop(); } self.skip(); + self.convert_to_error(eco_format!("unexpected {}", self.current.name())); } - // Adds a hint to the last node, if the last node is an error. + /// 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(message); + } + } + + /// Adds a hint to the last node, if the last node is an error. fn hint(&mut self, hint: impl Into) { self.unskip(); if let Some(last) = self.nodes.last_mut() { @@ -1622,32 +1676,4 @@ impl<'s> Parser<'s> { } self.skip(); } - - 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); - } - - fn unexpected(&mut self) { - self.unskip(); - while self - .nodes - .last() - .map_or(false, |child| child.kind() == SyntaxKind::Error && child.is_empty()) - { - self.nodes.pop(); - } - self.skip(); - - 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())); - } - } } diff --git a/tests/typ/compiler/embedded-expr.typ b/tests/typ/compiler/embedded-expr.typ new file mode 100644 index 000000000..ab426f6a4 --- /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 +}