1(*
2 * Copyright 2014, NICTA
3 *
4 * This software may be distributed and modified according to the terms of
5 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
6 * See "LICENSE_BSD2.txt" for details.
7 *
8 * @TAG(NICTA_BSD)
9 *)
10
11fun all_eq (a :: b :: l) = if a = b then all_eq (b :: l) else false
12  | all_eq [_] = true
13  | all_eq [] = true
14
15fun common_head (lss as (l :: _)) =
16let
17  fun common_head i = if all_eq (map (fn ls => List.nth (ls,i)) lss) handle Subscript => false then common_head (i + 1) else i
18in
19  List.take (l,common_head 0) end
20 | common_head [] = []
21
22fun common_prefix strs = String.implode (common_head (map String.explode strs))
23
24
25signature PROOF_GRAPH =
26sig
27
28
29type proof_entry = {name : string, file : string, lines : int * int,
30  prems : int list list, concl : int list, kind : Proof_Count.lemmaT option}
31
32val contains : proof_entry -> int list
33
34val map_contains : (int list -> int list) -> proof_entry -> proof_entry
35
36val size_of : proof_entry -> int
37
38val proper_theory_list : (string * string list) Symtab.table -> string list -> string list
39
40val get_full_spec : theory -> Spec_Graph.entry Int_Graph.T * proof_entry String_Graph.T * (string * string list) Symtab.table
41
42val write_graph_spec_of : Spec_Graph.entry Int_Graph.T * proof_entry String_Graph.T  * (string * string list) Symtab.table -> string -> string -> unit
43
44val read_graph_spec_from : string ->
45    Spec_Graph.entry Int_Graph.T * proof_entry String_Graph.T  * (string * string list) Symtab.table
46
47val restrict_subgraph :  (String_Graph.key * 'a -> bool) -> 'a String_Graph.T -> 'a String_Graph.T
48
49val merge_multi_thms : proof_entry String_Graph.T -> proof_entry String_Graph.T
50
51val relative_path_of : string -> string
52
53
54
55end
56
57structure Proof_Graph : PROOF_GRAPH =
58struct
59
60val isabelle_home = File.full_path Path.root (Path.variable "ISABELLE_HOME")
61
62(*FIXME: Not general *)
63fun relative_path_of s = if s = "" then "" else
64  let
65    val home_base = Path.dir isabelle_home |> Path.implode
66  in
67    try (fn s => "~~/.." ^ (unprefix home_base s)) s
68    |> the_default s end
69
70type proof_entry = {name : string, file : string, lines : int * int,
71   prems : int list list, concl : int list, kind : Proof_Count.lemmaT option}
72
73fun contains ({prems, concl, ...} : proof_entry) = flat prems @ concl
74
75fun map_contains f ({name, file, lines, prems, concl, kind}) =
76let
77  val (concl' :: prems') = (burrow f (concl :: prems))
78in
79  ({name = name, file = file, lines = lines, prems = prems', concl = concl', kind = kind} : proof_entry)
80end
81
82fun restrict_subgraph f graph =
83let
84  val restrs = String_Graph.fold (fn (id,(e,edge)) => if f (id,e) then I else cons edge) graph []
85  |> map (fn (preds,sucs) => map_product pair (String_Graph.Keys.dest preds) (String_Graph.Keys.dest sucs))
86in
87  fold (fold (String_Graph.add_edge)) restrs graph
88  |> String_Graph.restrict (fn id => f (id,(String_Graph.get_node graph id)))
89end
90
91fun size_of {lines, ...} = case lines of (a,b) => (b - a) + 1
92
93fun thms_of (PBody {thms,...}) = thms
94
95fun proof_body_descend' f (_,(nm,_,body)) =
96  if f nm then
97    fold (append o (proof_body_descend' f)) (thms_of (Future.join body)) []
98  else
99    [nm]
100
101fun used_facts' f thm = fold (append o (proof_body_descend' f)) (thms_of (Thm.proof_body_of thm)) []
102
103
104
105fun used_facts f thm =
106  let
107    val nm = Thm.get_name_hint thm
108  in
109    used_facts' (fn nm' => nm' = "" orelse nm' = nm orelse f nm) thm
110  end
111
112fun graph_proof thy =
113  let
114
115    val report = Proof_Count.get_size_report ()
116    |> Proof_Count.compute_sizes
117
118
119    fun get_lines (SOME (_,(begin,done))) =
120      (((the (Position.line_of begin),the (Position.line_of done))) handle Option => (~1,~1))
121      | get_lines NONE = (~1,~1)
122
123    fun get_transaction (SOME (t,_)) = SOME t
124     | get_transaction NONE = NONE
125
126    val all_thms = Global_Theory.all_thms_of thy true
127
128    val (graph,access) = Spec_Graph.get_graph thy
129
130    val all_defs = Int_Graph.fold (fn (_,(e,_)) => (case (#def_name e) of SOME d => Symtab.update (d,()) | NONE => I)) graph Symtab.empty
131
132    (* "interesting" defined as being a phyiscal lemma or a definitino *)
133    fun is_interesting name =
134    let
135      val lines = Symtab.lookup report name |> get_lines
136    in
137      (Symtab.defined all_defs name) orelse
138      (not (lines = (~1,~1)))
139    end
140
141    val _ = tracing ((@{make_string} (length all_thms)) ^ " total theorems found")
142
143    val interesting_thms = filter (fn (i,_) => is_interesting i) all_thms
144    |> sort_distinct (prod_ord string_ord (make_ord (K false)))
145
146
147    fun mk_entry (name,thm) =
148    let
149      (* Skip uninteresting lemmas, finding their first interesting dependant *)
150      val used = used_facts (not o is_interesting) thm
151      |> filter is_interesting
152
153      fun contains t = Term.fold_aterms (fn (Const c) => (case (access c) of SOME i => cons i | _ => I) | _ => I) t []
154
155      val report_entry = Symtab.lookup report name
156
157      val t = prop_of thm
158
159      val e = {name = Long_Name.base_name name,
160               file = ((Symtab.lookup report name) |> the |> snd |> fst |> Position.file_of |> the |> relative_path_of) handle Option => "",
161               lines = get_lines report_entry,
162               prems = map contains (Logic.strip_imp_prems t),
163               concl = contains (Logic.strip_imp_concl t),
164               kind = get_transaction report_entry}
165    in
166      ((name,e),used)
167    end
168
169    val _ = tracing ((@{make_string} (length interesting_thms)) ^ " facts to process")
170
171
172    val raw_graph = Par_List.map (fn e => (mk_entry e)) interesting_thms
173
174    val proof_graph = String_Graph.make raw_graph
175
176
177  in (graph,proof_graph) end;
178
179
180(*Attempt to merge lemmas statements back together*)
181fun merge_entries (entries as (n,e) :: _)  graph =
182let
183  val id = unsuffix "_" (common_prefix (map fst entries))
184
185  val name = unsuffix "_" (common_prefix (map (#name o snd) entries))
186  val prems = flat (map (#prems o snd) entries)
187  val concl = flat (map (#concl o snd) entries)
188
189  fun rep_old nm' = if exists (fn (n,_) => nm' = n) entries then id else nm'
190
191  val preds = flat (map (String_Graph.immediate_preds graph o fst) entries) |> map rep_old
192  val succs = flat (map (String_Graph.immediate_succs graph o fst) entries) |> map rep_old
193in
194   fold (fn (n,_) => String_Graph.del_node n) entries graph
195  |> String_Graph.new_node (id,{name = name,prems = prems,concl = concl, file = #file e,lines = #lines e, kind = #kind e})
196  |> fold (fn e => String_Graph.add_edge (e,id)) preds
197  |> fold (fn e => String_Graph.add_edge (id,e)) succs
198  end handle General.Fail "unsuffix" => graph
199
200(*Merge theorems which share document position*)
201fun merge_multi_thms graph =
202let
203  val files = String_Graph.fold (fn (n,(e,_)) => Symtab.insert_list (K false) (#file e,(n,e))) graph Symtab.empty
204  fun do_partition es =
205  let
206    val parts = partition_eq (fn ((_,e),(_,e')) => #lines e = #lines e') (filter_out (fn (_,e) => #lines e = (~1,~1)) es)
207    |> filter (fn l => length l > 1)
208    |> filter_out (fn l => all_eq (map (#name o snd) l))
209
210  in
211    fold merge_entries parts end
212in
213  Symtab.fold (fn (_,es) => do_partition es) files graph end
214
215val lemma_prefixK = "Lemma: "
216val declare_prefixK = "Declare: "
217val noneK = "NONE"
218
219fun transaction_to_str s = let
220  open Proof_Count in case s of
221  SOME (Lemma n) => lemma_prefixK ^ n
222 | SOME (Declare n) => declare_prefixK ^ n
223 | NONE => noneK
224end
225
226fun str_to_transaction s = if s = noneK then NONE else
227SOME (
228  case (try (unprefix "Lemma: ") s) of
229  SOME x => Proof_Count.Lemma x
230  | NONE => case (try (unprefix "Declare: ") s) of
231      SOME x => Proof_Count.Declare x
232    | NONE => error ("Deserialization failure: unexpected lemma type: " ^ s))
233
234fun to_props (e : proof_entry) = []
235  |> Properties.put ("name", #name e)
236  |> Properties.put ("file", #file e)
237  |> Properties.put ("start", Int.toString (#lines e |> fst))
238  |> Properties.put ("end", Int.toString (#lines e |> snd))
239  |> Properties.put ("kind",transaction_to_str (#kind e))
240
241
242fun from_props (prop,(prems,concl)) =
243  ({ name = Properties.get prop "name" |> the,
244    file = Properties.get prop "file" |> the,
245    lines = (Int.fromString (Properties.get prop "start" |> the) |> the,
246             Int.fromString (Properties.get prop "end" |> the) |> the),
247    kind = Properties.get prop "kind" |> the_default "Unknown" |> str_to_transaction,
248    prems = prems,
249    concl = concl} : proof_entry)
250
251fun encode_proof_graph_entry e =
252  let open XML.Encode
253  in
254    (pair properties (pair (list (list int)) (list int))) (to_props e, (#prems e,#concl e)) end
255
256val encode_proof_graph = String_Graph.encode XML.Encode.string encode_proof_graph_entry
257
258fun decode_proof_graph_entry e =
259  let open XML.Decode
260  in
261    (pair properties (pair (list (list int)) (list int))) e |> from_props end
262
263val decode_proof_graph = String_Graph.decode XML.Decode.string decode_proof_graph_entry
264
265fun get_thy_deps' thy tab =
266let
267  val ancestors = Theory.nodes_of thy
268  val nm = Context.theory_name thy
269in
270  if Symtab.defined tab nm then tab else
271  Symtab.update (nm,(Resources.master_directory thy |> Path.implode |> relative_path_of,(map Context.theory_name ancestors))) tab
272  |> fold get_thy_deps' ancestors end
273
274fun get_thy_deps thy = get_thy_deps' thy Symtab.empty
275
276fun encode_thy_deps deps =
277  let open XML.Encode in
278    (list (pair (string) (pair (string) (list string)))) (Symtab.dest deps) end
279
280fun decode_thy_deps body =
281  let open XML.Decode in
282    (list (pair (string) (pair (string) (list string)))) body
283    |> Symtab.make end
284
285fun proper_theory_list tab (bottoms : string list) =
286  let
287    fun has_bottom (_,(_,deps)) = exists (fn th => member (op =) bottoms th) deps
288  in
289    Symtab.dest tab
290    |> filter has_bottom
291    |> map fst end;
292
293fun get_full_spec thy =
294let
295
296    val thy_deps = get_thy_deps thy
297    |> Symtab.delete (Context.theory_name thy)
298
299    val (full_graph,proof_graph) = graph_proof thy
300
301in
302  (full_graph,proof_graph,thy_deps) end
303
304
305fun write_graph_spec_of (full_graph,proof_graph,thy_deps) metadata file =
306  let
307
308    val spec_xml = XML.Elem (("Spec_Graph",[]),Spec_Graph.encode_graph full_graph)
309
310    val proof_xml = XML.Elem (("Proof_Graph",[]),encode_proof_graph proof_graph)
311
312    val thy_deps_xml = XML.Elem(("Thy_Deps",[]),encode_thy_deps thy_deps)
313
314    val toplevel_xml = XML.Elem(("Toplevel",[("metadata",metadata)]),[spec_xml,proof_xml,thy_deps_xml])
315
316  in
317     File.open_output (XML.output (toplevel_xml)) (Path.explode file) end
318
319fun read_graph_spec_from file =
320  let
321    val tree = File.read (Path.explode file)
322    |> XML.parse
323
324
325    fun deconstruct (
326      XML.Elem (("Toplevel",_),
327        [XML.Elem(("Spec_Graph",[]),spec_body),
328         XML.Elem(("Proof_Graph",[]),proof_body),
329         XML.Elem(("Thy_Deps",[]),thy_deps)])) = (spec_body,proof_body,thy_deps)
330     | deconstruct _ = error "Not a valid spec graph"
331
332   val (spec_body,proof_body,thy_deps) = deconstruct tree
333
334   val full_graph = Spec_Graph.decode_graph spec_body
335   val proof_graph = decode_proof_graph proof_body
336   val thy_deps = decode_thy_deps thy_deps
337
338  in
339    (full_graph,proof_graph,thy_deps) end
340
341end
342