Source file headers.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
module Field = struct
type t =
| Lang_version
| Problem_version
| Problem_source
| Problem_license
| Problem_category
| Problem_status
let equal = (=)
let compare = compare
let hash = Hashtbl.hash
let name lang field =
match lang, field with
| Some Logic.Smtlib2 _, Lang_version -> ":smt-lib-version"
| Some Logic.Smtlib2 _, Problem_source -> ":source"
| Some Logic.Smtlib2 _, Problem_license -> ":license"
| Some Logic.Smtlib2 _, Problem_category -> ":category"
| Some Logic.Smtlib2 _, Problem_status -> ":status"
| _, Lang_version -> "Language version"
| _, Problem_version -> "Problem version"
| _, Problem_source -> "Problem source"
| _, Problem_license -> "Problem license"
| _, Problem_category -> "Problem_category"
| _, Problem_status -> "Problem status"
let print ?lang fmt field =
Format.fprintf fmt "%s" (name lang field)
module Id = Dolmen.Std.Id
module Loc = Dolmen.Std.Loc
module Ast = Dolmen.Std.Term
type res =
| Ok of t * string
| Error of Loc.t * string
module Smtlib2 = struct
let check_version_number version =
if String.length version >= 3 &&
String.sub version 0 2 = "2." then begin
try
let s = String.sub version 2 (String.length version - 2) in
let _ = int_of_string s in
true
with Failure _ ->
false
end else
false
let rec parse = function
| { Ast.term = Ast.App ({ Ast.term = Ast.Symbol s; _ }, args); loc; _ } ->
parse_aux loc s args
| _ ->
Not_a_header
and parse_aux loc s args =
match s with
| { Id.ns = Attr; Id.name = Simple ":smt-lib-version"; } ->
begin match args with
| [ { Ast.term = Ast.Symbol {
Id.ns = Value Real; Id.name = Simple version; }; _ } ] ->
if check_version_number version then
Ok (Lang_version, version)
else
Error (loc, ":smt-lib-version number must be in the form 2.X")
| [] -> Error (loc, "empty value for :smt-lib-version")
| { Ast.loc; _ } :: _ -> Error (loc, "Expected a version number")
end
| { Id.ns = Attr; Id.name = Simple ":source"; } ->
begin match args with
| [ { Ast.term = Ast.Symbol {
Id.ns = Attr; Id.name = Simple descr }; _ } ] ->
Ok (Problem_source, descr)
| [] -> Error (loc, "empty value for :source")
| { Ast.loc; _ } :: _ -> Error (loc, "Expected a single symbol as description")
end
| { Id.ns = Attr; Id.name = Simple ":license"; } ->
begin match args with
| [ { Ast.term = Ast.Symbol {
Id.ns = Value String; Id.name = Simple license }; _ } ] ->
Ok (Problem_license, license)
| [] -> Error (loc, "empty value for :license")
| { Ast.loc; _ } :: _ -> Error (loc, "Expected a single string in quotes")
end
| { Id.ns = Attr; Id.name = Simple ":category"; } ->
begin match args with
| [ { Ast.term = Ast.Symbol {
Id.ns = Value String;
Id.name = Simple (("crafted"|"random"|"industrial") as category)
}; _ }; ] ->
Ok (Problem_category, category)
| [] -> Error (loc, "empty value for :category")
| { Ast.loc; _ } :: _ ->
Error (loc, {|Expected "crafted", "random", or "industrial" (in quotes)|})
end
| { Id.ns = Attr; Id.name = Simple ":status"; } ->
begin match args with
| [ { Ast.term = Ast.Symbol {
Id.name = Simple (("sat"|"unsat"|"unknown") as status) ; _ }; _ }; ] ->
Ok (Problem_status, status)
| _ -> Error (loc, "Expected sat|unsat|unknown")
end
| _ ->
Not_a_header
end
let parse ?lang t =
match lang with
| Some Logic.Smtlib2 _ -> Smtlib2.parse t
| _ -> Not_a_header
end
let code =
Code.create
~category:"Header"
~descr:"on header errors"
let =
Report.Error.mk ~code ~mnemonic:"header-missing"
~message:(fun fmt (lang, missing) ->
let pp_sep fmt () = Format.fprintf fmt ",@ " in
Format.fprintf fmt "The following header fields are missing: %a"
(Format.pp_print_list ~pp_sep (Field.print ?lang)) missing)
~name:"Missing header statement" ()
let =
Report.Error.mk ~code ~mnemonic:"header-invalid-value"
~message:(fun fmt (field, lang, msg) ->
Format.fprintf fmt "Invalid value for header %a: %s"
(Field.print ?lang) field msg)
~name:"Invalid header value" ()
let =
Report.Error.mk ~code ~mnemonic:"header-bad-payload"
~message:(fun fmt msg ->
Format.fprintf fmt "Could not parse the header: %s" msg)
~name:"Incorrect header payload" ()
let =
Report.Warning.mk ~code ~mnemonic:"empty-header-field"
~message:(fun fmt (lang, l) ->
let pp_sep fmt () = Format.fprintf fmt ",@ " in
Format.fprintf fmt
"The following header fields are missing and thus \
default values will be assumed: %a"
(Format.pp_print_list ~pp_sep (Field.print ?lang)) l)
~name:"Header field with a missing value" ()
module M = Map.Make(Field)
type t = {
fields : string M.t;
}
let empty = {
fields = M.empty;
}
let set h f v = {
fields = M.add f v h.fields;
}
let remove h f = {
fields = M.remove f h.fields;
}
let get h f =
try Some (M.find f h.fields)
with Not_found -> None
let mem h f =
M.mem f h.fields
let smtlib2_required : Field.t list = [
Lang_version;
Problem_source;
Problem_category;
]
let smtlib2_wanted : Field.t list = [
Problem_license;
]
module type S = Headers_intf.S
module Make(State : State.S) = struct
let pipe = "Headers"
let : bool State.key =
State.create_key ~pipe "header_check"
let : t State.key =
State.create_key ~pipe "header_state"
let : string list State.key =
State.create_key ~pipe "header_licenses"
let : string option State.key =
State.create_key ~pipe "header_lang_version"
let init
~header_check:
?header_state:(=empty)
~header_licenses:
~header_lang_version:
st =
st
|> State.set header_check header_check_value
|> State.set header_state header_state_value
|> State.set header_licenses header_licenses_value
|> State.set header_lang_version header_lang_version_value
let check_wanted st h =
let lang = (State.get State.logic_file st).lang in
let wanted =
match lang with
| Some Logic.Smtlib2 _ -> smtlib2_wanted
| _ -> []
in
match List.filter (fun f -> not (mem h f)) wanted with
| [] -> st
| missing ->
State.warn st empty_header_field (lang, missing)
let check_required st h =
let lang = (State.get State.logic_file st).lang in
let required =
match lang with
| Some Logic.Smtlib2 _ -> smtlib2_required
| _ -> []
in
match List.filter (fun f -> not (mem h f)) required with
| [] -> st
| missing -> State.error st missing_header_error (lang, missing)
let check st =
if not (State.get header_check st) then st
else begin
let h = State.get header_state st in
let st = check_wanted st h in
let st = check_required st h in
st
end
let error st loc err param =
let file = (State.get State.logic_file st).loc in
let loc : Dolmen.Std.Loc.full = { file; loc; } in
State.error ~loc st err param
let st loc field msg =
let lang = (State.get State.logic_file st).lang in
error st loc invalid_header_value_error (field, lang, msg)
let st loc field value =
match (field : Field.t) with
| Lang_version ->
begin match State.get header_lang_version st with
| None -> st
| Some v ->
if v = value then st
else invalid_header_value st loc Lang_version
(Format.sprintf "language version must be: %s" v)
end
| Problem_license ->
begin match State.get header_licenses st with
| [] -> st
| allowed ->
if List.mem value allowed then st
else invalid_header_value st loc Problem_license
"this license is not in the list of allowed licenses"
end
| _ -> st
let inspect st c =
if not (State.get header_check st) then (st, c)
else begin
let lang = (State.get State.logic_file st).lang in
let h = State.get header_state st in
let st =
match (c : Dolmen.Std.Statement.t) with
| { descr = Set_info t; loc; _ } ->
begin match Field.parse ?lang t with
| Not_a_header -> st
| Error (loc, msg) ->
error st loc bad_header_payload msg
| Ok (field, value) ->
let st = check_header st loc field value in
let st = State.set header_state (set h field value) st in
st
end
| { descr = Prove _; loc; _ } ->
if mem h Problem_status then
State.set header_state (remove h Problem_status) st
else
error st loc missing_header_error (lang, [Problem_status])
| _ -> st
in
st, c
end
end