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
410
411
412
413
414
415
416
417
(** Functions managing positions *)
(** Position in AST resulting from parsetree may be a non negligible part of
parsing complexity, both in speed and space. For instance: - computing column
number in Unicode is costly - all information about a position takes a lot of
machine words (up to 10)
Moreover, in most cases, position are only used in error messages!
Pacomb proposes to use three levels of positions to minimise the costs:
1°) the type Pos.t = Input.spos is the type return to action. It is a
position in bytes together with some information about the file.
2°) type type 'a located (or pos = unit located) is some data decorated with
a start and end position. This is the type recommended to be used in AST. it
can be constructed by the in_pos, no_pos and mk_pos functions below. Each
AST node located with a position costs 5 words.
3°) the type pos_info contain all the information about a position. It is
produced by rescanning the file (or the string). If one parses a stream which
is not a regular file, or if the regular file is no more available, the
position is not really useful and pacomb will still print the byte position.
*)
(** byte position from input, type type returned by parsing combinator *)
type spos = Input.spos
(** Type to represent data with a position *)
type pos =
{ offset_start : int
; offset_end : int
; infos : Input.infos }
let mk_pos s e infos =
{ offset_start = Input.int_of_byte_pos s
; offset_end = Input.int_of_byte_pos e
; infos }
(** merging of two positions, create the smallest position pair containing both
*)
let merge : pos -> pos -> pos = fun p1 p2 ->
{ p1 with
offset_start = min p1.offset_start p2.offset_end
; offset_end = max p1.offset_end p2.offset_end }
let phantom_pos =
{ offset_start = -1
; offset_end = -1
; infos = Input.phantom_infos }
let no_pos = phantom_pos
let has_pos p =
p.offset_start <> -1 &&
p.offset_end <> -1
let file_cache = Hashtbl.create 32
let str_cache = Hashtbl.create 32
exception No_detailed_position
exception File_changed
let init_file_cache file_name length date =
try
let ch = open_in file_name in
let open Unix in
let s = fstat (descr_of_in_channel ch) in
if s.st_size <> length || s.st_mtime <> date then raise File_changed;
let r =(ch, ref []) in
Hashtbl.add file_cache file_name r;
r
with
| Sys_error _ -> raise No_detailed_position
let init_str_cache str =
let r =ref [] in
Hashtbl.add str_cache str r;
r
type pos_info =
{ start_line : int
; start_col : int
; start_line_offset : int
; start_byte : int
; end_line : int
; end_col : int
; end_line_offset : int
; end_byte : int
; file_name : string
; text : string }
let cache_interval = 1024
let ch_sub_string ch n p =
seek_in ch n;
let s = p-n in
let buf = Bytes.create s in
really_input ch buf 0 s;
Bytes.unsafe_to_string buf
let ch_col_num ch utf8 n p =
if utf8 = Utf8.ASCII then p - n
else
begin
let str = ch_sub_string ch n p in
Utf8.length utf8 str
end
let str_col_num str utf8 n p =
if utf8 = Utf8.ASCII then p - n
else
begin
let str = String.sub str n (p-n) in
Utf8.length utf8 str
end
let pos_info
: ?relocate:(string -> string) -> ?text:bool -> pos -> pos_info
= fun ?(relocate=fun x -> x) ?(text=false) pos ->
let file_name, cache, seek_in, input_char, col_num, sub_string =
match Input.stream_infos pos.infos with
| File { name; length; date } ->
let name0 = relocate name in
let (ch, cache) = try Hashtbl.find file_cache name0
with Not_found -> init_file_cache name0 length date
in
let seek_in = seek_in ch in
let input_char () = input_char ch in
(name, cache, seek_in, input_char, ch_col_num ch, ch_sub_string ch)
| String str ->
let cache = try Hashtbl.find str_cache str
with Not_found -> init_str_cache str
in
let pos = ref 0 in
let seek_in n = pos := n in
let input_char () =
if !pos >= String.length str then raise End_of_file;
let x = !pos in incr pos;
str.[x]
in
("", cache, seek_in, input_char, str_col_num str, String.sub str)
| Stream ->
raise No_detailed_position
in
let n0 = pos.offset_start in
let n1 = pos.offset_end in
if n0 = -1 || n1 = -1 then raise No_detailed_position;
let utf8 = Input.utf8 pos.infos in
let rec fn n = function
| (p, _ , _)::ls when p > n -> fn n ls
| c ::_ -> c
| [] -> (0, 0, 1)
in
let rec gn n p lo ln =
if p mod cache_interval = 0 then cache := (p, lo, ln) :: !cache;
if p = n then (lo, ln) else
begin
assert(p < n);
let c = try input_char () with End_of_file -> assert false in
let p = p + 1 in
if c = '\n' then gn n p p (ln + 1)
else gn n p lo ln
end
in
let to_eol n =
let rec fn n =
try
let c = input_char () in
if c = '\n' then n else fn (n+1)
with
End_of_file -> n
in
seek_in n;
fn n
in
let (start_line_offset, start_line) =
let (p, lo, ln) = fn n0 !cache in
seek_in p;
gn n0 p lo ln
in
let (end_line_offset , end_line) =
let (p, lo, ln) = fn n1 !cache in
seek_in p;
gn n1 p lo ln
in
let start_col = col_num utf8 start_line_offset n0 in
let end_col = col_num utf8 end_line_offset n1 in
let text = if text then sub_string start_line_offset (to_eol n1)
else ""
in
{ start_line; start_col; start_line_offset
; end_line ; end_col ; end_line_offset
; start_byte = n0; end_byte = n1
; file_name ; text }
type quote =
{ numbers : bool
; prefix : string
; header : string
; footer : string
; enlight : string -> string }
type style =
| OCaml
| Short
let decorate : int -> string -> string = fun width s ->
if s = "" then
String.make width '='
else
let n = width - String.length s in
let n1 = n / 2 in
let n2 = n - n1 - 2 in
String.make n1 '=' ^ " " ^ s ^ " " ^ String.make n2 '='
let _red : string -> string =
fun s -> "\027[0m\027[31m" ^ s ^ "\027[0m"
let ulined : string -> string =
fun s -> "\027[0m\027[4m" ^ s ^ "\027[0m"
let default_quote =
{ numbers = true
; prefix = ""
; header = ""
; footer = ""
; enlight = ulined }
let quote_text : quote -> Format.formatter -> pos_info -> unit =
fun quote ch pos ->
let open Format in
if pos.text = "" then () else
let lines = String.split_on_char '\n' pos.text in
let start = pos.start_line in
let max_num =
String.length (string_of_int (List.length lines + start - 1))
in
let last = List.length lines - 1 in
let print i line =
let line =
if i = 0 && i = last then
begin
let byte_offset1 = pos.start_byte - pos.start_line_offset in
let byte_offset2 = pos.end_byte - pos.end_line_offset in
let s1 = byte_offset2 - byte_offset1 in
let s2 = String.length line - byte_offset2 in
String.sub line 0 byte_offset1 ^
quote.enlight (String.sub line byte_offset1 s1) ^
String.sub line byte_offset2 s2
end
else if i = 0 then
begin
let byte_offset = pos.start_byte - pos.start_line_offset in
let s = String.length line - byte_offset in
String.sub line 0 byte_offset ^
quote.enlight (String.sub line byte_offset s)
end
else if i = last then
begin
let byte_offset = pos.end_byte - pos.end_line_offset in
let s = String.length line - byte_offset in
quote.enlight (String.sub line 0 byte_offset) ^
String.sub line byte_offset s
end
else quote.enlight line
in
let number =
if quote.numbers then
let num = string_of_int (i + start) in
let pad = String.make (max_num - String.length num) ' ' in
pad ^ num ^ "|"
else ""
in
fprintf ch "%s%s%s\n" quote.prefix number line
in
if quote.header <> "" then fprintf ch "%s\n" quote.header;
List.iteri print lines;
if quote.footer <> "" then
fprintf ch "%s%s" quote.prefix quote.footer
let print_spos ?(style=OCaml) () ch ((infos,n):spos) =
let open Format in
if n = Input.phantom_byte_pos then
fprintf ch "NO POSITION"
else
let n = Input.int_of_byte_pos n in
let name = Input.filename infos in
if name = "" then
let format : (_,_,_) format = match style with
| OCaml -> "character %d"
| Short -> "%d"
in
fprintf ch format n
else
let format : (_,_,_) format = match style with
| OCaml -> "File %S, character %d"
| Short -> "%S:%d"
in
fprintf ch format name n
let print_pos ?(style=OCaml) () ch pos =
let open Format in
let n1 = pos.offset_start in
let n2 = pos.offset_end in
if n1 = -1 || n2 = -1 then
fprintf ch "NO POSITION"
else
let name = Input.filename pos.infos in
if name = "" then
let format : (_,_,_) format = match style with
| OCaml -> "character %d-%d"
| Short -> "%d-%d"
in
fprintf ch format n1 n2
else
let format : (_,_,_) format = match style with
| OCaml -> "File %S, character %d to %d"
| Short -> "%S:%d:%d"
in
fprintf ch format name n1 n2
let print_pos_info ?(style=OCaml) ?quote () ch (pos:pos_info) =
let open Format in
let str_pos =
if pos.file_name = "" then
if pos.start_line = pos.end_line then
if pos.start_col = pos.end_col then
let format : (_,_,_) format = match style with
| OCaml -> "line %d, characters %d"
| Short -> "%d:%d"
in
sprintf format pos.start_line pos.start_col
else
let format : (_,_,_) format = match style with
| OCaml -> "line %d, characters %d-%d"
| Short -> "%d:%d-%d"
in
sprintf format pos.start_line pos.start_col pos.end_col
else
let format : (_,_,_) format = match style with
| OCaml -> "line %d, character %d - line %d, character %d"
| Short -> "%d:%d-%d:%d"
in
sprintf format pos.start_line
pos.start_col pos.end_line pos.end_col
else
if pos.start_line = pos.end_line then
if pos.start_col = pos.end_col then
let format : (_,_,_) format = match style with
| OCaml -> "File %S, line %d, characters %d"
| Short -> "%S:%d:%d"
in
sprintf format pos.file_name pos.start_line pos.start_col
else
let format : (_,_,_) format = match style with
| OCaml -> "File %S, line %d, characters %d-%d"
| Short -> "%S:%d:%d-%d"
in
sprintf format pos.file_name pos.start_line
pos.start_col pos.end_col
else
let format : (_,_,_) format = match style with
| OCaml -> "File %S, line %d, character %d - line %d, character %d"
| Short -> "%S:%d:%d-%d:%d"
in
sprintf format pos.file_name pos.start_line
pos.start_col pos.end_line pos.end_col
in
match quote with
| None -> fprintf ch "%s" str_pos
| Some q -> quote_text { q with header = decorate 79 str_pos;
footer = decorate 70 "" }
ch pos
let print_spos ?(style=OCaml) ?quote () ch (infos,offset as p) =
try
let p2 = mk_pos offset offset infos in
print_pos_info ~style ?quote () ch (pos_info ~text:(quote<>None) p2)
with No_detailed_position ->
print_spos ~style () ch p
let print_pos ?(style=OCaml) ?quote () ch p =
try
print_pos_info ~style ?quote () ch (pos_info ~text:(quote<>None) p)
with No_detailed_position ->
print_pos ~style () ch p
let print_buf_pos ?(style=OCaml) ?quote () ch (buf,idx) =
print_spos ~style ?quote () ch (Input.spos buf idx)
(** exception returned by the parser *)
exception Parse_error of Input.buffer * Input.idx * string list
let fail_no_parse (_:exn) = exit 1
(** A helper to handle exceptions *)
let handle_exception ?(error=fail_no_parse) ?(style=OCaml) f a =
try f a with Parse_error(buf, pos, msgs) as e ->
let red fmt = "\027[31m" ^^ fmt ^^ "\027[0m%!" in
Format.eprintf (red "Parse error: %a.\n%!")
(print_buf_pos ~style ()) (buf, pos);
if msgs <> [] then
begin
let open Format in
let prl ch l = List.iter (fprintf ch "%s@ ") l in
eprintf "@[<hov 2>expecting:@ %a@]@." prl msgs
end;
error e