-
Notifications
You must be signed in to change notification settings - Fork 108
/
Insulin.thy
317 lines (290 loc) · 12.9 KB
/
Insulin.thy
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
(*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*)
(*
* Insulin.thy: regulate sugar in terms, thms and proof goals.
*
* Usage:
* This theory provides the improper commands
* desugar_term <term> <str> <str>...
* desugar_thm <thm> <str> <str>...
* desugar_goal [<goal_num>] <str> <str>...
* which print the term, thm or subgoal(s) with the syntax <str> replaced by the
* underlying constants.
*
* For example:
* > thm fun_upd_apply
* (?f(?x := ?y)) ?z = (if ?z = ?x then ?y else ?f ?z)
*
* But what is this ":="?
* > desugar_thm fun_upd_apply ":="
* Fun.fun_upd ?f ?x ?y ?z = (if ?z = ?x then ?y else ?f ?z)
*
* Remove all sugar:
* > desugar_thm fun_upd_apply "=" "if"
* HOL.eq (Fun.fun_upd ?f ?x ?y ?z)
* (HOL.If (HOL.eq ?z ?x) ?y (?f ?z))
*
* Desugar only one subterm:
* > desugar_thm fun_upd_apply "?z = ?x"
* (?f(?x := ?y)) ?z = (if HOL.eq ?z ?x then ?y else ?f ?z)
*
* Another example (l4v libraries):
* > desugar_term "\<lbrace> P and Q \<rbrace> f \<lbrace> \<lambda>r _. r \<in> {0..<5} \<rbrace>!" "\<lbrace>"
* Nondet_Total.validNF (P and Q) f (\<lambda>r _. r \<in> {0\<Colon>'b..<5\<Colon>'b})
*
* Desugar multiple operators:
* > desugar_term "\<lbrace> P and Q \<rbrace> f \<lbrace> \<lambda>r _. r \<in> {0..<5} \<rbrace>!" "\<lbrace>" "and" ".."
* Nondet_Monad.validNF (Lib.pred_conj P Q) f
* (\<lambda>r _. r \<in> Set_Interval.ord_class.atLeastLessThan (0\<Colon>'b) (5\<Colon>'b))
*
*
* Known problems:
* - The output may have bad indentation. This is because the mangled constant
* name has a different length compared to the original.
*
* - May not work with exotic syntax translations (e.g. ML-defined syntax).
*
* - May not work with terms that contain the magic string "dsfjdssdfsd".
*
* - Naive algorithm, takes \<approx>quadratic time.
*)
theory Insulin
imports
Pure
keywords
"desugar_term" "desugar_thm" "desugar_goal" :: diag
begin
(*
* Isabelle syntax rules are invoked based on constants appearing in a term.
* Insulin works by replacing each constant with a free variable, which is
* displayed without triggering syntax translations.
* It tries each constant in the term to find the ones that produce a given
* syntax fragment, then limits the replacement to those constants only.
*
* The tricky bit is determining which constants contribute to the proscribed
* syntax fragment. We do this somewhat naively by trying each constant
* one-at-a-time, then replacing those which reduce the *number of occurrences*
* of the proscribed syntax.
* Since syntax translations are arbitrary, it is possible that the final result
* still contains unwanted syntax, so we iterate this process.
*
* This one-at-a-time approach fails for syntax that can be triggered by any of
* several constants in a term. I don't know any examples of this, though.
*)
ML \<open>
structure Insulin = struct
val desugar_random_tag = "dsfjdssdfsd"
fun fresh_substring s = let
fun next [] = [#"a"]
| next (#"z" :: n) = #"a" :: next n
| next (c :: n) = Char.succ c :: n
fun fresh n = let
val ns = String.implode n
in if String.isSubstring ns s then fresh (next n) else ns end
in fresh [#"a"] end
(* Encode a (possibly qualified) constant name as an (expected-to-be-)unused name.
* The encoded name will be treated as a free variable. *)
fun escape_const c = let
val delim = fresh_substring c
in desugar_random_tag ^ delim ^ "_" ^
String.concat (case Long_Name.explode c of
(a :: b :: xs) => a :: map (fn x => delim ^ x) (b :: xs)
| xs => xs)
end
(* Decode; if it fails, return input string *)
fun unescape_const s =
if not (String.isPrefix desugar_random_tag s) then s else
let val cs = String.extract (s, String.size desugar_random_tag, NONE) |> String.explode
fun readDelim d (#"_" :: cs) = (d, cs)
| readDelim d (c :: cs) = readDelim (d @ [c]) cs
| readDelim _ [] = raise Match
val (delim, cs) = readDelim [] cs
val delimlen = length delim
fun splitDelim name cs =
if take delimlen cs = delim then name :: splitDelim [] (drop delimlen cs)
else case cs of [] => if null name then [] else [name]
| (c::cs) => splitDelim (name @ [c]) cs
val names = splitDelim [] cs
in Long_Name.implode (map String.implode names) end
handle Match => s
fun dropQuotes s = if String.isPrefix "\"" s andalso String.isSuffix "\"" s
then String.substring (s, 1, String.size s - 2) else s
(* Translate markup from consts-encoded-as-free-variables to actual consts *)
fun desugar_reconst ctxt (tr as XML.Elem ((tag, attrs), children))
= if tag = "fixed" orelse tag = "intensify" then
let val s = XML.content_of [tr]
val name = unescape_const s
fun get_entity_attrs (XML.Elem (("entity", attrs), _)) = SOME attrs
| get_entity_attrs (XML.Elem (_, body)) =
find_first (K true) (List.mapPartial get_entity_attrs body)
| get_entity_attrs (XML.Text _) = NONE
in
if name = s then tr else
(* try to look up the const's info *)
case Syntax.read_term ctxt name
|> Thm.cterm_of ctxt
|> Proof_Display.pp_cterm (fn _ => Proof_Context.theory_of ctxt)
|> Pretty.string_of
|> dropQuotes
|> YXML.parse
|> get_entity_attrs of
SOME attrs =>
XML.Elem (("entity", attrs), [XML.Text name])
| _ =>
XML.Elem (("entity", [("name", name), ("kind", "constant")]),
[XML.Text name]) end
else XML.Elem ((tag, attrs), map (desugar_reconst ctxt) children)
| desugar_reconst _ (t as XML.Text _) = t
fun term_to_string ctxt no_markup =
Syntax.pretty_term ctxt
#> Pretty.string_of
#> YXML.parse_body
#> map (desugar_reconst ctxt)
#> (if no_markup then XML.content_of else YXML.string_of_body)
#> dropQuotes
(* Strip constant names from a term.
* A term is split to a "term_unconst" and a "string list" of the
* const names in tree preorder. *)
datatype term_unconst =
UCConst of typ |
UCAbs of string * typ * term_unconst |
UCApp of term_unconst * term_unconst |
UCVar of term
fun is_ident_char c = Char.isAlphaNum c orelse c = #"_" orelse c = #"." orelse c = #"'"
fun term_to_unconst (Const (name, typ)) =
(* some magical constants have strange names, such as ==>; ignore them *)
if forall is_ident_char (String.explode name) then (UCConst typ, [name])
else (UCVar (Const (name, typ)), [])
| term_to_unconst (Abs (var, typ, body)) = let
val (body', consts) = term_to_unconst body
in (UCAbs (var, typ, body'), consts) end
| term_to_unconst (f $ x) = let
val (f', consts1) = term_to_unconst f
val (x', consts2) = term_to_unconst x
in (UCApp (f', x'), consts1 @ consts2) end
| term_to_unconst t = (UCVar t, [])
fun term_from_unconst (UCConst typ) (name :: consts) =
((if unescape_const name = name then Const else Free) (name, typ), consts)
| term_from_unconst (UCConst _) [] = raise Match
| term_from_unconst (UCAbs (var, typ, body)) consts = let
val (body', consts) = term_from_unconst body consts
in (Abs (var, typ, body'), consts) end
| term_from_unconst (UCApp (f, x)) consts = let
val (f', consts) = term_from_unconst f consts
val (x', consts) = term_from_unconst x consts
in (f' $ x', consts) end
| term_from_unconst (UCVar v) consts = (v, consts)
(* Count occurrences of bad strings.
* Bad strings are allowed to overlap, but for each string, non-overlapping occurrences are counted.
* Note that we search on string lists, to deal with symbols correctly. *)
fun count_matches (haystack: ''a list) (needles: ''a list list): int list =
let (* Naive algorithm. Probably ok, given that we're calling the term printer a lot elsewhere. *)
fun try_match xs [] = SOME xs
| try_match (x::xs) (y::ys) = if x = y then try_match xs ys else NONE
| try_match _ _ = NONE
fun count [] = 0
| count needle = let
fun f [] occs = occs
| f haystack' occs = case try_match haystack' needle of
NONE => f (tl haystack') occs
| SOME tail => f tail (occs + 1)
in f haystack 0 end
in map count needles end
fun focus_list (xs: 'a list): ('a list * 'a * 'a list) list =
let fun f head x [] = [(head, x, [])]
| f head x (tail as x'::tail') = (head, x, tail) :: f (head @ [x]) x' tail'
in case xs of [] => []
| (x::xs) => f [] x xs end
(* Do one rewrite pass: try every constant in sequence, then collect the ones which
* reduced the occurrences of bad strings *)
fun rewrite_pass (t: term) (improved: term -> bool) (escape_const: string -> string): term =
let val (ucterm, consts) = term_to_unconst t
fun rewrite_one (prev, const, rest) =
let val (t', _) = term_from_unconst ucterm (prev @ [escape_const const] @ rest)
in improved t' end
val consts_to_rewrite = focus_list consts |> map rewrite_one
val consts' = map2 (fn rewr => fn const => if rewr then escape_const const else const) consts_to_rewrite consts
val (t', _) = term_from_unconst ucterm consts'
in t' end
(* Do rewrite passes until bad strings are gone or no more rewrites are possible *)
fun desugar ctxt (t0: term) (bads: string list): term =
let fun count t = count_matches (Symbol.explode (term_to_string ctxt true t)) (map Symbol.explode bads)
val _ = if null bads then error "Nothing to desugar" else ()
fun rewrite t = let
val counts0 = count t
fun improved t' = exists (op <) (count t' ~~ counts0)
val t' = rewrite_pass t improved escape_const
in if forall (fn c => c = 0) (count t') (* bad strings gone *)
then t'
else if t = t' (* no more rewrites *)
then let
val bads' = filter (fn (c, _) => c > 0) (counts0 ~~ bads) |> map snd
val _ = warning ("Sorry, failed to desugar " ^ commas_quote bads')
in t end
else rewrite t'
end
in rewrite t0 end
fun span _ [] = ([],[])
| span p (a::s) =
if p a then let val (y, n) = span p s in (a::y, n) end else ([], a::s)
fun check_desugar s = let
fun replace [] = []
| replace xs =
if take (String.size desugar_random_tag) xs = String.explode desugar_random_tag
then case span is_ident_char xs of
(v, xs) => String.explode (unescape_const (String.implode v)) @ replace xs
else hd xs :: replace (tl xs)
val desugar_string = String.implode o replace o String.explode
in if not (String.isSubstring desugar_random_tag s) then s
else desugar_string s end
fun desugar_term ctxt t s =
desugar ctxt t s |> term_to_string ctxt false |> check_desugar
fun desugar_thm ctxt thm s = desugar_term ctxt (Thm.prop_of thm) s
fun desugar_goal ctxt goal n s = let
val subgoals = goal |> Thm.prems_of
val subgoals = if n = 0 then subgoals else
if n < 1 orelse n > length subgoals then
(* trigger error *) [Logic.get_goal (Thm.term_of (Thm.cprop_of goal)) n]
else [nth subgoals (n - 1)]
val results = map (fn t => (NONE, desugar_term ctxt t s)
handle ex as TERM _ => (SOME ex, term_to_string ctxt false t))
subgoals
in if null results
then error "No subgoals to desugar"
else if forall (Option.isSome o fst) results
then raise the (fst (hd results))
else map snd results
end
end
\<close>
ML \<open>
Outer_Syntax.command @{command_keyword "desugar_term"}
"term str str2... -> desugar str in term"
(Parse.term -- Scan.repeat1 Parse.string >> (fn (t, s) =>
Toplevel.keep (fn state => let val ctxt = Toplevel.context_of state in
Insulin.desugar_term ctxt (Syntax.read_term ctxt t) s
|> writeln end)))
\<close>
ML \<open>
Outer_Syntax.command @{command_keyword "desugar_thm"}
"thm str str2... -> desugar str in thm"
(Parse.thm -- Scan.repeat1 Parse.string >> (fn (t, s) =>
Toplevel.keep (fn state => let val ctxt = Toplevel.context_of state in
Insulin.desugar_thm ctxt (Attrib.eval_thms ctxt [t] |> hd) s |> writeln end)))
\<close>
ML \<open>
fun print_subgoals (x::xs) n = (writeln (Int.toString n ^ ". " ^ x); print_subgoals xs (n+1))
| print_subgoals [] _ = ();
Outer_Syntax.command @{command_keyword "desugar_goal"}
"goal_num str str2... -> desugar str in goal"
(Scan.option Parse.int -- Scan.repeat1 Parse.string >> (fn (n, s) =>
Toplevel.keep (fn state => let val ctxt = Toplevel.context_of state in
Insulin.desugar_goal ctxt (Toplevel.proof_of state |> Proof.raw_goal |> #goal) (Option.getOpt (n, 0)) s
|> (fn xs => case xs of
[x] => writeln x
| _ => print_subgoals xs 1) end)))
\<close>
end