Better error messages for keywords in place of identifiers

Fixes #1123
This commit is contained in:
Laurenz 2023-07-06 15:14:14 +02:00
parent 46a6f92bf3
commit adcc6e5506
3 changed files with 98 additions and 34 deletions

View File

@ -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 {

View File

@ -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<EcoString>) {
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()));
}
}
}

View File

@ -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
}