Source file recover.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
(* Token classification for the Wax parser's panic-mode error recovery, shared
   by the CLI ([Wax_conversion.Driver.wax_parse_recover], behind
   [wax check --all-errors]) and the editor ([src/editor/wax_format_js.ml]). It
   lives here, free of the [Fast_parser] tables, so the editor's size-sensitive
   wasm build can use it without linking the fast parser (see the module note in
   wax_format_js.ml).

   End-of-input stops recovery and everything not listed here is skipped while
   scanning for one of these boundaries. The kinds (all handled by the same
   "unwind the stack to a state that can shift this token, then shift it" step,
   see {!Wax_wasm.Parsing.Make.parse_recover}):

   - Openers ([Open]: ["{"], ["("], ["["]) and closers ([Close]: ["}"], [")"],
     ["]"]). The skip is nesting-aware: an opener met while skipping descends a
     level and the matching closer ascends it again, so only a closer at the
     {e outer} level resyncs — a closer or [";"] belonging to a group opened
     inside the skipped span no longer resyncs the enclosing construct. The
     paren/bracket closers give finer, expression-internal recovery than the
     statement/block ones.
   - The statement separator [";"] ([Boundary]) — a resync point, but only at
     the outer nesting level (like a closer).
   - Leading keywords that begin a new top-level item or statement ([Leader]).
     They let recovery resync at the next construct even when the error consumed
     the trailing closer, so they resync at {e any} depth (an unbalanced opener
     must not swallow the next item). Only keywords that can never continue an
     expression are included: the expression forms [if]/[loop]/[block]/[match]/
     [do]/[while] are left out, since in this expression-oriented grammar they
     can occur mid-expression and stopping at one would resync too early. *)
let sync : Tokens.token -> Wax_wasm.Parsing.sync_class = function
  | Tokens.LBRACE | Tokens.LPAREN | Tokens.LBRACKET -> Open
  | Tokens.RBRACE | Tokens.RPAREN | Tokens.RBRACKET -> Close
  | Tokens.SEMI -> Boundary
  | Tokens.FN | Tokens.TYPE | Tokens.REC | Tokens.IMPORT | Tokens.MEMORY
  | Tokens.DATA | Tokens.TABLE | Tokens.ELEM | Tokens.TAG | Tokens.CONST
  | Tokens.LET | Tokens.RETURN | Tokens.BR | Tokens.BR_IF | Tokens.BR_TABLE
  | Tokens.THROW | Tokens.THROW_REF | Tokens.BECOME | Tokens.NOP
  | Tokens.UNREACHABLE ->
      Leader
  | Tokens.EOF -> Terminal
  | _ -> Skip

(* Candidate tokens recovery may insert in front of an offending token, each
   with the diagnostic to report. For Wax the only candidate is a statement
   separator [";"]: a dropped [;] between two statements is by far the most
   common slip, and the state after a complete statement can shift [SEMI], so
   [Parsing.parse_recover] inserts one there (reporting "Missing ';'") rather
   than skipping to the next boundary. *)
let insert =
  [
    (Tokens.SEMI, Wax_utils.Message.(text "Missing" ++ (code ";" ^^ text ".")));
  ]

(* The closing brackets recovery may insert to auto-close a construct left open
   at end of input, so the function/block the user is still typing reduces into
   the best-effort AST (for the editor outline) instead of being unwound away.
   See the [closers] argument of [Parsing.parse_recover]. *)
let closers = [ Tokens.RBRACE; Tokens.RPAREN; Tokens.RBRACKET ]