Appearance
| 1 | namespace Syntax.Parsers is | |
| 2 | use Ghul.Pipes | |
| 3 | ||
| 4 | // The value of a `let` or an assignment whose `=` has just been consumed. | |
| 5 | // | |
| 6 | // An initializer wraps onto the next line often enough that the value has | |
| 7 | // to be read across the boundary, and how the continuation is laid out | |
| 8 | // settles nothing: the grammar demands a value here, so the next token is | |
| 9 | // one wherever it sits. But where the value is simply absent - a | |
| 10 | // half-written line, which in an editor is the ordinary case rather than | |
| 11 | // the exceptional one - that same rule takes the subject of the following | |
| 12 | // statement as the value, and leaves that statement's own `=` behind as | |
| 13 | // the token everything downstream then fails on. | |
| 14 | // | |
| 15 | // So a value beginning on a later line is read speculatively and kept | |
| 16 | // when it reaches somewhere a value can end and either parsed without | |
| 17 | // complaint or began with a token that starts a value in its own right | |
| 18 | // (see _starts_only_a_value), whose mistake is then reported alone. A | |
| 19 | // program that parses is unaffected whatever its layout, since its | |
| 20 | // speculation always commits; otherwise the value is reported missing | |
| 21 | // where it was written, instead of a stray token surfacing further on. | |
| 22 | parse_assigned_value( | |
| 23 | context: CONTEXT, | |
| 24 | expression_parser: Parser[Trees.Expressions.Expression] | |
| 25 | ) -> Trees.Expressions.Expression is | |
| 26 | if !context.current.first_on_line then | |
| 27 | return expression_parser.parse(context)! | |
| 28 | fi | |
| 29 | ||
| 30 | let missing_at = context.previous_end | |
| 31 | let starts_only_a_value = _starts_only_a_value(context.current_token) | |
| 32 | ||
| 33 | let use diagnostics_snapshot = context.diagnostics_speculate_then_backtrack() | |
| 34 | let use snapshot = context.tokenizer_speculate_then_backtrack_bounded() | |
| 35 | ||
| 36 | let errors_before = context.logger.error_count | |
| 37 | ||
| 38 | let value = expression_parser.parse(context)! | |
| 39 | ||
| 40 | if | |
| 41 | !isa Trees.Expressions.Literals.NONE(value) /\ | |
| 42 | (starts_only_a_value \/ (!value.is_poisoned /\ context.logger.error_count == errors_before)) /\ | |
| 43 | ends_assigned_value(context) | |
| 44 | then | |
| 45 | snapshot.commit() | |
| 46 | diagnostics_snapshot.commit() | |
| 47 | ||
| 48 | return value | |
| 49 | fi | |
| 50 | ||
| 51 | snapshot.backtrack() | |
| 52 | diagnostics_snapshot.backtrack() | |
| 53 | ||
| 54 | context.error(missing_at, "expected a value to assign here") | |
| 55 | ||
| 56 | return Trees.Expressions.Literals.NONE(missing_at) | |
| 57 | si | |
| 58 | ||
| 59 | // A token that begins a value in its own right. A value starting with | |
| 60 | // one and reaching a place a value can end is kept, even with a mistake | |
| 61 | // inside it: an error-free one is kept already, so the mistake is the | |
| 62 | // only difference, and reporting the value missing as well would | |
| 63 | // describe it twice. An identifier is not one of these, since it is as | |
| 64 | // likely to be the subject of the statement after a value that really | |
| 65 | // is missing. | |
| 66 | _starts_only_a_value(token: Lexical.TOKEN) -> bool => | |
| 67 | token == Lexical.TOKEN.CASE \/ | |
| 68 | token == Lexical.TOKEN.IF \/ | |
| 69 | token == Lexical.TOKEN.INT_LITERAL \/ | |
| 70 | token == Lexical.TOKEN.FLOAT_LITERAL \/ | |
| 71 | token == Lexical.TOKEN.DOUBLE_LITERAL \/ | |
| 72 | token == Lexical.TOKEN.CHAR_LITERAL \/ | |
| 73 | token == Lexical.TOKEN.STRING_LITERAL \/ | |
| 74 | token == Lexical.TOKEN.TRUE \/ | |
| 75 | token == Lexical.TOKEN.FALSE \/ | |
| 76 | token == Lexical.TOKEN.NULL \/ | |
| 77 | token == Lexical.TOKEN.PAREN_OPEN \/ | |
| 78 | token == Lexical.TOKEN.SQUARE_OPEN | |
| 79 | ||
| 80 | // Where a value may end: a statement boundary, or a token that closes or | |
| 81 | // continues whatever the `let` was written inside. A value that stops | |
| 82 | // anywhere else has run into something it could not read, which is the | |
| 83 | // reading the speculation exists to decline. | |
| 84 | ends_assigned_value(context: CONTEXT) -> bool is | |
| 85 | if context.at_inferred_terminator then | |
| 86 | return true | |
| 87 | fi | |
| 88 | ||
| 89 | let token = context.current_token | |
| 90 | ||
| 91 | token == Lexical.TOKEN.SEMICOLON \/ | |
| 92 | token == Lexical.TOKEN.COMMA \/ | |
| 93 | token == Lexical.TOKEN.IN \/ | |
| 94 | token == Lexical.TOKEN.THEN \/ | |
| 95 | token == Lexical.TOKEN.DO \/ | |
| 96 | token == Lexical.TOKEN.ELSE \/ | |
| 97 | token == Lexical.TOKEN.ELIF \/ | |
| 98 | token == Lexical.TOKEN.FI \/ | |
| 99 | token == Lexical.TOKEN.OD \/ | |
| 100 | token == Lexical.TOKEN.CATCH \/ | |
| 101 | token == Lexical.TOKEN.FINALLY \/ | |
| 102 | token == Lexical.TOKEN.YRT \/ | |
| 103 | token == Lexical.TOKEN.WHEN \/ | |
| 104 | token == Lexical.TOKEN.ESAC \/ | |
| 105 | token == Lexical.TOKEN.LAV \/ | |
| 106 | token == Lexical.TOKEN.SI \/ | |
| 107 | token == Lexical.TOKEN.PAREN_CLOSE \/ | |
| 108 | token == Lexical.TOKEN.SQUARE_CLOSE | |
| 109 | si | |
| 110 | si |