Appearance
| 1 | namespace Syntax.Parsers.Statements is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | use IO.Std | |
| 5 | ||
| 6 | use Source | |
| 7 | ||
| 8 | use Ghul.Pipes | |
| 9 | ||
| 10 | class LIST( | |
| 11 | terminators: Collections.LIST[Lexical.TOKEN], | |
| 12 | statement_parser: Parser[Trees.Statements.Statement] | |
| 13 | ): Base[Trees.Statements.LIST] is | |
| 14 | terminators: Collections.List[Lexical.TOKEN] | |
| 15 | description: string => "statement list" | |
| 16 | ||
| 17 | super() | |
| 18 | ||
| 19 | parse(context: CONTEXT) -> Trees.Statements.LIST is | |
| 20 | self.terminators = terminators | |
| 21 | ||
| 22 | let start = context.location | |
| 23 | let end mut = context.location | |
| 24 | let statements = Collections.LIST[Trees.Statements.Statement]() | |
| 25 | ||
| 26 | let want_semicolon mut = false | |
| 27 | ||
| 28 | // Whether the final statement of the list was terminated with | |
| 29 | // a written `;`. Compound statements never demand one, so a | |
| 30 | // `;` after them is consumed here and recorded - at end-of-body | |
| 31 | // the fact distinguishes a discarded statement from an | |
| 32 | // implicit tail return. | |
| 33 | let last_terminated mut = true | |
| 34 | ||
| 35 | // Set while a stray clause keyword skipped as debris has not yet | |
| 36 | // met its closer: a pasted fragment such as `else ... esac` is | |
| 37 | // one mistake, so the closer that ends it is skipped unreported. | |
| 38 | let in_debris_fragment mut = false | |
| 39 | ||
| 40 | while !context.is_end_of_file /\ !at_terminator(context) do | |
| 41 | if context.at_debris_closer then | |
| 42 | let is_clause = context.is_clause_keyword(context.current_token) | |
| 43 | ||
| 44 | if is_clause \/ !in_debris_fragment then | |
| 45 | context.error(context.location, "syntax error: unexpected {context.current_token_name}") | |
| 46 | fi | |
| 47 | ||
| 48 | in_debris_fragment = is_clause | |
| 49 | ||
| 50 | context.next_token() | |
| 51 | elif context.current.token == Lexical.TOKEN.SEMICOLON then | |
| 52 | let semicolon = context.current | |
| 53 | context.next_token() | |
| 54 | context.note_written_terminator(semicolon) | |
| 55 | ||
| 56 | want_semicolon = false | |
| 57 | last_terminated = true | |
| 58 | elif want_semicolon then | |
| 59 | case context.accept_terminator() | |
| 60 | when Parsers.TERMINATOR.WRITTEN then | |
| 61 | last_terminated = true | |
| 62 | when Parsers.TERMINATOR.INFERRED then | |
| 63 | last_terminated = false | |
| 64 | when Parsers.TERMINATOR.MISSING then | |
| 65 | // reported; recovery continues below as before | |
| 66 | esac | |
| 67 | ||
| 68 | want_semicolon = false | |
| 69 | else | |
| 70 | try | |
| 71 | let statement = statement_parser.parse(context) | |
| 72 | ||
| 73 | if statement? then | |
| 74 | statements.add(statement) | |
| 75 | ||
| 76 | end = statement.location | |
| 77 | ||
| 78 | if statement.expects_semicolon then | |
| 79 | want_semicolon = true | |
| 80 | last_terminated = false | |
| 81 | elif context.current.token == Lexical.TOKEN.SEMICOLON then | |
| 82 | let semicolon = context.current | |
| 83 | context.next_token() | |
| 84 | context.note_written_terminator(semicolon) | |
| 85 | last_terminated = true | |
| 86 | else | |
| 87 | // A compound statement demands no terminator, | |
| 88 | // so its boundary never reaches | |
| 89 | // accept_terminator - but a `;` written here | |
| 90 | // is redundant, so the boundary is one the | |
| 91 | // inlay marks. | |
| 92 | context.note_inferred_terminator() | |
| 93 | ||
| 94 | last_terminated = false | |
| 95 | fi | |
| 96 | else | |
| 97 | // the statement parser reported its errors and gave up: | |
| 98 | // skip ahead so the unparsed tokens don't produce a | |
| 99 | // cascade of follow-on errors | |
| 100 | end = recover_to_terminator(context, start.start_column) | |
| 101 | fi | |
| 102 | catch e: UnwindException | |
| 103 | throw e | |
| 104 | catch e: Exception | |
| 105 | end = recover_to_terminator(context, start.start_column) | |
| 106 | yrt | |
| 107 | fi | |
| 108 | od | |
| 109 | ||
| 110 | // The last statement of the list falls out of the loop before | |
| 111 | // reaching accept_terminator, so its boundary is recorded here. | |
| 112 | if want_semicolon then | |
| 113 | context.note_inferred_terminator() | |
| 114 | fi | |
| 115 | ||
| 116 | let result = Trees.Statements.LIST(start::end, statements) | |
| 117 | result.last_was_terminated = last_terminated | |
| 118 | ||
| 119 | return result | |
| 120 | si | |
| 121 | ||
| 122 | at_terminator(context: CONTEXT) -> bool => | |
| 123 | terminators |> any(t => t == context.current_token) /\ !context.at_debris_closer | |
| 124 | ||
| 125 | // Resynchronise after a statement the parser could not build. The | |
| 126 | // tokens from here to the next statement boundary are unparsed, so | |
| 127 | // skipping them stops each producing an error of its own - but | |
| 128 | // skipping too many is far worse than skipping too few. A keyword | |
| 129 | // consumed here that some enclosing construct was waiting for | |
| 130 | // leaves that construct hunting an ender that no longer exists, and | |
| 131 | // one unparseable statement takes the rest of the body with it. | |
| 132 | // | |
| 133 | // Three things stop the skip: | |
| 134 | // | |
| 135 | // a token some construct still being parsed would end at, which | |
| 136 | // is read from those parsers rather than guessed at - see | |
| 137 | // CONTEXT._awaited_enders. A closing keyword that ends nothing | |
| 138 | // open belongs to a construct the parser never entered, so it | |
| 139 | // is debris and the skip goes past it; | |
| 140 | // a line indented no further than the list itself; | |
| 141 | // a keyword that can only begin a statement. | |
| 142 | // | |
| 143 | // Nothing is counted. An earlier version tracked how deep in | |
| 144 | // brackets the skip had gone, which fails for the reason such | |
| 145 | // counting always fails here: the code being recovered from is | |
| 146 | // incomplete, so its brackets do not balance, and a count taken | |
| 147 | // from them is a count of the mistake. Asking the parsers what they | |
| 148 | // are waiting for needs no such assumption. | |
| 149 | // | |
| 150 | // The skip always consumes at least one token once it has started, | |
| 151 | // or the caller re-attempts the same failing parse forever. | |
| 152 | recover_to_terminator(context: CONTEXT, indent: int) -> LOCATION is | |
| 153 | let end mut = context.previous_end | |
| 154 | let consumed mut = false | |
| 155 | ||
| 156 | while !context.is_end_of_file do | |
| 157 | if context.is_awaited_ender(context.current_token) then | |
| 158 | break | |
| 159 | fi | |
| 160 | ||
| 161 | if consumed /\ _at_resync_point(context, indent) then | |
| 162 | break | |
| 163 | fi | |
| 164 | ||
| 165 | end = context.current.location | |
| 166 | ||
| 167 | context.next_token() | |
| 168 | ||
| 169 | consumed = true | |
| 170 | od | |
| 171 | ||
| 172 | return end | |
| 173 | si | |
| 174 | ||
| 175 | // Where the skip can hand back to the statement parser. A keyword | |
| 176 | // that can begin nothing but a statement is one wherever it stands, | |
| 177 | // including on the line the skip started on - a construct written | |
| 178 | // after a stray token is still a construct, and stopping at its | |
| 179 | // first keyword parses it rather than discarding it. An identifier | |
| 180 | // or a literal is not, since either is far more often the tail of | |
| 181 | // whatever is being skipped; those resynchronise only once a line | |
| 182 | // has dedented back to the list's own level, which starts something | |
| 183 | // new whatever it opens with. | |
| 184 | _at_resync_point(context: CONTEXT, indent: int) -> bool => | |
| 185 | _starts_statement(context.current_token) \/ | |
| 186 | (context.current.first_on_line /\ context.current.location.start_column <= indent) | |
| 187 | ||
| 188 | _starts_statement(token: Lexical.TOKEN) -> bool => | |
| 189 | token == Lexical.TOKEN.LET \/ | |
| 190 | token == Lexical.TOKEN.IF \/ | |
| 191 | token == Lexical.TOKEN.CASE \/ | |
| 192 | token == Lexical.TOKEN.WHILE \/ | |
| 193 | token == Lexical.TOKEN.FOR \/ | |
| 194 | token == Lexical.TOKEN.TRY \/ | |
| 195 | token == Lexical.TOKEN.RETURN \/ | |
| 196 | token == Lexical.TOKEN.YIELD \/ | |
| 197 | token == Lexical.TOKEN.THROW \/ | |
| 198 | token == Lexical.TOKEN.BREAK \/ | |
| 199 | token == Lexical.TOKEN.CONTINUE \/ | |
| 200 | token == Lexical.TOKEN.ASSERT | |
| 201 | ||
| 202 | ||
| 203 | si | |
| 204 | si |