-
Notifications
You must be signed in to change notification settings - Fork 125
/
opaParser.ml
269 lines (240 loc) · 10.7 KB
/
opaParser.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
(*
Copyright © 2011, 2012 MLstate
This file is part of OPA.
OPA is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License, version 3, as published by
the Free Software Foundation.
OPA is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
more details.
You should have received a copy of the GNU Affero General Public License
along with OPA. If not, see <http://www.gnu.org/licenses/>.
*)
(* CF mli *)
module String = BaseString
(* type alias *)
type filename = string
type contents = string
type nonuid = SurfaceAst.nonuid
(* meta-information *)
let hash = OpaParserVersion.hash
module Opa_parser = struct
module A = OpaSyntax.Args
let select ~js ~classic ?_filename ?_start v =
match (!A.r).A.parser with
| OpaSyntax.Classic -> classic ?_filename ?_start v
| OpaSyntax.Js -> js ?_filename ?_start v
let parse_opa_parser_expr_eoi = select ~js:Opa_js_parser.parse_opa_parser_expr_eoi ~classic:Opa_classic_parser.parse_opa_parser_expr_eoi
let parse_opa_parser_ty_eoi = select ~js:Opa_js_parser.parse_opa_parser_ty_eoi ~classic:Opa_classic_parser.parse_opa_parser_ty_eoi
let parse_opa_parser_main_eoi = select ~js:Opa_js_parser.parse_opa_parser_main_eoi ~classic:Opa_classic_parser.parse_opa_parser_main_eoi
end
(* low level parsing *)
let ll_factory parser_rule ?filename contents =
let _filename = filename in
let _start = None in
let _pos, result = parser_rule ?_filename ?_start contents in
result
let ll_expr = ll_factory Opa_classic_parser.parse_opa_parser_expr_eoi
let ll_ty = ll_factory Opa_parser.parse_opa_parser_ty_eoi
let ll_code = ll_factory Opa_parser.parse_opa_parser_main_eoi
(* ====================================================================================== *)
(* TODO: clean-up *)
exception Specific_parse_error = Parser_utils.Specific_parse_error
let parse_file_content ~filename content =
let _pos, code = Opa_parser.parse_opa_parser_main_eoi ~_filename:filename content in
code
exception No_such_file of string
let parse_file filename =
match File.content_opt filename with
| None -> raise (No_such_file filename)
| Some content -> parse_file_content ~filename content
let parse_content_expr ~filename content =
let _pos, code = Opa_parser.parse_opa_parser_expr_eoi ~_filename:filename content in
code
(* ====================================================================================== *)
module CacheParse =
struct
open Digest
open Unix
let caching_directory = Lazy.lazy_from_val (
Filename.concat (Lazy.force File.mlstate_dir) "opa/cache/parser"
)
let check_directory () =
if Sys.file_exists (Lazy.force caching_directory) then ()
else (
#<If:TESTING>
()
#<Else>
OManager.verbose "opa-parser: creating @{<bright>%s@} to store opa compiler caches" (Lazy.force caching_directory)
#<End>;
if not (File.check_create_path (Lazy.force caching_directory))
then
OManager.error (
"cannot create cache directory @{<bright>%s@}^@\n"^^
"@{<bright>Hint@}:@\n"^^
"You can try to create manually the directory, with permissions 755"
)
(Lazy.force caching_directory)
)
(* cache file naming option *)
(* setting true everywhere minimize disk consumption *)
(* cannot set the 3 to true !!!*)
(* see filename *)
let k_last_version = true (* only last compiler version *) (* for faster debugging *)
let k_last_hash = true (* only last file content *) (* not proof to undo redo *)
let k_last_name = false (* only last file name *) (* not proof to name changing,multiple file with same name *)
let _ = assert( (not k_last_name) || (not k_last_hash))
let verify_last_version = true
let keep_only last value =
if (*keep_only*)last(*value*) then "last" else value
(* name is form of [hash| "last"] concatenation for name hash version
that is why having "last" everywhere is bad
*)
let filename name hash =
let name = String.replace name "/" ":" in
Printf.sprintf "%s/%s-%s-opaparse.%s.bin"
(Lazy.force caching_directory)
(keep_only k_last_name name)
(keep_only k_last_hash hash)
(Unix.gethostname ())
let set name content result =
let internal_version = OpaParserVersion.hash in
let _ = check_directory () in
let hash = to_hex (Digest.string content) in
let filename = filename name hash in
match (try Some (open_out_bin filename)
with Sys_error _msg ->
#<If:PARSER_CACHE_DEBUG>OManager.printf "Failed to write in %s: %s@." filename _msg#<End>;
None
) with
| None -> ()
| Some c ->
Marshal.to_channel c (name,hash,internal_version,(result: (string,'b) SurfaceAst.code)) [];
close_out c
let get name content =
let internal_version = OpaParserVersion.hash in
let _ = check_directory () in
let hash = to_hex (Digest.string content) in
let cache_file = filename name hash in
if Sys.file_exists cache_file then (
match (try Some (open_in_bin cache_file) with Sys_error _ -> None) with
| None -> None
| Some c ->
let (_oldname,oldhash,oldversion,res) =
try (Marshal.from_channel c : string * string * string * (string,'b) SurfaceAst.code)
with End_of_file -> OManager.printf "Bad cache@\n" ; "","","",[]
in
close_in c;
if oldhash=hash && ((not verify_last_version) || oldversion=internal_version) then (Some res)
else None
) else None
end
let rec get_index_N_lines_before s i nl =
if nl=0 then i else
let i' = try String.rindex_from s i '\n' with Not_found | Invalid_argument _ -> 0 in
if i' = 0 then 0 else get_index_N_lines_before s (i'-1) (nl-1)
let get_index_N_lines_after s i nl =
let len = String.length s - 1 in
let rec get i nl =
if nl=0 then i else
let i' = try String.index_from s i '\n' with Not_found | Invalid_argument _ -> len in
if i' = len then len else get (i'+1) (nl-1)
in get i nl
let parse_error_flag =
let search_for = Str.regexp "\\b[Uu][Tt][Ff]-?8\\b" in
try
let lang = try Sys.getenv "LC_CTYPE" with Not_found -> Sys.getenv "LANG" in
let _ = Str.search_forward search_for lang 0 in "⚐"
with Not_found -> "-->"
module OA = OpaSyntax.Args
(* FIXME, use FilePos for obtaining citations etc. *)
let show_parse_error file_name content error_summary error_details pos =
let n = max 0 (min pos (String.length content-1)) in
let begin_citation = get_index_N_lines_before content n 5 in
let length_citation = n - begin_citation in
let begin_error_zone = get_index_N_lines_before content n 0 in
let length_error_zone = min( (get_index_N_lines_after content n 5)-begin_error_zone +1) (String.length content -begin_error_zone) in
let red = Ansi.print `red
and green = Ansi.print `green in
let line, col, gchar =
if pos = -1 then
"??", "??", "??"
else
let line_int, col_int = FilePos.get_pos file_name pos in
string_of_int line_int, string_of_int col_int, string_of_int pos
in
let syntax_old = "classical ('old') syntax " in
let syntax_new = "revised ('new') syntax" in
let option_old = "--parser classic" in
let option_new = "--parser js-like" in
let used, other, hint_option =
match !OA.r.OA.parser with
| OpaSyntax.Classic -> syntax_old, syntax_new, option_new
| OpaSyntax.Js -> syntax_new, syntax_old, option_old
in
(* FIXME: use really format *)
OManager.printf "%s" (
(Printf.sprintf "In %s [%s:%s-%s:%s | global chars=%s-%s]\n%s at line %s, column %s\n"
(red file_name) line col line col gchar gchar
(red error_summary) (red line) (red col)
)
^ (Printf.sprintf "The error may be in the following citation, usually in the %s part (starting at %s) or just before:" (red"red") (parse_error_flag))
^ (Printf.sprintf "\n<<%s%s>>\n"
(green (String.sub content begin_citation length_citation ))
(red (parse_error_flag^(String.sub content begin_error_zone length_error_zone))))
^ (Printf.sprintf "%s: %s\n" (red "Hint") error_details)
^ (Printf.sprintf "%s: You are now using the parser for the %s; if the source uses %s syntax then you should compile with %s option\n"
(red "Another hint") (red used) (red other) (red hint_option)
)
) ;
OManager.error "Syntax error"
(* ====================================================================================== *)
(* high level parsing *)
let hl_factory parser_rule name ?filename contents =
try
ll_factory parser_rule ?filename contents
with
| Trx_runtime.SyntaxError (loc, err) ->
let filename = Option.default name filename in
show_parse_error filename contents "Syntax error" err loc
let expr = hl_factory Opa_parser.parse_opa_parser_expr_eoi "Expression"
let ty = hl_factory Opa_parser.parse_opa_parser_ty_eoi "Type"
let code ?(parser_=(!OA.r).OA.parser) ?(cache=false) ?(filename="") content =
(*print_string content;*)
FilePos.add_file filename content;
match if cache then CacheParse.get filename content else None with
| None ->
let r = OA.r in
let old = (!r).OA.parser in
r := {!r with OA.parser=parser_} ;
#<If:PARSER_CACHE_DEBUG>OManager.printf "Cache @{<red>miss@} for %s@." filename#<End>;
let res =
try
Chrono.measure (fun () -> parse_file_content ~filename content)
(fun f ->
#<If:TESTING>
()
#<Else>
if f > 1.0 && (not BuildInfos.is_release) then
Printf.printf
"Parsing of %s is too long : %1.3f seconds \n%!"
filename f
#<End>)
with
| Trx_runtime.SyntaxError (pos, err) ->
show_parse_error filename content "Syntax error" err pos
| Specific_parse_error (pos, err) ->
show_parse_error filename content "Error" err (FilePos.get_first_char pos) (* FIXME, whole range should be used *)
| err ->
(* All parser error in OPA should use the [Specific_parse_error] exception.
Otherwise we have no location to report... *)
show_parse_error filename content "Error" (Printexc.to_string err) (-1)
in
OManager.flush_errors (); (* make sure that if someone threw errors, then we stop before saving the cache *)
if cache then CacheParse.set filename content res;
r := {!r with OA.parser=old} ;
res
| Some l ->
#<If:PARSER_CACHE_DEBUG>OManager.printf "Cache @{<green>hit@} for %s@." filename#<End>;
l