package ocamlformat-lib

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file merlin_recovery.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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
let split_pos { Lexing.pos_lnum; pos_bol; pos_cnum; _ } =
  (pos_lnum, pos_cnum - pos_bol)

let rev_filter ~f xs =
  let rec aux f acc = function
    | x :: xs when f x -> aux f (x :: acc) xs
    | _ :: xs -> aux f acc xs
    | [] -> acc
  in
  aux f [] xs

let rec rev_scan_left acc ~f ~init = function
  | [] -> acc
  | x :: xs ->
      let init = f init x in
      rev_scan_left (init :: acc) ~f ~init xs

module Make
    (Parser : MenhirLib.IncrementalEngine.EVERYTHING) (Recovery : sig
      val default_value : Location.t -> 'a Parser.symbol -> 'a

      type action =
        | Abort
        | R of int
        | S : 'a Parser.symbol -> action
        | Sub of action list

      type decision =
        | Nothing
        | One of action list
        | Select of (int -> action list)

      val depth : int array

      val recover : int -> decision

      val guide : 'a Parser.symbol -> bool

      val token_of_terminal : 'a Parser.terminal -> 'a -> Parser.token

      val nullable : 'a Parser.nonterminal -> bool
    end) =
struct
  type 'a candidate = {
    line : int;
    min_col : int;
    max_col : int;
    env : 'a Parser.env;
  }

  type 'a candidates = {
    shifted : Parser.xsymbol option;
    final : 'a option;
    candidates : 'a candidate list;
  }

  module T = struct
    [@@@ocaml.warning "-37"]

    type 'a checkpoint =
      | InputNeeded of 'a Parser.env
      | Shifting of 'a Parser.env * 'a Parser.env * bool
      | AboutToReduce of 'a Parser.env * Parser.production
      | HandlingError of 'a Parser.env
      | Accepted of 'a
      | Rejected

    external inj : 'a checkpoint -> 'a Parser.checkpoint = "%identity"
  end

  let feed_token ~allow_reduction token env =
    let rec aux allow_reduction = function
      | Parser.HandlingError _ | Parser.Rejected -> `Fail
      | Parser.AboutToReduce _ when not allow_reduction -> `Fail
      | Parser.Accepted v -> `Accept v
      | (Parser.Shifting _ | Parser.AboutToReduce _) as checkpoint ->
          aux true (Parser.resume checkpoint)
      | Parser.InputNeeded env as checkpoint -> `Recovered (checkpoint, env)
    in
    aux allow_reduction (Parser.offer (T.inj (T.InputNeeded env)) token)

  let rec follow_guide col env =
    match Parser.top env with
    | None -> col
    | Some (Parser.Element (state, _, pos, _)) ->
        if Recovery.guide (Parser.incoming_symbol state) then
          match Parser.pop env with
          | None -> col
          | Some env -> follow_guide (snd (split_pos pos)) env
        else col

  let candidate env =
    let line, min_col, max_col =
      match Parser.top env with
      | None -> (1, 0, 0)
      | Some (Parser.Element (state, _, pos, _)) ->
          let depth = Recovery.depth.(Parser.number state) in
          let line, col = split_pos pos in
          if depth = 0 then (line, col, col)
          else
            let col' =
              match Parser.pop_many depth env with
              | None -> max_int
              | Some env -> (
                  match Parser.top env with
                  | None -> max_int
                  | Some (Parser.Element (_, _, pos, _)) ->
                      follow_guide (snd (split_pos pos)) env )
            in
            (line, min col col', max col col')
    in
    { line; min_col; max_col; env }

  let attempt r token =
    let _, startp, _ = token in
    let line, col = split_pos startp in
    let more_indented candidate =
      line <> candidate.line && candidate.min_col > col
    in
    let recoveries =
      let rec aux = function
        | x :: xs when more_indented x -> aux xs
        | xs -> xs
      in
      aux r.candidates
    in
    let same_indented candidate =
      line = candidate.line
      || (candidate.min_col <= col && col <= candidate.max_col)
    in
    let recoveries =
      let rec aux = function
        | x :: xs when same_indented x -> x :: aux xs
        | _ -> []
      in
      aux recoveries
    in
    let rec aux = function
      | [] -> `Fail
      | x :: xs -> (
          match feed_token ~allow_reduction:true token x.env with
          | `Fail -> aux xs
          | `Recovered (checkpoint, _) -> `Ok (checkpoint, x.env)
          | `Accept v -> ( match aux xs with `Fail -> `Accept v | x -> x ) )
    in
    aux recoveries

  let decide env =
    let rec nth_state env n =
      if n = 0 then
        match Parser.top env with
        | None -> -1 (*allow giving up recovery on empty files*)
        | Some (Parser.Element (state, _, _, _)) -> Parser.number state
      else
        match Parser.pop env with
        | None ->
            assert (n = 1);
            -1
        | Some env -> nth_state env (n - 1)
    in
    let st = nth_state env 0 in
    match Recovery.recover st with
    | Recovery.Nothing -> []
    | Recovery.One actions -> actions
    | Recovery.Select f -> f (nth_state env Recovery.depth.(st))

  let generate (type a) (env : a Parser.env) =
    let module E = struct
      exception Result of a
    end in
    let shifted = ref None in
    let rec aux acc env =
      match Parser.top env with
      | None -> (None, acc)
      | Some (Parser.Element (_state, _, _startp, endp)) -> (
          let actions = decide env in
          let candidate0 = candidate env in
          let rec eval (env : a Parser.env) : Recovery.action -> a Parser.env =
            function
            | Recovery.Abort -> raise Not_found
            | Recovery.R prod ->
                let prod = Parser.find_production prod in
                Parser.force_reduction prod env
            | Recovery.S (Parser.N n as sym) ->
                let xsym = Parser.X sym in
                if !shifted = None && not (Recovery.nullable n) then
                  shifted := Some xsym;
                let loc =
                  {
                    Location.loc_start = endp;
                    loc_end = endp;
                    loc_ghost = true;
                  }
                in
                let v = Recovery.default_value loc sym in
                Parser.feed sym endp v endp env
            | Recovery.S (Parser.T t as sym) -> (
                let xsym = Parser.X sym in
                if !shifted = None then shifted := Some xsym;
                let loc =
                  {
                    Location.loc_start = endp;
                    loc_end = endp;
                    loc_ghost = true;
                  }
                in
                let v = Recovery.default_value loc sym in
                let token = (Recovery.token_of_terminal t v, endp, endp) in
                match feed_token ~allow_reduction:true token env with
                | `Fail -> assert false
                | `Accept v -> raise (E.Result v)
                | `Recovered (_, env) -> env )
            | Recovery.Sub actions -> List.fold_left eval env actions
          in
          match
            rev_scan_left [] ~f:eval ~init:env actions
            |> List.map (fun env -> { candidate0 with env })
          with
          | exception Not_found -> (None, acc)
          | exception E.Result v -> (Some v, acc)
          | [] -> (None, acc)
          | candidate :: _ as candidates -> aux (candidates @ acc) candidate.env
          )
    in
    let final, candidates = aux [] env in
    (!shifted, final, candidates)

  let generate env =
    let shifted, final, candidates = generate env in
    let candidates =
      rev_filter candidates ~f:(fun t ->
          not (Parser.env_has_default_reduction t.env))
    in
    { shifted; final; candidates = candidate env :: candidates }
end
OCaml

Innovation. Community. Security.