Source file StrategyDebugger.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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
open Server
module Md = Markdown
type diagnostic = {
message : string ;
severity : [ `Ok | `Ignored | `Warning | `Error ] ;
location : Cil_types.location option ;
}
let valid ~loc ~message =
{ message ; severity = `Ok ; location = Some loc }
let ignored ~loc ~reason =
{ message = reason ; severity = `Ignored ; location = Some loc }
let warning ~loc ~message =
{ message ; severity = `Warning ; location = Some loc }
let error ~loc ~message =
{ message ; severity = `Error ; location = Some loc }
type field = {
label: string ;
title: string ;
value: string ;
debug: string ;
target: Ptip.target ;
}
type alternative = {
location: Cil_types.location option ;
diagnostic: diagnostic list ;
fields: field list ;
}
let result ?loc ?(fields=[]) diagnostic =
{ location = loc ; diagnostic ; fields }
let failed ?loc message =
result ?loc [{ message ; severity = `Error ; location = loc }]
let package =
Package.package ~plugin:"wp"
~name:"strategydebugger"
~title:"WP Strategy Debugger" ()
module Range : Data.S with type t = Cil_types.location =
struct
type t = Cil_types.location
let jtype =
Data.declare ~package ~name:"range" @@
Jrecord [ "offset", Jnumber ; "length", Jnumber ]
let to_json (loc : t) =
let offset = Filepos.input_offset (fst loc) in
let length = Filepos.input_offset (snd loc) - offset in
`Assoc [ "offset", `Int offset ; "length", `Int length ]
let of_json _ =
failwith "Wp.StrategyDebugger.Range" [@coverage off]
end
module Target : Data.S with type t = Ptip.target =
struct
type t = Ptip.target
module WpTipApioTerm = Data.Joption(WpTipApi.Term)
let jtype =
Data.declare ~package ~name:"target" @@
Jrecord [
"part", WpTipApi.Part.jtype ;
"term", WpTipApioTerm.jtype ;
]
let to_json tgt =
let part = match fst tgt with
| Ptip.Term -> `Term
| Ptip.Goal -> `Goal
| Ptip.Step s -> `Step s.id in
let term = snd tgt in
`Assoc [
"part" , WpTipApi.Part.to_json part ;
"term" , WpTipApioTerm.to_json term ;
]
let of_json _ =
failwith "Wp.StrategyDebugger.Target" [@coverage off]
end
module Field : Data.S with type t = field =
struct
type t = field
let jtype =
Data.declare ~package ~name:"field" @@
Jrecord [
"label", Jstring ;
"title", Jstring ;
"value", Jstring ;
"debug", Jstring ;
"target" , Target.jtype ;
]
let to_json fd =
`Assoc [
"label" , `String fd.label ;
"title" , `String fd.title ;
"value" , `String fd.value ;
"debug" , `String fd.debug ;
"target" , Target.to_json fd.target ;
]
let of_json _ =
failwith "Wp.StrategyDebugger.Field" [@coverage off]
end
module Fields = Data.Jlist(Field)
module RangeOpt = Data.Joption(Range)
module Diagnostic : Data.S with type t = diagnostic =
struct
type t = diagnostic
let jseverity_tag = function
| `Ok -> "Ok"
| `Ignored -> "Ignored"
| `Warning -> "Warning"
| `Error -> "Error"
let jseverity =
Data.declare ~package ~name:"severity" @@
Junion [
Jtag "Ok" ;
Jtag "Ignored" ;
Jtag "Warning" ;
Jtag "Error" ;
]
let jtype =
Data.declare ~package ~name:"diagnostic" @@
Package.(Jrecord [
"message", Jstring ;
"severity", jseverity ;
"range", RangeOpt.jtype ;
])
let to_json diag = `Assoc [
"severity" , `String (jseverity_tag diag.severity) ;
"message" , `String diag.message ;
"range" , RangeOpt.to_json diag.location ;
]
let of_json _ =
failwith "Wp.StrategyDebugger.Diag" [@coverage off]
end
module Diagnostics = Data.Jlist(Diagnostic)
module Alternative : Data.S with type t = alternative =
struct
type t = alternative
let jtype =
Data.declare ~package ~name:"alternative" @@
Package.(Jrecord [
"location", RangeOpt.jtype ;
"diagnostics", Diagnostics.jtype ;
"fields", Fields.jtype ;
])
let to_json alt =
`Assoc [
"location" , RangeOpt.to_json alt.location ;
"diagnostics" , Diagnostics.to_json alt.diagnostic ;
"fields", Fields.to_json alt.fields ;
]
let of_json _ =
failwith "Wp.StrategyDebugger.Alternative_result" [@coverage off]
end
module Alternatives = Data.Jlist(Alternative)
exception ParseError of Cil_types.location * string
let set_initial_position dest_lexbuf src_pos =
dest_lexbuf.Lexing.lex_curr_p <- src_pos;
dest_lexbuf.lex_abs_pos <- src_pos.pos_cnum
let parse_string s =
let open Current_loc.Operators in
let path = Filepath.of_string "<user-string>" in
let s = String.cat s "\n" in
let column = String.length s in
let line =
let i = ref 0 in
String.iter (function '\n' -> incr i | _ -> ()) s ; !i in
let pbeg = Filepos.make ~path ~line:0 ~column:0 ~offset:0 () in
let pend = Filepos.make ~path ~line ~column ~offset:0 () in
let lb = Lexing.from_string s in
let get_loc () =
Filepos.of_lexing_pos @@ Lexing.lexeme_start_p lb,
Filepos.of_lexing_pos @@ Lexing.lexeme_end_p lb
in
let<> UpdatedCurrentLoc = (pbeg, pend) in
set_initial_position lb (Filepos.to_lexing_pos pbeg);
try Logic_parser.lexpr_list_eof Logic_lexer.token lb
with
| Logic_utils.Not_well_formed (loc, msg) ->
raise (ParseError (loc, msg))
| Logic_lexer.Error (_, msg) ->
raise (ParseError(get_loc (), msg))
| Parsing.Parse_error ->
let loc = get_loc () in
let tok = Lexing.lexeme lb in
let msg =
if tok = "" then "unexpected end of strategy" else
Printf.sprintf "unexpected token %S" tok in
raise (ParseError (loc, msg))
let parse_string s =
Logic_env.builtin_types_as_typenames () ;
let finally = Logic_env.reset_typenames in
let work () = parse_string s in
Fun.protect ~finally work
let rec pp_selection (printer : Ptip.pseq) fmt = function
| Tactical.Empty ->
Format.pp_print_string fmt "None."
| Inside(_,t) ->
Format.fprintf fmt "Term: %a" printer#pp_term t
| Clause (Goal p) -> Format.fprintf fmt "Goal: %a" printer#pp_pred p
| Clause (Step s) -> printer#pp_step fmt s
| Compose(Cint k) ->
Format.fprintf fmt "Value: %a" Z.pretty k
| Compose(Range(a,b)) ->
Format.fprintf fmt "Range: %d..%d" a b
| Compose(Code(e,_,_)) ->
Format.fprintf fmt "@[<hov 2>Calc: %a@]" printer#pp_term e ;
| Multi es ->
Format.fprintf fmt "@[<hov 2>Multi:" ;
List.iter (Format.fprintf fmt "@ %a;" @@ pp_selection printer) es ;
Format.fprintf fmt "@]"
let field ~label ?(title="") (printer : Ptip.pseq) pvalue =
let value = Format.asprintf "%a" (pp_selection printer) pvalue in
let debug = Format.asprintf "%a" Tactical.pp_selection pvalue in
let target = printer#selection_to_target pvalue in
{ label ; title ; value ; debug ; target }
type parameter =
| Selection of Tactical.selection
| String of string
let debug_table printer ?select ?(params=[]) sigma =
let selection =
match select with
| None -> []
| Some pvalue -> [field ~label:"Selection" printer pvalue] in
let params =
List.map
begin fun (a, param) ->
let label = Format.asprintf "Parameter %S" a in
match param with
| Selection sel -> field ~label printer sel
| String s ->
{ label ; title = "" ; value = s ; debug = s ; target = Term, None }
end params
in
let matched = ref [] in
Pattern.iter_sigma
(fun name pvalue ->
let label =
if name = "" then "Pattern" else
if name.[0] = '$' then Printf.sprintf "Pattern %s" name else
Printf.sprintf "Variable %s" name in
let title =
match Hashtbl.find_opt debug_table name with
| None -> "Pattern variable"
| Some pattern -> Format.asprintf "%a" Pattern.pp_pattern pattern
in let fd = field ~label ~title printer pvalue in
matched := fd :: !matched
) sigma ;
let by_name f g = String.compare f.label g.label in
selection @ params @ List.sort by_name !matched
let parameter (t : Tactical.tactical) (a: string ProofStrategy.loc) =
try List.find (fun p -> Tactical.pident p = a.value) t#params
with Not_found ->
Format.kasprintf
(fun e -> raise (Pattern.TypeError(a.loc, e)))
"Parameter '%s' not found" a.value
let configure_parameter env tactic sigma (a,v) =
a.ProofStrategy.value,
match parameter tactic a with
| Checkbox _ | Spinner _ | Composer _ ->
ProofStrategy.configure env tactic sigma (a, v) ;
Selection(Pattern.select sigma v)
| Selector _ | Search _ ->
ProofStrategy.configure env tactic sigma (a, v) ;
String(Pattern.string v)
let configure env sigma tactical params =
let fold_parameter (sels, diags) (a, v) =
try (configure_parameter env tactical sigma (a, v) :: sels), diags
with Pattern.TypeError(loc, message) -> sels, (error ~loc ~message :: diags)
in
List.fold_left fold_parameter ([], []) params
let debug_apply ~loc (tactical : Tactical.tactical) select sequent =
let pool = Lang.new_pool ~vars:(Conditions.vars_seq sequent) () in
let console = new ProofScript.console ~pool ~title:"debug" in
match Lang.local ~pool (tactical#select console) select with
| exception exn ->
let message =
Format.asprintf
"Tactic configuration error (%s)"
(Printexc.to_string exn) in
[ error ~loc ~message ]
| Not_configured ->
let message =
match console#get_error with
| Some msg -> msg
| None -> "Tactic configuration error"
in [ error ~loc ~message ]
| Not_applicable ->
[ warning ~loc ~message:"Tactic cannot be applied" ]
| Applicable _ ->
[ valid ~loc ~message:"Applicable tactic" ]
let debug_tactic env ctxt loc (tac: ProofStrategy.tactic) node =
match node with
| None -> result ~loc [valid ~loc ~message:"Valid tactic (syntax only)"]
| Some node ->
let printer = WpTipApi.lookup_printer node in
let dtable = ProofStrategy.debug_table ctxt in
let sequent = snd @@ Wpo.compute @@ ProofEngine.goal node in
let rec apply_all sigma = function
| [] ->
let goal = if tac.lookup = [] then Some (snd sequent) else None in
let tactical = ProofStrategy.tactical tac.tactic in
let select = ProofStrategy.select sigma ?goal tac.select in
let params, diags = configure env sigma tactical tac.params in
let fields = extract_matchings dtable printer ~select ~params sigma in
if diags <> [] then
result ~loc ~fields diags
else
result ~loc ~fields @@ debug_apply ~loc tactical select sequent
| p::ps ->
match Pattern.psequent p sigma sequent with
| Some sigma ->
apply_all sigma ps
| None ->
let loc = Pattern.pattern_loc p.pattern in
let fields = extract_matchings dtable printer sigma in
let diag = warning ~loc ~message:"Unmatched pattern" in
result ~loc ~fields [diag]
in apply_all Pattern.empty tac.lookup
let debug_alternative ctxt strategy node alt =
let mk_result diags = Some (result ~loc:alt.ProofStrategy.loc diags) in
let env = Pattern.env ~raise:true () in
try
match alt with
| ProofStrategy.{ value = Default } ->
None
| { value = Strategy s } ->
if s.value <> strategy then ProofStrategy.typecheck_strategy env s ;
let reason = "Debugging is not recursively applied" in
mk_result [ignored ~loc:s.loc ~reason]
| { value = Auto a } ->
ProofStrategy.typecheck_auto env a ;
let reason = "Debugging is not recursively applied" in
mk_result [ignored ~loc:a.loc ~reason]
| { value = Provers (provers, _) } ->
let diag prover =
try ProofStrategy.typecheck_prover env prover ; None
with Pattern.TypeError (loc, message) -> Some(error ~loc ~message)
in
let reason = "Debugging does not execute provers" in
mk_result @@
begin
match List.filter_map diag provers with
| [] -> [ignored ~loc:alt.loc ~reason]
| l -> l
end
| { value = Tactic t ; loc = alt_loc } ->
ProofStrategy.typecheck_tactic env t ;
Some (debug_tactic env ctxt alt_loc t node)
with Pattern.TypeError(loc, message) ->
mk_result [error ~loc ~message]
exception Empty
let debug strategy ?node () =
let ctxt = ProofStrategy.context () in
let parse string =
match parse_string string with
| [] ->
raise Empty
| Logic_ptree.{ lexpr_node = PLnamed(value, p) } :: ps ->
value, ProofStrategy.parse_alternatives ctxt (p :: ps)
| ps ->
"", ProofStrategy.parse_alternatives ctxt ps
in
try match parse strategy with
| exception Empty -> []
| exception ParseError (loc, message)
| exception Pattern.TypeError (loc, message) ->
[ failed ~loc message ]
| strategy, alternatives ->
List.filter_map (debug_alternative ctxt strategy node) alternatives
with exn ->
[ failed @@ Printf.sprintf "Failure (%s)" (Printexc.to_string exn) ]
let () =
let signature = Request.signature ~output:(module Alternatives) () in
let get_text = Request.param signature ~name:"strategy"
~descr:(Md.plain "Strategy text")
~default:"" (module Data.Jstring) in
let get_node = Request.param_opt signature ~name:"node"
~descr:(Md.plain "Node to check strategy on (optional)")
(module WpTipApi.Node) in
Request.register_sig ~package ~kind:`GET ~name:"debug"
~descr:(Md.plain "Debug strategy")
signature
begin fun rq () ->
let text = get_text rq in
let node = get_node rq in
debug text ?node ()
end
module Test =
struct
[@@@ coverage off]
let equal_diagnostic d1 d2 =
let r = d1.severity = d2.severity in
if r then String.equal d1.message d2.message else r
let equal_field f1 f2 =
let l1 = [ f1.label ; f1.title ; f1.value ; f1.debug ] in
let l2 = [ f2.label ; f2.title ; f2.value ; f2.debug ] in
List.equal String.equal l1 l2
let equal_alternative a1 a2 =
let r = List.equal equal_diagnostic a1.diagnostic a2.diagnostic in
if r then List.equal equal_field a1.fields a2.fields
else r
let equal_alternatives l1 l2 = List.equal equal_alternative l1 l2
let error = error ~loc:Fileloc.unknown
let ignored = ignored ~loc:Fileloc.unknown
let valid = valid ~loc:Fileloc.unknown
let debug ?node content () =
let res = debug ?node content () in
ignore @@ Alternatives.to_json res ;
res
end
let%test "Empty alternatives" =
let content = {||} in
let alts = Test.debug content () in
let expected = [] in
Test.equal_alternatives alts expected
let%test "Silently ignore default" =
let content = {|\default|} in
let alts = Test.debug content () in
let expected = [] in
Test.equal_alternatives alts expected
let%test "Recursion (ignored)" =
let content = {|name: name|} in
let alts = Test.debug content () in
let ignored = Test.ignored ~reason:"Debugging is not recursively applied" in
let expected = [result [ignored]] in
Test.equal_alternatives alts expected
let%test "Syntax error: unexpected end" =
let content = {|\tactic(|} in
let alts = Test.debug content () in
let error = Test.error ~message:"unexpected end of strategy" in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Syntax error: unexpected token" =
let content = {|name: +,|} in
let alts = Test.debug content () in
let error = Test.error ~message:"unexpected token \",\"" in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Syntax error: wide strings" =
let content = {|L"name": a|} in
let alts = Test.debug content () in
let error = Test.error ~message:"Wide strings are not allowed as labels." in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Lexer error" =
let content = {|name: */|} in
let alts = Test.debug content () in
let error = Test.error ~message:"lexical error, unexpected block-comment closing" in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Unexisting strategy" =
let content = {|name: unexisting|} in
let alts = Test.debug content () in
let error = Test.error ~message:"Strategy 'unexisting' undefined (skipped)." in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Existing strategy" = true
let%test "Unexisting deprecated strategy" =
let content = {|\auto("unexisting")|} in
let alts = Test.debug content () in
let error = Test.error ~message:"Auto-Strategy 'unexisting' not found (skipped)." in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Existing deprecated strategy" =
let content = {|\auto("wp:bitrange")|} in
let alts = Test.debug content () in
let ignored = Test.ignored ~reason:"Debugging is not recursively applied" in
let expected = [result [ignored]] in
Test.equal_alternatives alts expected
let%test "Unexisting provers" =
let content = {|\prover("alt-ergo", "fake", "other")|} in
let alts = Test.debug content () in
let unknown_prover name =
Test.error
~message:(Format.asprintf "Prover '%s' not found (skipped)." name)
in
let expected = [result @@ List.map unknown_prover [ "fake" ; "other" ]] in
Test.equal_alternatives alts expected
let%test "Existing provers" =
let content = {|\prover("alt-ergo")|} in
let alts = Test.debug content () in
let ignored = Test.ignored ~reason:"Debugging does not execute provers" in
let expected = [result [ignored]] in
Test.equal_alternatives alts expected
let%test "Basic type error in pattern" =
let content = {|\tactic("Wp.range", \pattern( (0..x) ))|} in
let alts = Test.debug content () in
let error = Test.error ~message:"Invalid bound (int expected)" in
let expected = [result [error]] in
Test.equal_alternatives alts expected
let%test "Syntactically correct tactic" =
let content = {|\tactic("Wp.range", \pattern(_))|} in
let alts = Test.debug content () in
let valid = Test.valid ~message:"Valid tactic (syntax only)" in
let expected = [result [valid]] in
Test.equal_alternatives alts expected