gdp-wired 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/core/dsl.d.ts +31 -14
- package/dist/core/dsl.js +143 -103
- package/dist/kernel/kernel.d.ts +8 -0
- package/dist/kernel/kernel.js +139 -27
- package/dist/kernel/prove.js +174 -50
- package/dist/lean-kernel/emit.d.ts +17 -2
- package/dist/lean-kernel/emit.js +110 -131
- package/dist/lean-kernel/library.js +5 -5
- package/package.json +2 -2
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { atMost, n, not, pointWrite } from "../core/machine.js";
|
|
2
2
|
import { add, k, scale, sub } from "../core/solver.js";
|
|
3
|
-
import { canon, historyMachine, foldObligations, leafConstraints, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
3
|
+
import { canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
4
4
|
import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
5
5
|
import { refinement, refinementSides } from "../core/compose.js";
|
|
6
6
|
import { leanTerms } from "./lean-terms.js";
|
|
@@ -52,11 +52,13 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
52
52
|
// Columns: every variable of the hypotheses, then every cell any leaf uses (and the cells inside their keys),
|
|
53
53
|
// shallower cells first so a key reads only earlier columns.
|
|
54
54
|
const vars = globalVars ?? [...new Set(goal.hyps.flatMap(varsOf))];
|
|
55
|
-
const cells = new Set();
|
|
55
|
+
const cells = new Set(), terms = new Set();
|
|
56
56
|
const addCell = (name) => { if (cells.has(name))
|
|
57
57
|
return; cells.add(name); for (const key of Object.keys(cellKey(name)))
|
|
58
58
|
if (key.includes('['))
|
|
59
|
-
addCell(key);
|
|
59
|
+
addCell(key);
|
|
60
|
+
else if (key.startsWith('⟦'))
|
|
61
|
+
terms.add(key); };
|
|
60
62
|
// Monomials: every one a leaf's rows or products use, or that a product term has under the leaf's path.
|
|
61
63
|
const monos = new Set();
|
|
62
64
|
const subterms = (c) => {
|
|
@@ -77,11 +79,29 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
77
79
|
wc(c);
|
|
78
80
|
return out2;
|
|
79
81
|
};
|
|
82
|
+
// Every choice in the hypotheses has its column, whether or not a leaf's rows keep it (a column can cancel, T − T,
|
|
83
|
+
// yet Lean's linear form of T still needs it): each distinct subterm once, as a run's states are shared.
|
|
84
|
+
const seenTerms = new WeakSet();
|
|
85
|
+
const choices = (t) => {
|
|
86
|
+
if (seenTerms.has(t))
|
|
87
|
+
return;
|
|
88
|
+
seenTerms.add(t);
|
|
89
|
+
if (t.op === 'sub' || t.op === 'min' || t.op === 'max' || t.op === 'if')
|
|
90
|
+
terms.add(termColumn(t));
|
|
91
|
+
for (const val of Object.values(t))
|
|
92
|
+
if (Array.isArray(val))
|
|
93
|
+
val.forEach(x => choices(x));
|
|
94
|
+
else if (typeof val === 'object' && val !== null)
|
|
95
|
+
choices(val);
|
|
96
|
+
};
|
|
97
|
+
goal.hyps.forEach(choices);
|
|
80
98
|
leaves(tree, new Map(), (leaf, path) => {
|
|
81
99
|
for (const r of withProducts(leafConstraints(goal, path), leaf.products ?? []))
|
|
82
100
|
for (const key of Object.keys(r.lin)) {
|
|
83
101
|
if (key.includes('['))
|
|
84
102
|
addCell(key);
|
|
103
|
+
else if (key.startsWith('⟦'))
|
|
104
|
+
terms.add(key);
|
|
85
105
|
else if (key.includes('*'))
|
|
86
106
|
monos.add(key);
|
|
87
107
|
}
|
|
@@ -93,7 +113,8 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
93
113
|
monos.add(key);
|
|
94
114
|
});
|
|
95
115
|
const depth = (name) => name.split('[').length;
|
|
96
|
-
|
|
116
|
+
// Term columns (open choices, each standing for its term's value) before the cells, whose keys may read them.
|
|
117
|
+
const cols = [...vars, ...[...monos].sort((a, b) => a.split('*').length - b.split('*').length), ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...[...cells].sort((a, b) => depth(a) - depth(b))];
|
|
97
118
|
const col = new Map(cols.map((name, j) => [name, j]));
|
|
98
119
|
const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
|
|
99
120
|
const dense = (lin) => {
|
|
@@ -110,11 +131,12 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
110
131
|
return coefs;
|
|
111
132
|
};
|
|
112
133
|
const row = (lin) => `⟨${list(dense(lin).map(int))}, ${int(lin['1'] ?? 0)}⟩`;
|
|
113
|
-
const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
|
|
114
|
-
: name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
|
|
115
|
-
throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
|
|
116
|
-
: `.var ${vars.indexOf(name)}`));
|
|
117
134
|
const { e, c } = leanTerms(vars, maps, ex.name);
|
|
135
|
+
const tab = cols.map(name => (name.includes('[') ? `.cell ${maps.indexOf(cellMap(name))} ${row(cellKey(name))}`
|
|
136
|
+
: name.startsWith('⟦') ? `.term (${e(columnTerm(name))})`
|
|
137
|
+
: name.includes('*') ? `.mono ${list(name.split('*').map(f => { const j = vars.indexOf(f); if (j < 0)
|
|
138
|
+
throw new Error(`${ex.name}: factor ${f}`); return j; }).sort((a, b) => a - b).map(String))}`
|
|
139
|
+
: `.var ${vars.indexOf(name)}`));
|
|
118
140
|
let leafNo = 0;
|
|
119
141
|
const lean = (t, path) => {
|
|
120
142
|
if (!('leaf' in t)) {
|
|
@@ -222,36 +244,11 @@ theorem ${id}_laws : ∀ maps ρ, Reach ${id}_machine maps ρ → ∀ l ∈ ${id
|
|
|
222
244
|
export const TRUE = '.le (.const 0) (.const 0)';
|
|
223
245
|
export const grown = (g) => { const o = grow(g); if (!('tree' in o))
|
|
224
246
|
throw new Error(`${g.name}: stuck`); return o.tree; };
|
|
225
|
-
// Loops through Kern.
|
|
247
|
+
// Loops through Kern.loop_halts_of: the loop's machine (Live.lean's Loop.machine) with its invariants as laws, and its
|
|
248
|
+
// halting as a liveness (kernel.ts's loopMachine, loopLiveness); the trees are the kernel's.
|
|
226
249
|
export function loopToLean(id, l, laws, rank, tamper) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const enc = (g, tree = grown(g)) => encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars);
|
|
230
|
-
const any0 = enc(gs[0], { leaf: [] });
|
|
231
|
-
const ix = (name) => vars.indexOf(name);
|
|
232
|
-
const lit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
|
|
233
|
-
${list(Object.entries(lp.state).map(([name, e]) => `(${ix(name)}, ${any0.e(e)})`))},
|
|
234
|
-
${list(Object.entries(lp.body).map(([name, e]) => `(${ix(name)}, ${any0.e(e)})`))},
|
|
235
|
-
${any0.c(lp.while)},
|
|
236
|
-
${lp.pre ? any0.c(lp.pre) : TRUE},
|
|
237
|
-
${lp.assume ? any0.c(lp.assume) : TRUE}⟩`;
|
|
238
|
-
const proof = laws.map((law, j) => {
|
|
239
|
-
const s2 = enc(gs[2 * j]), t = enc(gs[2 * j + 1]);
|
|
240
|
-
return `(${any0.c(law.holds)},\n (${s2.tab}, ${s2.tree}),\n (${t.tab}, ${t.tree}))`;
|
|
241
|
-
});
|
|
242
|
-
const r = enc(gs.at(-1));
|
|
243
|
-
const ranks = Array.isArray(rank) ? rank : [rank];
|
|
244
|
-
return `-- ${l.name}: halts with ranks ${JSON.stringify(ranks.length)}${laws.length ? `, invariants ${laws.map(law => law.name).join('; ')}` : ''} (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')})
|
|
245
|
-
def ${id}_loop : Loop := ${lit(l)}
|
|
246
|
-
def ${id}_proof : LoopProof := ⟨${list(proof)},
|
|
247
|
-
${list(ranks.map(any0.e))},
|
|
248
|
-
(${r.tab}, ${r.tree})⟩
|
|
249
|
-
theorem ${id}_halts : ∀ maps ρ0, evalC ρ0 maps ${id}_loop.pre = true →
|
|
250
|
-
¬ ∃ f : Nat → Nat → Nat, f 0 = applyS ${id}_loop.init ρ0 maps ∧ ∀ n, Turn ${id}_loop maps (f n) (f (n + 1)) :=
|
|
251
|
-
loop_sound ${id}_loop ${id}_proof (by decide +kernel)${tamper ? `
|
|
252
|
-
-- TAMPERED: ${tamper.what}. Lean computes the check to false.
|
|
253
|
-
def ${id}_tampered : Loop := ${lit(tamper.loop)}
|
|
254
|
-
example : checkLoop ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}`;
|
|
250
|
+
loopObligations(l, laws, rank);
|
|
251
|
+
return smachineToLean(id, loopMachine(l), laws, { liveness: [loopLiveness(l, rank)], loop: { loop: l, ...(tamper ? { tamper } : {}) } });
|
|
255
252
|
}
|
|
256
253
|
// Folds through Kern.fold_sound: Lean builds the leaf and node goals; the trees are the kernel's.
|
|
257
254
|
export function foldToLean(id, f, laws, extra = '') {
|
|
@@ -419,7 +416,8 @@ theorem ${pc.id}_holds : ProgressHolds ${id}_machine ${pc.id} :=
|
|
|
419
416
|
def ${pc.id}_tampered : Progress := ${record(pc.tamper.when)}
|
|
420
417
|
example : checkProgress ${id}_machine ${id}_proof ${pc.id}_tampered = false := by decide +kernel` : ''}`;
|
|
421
418
|
});
|
|
422
|
-
// Step laws through Kern.steplaw_sound: `when` and `then` sent with the flags inlined (as kernel.ts's goal has them)
|
|
419
|
+
// Step laws through Kern.steplaw_sound: `when` and `then` sent with the flags inlined (as kernel.ts's goal has them);
|
|
420
|
+
// a law over several steps goes through smachineToLean.
|
|
423
421
|
const sp = opts.stepLaws?.length ? proveStepLaws(m, laws, opts.stepLaws) : undefined;
|
|
424
422
|
if (opts.stepLaws?.length && (!sp?.proven || !sp.proof))
|
|
425
423
|
throw new Error(`${id}: step laws not proven`);
|
|
@@ -430,9 +428,11 @@ example : checkProgress ${id}_machine ${id}_proof ${pc.id}_tampered = false := b
|
|
|
430
428
|
const insts = laws.flatMap((l, li) => (l.forall ? (sp.proof.instances[l.name] ?? []).map(key => `(${li}, ${any0.e(key)})`) : []));
|
|
431
429
|
const primes = list(Object.keys(m.state).map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMaps = list(baseMaps.map(mp => `(${mapNames.indexOf(mp)}, ${mapNames.indexOf(`${mp}'`)})`));
|
|
432
430
|
const k = sl.steps ?? 1;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
431
|
+
const when = Array.isArray(sl.when) ? sl.when[0] : sl.when;
|
|
432
|
+
if (k !== 1)
|
|
433
|
+
throw new Error(`${id}: step law ${sl.name}: a law over several steps goes through smachineToLean (Sets.lean's skstep_sound)`);
|
|
434
|
+
return `-- Step law: ${sl.name}
|
|
435
|
+
def ${id}_step${j} : StepLaw := ⟨${when ? any0.c(inlineFlags(when)) : TRUE},
|
|
436
436
|
${any0.c(inlineFlags(sl.then))},
|
|
437
437
|
${primes},
|
|
438
438
|
${primeMaps},
|
|
@@ -440,19 +440,6 @@ def ${id}_step${j} : StepLaw := ⟨${sl.when ? any0.c(inlineFlags(sl.when)) : TR
|
|
|
440
440
|
(${pr.tab}, ${pr.tree})⟩
|
|
441
441
|
theorem ${id}_step${j}_holds : StepLawHolds ${id}_machine ${id}_step${j} :=
|
|
442
442
|
steplaw_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
|
|
443
|
-
// Through Kern.kstep_sound (Steps.lean): one `when` per step (0 <= 0 where none), the j-th request's inputs renamed.
|
|
444
|
-
const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
|
|
445
|
-
const ren = Array.from({ length: k }, (_, s2) => (s2 === 0 ? '[]' : list(m.inputs.map(i => `(${ix(i)}, ${ix(`${i}@${s2 + 1}`)})`))));
|
|
446
|
-
return `-- Step law over ${k} steps: ${sl.name}
|
|
447
|
-
def ${id}_step${j} : KStep := ⟨${list(Array.from({ length: k }, (_, s2) => (whens[s2] ? any0.c(inlineFlags(whens[s2])) : TRUE)))},
|
|
448
|
-
${any0.c(inlineFlags(sl.then))},
|
|
449
|
-
${list(ren)},
|
|
450
|
-
${primes},
|
|
451
|
-
${primeMaps},
|
|
452
|
-
${list(insts)},
|
|
453
|
-
(${pr.tab}, ${pr.tree})⟩
|
|
454
|
-
theorem ${id}_step${j}_holds : KStepHolds ${id}_machine ${id}_step${j} :=
|
|
455
|
-
kstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
|
|
456
443
|
});
|
|
457
444
|
const machineDef = `def ${id}_machine : FMMachine := ${lit(m)}`;
|
|
458
445
|
return `-- ${m.name}: ${laws.map(l => l.name).join('; ')}${flagNames.length ? `, with flags ${flagNames.join(', ')}` : ''} (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')}; maps ${mapNames.map((v2, j) => `${j} ${v2}`).join(', ')})
|
|
@@ -481,7 +468,13 @@ const flagsOfM = (m) => {
|
|
|
481
468
|
export const hasSets = (m) => !!m.inputMaps?.length || Object.values(m.step.flags ?? {}).some(c => c.op === 'all') || Object.values(m.step.maps ?? {}).some(w => 'each' in w)
|
|
482
469
|
|| !!Object.keys(m.fns ?? {}).length || !!m.facts?.length;
|
|
483
470
|
const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? [c.key] : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))];
|
|
484
|
-
|
|
471
|
+
// A machine with a history goes as its plain machine (kernel.ts's historyMachine: sums as state, queries as inputs,
|
|
472
|
+
// the relations assumed), built in Lean from the machine and its history (History.lean's HMachine.plain), so every claim
|
|
473
|
+
// below is about it; with the relations' record goals, its laws hold over histories too (hmachine_sound).
|
|
474
|
+
// `historyTampers`: the history with other relations, each refused.
|
|
475
|
+
export function smachineToLean(id, given, laws, opts = {}) {
|
|
476
|
+
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
477
|
+
const hplain = hm ? historyMachine(hm) : undefined, m = hplain ? hplain.machine : given;
|
|
485
478
|
const stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
|
|
486
479
|
const claims = [];
|
|
487
480
|
const claimSteps = () => claims.flatMap(cl => cl.steps);
|
|
@@ -504,7 +497,8 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) =
|
|
|
504
497
|
${list(qinsts(inst))},
|
|
505
498
|
${list(finsts(inst))},
|
|
506
499
|
${list(pinsts(inst))},
|
|
507
|
-
[]
|
|
500
|
+
[],
|
|
501
|
+
${list(Array.from({ length: kk - 1 }, (_, s2) => list(Object.keys(m.state).map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`))))}⟩
|
|
508
502
|
theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
|
|
509
503
|
skstep_sound ${id}_machine ${id}_proof ${id}_step${j} ${id}_check (by decide +kernel)`;
|
|
510
504
|
} }));
|
|
@@ -554,6 +548,19 @@ theorem ${lid}_holds (run : SRun ${id}_machine)
|
|
|
554
548
|
sleads_sound ${id}_machine ${id}_proof ${lid} _ _ _ ${id}_check ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps run fair${wf}`;
|
|
555
549
|
} });
|
|
556
550
|
}
|
|
551
|
+
// A loop (Live.lean's loop_halts_of): its machine is the loop's, and its liveness says the loop halts.
|
|
552
|
+
const lp0 = opts.loop;
|
|
553
|
+
if (lp0)
|
|
554
|
+
claims.push({ steps: [], allows: [], render: () => `/-- ${lp0.loop.name} halts: for params meeting its precondition, no run of turns goes on forever. -/
|
|
555
|
+
theorem ${id}_halts : ∀ maps ρ0, evalC ρ0 maps ${id}_loop.pre = true →
|
|
556
|
+
¬ ∃ f : Nat → Nat → Nat, f 0 = applyS ${id}_loop.init ρ0 maps ∧ ∀ n, Turn ${id}_loop maps (f n) (f (n + 1)) :=
|
|
557
|
+
loop_halts_of ${id}_loop ${id}_live0 rfl rfl (by intro h hh; simp only [${id}_live0, List.mem_singleton] at hh; subst hh; rfl) (by decide +kernel) ${id}_live0_holds${lp0.tamper ? `
|
|
558
|
+
-- TAMPERED: ${lp0.tamper.what}. Lean computes the checks to false.
|
|
559
|
+
def ${id}_tampered : SMachine := Loop.machine ${loopLit(lp0.tamper.loop)}
|
|
560
|
+
example : (checkSMachine ${id}_tampered ${id}_proof && sliveOk ${id}_tampered ${id}_live0
|
|
561
|
+
&& checkSKStep ${id}_tampered ${id}_proof (sliveStep ${id}_live0 ${id}_live0.pend (liveStayC ${id}_live0) ${id}_live0_sp)
|
|
562
|
+
&& checkSKStep ${id}_tampered ${id}_proof (sliveStep ${id}_live0 ${id}_live0.pend (liveCoverC ${id}_live0) ${id}_live0_cp)
|
|
563
|
+
&& sliveHelpsOk ${id}_tampered ${id}_proof ${id}_live0 ${id}_live0_hp) = false := by decide +kernel` : ''}` });
|
|
557
564
|
// Ghosts (Ghost.lean's sghost_laws): the laws that read no ghost hold of the machine without its ghosts.
|
|
558
565
|
if (gv.length)
|
|
559
566
|
claims.push({ steps: [], allows: [], render: () => `-- The laws that read no ghost hold of the machine without its ghosts.
|
|
@@ -625,7 +632,8 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
625
632
|
const allowGoals = [...allowed.values()].flatMap(a => (a ? [a.out.goal] : []));
|
|
626
633
|
const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
|
|
627
634
|
...quantified.map(f => `w?${f}`), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
|
|
628
|
-
...primes, ...later, ...
|
|
635
|
+
...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(),
|
|
636
|
+
...(hm ? hm.history.record.map(f => `r.${f}`) : []), ...[...goalsL, ...goalsS, ...allowGoals, ...(hplain?.goals ?? [])].flatMap(g => g.hyps.flatMap(varsOf))])];
|
|
629
637
|
const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
|
|
630
638
|
const mapNames = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps,
|
|
631
639
|
...[...JSON.stringify([...goalsL, ...goalsS, ...allowGoals]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x2 => x2[1])])];
|
|
@@ -651,6 +659,39 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
651
659
|
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(c2.key)}, ${c(c2.c)})`] : [])))},
|
|
652
660
|
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(`w?${f}`)})`] : [])))},
|
|
653
661
|
${list((mm.facts ?? []).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))}⟩`;
|
|
662
|
+
};
|
|
663
|
+
// A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
|
|
664
|
+
const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
|
|
665
|
+
${list(Object.entries(lp.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
666
|
+
${list(Object.entries(lp.body).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
667
|
+
${c(lp.while)},
|
|
668
|
+
${lp.pre ? c(lp.pre) : TRUE},
|
|
669
|
+
${lp.assume ? c(lp.assume) : TRUE}⟩`;
|
|
670
|
+
// The history: the machine without it (its state without the sums, its assumption without the relations), what a
|
|
671
|
+
// record keeps and the sums, queries and relations over it.
|
|
672
|
+
const hlit = (rels) => {
|
|
673
|
+
const h = hm.history, base = { ...hm, history: undefined };
|
|
674
|
+
return `⟨${lit(base)},
|
|
675
|
+
${c(h.keep)},
|
|
676
|
+
${list(h.record.map(f => String(ix(f))))},
|
|
677
|
+
${list(h.record.map(f => String(ix(`r.${f}`))))},
|
|
678
|
+
${list(Object.entries(h.sums).map(([name, g]) => `(${ix(name)}, ${e(g)})`))},
|
|
679
|
+
${list(Object.entries(h.queries).map(([name, g]) => `(${ix(name)}, ${e(g)})`))},
|
|
680
|
+
${list(rels.map(rel => { if (rel.holds.op !== '<=')
|
|
681
|
+
throw new Error(`${id}: a relation is a <= b`); return `(${c(rel.when)}, ${e(rel.holds.a)}, ${e(rel.holds.b)})`; }))}⟩`;
|
|
682
|
+
};
|
|
683
|
+
// Each relation's record goal and its tree, the history's check, and the laws over histories.
|
|
684
|
+
const histBlock = () => {
|
|
685
|
+
const records = hplain.goals.map(g => { const out = grow(g); if (!('tree' in out))
|
|
686
|
+
throw new Error(`${id}: ${g.name} not proven`); const x2 = enc(g, out.tree); return `(${x2.tab}, ${x2.tree})`; });
|
|
687
|
+
const tampered = (opts.historyTampers ?? []).map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
|
|
688
|
+
def ${id}_tampered${j} : HMachine := ${hlit(t.relations)}
|
|
689
|
+
example : checkHMachine ${id}_tampered${j} ${id}_proof ${t.withRecordTrees ? `${id}_records` : '[]'} = false := by decide +kernel`);
|
|
690
|
+
return `def ${id}_records : List (List VDef × Tree) := ${list(records)}
|
|
691
|
+
theorem ${id}_hcheck : checkHMachine ${id}_hist ${id}_proof ${id}_records = true := by decide +kernel
|
|
692
|
+
theorem ${id}_hlaws : ∀ ρ M hist, HReach ${id}_hist ρ M hist → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
|
|
693
|
+
hmachine_sound ${id}_hist ${id}_proof ${id}_records ${id}_hcheck
|
|
694
|
+
${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat) (hist : List (Nat → Nat))', hyp: `HReach ${id}_hist ρ μ hist`, call: `${id}_hlaws ρ μ hist h` })}${tampered.length ? `\n${tampered.join('\n')}` : ''}`;
|
|
654
695
|
};
|
|
655
696
|
const lawIx = (name) => laws.findIndex(l => l.name === name);
|
|
656
697
|
const insts = (inst) => Object.entries(inst).filter(([name]) => lawIx(name) >= 0 && !laws[lawIx(name)].keys?.length).flatMap(([name, keys]) => keys.map(key => `(${lawIx(name)}, ${e(key)})`));
|
|
@@ -692,13 +733,15 @@ ${rf ? `def ${id}_refined : SRefined := ⟨${lit(rf.impl)},
|
|
|
692
733
|
def ${id}_machine : SMachine := ${id}_refined.full` : gv.length ? `def ${id}_ghosted : SGhosted := ⟨${lit(orig)},
|
|
693
734
|
${list(gv.map(v2 => `(${ix(v2)}, ${e(m.state[v2])}, ${e(m.step.state?.[v2] ?? { op: 'var', name: v2 })})`))}⟩
|
|
694
735
|
def ${id}_machine : SMachine := ${id}_ghosted.full
|
|
695
|
-
theorem ${id}_ghostOk : sghostOk ${id}_ghosted = true := by decide +kernel` : `def ${id}
|
|
736
|
+
theorem ${id}_ghostOk : sghostOk ${id}_ghosted = true := by decide +kernel` : hm ? `def ${id}_hist : HMachine := ${hlit(hm.history.relations ?? [])}
|
|
737
|
+
def ${id}_machine : SMachine := ${id}_hist.plain` : lp0 ? `def ${id}_loop : Loop := ${loopLit(lp0.loop)}
|
|
738
|
+
def ${id}_machine : SMachine := ${id}_loop.machine` : `def ${id}_machine : SMachine := ${lit(m)}`}
|
|
696
739
|
${entries.join('\n')}
|
|
697
740
|
def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
|
|
698
741
|
theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by decide +kernel
|
|
699
742
|
theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
|
|
700
743
|
smachine_sound ${id}_machine ${id}_proof ${id}_check
|
|
701
|
-
${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, opts.simp)}${opts.tamper ? `
|
|
744
|
+
${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, opts.simp)}${opts.tamper ? `
|
|
702
745
|
-- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
|
|
703
746
|
def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
|
|
704
747
|
example : checkSMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
|
|
@@ -721,61 +764,9 @@ theorem ${id}_facts_hold (ρ : Nat → Nat) (μ : Nat → Nat → Nat) ${fns.map
|
|
|
721
764
|
// relations in the assumption) and each relation's record goal; the trees and the instance keys are the kernel's.
|
|
722
765
|
// Pruning is left out: its goals are about the runtimes (dropping a record changes no query), the laws and relations
|
|
723
766
|
// are proven over the whole history either way, and Lean's HMachine has no pruning; the TypeScript kernel checks them.
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const r = proveLaws(m, laws);
|
|
728
|
-
if (!r.proven || !r.proof)
|
|
729
|
-
throw new Error(`${id}: not proven`);
|
|
730
|
-
const keysOf = [...new Set(laws.flatMap(l => (l.forall ? [l.forall] : [])))];
|
|
731
|
-
const flagNames = Object.keys(m.step.flags ?? {});
|
|
732
|
-
const sums = Object.keys(h.sums), fields = h.record.map(f => `r.${f}`);
|
|
733
|
-
const vars = [...m.params, ...m.inputs, ...Object.keys(m.state), ...sums, ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flagNames.map(f => `?${f}`), ...fields];
|
|
734
|
-
const mapNames = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...[...JSON.stringify(laws).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x2 => x2[1])])];
|
|
735
|
-
const gs = obligations(m, laws, r.proof.instances);
|
|
736
|
-
const enc = (g, tree = r.proof.trees[g.name]) => encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, mapNames);
|
|
737
|
-
const any0 = enc(gs[0], { leaf: [] });
|
|
738
|
-
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
739
|
-
throw new Error(`${id}: no variable ${name}`); return j; };
|
|
740
|
-
const rels = (rs) => list(rs.map(rel => {
|
|
741
|
-
if (rel.holds.op !== '<=')
|
|
742
|
-
throw new Error(`${id}: a relation is a <= b`);
|
|
743
|
-
return `(${any0.c(rel.when)}, ${any0.e(rel.holds.a)}, ${any0.e(rel.holds.b)})`;
|
|
744
|
-
}));
|
|
745
|
-
const lit = (rs) => `⟨⟨⟨${list(m.inputs.map(name => String(ix(name))))},
|
|
746
|
-
${list(Object.entries(m.state).map(([name, e]) => `(${ix(name)}, ${any0.e(e)})`))},
|
|
747
|
-
${list(Object.entries(m.maps ?? {}).map(([name, c2]) => `(${mapNames.indexOf(name)}, ${c2})`))},
|
|
748
|
-
${list(Object.entries(m.step.state ?? {}).map(([name, e]) => `(${ix(name)}, ${any0.e(e)})`))},
|
|
749
|
-
${list(Object.entries(m.step.maps ?? {}).map(([name, w]) => { const pw = pointWrite(w, 'Lean (for now)'); return `(${mapNames.indexOf(name)}, ${any0.e(pw.key)}, ${any0.e(pw.value)})`; }))},
|
|
750
|
-
${m.pre ? any0.c(m.pre) : TRUE},
|
|
751
|
-
${m.assume ? any0.c(m.assume) : TRUE}⟩,
|
|
752
|
-
${list(flagNames.map(f => `(${ix(`?${f}`)}, ${any0.c(m.step.flags[f])})`))}⟩,
|
|
753
|
-
${any0.c(h.keep)},
|
|
754
|
-
${list(h.record.map(f => String(ix(f))))},
|
|
755
|
-
${list(fields.map(f => String(ix(f))))},
|
|
756
|
-
${list(Object.entries(h.sums).map(([name, g]) => `(${ix(name)}, ${any0.e(g)})`))},
|
|
757
|
-
${list(Object.entries(h.queries).map(([name, g]) => `(${ix(name)}, ${any0.e(g)})`))},
|
|
758
|
-
${rels(rs)}⟩`;
|
|
759
|
-
const entries = laws.map((law, j) => {
|
|
760
|
-
const a = enc(gs[2 * j]), b = enc(gs[2 * j + 1]);
|
|
761
|
-
const insts = Object.entries(r.proof.instances[law.name] ?? {}).flatMap(([lname, keys]) => keys.map(key => `(${laws.findIndex(l => l.name === lname)}, ${any0.e(key)})`));
|
|
762
|
-
return `def ${id}_l${j} : MLaw := ⟨${any0.c(law.holds)}, ${law.forall ? `some ${ix(law.forall)}` : 'none'}⟩\ndef ${id}_e${j} : MEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list(insts)},\n (${a.tab}, ${a.tree}),\n (${b.tab}, ${b.tree})⟩`;
|
|
763
|
-
});
|
|
764
|
-
const records = gs.slice(2 * laws.length).map(g => { const x2 = enc(g); return `(${x2.tab}, ${x2.tree})`; });
|
|
765
|
-
const tamperText = tampers.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
|
|
766
|
-
def ${id}_tampered${j} : HMachine := ${lit(t.relations)}
|
|
767
|
-
example : checkHMachine ${id}_tampered${j} ${id}_proof ${t.withRecordTrees ? `${id}_records` : '[]'} = false := by decide +kernel`);
|
|
768
|
-
return `-- ${m.name}: ${laws.map(l => l.name).join('; ')}, with a history (sums ${sums.join(', ')}; queries ${Object.keys(h.queries).join(', ')}; ${(h.relations ?? []).length} relation(s))
|
|
769
|
-
-- (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')}; maps ${mapNames.map((v2, j) => `${j} ${v2}`).join(', ')})
|
|
770
|
-
def ${id}_machine : HMachine := ${lit(h.relations ?? [])}
|
|
771
|
-
${entries.join('\n')}
|
|
772
|
-
def ${id}_proof : List MEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
|
|
773
|
-
def ${id}_records : List (List VDef × Tree) := ${list(records)}
|
|
774
|
-
theorem ${id}_check : checkHMachine ${id}_machine ${id}_proof ${id}_records = true := by decide +kernel
|
|
775
|
-
theorem ${id}_laws : ∀ ρ M hist, HReach ${id}_machine ρ M hist → ∀ e ∈ ${id}_proof, e.law.Holds ρ M :=
|
|
776
|
-
hmachine_sound ${id}_machine ${id}_proof ${id}_records ${id}_check
|
|
777
|
-
${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat) (hist : List (Nat → Nat))', hyp: `HReach ${id}_machine ρ μ hist`, call: `${id}_laws ρ μ hist h` })}${corollary ? `\n${corollary(ix)}` : ''}
|
|
778
|
-
${tamperText.join('\n')}`;
|
|
767
|
+
// A machine with a history: its laws over histories (History.lean's hmachine_sound), through smachineToLean.
|
|
768
|
+
export function hmachineToLean(id, m, laws, tampers = [], corollary) {
|
|
769
|
+
return smachineToLean(id, m, laws, { historyTampers: tampers, ...(corollary ? { after: corollary } : {}) });
|
|
779
770
|
}
|
|
780
771
|
// A Lean file proving each item, to compile against the twin (`leanLibrary`, or `leanProject` for a ready Lake project).
|
|
781
772
|
// Each machine's laws come with `<id>_laws` (the twin's theorem) and `<id>.lawN` (the law as a plain Lean statement
|
|
@@ -798,23 +789,11 @@ export function toLean(items, opts = {}) {
|
|
|
798
789
|
throw new Error(`${item.id}: not proven (${r.failed?.stuck ?? ''})`);
|
|
799
790
|
item = { ...item, laws: r.laws ?? item.laws, ...(item.stepLaws ? { stepLaws: r.stepLaws ?? item.stepLaws } : {}) };
|
|
800
791
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
// One twin: a machine without a history goes to Lean as a machine with sets (one with none, if it has none): its laws,
|
|
804
|
-
// step laws, liveness and ghosts in one export. A history's laws go through History.lean, and its step laws and
|
|
805
|
-
// liveness through its plain machine (its sums as state, its queries as inputs).
|
|
806
|
-
if (m.history) {
|
|
807
|
-
parts.push(`-- ${item.id}\n${hmachineToLean(id, m, item.laws)}`);
|
|
808
|
-
theorems.push(`${id}_laws`);
|
|
809
|
-
if (item.stepLaws?.length || item.liveness?.length) {
|
|
810
|
-
parts.push(`-- ${item.id}: step laws and liveness, on its plain machine\n${smachineToLean(`${id}s`, historyMachine(m).machine, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}) })}`);
|
|
811
|
-
theorems.push(...(item.stepLaws ?? []).map((_, j) => `${id}s_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}s_live${j}_holds`));
|
|
812
|
-
}
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
792
|
+
// One twin: every machine goes to Lean as a machine with sets (one with none, if it has none): its laws, step laws,
|
|
793
|
+
// liveness and ghosts in one export; a machine with a history as its plain machine, and its laws over histories.
|
|
815
794
|
parts.push(`-- ${item.id}\n${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
|
|
816
795
|
...(item.ghosts?.length ? { ghosts: item.ghosts } : {}), ...(item.lean?.simp ? { simp: item.lean.simp } : {}), ...(item.lean?.after ? { after: item.lean.after } : {}) })}`);
|
|
817
|
-
theorems.push(`${id}_laws`, ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
|
|
796
|
+
theorems.push(`${id}_laws`, ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
|
|
818
797
|
if (item.lean?.after)
|
|
819
798
|
theorems.push(...[...item.lean.after(() => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1]));
|
|
820
799
|
}
|
|
@@ -839,9 +818,9 @@ export function toLean(items, opts = {}) {
|
|
|
839
818
|
theorems.push(id);
|
|
840
819
|
}
|
|
841
820
|
}
|
|
842
|
-
// Sets.lean (which imports Steps.lean)
|
|
821
|
+
// The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
|
|
843
822
|
return `import History
|
|
844
|
-
import ${items.some(item => 'refinement' in item) ? 'Refine' : items.some(item => 'machine' in item && item.ghosts?.length) ? 'Ghost' : items.some(item => 'machine' in item && item.liveness?.length) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
|
|
823
|
+
import ${items.some(item => 'refinement' in item) ? 'Refine' : items.some(item => 'machine' in item && item.ghosts?.length) ? 'Ghost' : items.some(item => ('machine' in item && item.liveness?.length) || 'loop' in item) ? 'Live' : items.some(item => 'machine' in item) ? 'Sets' : 'Steps'}
|
|
845
824
|
open Kern Cert
|
|
846
825
|
set_option maxRecDepth 100000
|
|
847
826
|
set_option maxHeartbeats 0
|