-
Notifications
You must be signed in to change notification settings - Fork 465
/
Copy pathlocation.ml
409 lines (348 loc) · 13.9 KB
/
location.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
open Lexing
let absname = ref false
(* This reference should be in Clflags, but it would create an additional
dependency and make bootstrapping Camlp4 more difficult. *)
type t = Warnings.loc = { loc_start: position; loc_end: position; loc_ghost: bool };;
let in_file name =
let loc = {
pos_fname = name;
pos_lnum = 1;
pos_bol = 0;
pos_cnum = -1;
} in
{ loc_start = loc; loc_end = loc; loc_ghost = true }
;;
let none = in_file "_none_";;
let curr lexbuf = {
loc_start = lexbuf.lex_start_p;
loc_end = lexbuf.lex_curr_p;
loc_ghost = false
};;
let init lexbuf fname =
lexbuf.lex_curr_p <- {
pos_fname = fname;
pos_lnum = 1;
pos_bol = 0;
pos_cnum = 0;
}
;;
let symbol_rloc () = {
loc_start = Parsing.symbol_start_pos ();
loc_end = Parsing.symbol_end_pos ();
loc_ghost = false;
};;
let symbol_gloc () = {
loc_start = Parsing.symbol_start_pos ();
loc_end = Parsing.symbol_end_pos ();
loc_ghost = true;
};;
let rhs_loc n = {
loc_start = Parsing.rhs_start_pos n;
loc_end = Parsing.rhs_end_pos n;
loc_ghost = false;
};;
let input_name = ref "_none_"
let input_lexbuf = ref (None : lexbuf option)
let set_input_name name =
if name <> "" then input_name := name
(* Terminal info *)
let num_loc_lines = ref 0 (* number of lines already printed after input *)
(* Print the location in some way or another *)
open Format
let absolute_path s = (* This function could go into Filename *)
let open Filename in
let s = if is_relative s then concat (Sys.getcwd ()) s else s in
(* Now simplify . and .. components *)
let rec aux s =
let base = basename s in
let dir = dirname s in
if dir = s then dir
else if base = current_dir_name then aux dir
else if base = parent_dir_name then dirname (aux dir)
else concat (aux dir) base
in
aux s
let show_filename file =
let file = if file = "_none_" then !input_name else file in
if !absname then absolute_path file else file
let print_filename ppf file =
Format.fprintf ppf "%s" (show_filename file)
let reset () =
num_loc_lines := 0
(* return file, line, char from the given position *)
let get_pos_info pos =
(pos.pos_fname, pos.pos_lnum, pos.pos_cnum - pos.pos_bol)
;;
let setup_colors () =
Misc.Color.setup !Clflags.color;
Code_frame.setup !Clflags.color
(* ocaml's reported line/col numbering is horrible and super error-prone
when being handled programmatically (or humanly for that matter. If you're
an ocaml contributor reading this: who the heck reads the character count
starting from the first erroring character?) *)
let normalize_range loc =
(* TODO: lots of the handlings here aren't needed anymore because the new
rescript syntax has much stronger invariants regarding positions, e.g.
no -1 *)
let (_, start_line, start_char) = get_pos_info loc.loc_start in
let (_, end_line, end_char) = get_pos_info loc.loc_end in
(* line is 1-indexed, column is 0-indexed. We convert all of them to 1-indexed to avoid confusion *)
(* start_char is inclusive, end_char is exclusive *)
if start_char == -1 || end_char == -1 then
(* happens sometimes. Syntax error for example *)
None
else if start_line = end_line && start_char >= end_char then
(* in some errors, starting char and ending char can be the same. But
since ending char was supposed to be exclusive, here it might end up
smaller than the starting char if we naively did start_char + 1 to
just the starting char and forget ending char *)
let same_char = start_char + 1 in
Some ((start_line, same_char), (end_line, same_char))
else
(* again: end_char is exclusive, so +1-1=0 *)
Some ((start_line, start_char + 1), (end_line, end_char))
let print_loc ppf (loc : t) =
setup_colors ();
let normalized_range = normalize_range loc in
let dim_loc ppf = function
| None -> ()
| Some ((start_line, start_line_start_char), (end_line, end_line_end_char)) ->
if start_line = end_line then
if start_line_start_char = end_line_end_char then
fprintf ppf ":@{<dim>%i:%i@}" start_line start_line_start_char
else
fprintf ppf ":@{<dim>%i:%i-%i@}" start_line start_line_start_char end_line_end_char
else
fprintf ppf ":@{<dim>%i:%i-%i:%i@}" start_line start_line_start_char end_line end_line_end_char
in
fprintf ppf "@{<filename>%a@}%a" print_filename loc.loc_start.pos_fname dim_loc normalized_range
;;
let print ?(src = None) ~message_kind intro ppf (loc : t) =
begin match message_kind with
| `warning -> fprintf ppf "@[@{<info>%s@}@]@," intro
| `warning_as_error -> fprintf ppf "@[@{<error>%s@} (configured as error) @]@," intro
| `error -> fprintf ppf "@[@{<error>%s@}@]@," intro
end;
(* ocaml's reported line/col numbering is horrible and super error-prone
when being handled programmatically (or humanly for that matter. If you're
an ocaml contributor reading this: who the heck reads the character count
starting from the first erroring character?) *)
let (file, start_line, start_char) = get_pos_info loc.loc_start in
let (_, end_line, end_char) = get_pos_info loc.loc_end in
(* line is 1-indexed, column is 0-indexed. We convert all of them to 1-indexed to avoid confusion *)
(* start_char is inclusive, end_char is exclusive *)
let normalizedRange =
(* TODO: lots of the handlings here aren't needed anymore because the new
rescript syntax has much stronger invariants regarding positions, e.g.
no -1 *)
if start_char == -1 || end_char == -1 then
(* happens sometimes. Syntax error for example *)
None
else if start_line = end_line && start_char >= end_char then
(* in some errors, starting char and ending char can be the same. But
since ending char was supposed to be exclusive, here it might end up
smaller than the starting char if we naively did start_char + 1 to
just the starting char and forget ending char *)
let same_char = start_char + 1 in
Some ((start_line, same_char), (end_line, same_char))
else
(* again: end_char is exclusive, so +1-1=0 *)
Some ((start_line, start_char + 1), (end_line, end_char))
in
fprintf ppf " @[%a@]@," print_loc loc;
match normalizedRange with
| None -> ()
| Some _ -> begin
try
(* Print a syntax error that is a list of Res_diagnostics.t.
Instead of reading file for every error, it uses the source that the parser already has. *)
let src = match src with
| Some src -> src
| None -> Ext_io.load_file file
in
(* we're putting the line break `@,` here rather than above, because this
branch might not be reached (aka no inline file content display) so
we don't wanna end up with two line breaks in the the consequent *)
fprintf ppf "@,%s"
(Code_frame.print
~is_warning:(message_kind=`warning)
~src
~startPos:loc.loc_start
~endPos:loc.loc_end
)
with
(* this might happen if the file is e.g. "", "_none_" or any of the fake file name placeholders.
we've already printed the location above, so nothing more to do here. *)
| Sys_error _ -> ()
end
;;
let error_prefix = "Error"
let print_error_prefix ppf =
setup_colors ();
fprintf ppf "@{<error>%s@}" error_prefix;
;;
let print_compact ppf loc =
begin
let (file, line, startchar) = get_pos_info loc.loc_start in
let endchar = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum + startchar in
fprintf ppf "%a:%i" print_filename file line;
if startchar >= 0 then fprintf ppf ",%i--%i" startchar endchar
end
;;
let print_error intro ppf loc =
fprintf ppf "%a%t:" (print ~message_kind:`error intro) loc print_error_prefix;
;;
let default_warning_printer loc ppf w =
match Warnings.report w with
| `Inactive -> ()
| `Active { Warnings. number = _; message = _; is_error; sub_locs = _} ->
setup_colors ();
let message_kind = if is_error then `warning_as_error else `warning in
Format.fprintf ppf "@[<v>@, %a@, %s@,@]@."
(print ~message_kind ("Warning number " ^ (Warnings.number w |> string_of_int)))
loc
(Warnings.message w);
(* at this point, you can display sub_locs too, from e.g. https://github.com/ocaml/ocaml/commit/f6d53cc38f87c67fbf49109f5fb79a0334bab17a
but we won't bother for now *)
;;
let warning_printer = ref default_warning_printer ;;
let print_warning loc ppf w =
!warning_printer loc ppf w
;;
let formatter_for_warnings = ref err_formatter;;
let prerr_warning loc w =
print_warning loc !formatter_for_warnings w;;
let echo_eof () =
print_newline ();
incr num_loc_lines
type 'a loc = {
txt : 'a;
loc : t;
}
let mkloc txt loc = { txt ; loc }
let mknoloc txt = mkloc txt none
type error =
{
loc: t;
msg: string;
sub: error list;
if_highlight: string; (* alternative message if locations are highlighted *)
}
let pp_ksprintf ?before k fmt =
let buf = Buffer.create 64 in
let ppf = Format.formatter_of_buffer buf in
Misc.Color.set_color_tag_handling ppf;
begin match before with
| None -> ()
| Some f -> f ppf
end;
kfprintf
(fun _ ->
pp_print_flush ppf ();
let msg = Buffer.contents buf in
k msg)
ppf fmt
(* taken from https://github.com/rescript-lang/ocaml/blob/d4144647d1bf9bc7dc3aadc24c25a7efa3a67915/parsing/location.ml#L354 *)
(* Shift the formatter's offset by the length of the error prefix, which
is always added by the compiler after the message has been formatted *)
let print_phanton_error_prefix ppf =
(* modified from the original. We use only 2 indentations for error report
(see super_error_reporter above) *)
Format.pp_print_as ppf 2 ""
let errorf ?(loc = none) ?(sub = []) ?(if_highlight = "") fmt =
pp_ksprintf
~before:print_phanton_error_prefix
(fun msg -> {loc; msg; sub; if_highlight})
fmt
let error ?(loc = none) ?(sub = []) ?(if_highlight = "") msg =
{loc; msg; sub; if_highlight}
let error_of_exn : (exn -> error option) list ref = ref []
let register_error_of_exn f = error_of_exn := f :: !error_of_exn
exception Already_displayed_error = Warnings.Errors
let error_of_exn exn =
match exn with
| Already_displayed_error -> Some `Already_displayed
| _ ->
let rec loop = function
| [] -> None
| f :: rest ->
match f exn with
| Some error -> Some (`Ok error)
| None -> loop rest
in
loop !error_of_exn
(* taken from https://github.com/rescript-lang/ocaml/blob/d4144647d1bf9bc7dc3aadc24c25a7efa3a67915/parsing/location.ml#L380 *)
(* This is the error report entry point. We'll replace the default reporter with this one. *)
let rec default_error_reporter ?(src = None) ppf ({loc; msg; sub}) =
setup_colors ();
(* open a vertical box. Everything in our message is indented 2 spaces *)
(* If src is given, it will display a syntax error after parsing. *)
let intro = match src with
| Some _ -> "Syntax error!"
| None -> "We've found a bug for you!"
in
Format.fprintf ppf "@[<v>@, %a@, %s@,@]" (print ~src ~message_kind:`error intro) loc msg;
List.iter (Format.fprintf ppf "@,@[%a@]" (default_error_reporter ~src)) sub
(* no need to flush here; location's report_exception (which uses this ultimately) flushes *)
let error_reporter = ref default_error_reporter
let report_error ?(src = None) ppf err =
!error_reporter ~src ppf err
;;
let error_of_printer loc print x =
errorf ~loc "%a@?" print x
let error_of_printer_file print x =
error_of_printer (in_file !input_name) print x
let () =
register_error_of_exn
(function
| Sys_error msg ->
Some (errorf ~loc:(in_file !input_name)
"I/O error: %s" msg)
| Misc.HookExnWrapper {error = e; hook_name;
hook_info={Misc.sourcefile}} ->
let sub = match error_of_exn e with
| None | Some `Already_displayed -> error (Printexc.to_string e)
| Some (`Ok err) -> err
in
Some
(errorf ~loc:(in_file sourcefile)
"In hook %S:" hook_name
~sub:[sub])
| _ -> None
)
external reraise : exn -> 'a = "%reraise"
let rec report_exception_rec n ppf exn =
try
match error_of_exn exn with
| None -> reraise exn
| Some `Already_displayed -> ()
| Some (`Ok err) -> fprintf ppf "@[%a@]@." (report_error ~src:None) err
with exn when n > 0 -> report_exception_rec (n-1) ppf exn
let report_exception ppf exn = report_exception_rec 5 ppf exn
exception Error of error
let () =
register_error_of_exn
(function
| Error e -> Some e
| _ -> None
)
let raise_errorf ?(loc = none) ?(sub = []) ?(if_highlight = "") =
pp_ksprintf
~before:print_phanton_error_prefix
(fun msg -> raise (Error ({loc; msg; sub; if_highlight})))
let deprecated ?(def = none) ?(use = none) loc msg =
prerr_warning loc (Warnings.Deprecated (msg, def, use))