-
Notifications
You must be signed in to change notification settings - Fork 108
/
autocorres_util.ML
625 lines (584 loc) · 25.5 KB
/
autocorres_util.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
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
(*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*)
(*
* Common code for all translation phases: defining funcs, calculating dependencies,
* variable fixes, etc.
*)
(*
* Here is a brief explanation of how most AutoCorres phases work
* with each other.
*
* AutoCorres's phases are L1, L2, HL, WA and TS. (TS doesn't share these
* utils for historical reasons; fixing that is another story.)
* Basically, each of L1, L2, HL and WA:
* 1. takes a list of input function specs;
* 2. converts each function individually;
* 3. defines each new function (or recursive function group).
* This updates the local theory sequentially;
* 4. proves monad_mono theorems and places them into the
* output list of functions;
* 5. outputs a list of new function specs in the original format.
*
* === Concurrency ===
* To support concurrent processing better, we do not use lists.
* Instead, we use a future-chained sequence (FSeq, below) so that
* define and convert steps can be done in parallel (up to the
* dependencies between them, of course).
*
* (We do not use a plain list of futures because a define
* step may produce one or more function groups, so we can't
* know how many groups there will be in advance. See the
* recursive group splitting comment for define_funcs_sequence.)
*
* Additionally, AutoCorres is structured so that conversions
* do not require the most up-to-date local theory, so we also
* output a stream of intermediate local theories. This allows
* conversions of phase N+1 to be pipelined with define steps of
* phase N.
*
* FunctionInfo.phase_results is the uniform sequence type that
* most AutoCorres translation phases adhere to.
*
* === (2) Conversion ===
* Converting a function starts by assuming correspondence theorems
* for all the functions that it calls (including itself, if
* recursive). We invent free variables to stand for those functions;
* see assume_called_functions_corres.
*
* Because it's fiddly to have these assumptions everywhere,
* we use Assumption to hide them in the thm hyps during conversion.
* When done, we export the assumptions using Morphism.thm.
*
* After performing these conversions, we get a corres theorem
* with corres assumptions for called functions (along with other
* auxillary info). These are generally packaged into a
* convert_result record.
*
* The conversions are all independent, so we launch them in
* topological order; see par_convert. This is the most convenient
* because each conversion takes place between the previous and next
* define step, which already require topological order.
*
* === (3) Definition ===
* We take the sequence of conversion results and define each
* function (or recursive group) in the theory.
*
* Each function group and its convert_results are processed by
* define_funcs. Conventionally, AutoCorres phases provide a
* "define" wrapper that sets up the required inputs to define_funcs
* and constructs function_infos for the newly defined functions.
*
* There is also a high-level wrapper, define_funcs_sequence, that
* calls these "define" wrappers in the correct order.
* It also splits recursive groups after defining them; see its
* documentation for details.
*
* === (4) Corollaries ===
* Currently, each phase only proves one type of corollary,
* monad_mono theorems. These proofs are duplicated in the source
* of the individual phases (this should be fixed) and do not make
* use of the utils here.
*
* === Incremental mode support ===
* AutoCorres supports incremental translation, which means that
* we need to insert previously-translated function data at the
* appropriate places. The par_convert and define_funcs_sequence
* wrappers take "existing_foo" arguments and ensure that these
* are available to the per-phase convert and define steps.
*)
structure AutoCorresUtil =
struct
(*
* Maximum time to let an individual function translation phase to run for.
*
* Note that this is wall time, and not CPU time, so it is a very rough
* tool.
* FIXME: convert to proper option
*)
val max_run_time = Unsynchronized.ref NONE
(*
val max_run_time = Unsynchronized.ref (SOME (seconds 900.0))
*)
exception AutocorresTimeout of string list
fun time_limit f v =
case !max_run_time of
SOME t =>
(Timeout.apply t f ()
handle Timeout.TIMEOUT _ =>
raise AutocorresTimeout v)
| NONE =>
f ()
(* Should we use concurrency? *)
val concurrent = Unsynchronized.ref true;
(*
* Conditionally fork a group of tasks, depending on the value
* of "concurrent".
*)
fun maybe_fork f = if !concurrent then Future.fork f else Future.value (f ());
(*
* Get functions called by a particular function.
*
* We split the result into standard calls and recursive calls (i.e., calls
* which may recursively call back into us).
*)
fun get_callees fn_infos fn_name =
let
val fn_info = the (Symtab.lookup fn_infos fn_name)
in
(Symset.dest (#callees fn_info), Symset.dest (#rec_callees fn_info))
end
(* Measure variables are currently hardcoded as nats. *)
val measureT = @{typ nat};
(*
* Assume theorems for called functions.
*
* A new context is returned with the assumptions in it, along with a morphism
* used for exporting the theorems out, and a list of the functions assumed:
*
* (<function name>, (<is_mutually_recursive>, <function free>, <arg frees>, <function thm>))
*
* In this context, the theorems refer to functions by fixed free variables.
*
* get_fn_args may return user-friendly argument names that clash with other names.
* We will process these names to avoid conflicts.
*
* get_fn_assumption should produce the desired theorems to assume. Its arguments:
* context (with fixed vars), callee name, callee term, arg terms, is recursive, measure term
* (all terms are fixed free vars).
*
* get_const_name generates names for the free function placeholders.
* FIXME: probably unnecessary and/or broken.
*
* We return a morphism that exposes the assumptions and generalises over the
* assumed constants.
* FIXME: automatically generalising over the assumed free variables is
* probably broken. Instead, the caller should manually generalise and
* instantiate the frees to avoid clashes.
*)
fun assume_called_functions_corres ctxt callees rec_callees
get_fn_type get_fn_assumption get_fn_args get_const_name callers_measure_var =
let
(* Assume the existence of a function, along with a theorem about its
* behaviour. *)
fun assume_func ctxt fn_name is_recursive_call =
let
val fn_args = get_fn_args fn_name
(* Fix a variable for the function. *)
val ([fixed_fn_name], ctxt') = Variable.variant_fixes [get_const_name fn_name] ctxt
val fn_free = Free (fixed_fn_name, get_fn_type fn_name)
(* Fix a variable for the measure and function arguments. *)
val (measure_var_name :: arg_names, ctxt'')
= Variable.variant_fixes ("rec_measure'" :: (map fst fn_args)) ctxt'
val fn_arg_terms = map (fn (n, T) => Free (n, T)) (arg_names ~~ (map snd fn_args))
val my_measure_var = Free (measure_var_name, @{typ nat})
(*
* A measure variable is needed to handle recursion: for recursive calls,
* we need to decrement the caller's input measure value (and our
* assumption will need to assume this to). This is so we can later prove
* termination of our function definition: the measure always reaches zero.
*
* Non-recursive calls can have a fresh value.
*)
val measure_var =
if is_recursive_call then
@{const "recguard_dec"} $ callers_measure_var
else
my_measure_var
(* Create our assumption. *)
val assumption =
get_fn_assumption ctxt'' fn_name fn_free fn_arg_terms
is_recursive_call measure_var
|> fold Logic.all (rev ((if is_recursive_call then [] else [my_measure_var]) @ fn_arg_terms))
|> Sign.no_vars ctxt'
|> Thm.cterm_of ctxt'
val ([thm], ctxt''') = Assumption.add_assumes [assumption] ctxt'
(* Generate a morphism for escaping this context. *)
val m = (Assumption.export_morphism ctxt''' ctxt')
$> (Variable.export_morphism ctxt' ctxt)
in
(fn_free, thm, ctxt''', m)
end
(* Add assumptions: recursive calls first, matching the order in define_functions *)
val (res, (ctxt', m)) = fold_map (
fn (fn_name, is_recursive_call) =>
fn (ctxt, m) =>
let
val (free, thm, ctxt', m') =
assume_func ctxt fn_name is_recursive_call
in
((fn_name, (is_recursive_call, free, thm)), (ctxt', m' $> m))
end)
(map (fn f => (f, false)) (Symset.dest callees) @
map (fn f => (f, true)) (Symset.dest rec_callees))
(ctxt, Morphism.identity)
in
(ctxt', m, res)
end
(* Determine which functions are called by a code fragment.
* Only function terms in callee_consts are used. *)
fun get_body_callees
(callee_consts: string Termtab.table)
(body: term)
: symset =
Term.fold_aterms (fn t => fn a =>
(Termtab.lookup callee_consts t
|> Option.map single
|> the_default []) @ a)
body []
|> Symset.make;
(* Determine which recursive calls are actually used by a code fragment.
* This is used to make adjustments to recursive function groups
* between conversion and definition steps.
*
* callee_terms is a list of (is_recursive, func const, thm)
* as provided by assume_called_functions_corres *)
fun get_rec_callees
(callee_terms: (string * (bool * term * thm)) list)
(body: term)
: symset = let
val callee_lookup =
callee_terms |> List.mapPartial (fn (callee, (is_rec, const, _)) =>
if is_rec then SOME (const, callee) else NONE)
|> Termtab.make;
in get_body_callees callee_lookup body end;
(*
* Given one or more function specs, define them and instantiate corres proofs.
*
* "callee_thms" contains corres theorems for already-defined functions.
*
* "fn_infos" is used to look up function callees. It is expected
* to consist of the previous translation output for the functions
* being defined, but may of course contain other entries.
*
* "functions" contains a list of (fn_name, (body, corres proof, arg_frees)).
* The body should be of the form generated by abstract_fn_body,
* with lambda abstractions for all callees and arguments.
*
* The given corres proof is expected to use the free variables in
* arg_frees for the function's arguments (including the measure variable,
* if there if there is one). It is also expected to use schematic
* variables for assumed callees.
* (FIXME: this interface should be simplified a bit.)
*
* We assume that all functions in this list are mutually recursive.
* (If not, you should call "define_funcs" multiple times, each
* time with a single function.)
*
* Returns the new function constants, definitions, final corres proofs,
* and local theory.
*)
fun define_funcs
(phase : FunctionInfo.phase)
(filename : string)
(fn_infos : FunctionInfo.function_info Symtab.table)
(get_const_name : string -> string)
(get_fn_type : string -> typ)
(get_fn_assumption : Proof.context -> string -> term -> term list -> bool -> term -> term)
(get_fn_args : string -> (string * typ) list)
(rec_base_case : thm)
(ctxt : Proof.context)
(callee_thms : thm Symtab.table)
(functions : (string * (term * thm * (string * typ) list)) list)
: (term * thm * thm) Symtab.table * Proof.context =
let
val fn_names = map fst functions
val fn_bodies = map (snd #> #1) functions
(* Generalise over the function arguments *)
val fn_thms = map (snd #> (fn (_, thm, frees) =>
(Thm.generalize (Names.empty, Names.make_set (map fst frees)) (Thm.maxidx_of thm + 1) thm)))
functions
val _ = writeln ("Defining (" ^ FunctionInfo.string_of_phase phase ^ ") " ^
(Utils.commas (map get_const_name fn_names)))
(*
* Determine if we are in a recursive case by checking to see if the
* first function in our list makes recursive calls to any other
* function. (This "other function" will be itself if it is simple
* recursion, but may be a different function if we are mutually
* recursive.)
*)
val is_recursive = FunctionInfo.is_function_recursive (the (Symtab.lookup fn_infos (hd fn_names)))
val _ = assert (length fn_names = 1 orelse is_recursive)
"define_funcs passed multiple functions, but they don't appear to be recursive."
(*
* Patch in functions into our function body in the following order:
*
* * Non-recursive calls;
* * Recursive calls
*)
fun fill_body fn_name body =
let
val fn_info = the (Symtab.lookup fn_infos fn_name)
val non_rec_calls = map (fn x => Utils.get_term ctxt (get_const_name x)) (Symset.dest (#callees fn_info))
val rec_calls = map (fn x => Free (get_const_name x, get_fn_type x)) (Symset.dest (#rec_callees fn_info))
in
body
|> (fn t => betapplys (t, non_rec_calls))
|> (fn t => betapplys (t, rec_calls))
end
(*
* Define our functions.
*
* Definitions should be of the form:
*
* %arg1 arg2 arg3. (arg1 + arg2 + arg3)
*
* Mutually recursive calls should be of the form "Free (fn_name, fn_type)".
*)
val defs = map (
fn (fn_name, fn_body) => let
val fn_args = get_fn_args fn_name
(* FIXME: this retraces assume_called_functions_corres *)
val (fn_free :: measure_free :: arg_frees, _) = Variable.variant_fixes
(get_const_name fn_name :: "rec_measure'" :: map fst fn_args) ctxt
in (get_const_name fn_name, (* be inflexible when it comes to fn_name *)
(measure_free, @{typ nat}) :: (arg_frees ~~ map snd fn_args), (* changing arg names is ok *)
fill_body fn_name fn_body) end)
(fn_names ~~ fn_bodies)
val (fn_def_thms, ctxt) = Utils.define_functions defs true is_recursive ctxt
(*
* Instantiate schematic function calls in our theorems with their
* concrete definitions.
*)
val combined_callees = map (get_callees fn_infos) (map fst functions)
val combined_normal_calls =
map fst combined_callees |> flat |> sort_distinct fast_string_ord
val combined_recursive_calls =
map snd combined_callees |> flat |> sort_distinct fast_string_ord
val callee_terms =
(combined_recursive_calls @ combined_normal_calls)
|> map (fn x => (get_const_name x, Utils.get_term ctxt (get_const_name x)))
|> Symtab.make
fun fill_proofs thm =
Utils.instantiate_thm_vars ctxt
(fn ((name, _), _) =>
Symtab.lookup callee_terms name
|> Option.map (Thm.cterm_of ctxt)) thm
val fn_thms = map fill_proofs fn_thms
(* Fix free variable for the measure. *)
val ([measure_var_name], ctxt') = Variable.variant_fixes ["m"] ctxt
val measure_var = Free (measure_var_name, @{typ nat})
(* Generate corres predicates for each function. *)
val preds = map (
fn fn_name =>
let
fun mk_forall v t = HOLogic.all_const (Term.fastype_of v) $ lambda v t
val fn_const = Utils.get_term ctxt' (get_const_name fn_name)
(* Fetch parameters to this function. *)
val free_params =
get_fn_args fn_name
|> Variable.variant_frees ctxt' [measure_var]
|> map Free
in
(* Generate the prop. *)
get_fn_assumption ctxt' fn_name fn_const
free_params is_recursive measure_var
|> fold Logic.all (rev free_params)
end) fn_names
(* We generate a goal which solves all the mutually recursive calls simultaneously. *)
val goal = map (Object_Logic.atomize_term ctxt') preds
|> Utils.mk_conj_list
|> HOLogic.mk_Trueprop
|> Thm.cterm_of ctxt'
(* Prove each of the predicates above, leaving any assumptions about called
* functions unsolved. *)
val pred_thms = map (
fn (pred, thm, body_def) =>
Thm.trivial (Thm.cterm_of ctxt' pred)
|> Utils.apply_tac "unfold body" (Hypsubst.stac ctxt' body_def 1)
|> Utils.apply_tac "apply rule" (resolve_tac ctxt' [thm] 1)
|> Goal.norm_result ctxt
|> singleton (Variable.export ctxt' ctxt)
)
(Utils.zip3 preds fn_thms fn_def_thms)
(* Create a set of "helper theorems", which should be sufficient to discharge
* all assumptions that our callees refine. *)
val helper_thms =
(map (Symtab.lookup callee_thms #> the) combined_normal_calls) @ pred_thms
|> map (Thm.forall_intr_vars)
|> map (Conv.fconv_rule (Object_Logic.atomize ctxt))
(* Generate a proof term of equivalence using the folded definitions. *)
val new_thm =
Goal.init goal
|> (fn thm =>
if is_recursive then (
Utils.apply_tac "start induction"
(resolve_tac ctxt'
[Utils.named_cterm_instantiate ctxt'
[("n", Thm.cterm_of ctxt' measure_var)] @{thm recguard_induct}]
1) thm
|> Utils.apply_tac "unfold bodies"
(EVERY (map (fn x => (EqSubst.eqsubst_tac ctxt' [1] [x] 1)) (rev fn_def_thms)))
|> Utils.apply_tac "solve induction base cases"
(SOLVES ((simp_tac (put_simpset HOL_ss ctxt' addsimps [rec_base_case]) 1)))
|> Utils.apply_tac "solve remaining goals"
(Utils.metis_insert_tac ctxt helper_thms 1)
) else (
Utils.apply_tac "solve remaining goals"
(Utils.metis_insert_tac ctxt helper_thms 1) thm
))
|> Goal.finish ctxt'
(*
* The proof above is of the form (L1corres a & L1corres b & ...).
* Split it up into several proofs.
*)
fun prove_partial_corres thm pred =
Thm.cterm_of ctxt' pred
|> Goal.init
|> Utils.apply_tac "solving using metis" (Utils.metis_tac ctxt [thm] 1)
|> Goal.finish ctxt'
(* Generate the final theorems. *)
val new_thms =
map (prove_partial_corres new_thm) preds
|> (Variable.export ctxt' ctxt)
|> map (Goal.norm_result ctxt)
val results =
fn_names ~~ (new_thms ~~ fn_def_thms)
|> map (fn (fn_name, (corres_thm, def_thm)) =>
(* FIXME: ugly way to get the function constant *)
(fn_name, (Utils.get_term ctxt (get_const_name fn_name), def_thm, corres_thm)))
|> Symtab.make;
in
(results, ctxt)
end
(* Support function for incremental translation.
* This updates #callees of the functions we are translating, to include
* background functions that have already been translated.
* (We don't need to handle #rec_callees because recursive groups
* cannot be translated piecemeal.) *)
fun add_background_callees
(background: FunctionInfo.function_info Symtab.table)
: FunctionInfo.function_info Symtab.table ->
FunctionInfo.function_info Symtab.table = let
val bg_consts =
Symtab.dest background
|> map (fn (f, bg_info) => (#raw_const bg_info, f))
|> Termtab.make;
in Symtab.map (K (fn fn_info => let
val bg_callees = get_body_callees bg_consts (Thm.prop_of (#definition fn_info));
in FunctionInfo.function_info_upd_callees (Symset.union (#callees fn_info) bg_callees) fn_info end))
end;
(* Utility for doing conversions in parallel.
* The conversion of each function f should depend only on the previous
* define phase for f (which necessarily also includes f's callees). *)
type convert_result = {
body: term, (* new body *)
proof: thm, (* corres thm *)
rec_callees: symset, (* minimal rec_callees after translation *)
callee_consts: term Symtab.table, (* assumed frees for other callees *)
arg_frees: (string * typ) list, (* fixed argument frees, including measure *)
traces: (string * AutoCorresData.Trace) list (* traces *)
}
fun par_convert
(* Worker: lthy -> function_infos for func and callees -> func name -> results *)
(convert: local_theory -> FunctionInfo.function_info Symtab.table ->
string -> convert_result)
(* Functions from prior incremental translation *)
(existing_infos: FunctionInfo.function_info Symtab.table)
(* Functions from previous phase *)
(prev_results: FunctionInfo.phase_results)
(* Add traces from the conversion result. *)
(add_trace: string -> string -> AutoCorresData.Trace -> unit)
(* Return converted functions in recursive groups.
* The groups are tagged with fn_infos from prev_results to identify them. *)
: (FunctionInfo.function_info Symtab.table * convert_result Symtab.table) FSeq.fseq =
(* Knowing that prev_results is in topological order,
* we accumulate its function_infos, which will be a superset
* of the callee infos that each conversion requires. *)
FSeq.fold_map (fn fn_infos_accum => fn (lthy, fn_infos) => let
val fn_infos = add_background_callees existing_infos fn_infos;
val fn_infos_accum = Symtab.merge (K false) (fn_infos_accum, fn_infos);
(* Convert fn_infos in parallel, but join the results right away.
* This is fine because we will define them together. *)
val conv_results =
Symtab.dest fn_infos
|> Par_List.map (fn (f, _) =>
(f, convert lthy fn_infos_accum f))
|> Symtab.make;
val _ = app (fn (f, result) => app (fn (typ, trace) =>
add_trace f typ trace) (#traces result))
(Symtab.dest conv_results);
in ((fn_infos, conv_results), fn_infos_accum) end)
existing_infos prev_results;
(* Given a function body containing arguments and assumed function calls,
* abstract the code over those parameters.
*
* The returned body will have free variables as placeholders for the function's
* measure parameter and other arguments, as well as for the functions it calls.
*
* We modify the body to be of the form:
*
* %fun1 fun2 rec1 rec2 measure arg1 arg2. f <...>
*
* That is, all non-recursive calls are abstracted out the front, followed by
* recursive calls, followed by the measure variable, followed by function
* arguments. This is the format expected by define_funcs.
*)
fun abstract_fn_body
(prev_fn_infos: FunctionInfo.function_info Symtab.table)
(fn_name, {body, callee_consts, arg_frees, ...} : convert_result) = let
val (callees, rec_callees) = get_callees prev_fn_infos fn_name;
val calls = map (the o Symtab.lookup callee_consts) callees;
val rec_calls = map (the o Symtab.lookup callee_consts) rec_callees;
val abs_body = body
|> fold lambda (rev (map Free arg_frees))
|> fold lambda (rev rec_calls)
|> fold lambda (rev calls);
in abs_body end;
(* Utility for defining functions.
*
* Definitions update the theory sequentially.
* Each definition step produces a lthy that contains the current function
* group, and can immediately be used in the next conversion phase for
* those functions. Hence we return the intermediate lthys as futures.
*
* The actual recursive function groups may be finer-grained than in
* converted_groups, as function calls can be removed by dead code
* elimination and other transformations. Hence we detect the actual
* function groups after defining them. *)
fun define_funcs_sequence
(lthy: local_theory)
(define_worker: local_theory ->
(* previous infos for functions *)
FunctionInfo.function_info Symtab.table ->
(* new infos for callees *)
FunctionInfo.function_info Symtab.table ->
(* data for functions *)
convert_result Symtab.table ->
(* new infos for functions *)
FunctionInfo.function_info Symtab.table * local_theory)
(* previous infos from prior translation (in incremental mode) *)
(existing_infos: FunctionInfo.function_info Symtab.table)
(* functions defined so far, initially populated from prior translation *)
(defined_so_far: FunctionInfo.function_info Symtab.table)
(converted_groups: (FunctionInfo.function_info Symtab.table *
convert_result Symtab.table) FSeq.fseq)
: FunctionInfo.phase_results = FSeq.mk (fn () =>
case FSeq.uncons converted_groups of
NONE => NONE
| SOME ((prev_infos, conv_group), remaining_groups) => SOME let
(* NB: we don't need to add_background_callees to prev_infos because
* par_convert already does that *)
(* Define the function group, then split into minimal recursive groups. *)
val (new_infos, lthy') =
define_worker lthy (Symtab.merge (K false) (prev_infos, existing_infos))
defined_so_far conv_group;
(* Minimise callees and split recursive group if needed. *)
val new_infoss = FunctionInfo.recalc_callees defined_so_far new_infos;
val defined_so_far = Symtab.merge (K false) (defined_so_far, new_infos);
(* We can't wrap the first result because we're already in FSeq.mk.
* Fortunately, one is guaranteed to exist *)
val new_infos1 :: new_infoss = new_infoss;
(* Output new group(s) in the result sequence. *)
val remaining_results =
define_funcs_sequence lthy' define_worker
existing_infos defined_so_far remaining_groups;
in
((lthy', new_infos1),
FSeq.append
(FSeq.of_list (map (fn defs => (lthy', defs)) new_infoss))
remaining_results)
end);
end