-
Notifications
You must be signed in to change notification settings - Fork 125
/
opaParser.ml
233 lines (207 loc) · 8.93 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
(*
Copyright © 2011 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
(* 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_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 -> 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 -> len in
if i' = len then len else get (i'+1) (nl-1)
in get i nl
(* 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
(* 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 ⚐) or just before:" (red"red"))
^ (Printf.sprintf "\n<<%s%s>>\n"
(green (String.sub content begin_citation length_citation ))
(red ("⚐"^(String.sub content begin_error_zone length_error_zone))))
^ (Printf.sprintf "Hint: %s\n" error_details)
) ;
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 ?(cache=false) ?(filename="") content =
(*print_string content;*)
FilePos.add_file filename content;
match if cache then CacheParse.get filename content else None with
| None ->
#<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;
res
| Some l ->
#<If:PARSER_CACHE_DEBUG>OManager.printf "Cache @{<green>hit@} for %s@." filename#<End>;
l