Fix parser bug with speculative parsing
19053951a14546126547aec491eb0f1304083e5e9aa52777fde1c3efa168b5d7
1 parent
3ca028fc
lib/std/lang/parser.rad
+39 -1
| 75 | 75 | list: [Error; MAX_ERRORS], |
|
| 76 | 76 | /// Number of errors currently in the list. |
|
| 77 | 77 | count: u32, |
|
| 78 | 78 | } |
|
| 79 | 79 | ||
| 80 | + | /// Snapshot of parser state for speculative parsing. |
|
| 81 | + | record SavedState { |
|
| 82 | + | parser: Parser, |
|
| 83 | + | arena: u32, |
|
| 84 | + | nextId: u32, |
|
| 85 | + | } |
|
| 86 | + | ||
| 80 | 87 | /// Operator metadata for precedence climbing. |
|
| 81 | 88 | record OpInfo { |
|
| 82 | 89 | op: ast::BinaryOp, |
|
| 83 | 90 | prec: i32, |
|
| 84 | 91 | } |
| 1139 | 1146 | } else { |
|
| 1140 | 1147 | node.span.length = 0; |
|
| 1141 | 1148 | } |
|
| 1142 | 1149 | } |
|
| 1143 | 1150 | ||
| 1151 | + | /// Save parser state for speculative parsing. |
|
| 1152 | + | fn saveState(p: *Parser) -> SavedState { |
|
| 1153 | + | return SavedState { |
|
| 1154 | + | parser: *p, |
|
| 1155 | + | arena: alloc::save(&p.arena.arena), |
|
| 1156 | + | nextId: p.arena.nextId, |
|
| 1157 | + | }; |
|
| 1158 | + | } |
|
| 1159 | + | ||
| 1160 | + | /// Restore parser state from a snapshot, fully undoing any |
|
| 1161 | + | /// side effects of a failed speculative parse. |
|
| 1162 | + | fn restoreState(p: *mut Parser, s: *SavedState) { |
|
| 1163 | + | *p = s.parser; |
|
| 1164 | + | alloc::restore(&mut p.arena.arena, s.arena); |
|
| 1165 | + | p.arena.nextId = s.nextId; |
|
| 1166 | + | } |
|
| 1167 | + | ||
| 1144 | 1168 | /// Report a parser error. |
|
| 1145 | 1169 | fn reportError(p: *mut Parser, token: scanner::Token, message: *[u8]) { |
|
| 1146 | 1170 | assert message.len > 0; |
|
| 1147 | 1171 | ||
| 1148 | 1172 | // Ignore errors once the error list is full. |
| 1347 | 1371 | /// Parse a `return` statement. |
|
| 1348 | 1372 | fn parseReturn(p: *mut Parser) -> *ast::Node |
|
| 1349 | 1373 | throws (ParseError) |
|
| 1350 | 1374 | { |
|
| 1351 | 1375 | try expect(p, scanner::TokenKind::Return, "expected `return`"); |
|
| 1352 | - | let value: ?*ast::Node = try? parseExpr(p); |
|
| 1353 | 1376 | ||
| 1377 | + | // Speculatively try to parse a return value expression. |
|
| 1378 | + | let saved = saveState(p); |
|
| 1379 | + | let value: ?*ast::Node = try? parseExpr(p); |
|
| 1380 | + | if value == nil { |
|
| 1381 | + | restoreState(p, &saved); |
|
| 1382 | + | } |
|
| 1354 | 1383 | return node(p, ast::NodeValue::Return { value }); |
|
| 1355 | 1384 | } |
|
| 1356 | 1385 | ||
| 1357 | 1386 | /// Parse a `throw` statement. |
|
| 1358 | 1387 | fn parseThrow(p: *mut Parser) -> *ast::Node |
| 1375 | 1404 | let message: ?*ast::Node = try parseExpr(p); |
|
| 1376 | 1405 | try expect(p, scanner::TokenKind::RBrace, "expected closing `}` after expression"); |
|
| 1377 | 1406 | return node(p, ast::NodeValue::Panic { message }); |
|
| 1378 | 1407 | } |
|
| 1379 | 1408 | // `panic` or `panic "message"`. |
|
| 1409 | + | let saved = saveState(p); |
|
| 1380 | 1410 | let message: ?*ast::Node = try? parseExpr(p); |
|
| 1411 | + | if message == nil { |
|
| 1412 | + | restoreState(p, &saved); |
|
| 1413 | + | } |
|
| 1381 | 1414 | return node(p, ast::NodeValue::Panic { message }); |
|
| 1382 | 1415 | } |
|
| 1383 | 1416 | ||
| 1384 | 1417 | /// Parse an `assert` statement. |
|
| 1385 | 1418 | /// |
| 1566 | 1599 | patterns.append(pattern, p.allocator); |
|
| 1567 | 1600 | ||
| 1568 | 1601 | if not consume(p, scanner::TokenKind::Comma) { |
|
| 1569 | 1602 | break; |
|
| 1570 | 1603 | } |
|
| 1604 | + | // After a comma, check for tokens that start a new prong. |
|
| 1605 | + | // This catches mistakes like `case A, case B`. |
|
| 1606 | + | if check(p, scanner::TokenKind::Case) or check(p, scanner::TokenKind::Else) { |
|
| 1607 | + | throw failParsing(p, "unexpected keyword after `,` in case pattern list"); |
|
| 1608 | + | } |
|
| 1571 | 1609 | } |
|
| 1572 | 1610 | if consume(p, scanner::TokenKind::If) { |
|
| 1573 | 1611 | guard = try parseCond(p); |
|
| 1574 | 1612 | } |
|
| 1575 | 1613 | try expect(p, scanner::TokenKind::FatArrow, "expected `=>` after case pattern"); |