Tweak parser error messages 🔮

This commit is contained in:
Laurenz 2020-12-16 16:24:06 +01:00
parent 6bbedeaa2c
commit 2336aeb4c3
5 changed files with 39 additions and 24 deletions

View File

@ -85,8 +85,9 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Spanned<SynNode>> {
}
// Bad tokens.
token => {
p.diag_unexpected(token.span_with(start .. p.pos()));
_ => {
p.jump(start);
p.diag_unexpected();
return None;
}
};
@ -233,6 +234,7 @@ fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall {
fn dict_contents(p: &mut Parser) -> (LitDict, bool) {
let mut dict = LitDict::new();
let mut comma_and_keyless = true;
let mut expected_comma = None;
loop {
p.skip_white();
@ -243,10 +245,15 @@ fn dict_contents(p: &mut Parser) -> (LitDict, bool) {
let entry = if let Some(entry) = dict_entry(p) {
entry
} else {
p.diag_expected("value");
expected_comma = None;
p.diag_unexpected();
continue;
};
if let Some(pos) = expected_comma.take() {
p.diag_expected_at("comma", pos);
}
if let Some(key) = &entry.key {
comma_and_keyless = false;
p.deco(Deco::DictKey.span_with(key.span));
@ -261,7 +268,7 @@ fn dict_contents(p: &mut Parser) -> (LitDict, bool) {
}
if !p.eat_if(Token::Comma) {
p.diag_expected_at("comma", behind);
expected_comma = Some(behind);
}
comma_and_keyless = false;
@ -286,6 +293,7 @@ fn dict_entry(p: &mut Parser) -> Option<LitDictEntry> {
expr,
})
} else {
p.diag_expected("value");
None
}
}

View File

@ -38,7 +38,7 @@ impl<'s> Parser<'s> {
self.f.diags.push(diag);
}
/// Eat the next token and add a diagnostic that it was not the expected
/// Eat the next token and add a diagnostic that it is not the expected
/// `thing`.
pub fn diag_expected(&mut self, thing: &str) {
let before = self.pos();
@ -60,9 +60,16 @@ impl<'s> Parser<'s> {
self.diag(error!(pos, "expected {}", thing));
}
/// Add a diagnostic that the given `token` was unexpected.
pub fn diag_unexpected(&mut self, token: Spanned<Token>) {
self.diag(error!(token.span, "unexpected {}", token.v.name()));
/// Eat the next token and add a diagnostic that it is unexpected.
pub fn diag_unexpected(&mut self) {
let before = self.pos();
if let Some(found) = self.eat() {
let after = self.pos();
self.diag(match found {
Token::Invalid(_) => error!(before .. after, "invalid token"),
_ => error!(before .. after, "unexpected {}", found.name()),
});
}
}
/// Add a decoration to the feedback.

View File

@ -415,6 +415,7 @@ fn test_parse_values() {
// Simple.
v!("_" => Id("_"));
v!("name" => Id("name"));
v!("ke-bab" => Id("ke-bab"));
v!("α" => Id("α"));
v!("\"hi\"" => Str("hi"));
v!("true" => Bool(true));
@ -457,6 +458,7 @@ fn test_parse_expressions() {
// Operations.
v!("-1" => Unary(Neg, Int(1)));
v!("-- 1" => Unary(Neg, Unary(Neg, Int(1))));
v!("--css" => Unary(Neg, Unary(Neg, Id("css"))));
v!("3.2in + 6pt" => Binary(Add, Length(3.2, In), Length(6.0, Pt)));
v!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01)));
v!("(3mm * 2)" => Binary(Mul, Length(3.0, Mm), Int(2)));
@ -547,7 +549,7 @@ fn test_parse_dicts_compute_func_calls() {
// Invalid name.
v!("👠(\"abc\", 13e-5)" => Dict!(Str("abc"), Float(13.0e-5)));
e!("[val: 👠(\"abc\", 13e-5)]" => s(6, 10, "expected value, found invalid token"));
e!("[val: 👠(\"abc\", 13e-5)]" => s(6, 10, "invalid token"));
}
#[test]
@ -567,12 +569,12 @@ fn test_parse_dicts_nested() {
#[test]
fn test_parse_dicts_errors() {
// Expected value.
e!("[val: (=)]" => s(7, 8, "expected value, found equals sign"));
e!("[val: (,)]" => s(7, 8, "expected value, found comma"));
e!("[val: (=)]" => s(7, 8, "unexpected equals sign"));
e!("[val: (,)]" => s(7, 8, "unexpected comma"));
v!("(\x07 abc,)" => Dict![Id("abc")]);
e!("[val: (\x07 abc,)]" => s(7, 8, "expected value, found invalid token"));
e!("[val: (\x07 abc,)]" => s(7, 8, "invalid token"));
e!("[val: (key=,)]" => s(11, 12, "expected value, found comma"));
e!("[val: hi,)]" => s(9, 10, "expected value, found closing paren"));
e!("[val: hi,)]" => s(9, 10, "unexpected closing paren"));
// Expected comma.
v!("(true false)" => Dict![Bool(true), Bool(false)]);
@ -586,13 +588,9 @@ fn test_parse_dicts_errors() {
// Bad key.
v!("true=you" => Bool(true), Id("you"));
e!("[val: true=you]" =>
s(10, 10, "expected comma"),
s(10, 11, "expected value, found equals sign"));
e!("[val: true=you]" => s(10, 11, "unexpected equals sign"));
// Unexpected equals sign.
v!("z=y=4" => "z" => Id("y"), Int(4));
e!("[val: z=y=4]" =>
s(9, 9, "expected comma"),
s(9, 10, "expected value, found equals sign"));
e!("[val: z=y=4]" => s(9, 10, "unexpected equals sign"));
}

View File

@ -75,7 +75,7 @@ impl<'s> Iterator for Tokens<'s> {
// Comments.
'/' if self.s.eat_if('/') => self.line_comment(),
'/' if self.s.eat_if('*') => self.block_comment(),
'*' if self.s.eat_if('/') => Token::Invalid("*/"),
'*' if self.s.eat_if('/') => Token::StarSlash,
// Functions and blocks.
'[' => Token::LeftBracket,
@ -779,8 +779,8 @@ mod tests {
#[test]
fn test_tokenize_invalid() {
// Test invalidly closed block comments.
t!(Both: "*/" => Invalid("*/"));
t!(Both: "/**/*/" => BC(""), Invalid("*/"));
t!(Both: "*/" => StarSlash);
t!(Both: "/**/*/" => BC(""), StarSlash);
// Test invalid expressions.
t!(Header: r"\" => Invalid(r"\"));

View File

@ -19,6 +19,8 @@ pub enum Token<'s> {
///
/// The comment can contain nested block comments.
BlockComment(&'s str),
/// An end of a block comment that was not started.
StarSlash,
/// A star: `*`.
Star,
@ -83,7 +85,7 @@ pub enum Token<'s> {
/// A quoted string: `"..."`.
Str(TokenStr<'s>),
/// Things that are not valid in the context they appeared in.
/// Things that are not valid tokens.
Invalid(&'s str),
}
@ -129,6 +131,7 @@ impl<'s> Token<'s> {
Self::LineComment(_) => "line comment",
Self::BlockComment(_) => "block comment",
Self::StarSlash => "end of block comment",
Self::Star => "star",
Self::Underscore => "underscore",
@ -162,7 +165,6 @@ impl<'s> Token<'s> {
Self::Hex(_) => "hex value",
Self::Str { .. } => "string",
Self::Invalid("*/") => "end of block comment",
Self::Invalid(_) => "invalid token",
}
}