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
58
59
60
61
62
63
64
65
66
67
68
(* Token classification for the WAT parser's panic-mode error recovery
   ({!Parsing.Make.parse_recover}). WAT is fully parenthesized, so unlike Wax
   there are no [Boundary]/[Leader] anchors: every construct is [( … )], and the
   only resynchronization points are the parentheses. Recovery therefore leans on
   the nesting-aware skip and on [close_pending] auto-closing with [")"]: an
   error inside a construct is repaired by inserting the [")"]s needed to close
   the pending constructs, so what parsed so far reduces into the best-effort AST
   rather than being unwound away when the enclosing [")"] is reached.

   Every opener — the bare [LPAREN] and the compound [(param]/[(result]/… tokens
   the lexer folds — is [Open]; [RPAREN] is [Close]; end-of-input is [Terminal];
   everything else (keywords, mnemonics, literals) is [Skip]. *)
let sync : Tokens.token -> Parsing.sync_class = function
  | Tokens.LPAREN | Tokens.LPAREN_DESCRIPTOR | Tokens.LPAREN_DESCRIBES
  | Tokens.LPAREN_CATCH | Tokens.LPAREN_CATCH_ALL | Tokens.LPAREN_CATCH_ALL_REF
  | Tokens.LPAREN_CATCH_REF | Tokens.LPAREN_ON | Tokens.LPAREN_EXPORT
  | Tokens.LPAREN_IMPORT | Tokens.LPAREN_LOCAL | Tokens.LPAREN_PARAM
  | Tokens.LPAREN_RESULT | Tokens.LPAREN_THEN | Tokens.LPAREN_TYPE ->
      Open
  | Tokens.RPAREN -> Close
  | Tokens.EOF -> Terminal
  | _ -> Skip

(* The closing bracket recovery may insert to auto-close a construct still open
   in front of a boundary (or at end of input); passed as [?closers]. *)
let closers = [ Tokens.RPAREN ]

(* WAT has no statement separator, but a missing {e operand} is the common
   mid-typing slip — [(call)] wants an index, [(i32.const)] a number. Offering a
   zero-width placeholder repairs the group in place, keeping the instruction in
   the best-effort AST (with a "Missing …" diagnostic) instead of dropping the
   whole group. Two candidates, tried in order, so the diagnostic names what is
   actually missing: a named index [$_] first — accepted exactly where an index
   is expected (a func / label / type / … use), which a bare number would also
   fit but describe less accurately — then a [0], accepted at the numeric-literal
   positions an index is not (the const family, lane / align operands, where a
   float may equally be wanted, hence "number" not "integer"). The parser picks
   the first candidate its erroring state can shift, so index positions report
   "Missing index" and the rest "Missing number". Passed as [?insert]. *)
let insert =
  [
    ( Tokens.ID (Wax_utils.Ast.no_loc "_"),
      Wax_utils.Message.text "Missing index." );
    (Tokens.NAT "0", Wax_utils.Message.text "Missing number.");
  ]

(* A missing closer — [(module (func … (func …] with a [)] left out — surfaces
   as a field-opening keyword offered where an instruction was expected. This
   [barrier] lets [Parsing.parse_recover] close the enclosing field and restart
   at the new one instead of letting paren-depth counting swallow it. It has
   three parts: the bare [(] to re-offer; the predicate recognizing a field
   keyword written after a bare [(] (offered as the pair [( ; keyword]); and the
   predicate recognizing a {e fused} field opener the lexer folds into one token
   ([(type]/[(import]/[(export]), offered alone. The bare field keywords have no
   fused form; the fused openers are also valid nested (a [(type …)] typeuse, an
   inline [(import …)]/[(export …)]), so both predicates fire only at the
   enclosing recovery level, never on skipped nested content. Passed as
   [?barrier]. *)
let barrier =
  ( Tokens.LPAREN,
    (function
    | Tokens.FUNC | Tokens.GLOBAL | Tokens.MEMORY | Tokens.TABLE | Tokens.ELEM
    | Tokens.DATA | Tokens.TAG | Tokens.START | Tokens.REC | Tokens.MODULE ->
        true
    | _ -> false),
    function
    | Tokens.LPAREN_TYPE | Tokens.LPAREN_IMPORT | Tokens.LPAREN_EXPORT -> true
    | _ -> false )