Skip to content

Commit a556bb3

Browse files
committed
improved error handling
1 parent 38c0f68 commit a556bb3

8 files changed

+231
-3
lines changed

src/parser.rs

+64-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ use crate::{
1818
#[derive(Clone, Debug, PartialEq)]
1919
#[non_exhaustive]
2020
pub enum ParseError {
21+
/// Expected is used when a token was expected but not found. Unlike UnexpectedWanted,
22+
/// we continue to parse as if this token was found.
23+
Expected(TextSize, Box<[SyntaxKind]>),
2124
/// Unexpected is used when the cause cannot be specified further
2225
Unexpected(TextRange),
2326
/// UnexpectedExtra is used when there are additional tokens to the root in the tree
@@ -37,6 +40,12 @@ pub enum ParseError {
3740
impl fmt::Display for ParseError {
3841
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3942
match self {
43+
ParseError::Expected(location, kinds) => write!(
44+
f,
45+
"wanted any of {:?} at {}",
46+
kinds,
47+
usize::from(*location),
48+
),
4049
ParseError::Unexpected(range) => {
4150
write!(
4251
f,
@@ -189,6 +198,41 @@ where
189198
fn peek(&mut self) -> Option<SyntaxKind> {
190199
self.peek_data().map(|&(t, _)| t)
191200
}
201+
202+
/// If the next token is in `allowed_slice`, expect_peek_any returns it.
203+
/// Otherwise, it appends `self.errors` without consuming any tokens.
204+
///
205+
/// This is generally preferred over expect_peek_any, but the callee must
206+
/// ensure that an infinite loop won't be caused by this method not bumping.
207+
fn peek_any_or_missing(&mut self, allowed_slice: &[SyntaxKind]) -> Option<SyntaxKind> {
208+
let allowed = TokenSet::from_slice(allowed_slice);
209+
210+
match self.peek() {
211+
Some(kind) if allowed.contains(kind) => Some(kind),
212+
Some(_) => {
213+
self.errors.push(ParseError::Expected(
214+
self.get_text_position(),
215+
allowed_slice.to_vec().into_boxed_slice(),
216+
));
217+
218+
None
219+
}
220+
None => {
221+
self.errors
222+
.push(ParseError::UnexpectedEOFWanted(allowed_slice.to_vec().into_boxed_slice()));
223+
224+
None
225+
},
226+
}
227+
}
228+
229+
/// If the next token is in `allowed_slice`, expect_peek_any returns it.
230+
/// Otherwise, it appends self.errors and creates a NODE_ERROR containing all tokens
231+
/// until a token in `allowed_slice` is found.
232+
///
233+
/// This is not always wanted. For example, in `{ foo = 1 } // { bar = 2; }`, it would
234+
/// be better to recognize the semicolon as missing then move on, rather than bumping
235+
/// all the way to the semicolon after `bar`.
192236
fn expect_peek_any(&mut self, allowed_slice: &[SyntaxKind]) -> Option<SyntaxKind> {
193237
let allowed = TokenSet::from_slice(allowed_slice);
194238

@@ -224,6 +268,17 @@ where
224268
self.bump();
225269
}
226270
}
271+
272+
// Expect a token or silently ignore if missing. This only bumps if the token is found,
273+
// so watch out for infinite loops. This returns whether the token was found.
274+
fn expect_or_missing(&mut self, expected: SyntaxKind) -> bool {
275+
let found = self.peek_any_or_missing(&[expected]).is_some();
276+
if found {
277+
self.bump();
278+
}
279+
found
280+
}
281+
227282
fn expect_ident(&mut self) {
228283
if self.expect_peek_any(&[TOKEN_IDENT]).is_some() {
229284
self.start_node(NODE_IDENT);
@@ -277,7 +332,14 @@ where
277332
fn parse_attrpath(&mut self) {
278333
self.start_node(NODE_ATTRPATH);
279334
loop {
280-
self.parse_attr();
335+
match self.peek_any_or_missing(&[TOKEN_INTERPOL_START, TOKEN_STRING_START, TOKEN_IDENT]) {
336+
Some(TOKEN_INTERPOL_START) => self.parse_dynamic(),
337+
Some(TOKEN_STRING_START) => self.parse_string(),
338+
Some(TOKEN_IDENT) => self.expect_ident(),
339+
_ => {
340+
break
341+
}
342+
}
281343

282344
if self.peek() == Some(TOKEN_DOT) {
283345
self.bump();
@@ -399,7 +461,7 @@ where
399461
self.parse_attrpath();
400462
self.expect(TOKEN_ASSIGN);
401463
self.parse_expr();
402-
self.expect(TOKEN_SEMICOLON);
464+
self.expect_or_missing(TOKEN_SEMICOLON);
403465
self.finish_node();
404466
}
405467
}

test_data/parser/invalid_syntax/1.expect

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: unexpected TOKEN_SQUARE_B_CLOSE at 0..1, wanted any of [TOKEN_PAREN_OPEN, TOKEN_REC, TOKEN_CURLY_B_OPEN, TOKEN_SQUARE_B_OPEN, TOKEN_STRING_START, TOKEN_IDENT]
2-
error: unexpected end of file, wanted any of [TOKEN_IDENT]
2+
error: unexpected end of file, wanted any of [TOKEN_INTERPOL_START, TOKEN_STRING_START, TOKEN_IDENT]
33
NODE_ROOT 0..2 {
44
NODE_SELECT 0..2 {
55
NODE_ERROR 0..1 {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
error: wanted any of [TOKEN_INTERPOL_START, TOKEN_STRING_START, TOKEN_IDENT] at 18
2+
NODE_ROOT 0..36 {
3+
NODE_ATTR_SET 0..36 {
4+
TOKEN_CURLY_B_OPEN("{") 0..1
5+
TOKEN_WHITESPACE("\n ") 1..6
6+
NODE_ATTRPATH_VALUE 6..19 {
7+
NODE_ATTRPATH 6..9 {
8+
NODE_IDENT 6..9 {
9+
TOKEN_IDENT("foo") 6..9
10+
}
11+
}
12+
TOKEN_WHITESPACE(" ") 9..10
13+
TOKEN_ASSIGN("=") 10..11
14+
TOKEN_WHITESPACE(" ") 11..12
15+
NODE_SELECT 12..18 {
16+
NODE_IDENT 12..17 {
17+
TOKEN_IDENT("hello") 12..17
18+
}
19+
TOKEN_DOT(".") 17..18
20+
NODE_ATTRPATH 18..18 {
21+
}
22+
}
23+
TOKEN_SEMICOLON(";") 18..19
24+
}
25+
TOKEN_WHITESPACE("\n ") 19..24
26+
NODE_ATTRPATH_VALUE 24..34 {
27+
NODE_ATTRPATH 24..27 {
28+
NODE_IDENT 24..27 {
29+
TOKEN_IDENT("xyz") 24..27
30+
}
31+
}
32+
TOKEN_WHITESPACE(" ") 27..28
33+
TOKEN_ASSIGN("=") 28..29
34+
TOKEN_WHITESPACE(" ") 29..30
35+
NODE_LITERAL 30..33 {
36+
TOKEN_INTEGER("123") 30..33
37+
}
38+
TOKEN_SEMICOLON(";") 33..34
39+
}
40+
TOKEN_WHITESPACE("\n") 34..35
41+
TOKEN_CURLY_B_CLOSE("}") 35..36
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
foo = hello.;
3+
xyz = 123;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
error: wanted any of [TOKEN_INTERPOL_START, TOKEN_STRING_START, TOKEN_IDENT] at 13
2+
error: wanted any of [TOKEN_SEMICOLON] at 13
3+
NODE_ROOT 0..30 {
4+
NODE_BIN_OP 0..30 {
5+
NODE_ATTR_SET 0..14 {
6+
TOKEN_CURLY_B_OPEN("{") 0..1
7+
TOKEN_WHITESPACE(" ") 1..2
8+
NODE_ATTRPATH_VALUE 2..13 {
9+
NODE_ATTRPATH 2..5 {
10+
NODE_IDENT 2..5 {
11+
TOKEN_IDENT("foo") 2..5
12+
}
13+
}
14+
TOKEN_WHITESPACE(" ") 5..6
15+
TOKEN_ASSIGN("=") 6..7
16+
TOKEN_WHITESPACE(" ") 7..8
17+
NODE_SELECT 8..13 {
18+
NODE_IDENT 8..11 {
19+
TOKEN_IDENT("bar") 8..11
20+
}
21+
TOKEN_DOT(".") 11..12
22+
TOKEN_WHITESPACE(" ") 12..13
23+
NODE_ATTRPATH 13..13 {
24+
}
25+
}
26+
}
27+
TOKEN_CURLY_B_CLOSE("}") 13..14
28+
}
29+
TOKEN_WHITESPACE(" ") 14..15
30+
TOKEN_UPDATE("//") 15..17
31+
TOKEN_WHITESPACE(" ") 17..18
32+
NODE_ATTR_SET 18..30 {
33+
TOKEN_CURLY_B_OPEN("{") 18..19
34+
TOKEN_WHITESPACE(" ") 19..20
35+
NODE_ATTRPATH_VALUE 20..28 {
36+
NODE_ATTRPATH 20..23 {
37+
NODE_IDENT 20..23 {
38+
TOKEN_IDENT("xyz") 20..23
39+
}
40+
}
41+
TOKEN_WHITESPACE(" ") 23..24
42+
TOKEN_ASSIGN("=") 24..25
43+
TOKEN_WHITESPACE(" ") 25..26
44+
NODE_LITERAL 26..27 {
45+
TOKEN_INTEGER("3") 26..27
46+
}
47+
TOKEN_SEMICOLON(";") 27..28
48+
}
49+
TOKEN_WHITESPACE(" ") 28..29
50+
TOKEN_CURLY_B_CLOSE("}") 29..30
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ foo = bar. } // { xyz = 3; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
error: wanted any of [TOKEN_SEMICOLON] at 13
2+
error: wanted any of [TOKEN_INTERPOL_START, TOKEN_STRING_START, TOKEN_IDENT] at 14
3+
error: wanted any of [TOKEN_SEMICOLON] at 17
4+
NODE_ROOT 0..34 {
5+
NODE_BIN_OP 0..34 {
6+
NODE_ATTR_SET 0..18 {
7+
TOKEN_CURLY_B_OPEN("{") 0..1
8+
TOKEN_WHITESPACE(" ") 1..2
9+
NODE_ATTRPATH_VALUE 2..13 {
10+
NODE_ATTRPATH 2..5 {
11+
NODE_IDENT 2..5 {
12+
TOKEN_IDENT("foo") 2..5
13+
}
14+
}
15+
TOKEN_WHITESPACE(" ") 5..6
16+
TOKEN_ASSIGN("=") 6..7
17+
TOKEN_WHITESPACE(" ") 7..8
18+
NODE_APPLY 8..13 {
19+
NODE_LITERAL 8..9 {
20+
TOKEN_INTEGER("1") 8..9
21+
}
22+
TOKEN_WHITESPACE(" ") 9..10
23+
NODE_IDENT 10..13 {
24+
TOKEN_IDENT("bar") 10..13
25+
}
26+
}
27+
}
28+
TOKEN_WHITESPACE(" ") 13..14
29+
NODE_ATTRPATH_VALUE 14..17 {
30+
NODE_ATTRPATH 14..14 {
31+
}
32+
TOKEN_ASSIGN("=") 14..15
33+
TOKEN_WHITESPACE(" ") 15..16
34+
NODE_LITERAL 16..17 {
35+
TOKEN_INTEGER("2") 16..17
36+
}
37+
}
38+
TOKEN_CURLY_B_CLOSE("}") 17..18
39+
}
40+
TOKEN_WHITESPACE(" ") 18..19
41+
TOKEN_UPDATE("//") 19..21
42+
TOKEN_WHITESPACE(" ") 21..22
43+
NODE_ATTR_SET 22..34 {
44+
TOKEN_CURLY_B_OPEN("{") 22..23
45+
TOKEN_WHITESPACE(" ") 23..24
46+
NODE_ATTRPATH_VALUE 24..32 {
47+
NODE_ATTRPATH 24..27 {
48+
NODE_IDENT 24..27 {
49+
TOKEN_IDENT("xyz") 24..27
50+
}
51+
}
52+
TOKEN_WHITESPACE(" ") 27..28
53+
TOKEN_ASSIGN("=") 28..29
54+
TOKEN_WHITESPACE(" ") 29..30
55+
NODE_LITERAL 30..31 {
56+
TOKEN_INTEGER("3") 30..31
57+
}
58+
TOKEN_SEMICOLON(";") 31..32
59+
}
60+
TOKEN_WHITESPACE(" ") 32..33
61+
TOKEN_CURLY_B_CLOSE("}") 33..34
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ foo = 1 bar = 2} // { xyz = 3; }

0 commit comments

Comments
 (0)