gdp-wired 0.17.0 → 0.19.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 +10 -3
- package/dist/core/cycles.d.ts +73 -0
- package/dist/core/cycles.js +899 -0
- package/dist/core/cyclic.d.ts +106 -6
- package/dist/core/cyclic.js +1138 -52
- package/dist/core/data.d.ts +258 -0
- package/dist/core/data.js +1858 -0
- package/dist/core/dsl.d.ts +7 -1
- package/dist/core/dsl.js +43 -10
- package/dist/core/translate.d.ts +30 -0
- package/dist/core/translate.js +572 -0
- package/dist/kernel/prove.d.ts +18 -0
- package/dist/kernel/prove.js +272 -15
- package/dist/lean-kernel/emit.d.ts +95 -1
- package/dist/lean-kernel/emit.js +615 -83
- package/dist/lean-kernel/library.js +4 -1
- package/package.json +10 -2
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { atMost, n, not, pointWrite } from "../core/machine.js";
|
|
2
2
|
import { add, k, scale, sub } from "../core/solver.js";
|
|
3
3
|
import { boundOf, canon, factorsOf, isCell, isMonomial, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, witnessesOf, withProducts } from "../kernel/kernel.js";
|
|
4
|
-
import { grow, minimal, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
4
|
+
import { grow, minimal, proveBuilt, proveLaws, proveStepLaws, withDeadline } from "../kernel/prove.js";
|
|
5
5
|
import { refinement, refinementSides } from "../core/compose.js";
|
|
6
6
|
import { leanSurface, leanTerms } from "./lean-terms.js";
|
|
7
7
|
import { descentGoal, mainGoal, premiseGoal } from "../kernel/cyclic.js";
|
|
8
|
+
import { allowNode, certMs, closingInsts, reqTS, withWitness, cyclicMachine, foldNodes, hasQuantified, kWhens, machineNodes, proveCyclic, qflagNodes, routeOf, searchedStepNodes, skMachine, stepLawNodes, tBack, tSub } from "../core/cyclic.js";
|
|
8
9
|
import { leanLibrary } from "./library.js";
|
|
9
10
|
export { leanLibrary };
|
|
10
11
|
// --- to Lean ------------------------------------------------------------------------------------------------------------
|
|
@@ -131,12 +132,24 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
131
132
|
// monomial with a cell among its factors after the cells.
|
|
132
133
|
const degree = (a, b) => factorsOf(a).length - factorsOf(b).length;
|
|
133
134
|
const plainMonos = [...monos].filter(m => factorsOf(m).every(f => !isCell(f))).sort(degree), cellMonos = [...monos].filter(m => factorsOf(m).some(isCell)).sort(degree);
|
|
134
|
-
|
|
135
|
+
// A cell whose key reads a product of cells (a cyclic proof's map at u + k·Ω, its key k read from a map over time)
|
|
136
|
+
// comes after that product, and the product after its cells: each column placed after the columns it reads.
|
|
137
|
+
const late = [], placed = new Set();
|
|
138
|
+
const place = (name) => {
|
|
139
|
+
if (placed.has(name))
|
|
140
|
+
return;
|
|
141
|
+
placed.add(name);
|
|
142
|
+
for (const d of isMonomial(name) ? factorsOf(name) : Object.keys(cellKey(name)))
|
|
143
|
+
if (cells.has(d) || cellMonos.includes(d))
|
|
144
|
+
place(d);
|
|
145
|
+
late.push(name);
|
|
146
|
+
};
|
|
147
|
+
for (const cell of [...cells].sort((a, b) => depth(a) - depth(b)))
|
|
148
|
+
place(cell);
|
|
149
|
+
for (const mono of cellMonos)
|
|
150
|
+
place(mono);
|
|
151
|
+
const cols = [...vars, ...plainMonos, ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...late];
|
|
135
152
|
const col = new Map(cols.map((name, j) => [name, j]));
|
|
136
|
-
for (const cell of cells)
|
|
137
|
-
for (const key of Object.keys(cellKey(cell)))
|
|
138
|
-
if (cellMonos.includes(key))
|
|
139
|
-
throw new Error(`${ex.name}: the key of ${cell} reads a product of cells`);
|
|
140
153
|
const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
|
|
141
154
|
const dense = (lin) => {
|
|
142
155
|
const coefs = new Array(cols.length).fill(0);
|
|
@@ -321,6 +334,7 @@ theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t,
|
|
|
321
334
|
// Readable corollaries: each law as a plain Lean statement over named variables (ρ the state, μ the maps, each name an
|
|
322
335
|
// abbreviation for its column), proven from the machine's theorem by unfolding the law's own definition. The statement
|
|
323
336
|
// is what a Lean development cites; the column numbers stay inside the namespace.
|
|
337
|
+
// `have`: the line that gives each law as a hypothesis hl (an SEntry's Holds by default; a cyclic proof's, holdsOver).
|
|
324
338
|
export function readable(id, laws, vars, maps, reach) {
|
|
325
339
|
for (const name of [...vars, ...maps])
|
|
326
340
|
if (name === 'ρ' || name === 'μ' || name.includes('«') || name.includes('»'))
|
|
@@ -363,35 +377,11 @@ export function readable(id, laws, vars, maps, reach) {
|
|
|
363
377
|
${[...vars.filter(v => used.has(v)).map(v => `abbrev ${q(v)} : Nat := ${vars.indexOf(v)}`), ...maps.filter(mp => used.has(mp)).map(mp => `abbrev ${q(mp)} : Nat := ${maps.indexOf(mp)}`)].join('\n')}
|
|
364
378
|
${laws.map((law, j) => (law.stated ? `-- ${law.name}: as written, with some key; proven (below) with the prover's witness in place\n` : '') + (JSON.stringify(law.holds).includes('"op":"if"') ? `-- ${law.name}: its terms read an if, so no plain statement here (${id}_laws holds it)` : `/-- ${law.name.replace(/-\//g, '- /')} -/
|
|
365
379
|
theorem law${j} ${reach.binders} (h : ${reach.hyp}) : ${statement(law)} := by
|
|
366
|
-
${law.keys?.length ? `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])
|
|
367
|
-
exact fun k ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} => (evalC_iff (upd (${law.keys.reduce((acc, key, i) => `upd (${acc}) ${vars.indexOf(key)} k${i + 2}`, 'ρ')}) ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} k)` : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])
|
|
380
|
+
${law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
|
|
381
|
+
exact fun k ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} => (evalC_iff (upd (${law.keys.reduce((acc, key, i) => `upd (${acc}) ${vars.indexOf(key)} k${i + 2}`, 'ρ')}) ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl ${law.keys.map((_, i) => `k${i + 2}`).join(' ')} k)` : `${reach.have ? reach.have(j) : `have hl : ${id}_l${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
|
|
368
382
|
${law.forall ? `exact fun k => (evalC_iff (upd ρ ${vars.indexOf(law.forall)} k) μ ${id}_l${j}.holds).mp (hl k)` : `exact (evalC_iff ρ μ ${id}_l${j}.holds).mp hl`}`}`)).join('\n')}
|
|
369
383
|
end ${id}`;
|
|
370
384
|
}
|
|
371
|
-
// Live.lean's reqC: each input named by its term, two parts per and.
|
|
372
|
-
const reqTS = (req) => (req.length ? { op: 'and', xs: [{ op: '=', a: { op: 'var', name: req[0][0] }, b: req[0][1] }, reqTS(req.slice(1))] } : { op: '<=', a: { op: 'const', value: 0 }, b: { op: 'const', value: 0 } });
|
|
373
|
-
// Witnesses for a helpful request's free inputs, for its allowed goal: all free first, then each free input fixed to 0, a
|
|
374
|
-
// state variable or a param (a history's query, say, which its relations bound), the first combination that proves.
|
|
375
|
-
function withWitness(m, request, attempt) {
|
|
376
|
-
const free = m.inputs.filter(i => !(i in request));
|
|
377
|
-
const cands = [undefined, { op: 'const', value: 0 }, ...Object.keys(m.state).map(v => ({ op: 'var', name: v })), ...m.params.map(v => ({ op: 'var', name: v }))];
|
|
378
|
-
const combos = [[]];
|
|
379
|
-
if (free.length <= 2) {
|
|
380
|
-
const go = (i, acc) => { if (i === free.length) {
|
|
381
|
-
if (acc.length)
|
|
382
|
-
combos.push(acc);
|
|
383
|
-
return;
|
|
384
|
-
} for (const c of cands)
|
|
385
|
-
go(i + 1, c ? [...acc, [free[i], c]] : acc); };
|
|
386
|
-
go(0, []);
|
|
387
|
-
}
|
|
388
|
-
for (const wit of combos) {
|
|
389
|
-
const out = attempt(wit);
|
|
390
|
-
if (out !== undefined)
|
|
391
|
-
return { wit, out };
|
|
392
|
-
}
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
385
|
// A liveness as Live.lean's record: from, to, the rank's parts, each helpful request with its region, the primes.
|
|
396
386
|
function liveLit(l, t, ix, primes, primeMaps) {
|
|
397
387
|
return `⟨${t.c(l.from)}, ${t.c(l.to)}, ${list(livenessRanks(l).map(t.e))},
|
|
@@ -511,6 +501,49 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
|
|
|
511
501
|
// the relations assumed), built in Lean from the machine and its history (History.lean's HMachine.plain), so every claim
|
|
512
502
|
// below is about it; with the relations' record goals, its laws hold over histories too (hmachine_sound).
|
|
513
503
|
// `historyTampers`: the history with other relations, each refused.
|
|
504
|
+
// A machine as Sets.lean's SMachine literal: its variables and maps numbered by `ix` and `mx`, its terms by `t`.
|
|
505
|
+
export function machineLit(mm, t, ix, mx) {
|
|
506
|
+
const { e, c } = t;
|
|
507
|
+
const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
|
|
508
|
+
return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
|
|
509
|
+
${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
510
|
+
${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
|
|
511
|
+
${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
512
|
+
${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
|
|
513
|
+
${mm.pre ? c(mm.pre) : TRUE},
|
|
514
|
+
${mm.assume ? c(mm.assume) : TRUE}⟩,
|
|
515
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
|
|
516
|
+
${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
|
|
517
|
+
${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
|
|
518
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
|
|
519
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
|
|
520
|
+
${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
|
|
521
|
+
${list((mm.facts ?? []).filter(l => l.keys?.length).map(l => `(⟨${c(l.holds)}, some ${ix(l.forall)}⟩, ${list(l.keys.map(k2 => String(ix(k2))))})`))}⟩`;
|
|
522
|
+
}
|
|
523
|
+
// A request's extended names: its inputs, each flag that holds at every key and its witnesses (Sets.lean's extVars, in
|
|
524
|
+
// the order the usual export lists a later step's names in).
|
|
525
|
+
const extOf = (m) => [...m.inputs, ...Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [`?${f}`, ...witnessesOf(f, c)] : []))];
|
|
526
|
+
// The names a machine's export numbers first, before those only its goals read: the machine's own (params, inputs, state,
|
|
527
|
+
// flags, the keys of its laws and facts, the keys its writes and quantified flags bind) and its claims' (each step law's
|
|
528
|
+
// key, the state after the step, each later request's names, the state between steps, a history's record). The usual
|
|
529
|
+
// export and the cyclic one (cycMachineToLean) number them alike, so that a machine, a law's statement and a step law are
|
|
530
|
+
// the same Lean text in both.
|
|
531
|
+
function machineNames(m, laws, allSteps, stepLaws, opts) {
|
|
532
|
+
const flags = Object.entries(m.step.flags ?? {}), quantified = flags.filter(([, c]) => c.op === 'all').map(([f]) => f);
|
|
533
|
+
const qwits = (f) => witnessesOf(f, m.step.flags[f]);
|
|
534
|
+
const carried = m.inputMaps ?? [], keysOf = [...new Set([...laws, ...(m.facts ?? [])].flatMap(l => [l.forall, ...(l.keys ?? [])].filter((k) => !!k)))];
|
|
535
|
+
const k = Math.max(1, ...allSteps.map(sl => sl.steps ?? 1));
|
|
536
|
+
const ext = [...m.inputs, ...quantified.flatMap(f => [`?${f}`, ...qwits(f)])];
|
|
537
|
+
const later = Array.from({ length: k - 1 }, (_, j) => ext.map(v => `${v}@${j + 2}`)).flat();
|
|
538
|
+
const laterMaps = Array.from({ length: k - 1 }, (_, j) => carried.map(mp => `${mp}@${j + 2}`)).flat();
|
|
539
|
+
const primes = allSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
|
|
540
|
+
const vars = [...new Set([...m.params, ...m.inputs, ...Object.keys(m.state), ...keysOf, ...keysOf.map(k2 => `${k2}#`), '#', ...flags.map(([f]) => `?${f}`),
|
|
541
|
+
...quantified.flatMap(qwits), ...boundKeys(m), ...(opts.tamper ? boundKeys(opts.tamper.machine) : []), ...stepLaws.flatMap(sl => (sl.forall ? [sl.forall] : [])), ...(opts.free ?? []),
|
|
542
|
+
...primes, ...later, ...Array.from({ length: k - 1 }, (_, j) => Object.keys(m.state).map(n2 => stateName(n2, j + 1))).flat(), ...(opts.record ?? []).map(f => `r.${f}`)])];
|
|
543
|
+
const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
|
|
544
|
+
const maps = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps])];
|
|
545
|
+
return { vars, maps };
|
|
546
|
+
}
|
|
514
547
|
export function smachineToLean(id, given, laws, opts = {}) {
|
|
515
548
|
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
516
549
|
const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
|
|
@@ -613,12 +646,8 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
613
646
|
const lp = r.proof.laws, si = r.proof.instances;
|
|
614
647
|
const flags = Object.entries(m.step.flags ?? {}), quantified = flags.filter(([, c]) => c.op === 'all').map(([f]) => f);
|
|
615
648
|
const qwits = (f) => witnessesOf(f, m.step.flags[f]);
|
|
616
|
-
const carried = m.inputMaps ?? [], facts = m.facts ?? []
|
|
617
|
-
const k = Math.max(1, ...allSteps.map(sl => sl.steps ?? 1));
|
|
649
|
+
const carried = m.inputMaps ?? [], facts = m.facts ?? [];
|
|
618
650
|
const ext = [...m.inputs, ...quantified.flatMap(f => [`?${f}`, ...qwits(f)])];
|
|
619
|
-
const later = Array.from({ length: k - 1 }, (_, j) => ext.map(v => `${v}@${j + 2}`)).flat();
|
|
620
|
-
const laterMaps = Array.from({ length: k - 1 }, (_, j) => carried.map(mp => `${mp}@${j + 2}`)).flat();
|
|
621
|
-
const primes = allSteps.length ? Object.keys(m.state).map(s2 => `${s2}'`) : [];
|
|
622
651
|
const instOf = (sl) => r.proof.perStep?.[sl.name] ?? si;
|
|
623
652
|
const goalsL = obligations(m, laws, lp.instances), goalsS = allSteps.map(sl => stepLawObligations(m, laws, instOf(sl), sl)[0]);
|
|
624
653
|
// Weak fairness over sets: each helpful request allowed in its region carrying empty sets (Live.lean's sAllowGoal,
|
|
@@ -684,13 +713,9 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
684
713
|
return r2.proven ? r2 : undefined;
|
|
685
714
|
})]));
|
|
686
715
|
const allowGoals = [...allowed.values()].flatMap(a => (a ? [a.out.goal] : []));
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
...(hm ? hm.history.record.map(f => `r.${f}`) : []), ...[...goalsL, ...goalsS, ...allowGoals, ...(hplain?.goals ?? [])].flatMap(g => g.hyps.flatMap(varsOf))])];
|
|
691
|
-
const baseMaps = [...new Set([...Object.keys(m.maps ?? {}), ...Object.keys(m.step.maps ?? {}), ...Object.keys(m.fns ?? {})])];
|
|
692
|
-
const mapNames = [...new Set([...baseMaps, ...carried, ...(allSteps.length ? Object.keys(m.maps ?? {}).map(mp => `${mp}'`) : []), ...laterMaps,
|
|
693
|
-
...[...JSON.stringify([...goalsL, ...goalsS, ...allowGoals]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x2 => x2[1])])];
|
|
716
|
+
const named = machineNames(m, laws, allSteps, stepLaws, { ...opts, ...(hm ? { record: hm.history.record } : {}) });
|
|
717
|
+
const vars = [...new Set([...named.vars, ...[...goalsL, ...goalsS, ...allowGoals, ...(hplain?.goals ?? [])].flatMap(g => g.hyps.flatMap(varsOf))])];
|
|
718
|
+
const mapNames = [...new Set([...named.maps, ...[...JSON.stringify([...goalsL, ...goalsS, ...allowGoals]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x2 => x2[1])])];
|
|
694
719
|
const enc = (g, tree) => encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, mapNames);
|
|
695
720
|
const { e, c } = leanTerms(vars, mapNames, id);
|
|
696
721
|
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
@@ -698,23 +723,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
698
723
|
const mx = (name) => { const j = mapNames.indexOf(name); if (j < 0)
|
|
699
724
|
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
700
725
|
const inline = (c2) => substC(c2, { vars: {}, maps: {}, flags: flagsOfM(m) });
|
|
701
|
-
const lit = (mm) => {
|
|
702
|
-
const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
|
|
703
|
-
return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
|
|
704
|
-
${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
705
|
-
${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
|
|
706
|
-
${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
707
|
-
${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
|
|
708
|
-
${mm.pre ? c(mm.pre) : TRUE},
|
|
709
|
-
${mm.assume ? c(mm.assume) : TRUE}⟩,
|
|
710
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
|
|
711
|
-
${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
|
|
712
|
-
${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
|
|
713
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
|
|
714
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
|
|
715
|
-
${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
|
|
716
|
-
${list((mm.facts ?? []).filter(l => l.keys?.length).map(l => `(⟨${c(l.holds)}, some ${ix(l.forall)}⟩, ${list(l.keys.map(k2 => String(ix(k2))))})`))}⟩`;
|
|
717
|
-
};
|
|
726
|
+
const lit = (mm) => machineLit(mm, { e, c }, ix, mx);
|
|
718
727
|
// A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
|
|
719
728
|
const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
|
|
720
729
|
${list(Object.entries(lp.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
@@ -814,6 +823,325 @@ ${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat
|
|
|
814
823
|
def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
|
|
815
824
|
example : checkSMachine ${id}_tampered ${id}_proof = false := by ${checkBy}` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
|
|
816
825
|
}
|
|
826
|
+
// --- the cyclic certificate's export (GDP_CERT=cyclic) -------------------------------------------------------------
|
|
827
|
+
// By default, or with GDP_CERT=cyclic (core/cyclic.ts's certOn: a covered proof's certificate is its cyclic proof; or toLean's
|
|
828
|
+
// `cyclic`), toLean exports each machine the cyclic route covers from one cyclic
|
|
829
|
+
// proof (kernel/cyclic.ts) of its laws and of every step law its claims bring (its own step laws and k-step laws, a
|
|
830
|
+
// liveness's), carried to the machine by Bridge.lean's cyc_machine_sound and BridgeRules.lean's cyc_kstep_sound,
|
|
831
|
+
// sleads_of_holds (liveness, loops), hreach_plain (histories) and cyc_ghost_holds (ghosts), and each theorem stated as the
|
|
832
|
+
// usual export states it: the same machine, laws, step laws and liveness (machineNames numbers them alike), the same
|
|
833
|
+
// `<id>.lawN`, `<id>_stepN_holds`, `<id>_liveN_holds`, `<id>_halts`. What the cyclic route doesn't cover goes the usual
|
|
834
|
+
// way: a flag that holds at every key, a law that reads a map at a key read from a map, a refinement, and a liveness's
|
|
835
|
+
// weak fairness (its helpful requests proven allowed: a claim about requests the run doesn't take).
|
|
836
|
+
const envOf = () => globalThis.process?.env;
|
|
837
|
+
export const cyclicCerts = () => envOf()?.GDP_CERT !== 'usual';
|
|
838
|
+
let certsOn = false, cycTexts = 0;
|
|
839
|
+
// What the last toLean exported from cyclic certificates, and what went the usual way (and why).
|
|
840
|
+
export const certReport = { cyclic: [], usual: [], translatedSteps: [] };
|
|
841
|
+
// Each item's cyclic certificate is made within one deadline (GDP_CERT_MS, 10 s by default), every goal it grows (the
|
|
842
|
+
// translation's leaves grown again, the searched nodes) bounded by it: past it, the item goes the usual way.
|
|
843
|
+
function cycMachineProof(m, laws, allSteps, allows = []) {
|
|
844
|
+
const r = proveStepLaws(m, laws, allSteps);
|
|
845
|
+
if (!r.proven || !r.proof)
|
|
846
|
+
return { why: 'not proven the usual way' };
|
|
847
|
+
const until = performance.now() + certMs();
|
|
848
|
+
return withDeadline(until, () => cycMachineProofBy(m, laws, allSteps, allows, r, until));
|
|
849
|
+
}
|
|
850
|
+
function cycMachineProofBy(m, laws, allSteps, allows, r, until) {
|
|
851
|
+
if (!r.proof)
|
|
852
|
+
return { why: 'not proven the usual way' };
|
|
853
|
+
const lp = r.proof.laws, instOf = (sl) => r.proof.perStep?.[sl.name] ?? r.proof.instances;
|
|
854
|
+
// A machine with flags that hold at every key: its sk machine's nodes, the flags' closing facts read where each node
|
|
855
|
+
// reads the run.
|
|
856
|
+
const q = hasQuantified(m), mm = skMachine(m);
|
|
857
|
+
const L = laws.length, mine = (facts, lawNodes) => searchedStepNodes(m, laws, allSteps.map(sl => ({ law: sl, instances: instOf(sl) })), facts, lawNodes);
|
|
858
|
+
const tries = [];
|
|
859
|
+
if (!q && routeOf() !== 'search' && allSteps.length) {
|
|
860
|
+
const usualSteps = allSteps.map(sl => { const inst = instOf(sl), g = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal: g, tree: r.proof.trees[g.name] }; });
|
|
861
|
+
const tr = usualSteps.every(u => u.tree) ? machineNodes(m, laws, lp.instances, { trees: lp.trees, steps: usualSteps }) : undefined;
|
|
862
|
+
if (tr && !tr.why && tr.specs.length === 3 * L + 2 * allSteps.length) {
|
|
863
|
+
// (either time form cycKOk reads: one step back at a time, or t − n at once)
|
|
864
|
+
const id = (x) => JSON.stringify(x), forms = [tBack, tSub].map(time => stepLawNodes(mm, laws, allSteps.map(sl => ({ law: sl, instances: instOf(sl) })), tr.facts, tr.specs, time));
|
|
865
|
+
const fits = allSteps.every((_, j) => forms.some(own => {
|
|
866
|
+
const a = tr.specs[3 * L + 2 * j + 1], b = own[j], hs = new Set(b.hyps.map(id));
|
|
867
|
+
return id(a.concl) === id(b.concl) && a.hyps.every(h => hs.has(id(h)));
|
|
868
|
+
}));
|
|
869
|
+
if (fits)
|
|
870
|
+
tries.push({ facts: tr.facts, specs: tr.specs, lawNodes: tr.specs, stepAt: allSteps.map((_, j) => 3 * L + 2 * j + 1), translated: true });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
for (const base of (q ? [qflagNodes(m, laws, lp.instances)]
|
|
874
|
+
: [...(routeOf() !== 'search' ? [machineNodes(m, laws, lp.instances, { trees: lp.trees })] : []), machineNodes(m, laws, lp.instances)]).filter(b => !b.why)) {
|
|
875
|
+
tries.push({ facts: base.facts, specs: [...base.specs, ...mine(base.facts, base.specs)], lawNodes: base.specs, stepAt: allSteps.map((_, j) => base.specs.length + j) });
|
|
876
|
+
}
|
|
877
|
+
let last = 'no nodes';
|
|
878
|
+
for (const { facts, specs: specs0, lawNodes, stepAt, translated } of tries) {
|
|
879
|
+
if (performance.now() > until)
|
|
880
|
+
return { why: `past the certificate's deadline (${certMs()} ms)` };
|
|
881
|
+
let specs = specs0, v = proveCyclic(facts, specs);
|
|
882
|
+
if (!(v.proven && v.checked)) {
|
|
883
|
+
last = performance.now() > until ? `past the certificate's deadline (${certMs()} ms)` : v.proven ? `not checked (${v.why})` : `${v.node}: ${v.goal}: ${v.stuck}`;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const allowAt = allows.map((group) => {
|
|
887
|
+
if ((lawNodes[0]?.rank.length ?? 2) < 2)
|
|
888
|
+
return undefined;
|
|
889
|
+
const before = specs, out = [];
|
|
890
|
+
for (const a of group) {
|
|
891
|
+
const found = withWitness(m, a.request, wit => {
|
|
892
|
+
if (performance.now() > until)
|
|
893
|
+
return undefined;
|
|
894
|
+
const spec = allowNode(mm, laws, r.proof.instances, { op: 'and', xs: [a.when, reqTS(wit)] }, facts, lawNodes, `${m.name}: allowed`, q ? closingInsts(m, facts, [r.proof.instances, ...Object.values(lp.instances)], [{ op: 'var', name: 't$' }], laws) : []);
|
|
895
|
+
const v2 = proveCyclic(facts, [...specs, spec]);
|
|
896
|
+
return v2.proven && v2.checked ? { spec, v2 } : undefined;
|
|
897
|
+
});
|
|
898
|
+
if (!found) {
|
|
899
|
+
specs = before;
|
|
900
|
+
return undefined;
|
|
901
|
+
}
|
|
902
|
+
out.push({ node: specs.length, wit: found.wit });
|
|
903
|
+
specs = [...specs, found.out.spec];
|
|
904
|
+
v = found.out.v2;
|
|
905
|
+
}
|
|
906
|
+
return out;
|
|
907
|
+
});
|
|
908
|
+
if (v.proven && v.checked) {
|
|
909
|
+
if (translated)
|
|
910
|
+
certReport.translatedSteps.push(m.name);
|
|
911
|
+
return { p: v.proof, stepAt, allowAt };
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return { why: last };
|
|
915
|
+
}
|
|
916
|
+
// A machine (with a history, ghosts, a liveness or as a loop's) from one cyclic proof, or why it can't be.
|
|
917
|
+
export function cycMachineToLean(id, given, laws, opts = {}) {
|
|
918
|
+
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
919
|
+
const hplain = hm ? historyMachine(hm) : undefined, m = hplain ? hplain.machine : given;
|
|
920
|
+
const stepLaws = opts.stepLaws ?? [], gv = opts.ghosts ?? [], lp0 = opts.loop;
|
|
921
|
+
if (!cyclicMachine(skMachine(m), laws))
|
|
922
|
+
return { why: 'a law reading a map at a key read from a map' };
|
|
923
|
+
// With flags that hold at every key, the sk machine's encoding and BridgeRules.lean's q bridges.
|
|
924
|
+
const mq = skMachine(m), Q = hasQuantified(m) ? 'Q' : '', q = hasQuantified(m) ? 'q' : '';
|
|
925
|
+
const lives = (opts.liveness ?? []).map(l => ({ l, ...livenessParts(m, l) }));
|
|
926
|
+
// A refinement's step law: the spec stutters or allows the request it is sent, its `then` Lean's SRefined.allowed (the
|
|
927
|
+
// spec's assumption at the witness, the composite's flags inlined).
|
|
928
|
+
const rf = opts.refined;
|
|
929
|
+
const rfStep = rf && { ...rf.step, when: undefined, then: { op: 'or', xs: [rf.stutter, substC(rf.spec.assume ?? trueC, { vars: Object.fromEntries(Object.entries(rf.wit).map(([inp, t2]) => [`b_${inp}`, t2])), maps: {},
|
|
930
|
+
flags: Object.fromEntries(Object.keys(m.step.flags ?? {}).map(f => [f, { op: 'flag', name: f }])) })] } };
|
|
931
|
+
const allSteps = [...(rfStep ? [rfStep] : stepLaws), ...lives.flatMap(x => [x.stay, x.cover, ...x.helps])];
|
|
932
|
+
const made = cycMachineProof(m, laws, allSteps, lives.map(x => x.helps.map((hs, i) => ({ when: hs.when, request: livenessHelpful(x.l)[i].request }))));
|
|
933
|
+
if ('why' in made)
|
|
934
|
+
return made;
|
|
935
|
+
const { p, stepAt, allowAt } = made;
|
|
936
|
+
const named = machineNames(m, laws, allSteps, stepLaws, { ...(opts.free ? { free: opts.free } : {}), ...(hm ? { record: hm.history.record } : {}) });
|
|
937
|
+
const sc = cycScope([p], { machine: mq, laws });
|
|
938
|
+
const vars = [...new Set([...named.vars, ...sc.vars])], maps = [...new Set([...named.maps, ...sc.maps])];
|
|
939
|
+
const { e, c } = leanTerms(vars, maps, id);
|
|
940
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
941
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
942
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
943
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
944
|
+
const lit = (mm) => machineLit(mm, { e, c }, ix, mx);
|
|
945
|
+
const inline = (c2) => substC(c2, { vars: {}, maps: {}, flags: flagsOfM(m) });
|
|
946
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
947
|
+
const keysL = (l) => list(keysOf(l).map(k2 => String(ix(k2))));
|
|
948
|
+
const state = Object.keys(m.state), carried = m.inputMaps ?? [];
|
|
949
|
+
const primesL = () => list(state.map(s2 => `(${ix(s2)}, ${ix(`${s2}'`)})`)), primeMapsL = () => list(Object.keys(m.maps ?? {}).map(mp => `(${mx(mp)}, ${mx(`${mp}'`)})`));
|
|
950
|
+
const node = (i) => `${id}_node${i}`;
|
|
951
|
+
const kstep = (sk, i) => `cyc_${q}kstep_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check ${sk} ${node(i)} ${node(i)}_mem`;
|
|
952
|
+
// The machine as the usual export defines it: with a history its plain machine, with ghosts the ghosted one, a loop's.
|
|
953
|
+
const orig = { ...m, state: Object.fromEntries(Object.entries(m.state).filter(([n2]) => !gv.includes(n2))), step: { ...m.step, state: Object.fromEntries(Object.entries(m.step.state ?? {}).filter(([n2]) => !gv.includes(n2))) } };
|
|
954
|
+
const hlit = () => {
|
|
955
|
+
const h = hm.history, base = { ...hm, history: undefined };
|
|
956
|
+
return `⟨${lit(base)},
|
|
957
|
+
${c(h.keep)},
|
|
958
|
+
${list(h.record.map(f => String(ix(f))))},
|
|
959
|
+
${list(h.record.map(f => String(ix(`r.${f}`))))},
|
|
960
|
+
${list(Object.entries(h.sums).map(([name, g]) => `(${ix(name)}, ${e(g)})`))},
|
|
961
|
+
${list(Object.entries(h.queries).map(([name, g]) => `(${ix(name)}, ${e(g)})`))},
|
|
962
|
+
${list((h.relations ?? []).map(rel => { if (rel.holds.op !== '<=')
|
|
963
|
+
throw new Error(`${id}: a relation is a <= b`); return `(${c(rel.when)}, ${e(rel.holds.a)}, ${e(rel.holds.b)})`; }))}⟩`;
|
|
964
|
+
};
|
|
965
|
+
const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
|
|
966
|
+
${list(Object.entries(lp.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
967
|
+
${list(Object.entries(lp.body).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
968
|
+
${c(lp.while)},
|
|
969
|
+
${lp.pre ? c(lp.pre) : TRUE},
|
|
970
|
+
${lp.assume ? c(lp.assume) : TRUE}⟩`;
|
|
971
|
+
const machineDef = rf ? `def ${id}_refined : SRefined := ⟨${lit(rf.impl)},
|
|
972
|
+
${lit(rf.spec)},
|
|
973
|
+
${list(Object.entries(rf.wit).map(([inp, t2]) => `(${ix(`b_${inp}`)}, ${e(t2)})`))},
|
|
974
|
+
${c(rf.stutter)}⟩
|
|
975
|
+
def ${id}_machine : SMachine := ${id}_refined.full` : gv.length ? `def ${id}_ghosted : SGhosted := ⟨${lit(orig)},
|
|
976
|
+
${list(gv.map(v2 => `(${ix(v2)}, ${e(m.state[v2])}, ${e(m.step.state?.[v2] ?? { op: 'var', name: v2 })})`))}⟩
|
|
977
|
+
def ${id}_machine : SMachine := ${id}_ghosted.full
|
|
978
|
+
theorem ${id}_ghostOk : sghostOk ${id}_ghosted = true := by ${checkBy}` : hm ? `def ${id}_hist : HMachine := ${hlit()}
|
|
979
|
+
def ${id}_machine : SMachine := ${id}_hist.plain` : lp0 ? `def ${id}_loop : Loop := ${loopLit(lp0.loop)}
|
|
980
|
+
def ${id}_machine : SMachine := ${id}_loop.machine` : `def ${id}_machine : SMachine := ${lit(m)}`;
|
|
981
|
+
const enc = `⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
|
|
982
|
+
${list([...state, ...mq.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
|
|
983
|
+
${list([...Object.keys(m.maps ?? {}), ...carried].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩`;
|
|
984
|
+
// Each law: Bridge.lean's check and theorem, then all of them as the usual export's `<id>_laws` states them.
|
|
985
|
+
const lawBlocks = laws.map((l, j) => `-- ${l.name}
|
|
986
|
+
def ${id}_law${j} : C := ${c(l.holds)}
|
|
987
|
+
def ${id}_l${j} : MLaw := ⟨${id}_law${j}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩
|
|
988
|
+
theorem ${id}_law${j}_ok : cyc${Q}MOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${node(j)} = true := by ${checkBy}
|
|
989
|
+
theorem ${id}_law${j}_holds : ∀ ρ M, SReach ${id}_machine ρ M → ∀ ρ' : Nat → Nat, (∀ i, i ∉ ${keysL(l)} → ρ' i = ρ i) → evalC ρ' M ${id}_law${j} = true :=
|
|
990
|
+
cyc_${q}machine_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check ${id}_law${j} ${keysL(l)} ${node(j)} ${node(j)}_mem ${id}_law${j}_ok`).join('\n');
|
|
991
|
+
const keys2 = (l) => list((l.keys ?? []).map(k2 => String(ix(k2))));
|
|
992
|
+
const oneOf = (hyp) => `simp only [${id}_mlaws, List.mem_cons, List.not_mem_nil, or_false] at ${hyp}`;
|
|
993
|
+
const lawsThm = `def ${id}_mlaws : List (MLaw × List Nat) := ${list(laws.map((l, j) => `(${id}_l${j}, ${keys2(l)})`))}
|
|
994
|
+
theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_mlaws, holdsOver e.1 M e.2 ρ := by
|
|
995
|
+
intro ρ M h e he
|
|
996
|
+
${laws.length ? `${oneOf('he')}
|
|
997
|
+
rcases he with ${laws.map(() => 'rfl').join(' | ')}
|
|
998
|
+
${laws.map((_, j) => `· exact cyc_machine_holds (${id}_law${j}_holds ρ M h)`).join('\n ')}` : `simp [${id}_mlaws] at he`}`;
|
|
999
|
+
// A history's record goals (leaf goals, as the usual export has them), and its laws over histories.
|
|
1000
|
+
const histBlock = () => {
|
|
1001
|
+
const records = hplain.goals.map(g => { const out = grow(g); if (!('tree' in out))
|
|
1002
|
+
throw new Error(`${id}: ${g.name} not proven`); const x2 = encode({ name: `${id}: ${g.name}`, goal: g, tree: out.tree }, false, true, vars, maps); return `(${x2.tab}, ${x2.tree})`; });
|
|
1003
|
+
const reach = (hyp) => `(hreach_plain ${id}_hist ${id}_records ${id}_hcheck ρ μ hist ${hyp})`;
|
|
1004
|
+
return `def ${id}_records : List (List VDef × Tree) := ${list(records)}
|
|
1005
|
+
theorem ${id}_hcheck : checkHMachine ${id}_hist [] ${id}_records = true := by ${checkBy}
|
|
1006
|
+
theorem ${id}_hlaws : ∀ ρ M hist, HReach ${id}_hist ρ M hist → ∀ e ∈ ${id}_mlaws, holdsOver e.1 M e.2 ρ :=
|
|
1007
|
+
fun ρ M hist h => ${id}_laws ρ M (hreach_plain ${id}_hist ${id}_records ${id}_hcheck ρ M hist h)
|
|
1008
|
+
${readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat) (hist : List (Nat → Nat))', hyp: `HReach ${id}_hist ρ μ hist`, call: '',
|
|
1009
|
+
have: j => `have hl : holdsOver ${id}_l${j} μ ${keys2(laws[j])} ρ := cyc_machine_holds (${id}_law${j}_holds ρ μ ${reach('h')})` })}`;
|
|
1010
|
+
};
|
|
1011
|
+
// The machine's own step laws, one step or k (BridgeRules.lean's cyc_kstep_sound): the SKStep as the usual export
|
|
1012
|
+
// writes it, with no certificate in it.
|
|
1013
|
+
const stepBlocks = (rf ? [] : stepLaws).map((sl, j) => {
|
|
1014
|
+
const kk = sl.steps ?? 1;
|
|
1015
|
+
const ren = Array.from({ length: kk }, (_, s2) => (s2 === 0 ? '[]' : list(extOf(m).map(v2 => `(${ix(v2)}, ${ix(`${v2}@${s2 + 1}`)})`))));
|
|
1016
|
+
const mren = Array.from({ length: kk }, (_, s2) => (s2 === 0 ? '[]' : list(carried.map(mp => `(${mx(mp)}, ${mx(`${mp}@${s2 + 1}`)})`))));
|
|
1017
|
+
const names = Array.from({ length: kk - 1 }, (_, s2) => list(state.map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`)));
|
|
1018
|
+
return `-- Step law${kk > 1 ? ` over ${kk} steps` : ''}: ${sl.name}
|
|
1019
|
+
def ${id}_step${j} : SKStep := ⟨⟨${list(kWhens(mq, sl).map(c))},
|
|
1020
|
+
${c(inline(sl.then))},
|
|
1021
|
+
${list(ren)},
|
|
1022
|
+
${primesL()},
|
|
1023
|
+
${primeMapsL()},
|
|
1024
|
+
[],
|
|
1025
|
+
([], .leaf [] [] [] [])⟩,
|
|
1026
|
+
${list(mren)},
|
|
1027
|
+
[], [], [], [], [],
|
|
1028
|
+
${list(names)}⟩
|
|
1029
|
+
theorem ${id}_step${j}_ok : cyc${Q}KOk ${id}_machine ${id}_enc ${id}_facts ${id}_step${j} ${node(stepAt[j])} = true := by ${checkBy}
|
|
1030
|
+
theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
|
|
1031
|
+
${kstep(`${id}_step${j}`, stepAt[j])} ${id}_step${j}_ok`;
|
|
1032
|
+
});
|
|
1033
|
+
// Each liveness from its step laws (BridgeRules.lean's sleads_of_holds), as the usual export states it.
|
|
1034
|
+
let at = stepLaws.length;
|
|
1035
|
+
const liveBlocks = lives.map(({ l, helps }, j) => {
|
|
1036
|
+
const lid = `${id}_live${j}`, at0 = at, s0 = stepAt[at0], c0 = stepAt[at0 + 1], hAt = (i) => stepAt[at0 + 2 + i];
|
|
1037
|
+
at += 2 + helps.length;
|
|
1038
|
+
// Under weak fairness, where each helpful request is proven allowed in its region (BridgeRules.lean's cyc_allow_sound).
|
|
1039
|
+
const al = allowAt[j], wfp = al ? `
|
|
1040
|
+
${al.map((a, i) => `def ${lid}_wit${i} : List (Nat × E) := ${list(a.wit.map(([inp, t2]) => `(${ix(inp)}, ${e(t2)})`))}`).join('\n')}
|
|
1041
|
+
theorem ${lid}_allows : ∀ h ∈ ${lid}.helps, ∃ wit : List (Nat × E), distinct ((h.2 ++ wit).map (·.1)) = true
|
|
1042
|
+
∧ (∀ p ∈ wit, (${id}_machine.f.m.inputs.contains p.1 = true ∧ avoids ${id}_machine.extVars (.eq p.2 p.2) = true) ∧ avoidsMaps ${id}_machine.carried (.eq p.2 p.2) = true)
|
|
1043
|
+
∧ AllowHolds ${id}_machine (.and (liveHelpWhen ${lid} h) (reqC wit)) := by
|
|
1044
|
+
intro h hh
|
|
1045
|
+
simp only [${lid}, List.mem_cons, List.not_mem_nil, or_false] at hh
|
|
1046
|
+
rcases hh with ${helps.map(() => 'rfl').join(' | ')}
|
|
1047
|
+
${al.map((a, i) => `· exact ⟨${lid}_wit${i}, by ${checkBy}, by ${checkBy}, cyc_${q}allow_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check _ ${node(a.node)} ${node(a.node)}_mem (by ${checkBy})⟩`).join('\n ')}
|
|
1048
|
+
/-- ${l.name}, under weak fairness: along every run that takes each helpful request whenever, from some point on, it
|
|
1049
|
+
stays allowed, \`from\` is followed by \`to\`. -/
|
|
1050
|
+
theorem ${lid}_holds_wf (run : SRun ${id}_machine)
|
|
1051
|
+
(fair : ∀ h ∈ ${lid}.helps, ∀ n, (∀ m, n ≤ m → ∃ ι κ, (∀ p ∈ h.2, ι p.1 = evalE (run.st m).1 (run.st m).2 p.2)
|
|
1052
|
+
∧ evalC (sView ${id}_machine (run.st m).1 (run.st m).2 ι κ).1 (sView ${id}_machine (run.st m).1 (run.st m).2 ι κ).2 ${id}_machine.f.m.assume = true) →
|
|
1053
|
+
∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
|
|
1054
|
+
∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
|
|
1055
|
+
sleads_wf_of_holds ${id}_machine ${lid} ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps ${lid}_allows run fair` : '';
|
|
1056
|
+
return `-- Liveness: ${l.name}
|
|
1057
|
+
def ${lid} : Live := ${liveLit(l, { e, c }, ix, primesL(), primeMapsL())}
|
|
1058
|
+
theorem ${lid}_ok : sliveOk ${id}_machine ${lid} = true := by ${checkBy}
|
|
1059
|
+
theorem ${lid}_stay : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveStayC ${lid})) := ${kstep('_', s0)} (by ${checkBy})
|
|
1060
|
+
theorem ${lid}_cover : SKStepHolds ${id}_machine (liveK ${lid} ${lid}.pend (liveCoverC ${lid})) := ${kstep('_', c0)} (by ${checkBy})
|
|
1061
|
+
theorem ${lid}_helps : ∀ h ∈ ${lid}.helps, SKStepHolds ${id}_machine (liveK ${lid} (liveHelpWhen ${lid} h) (liveHelpThen ${lid})) := by
|
|
1062
|
+
intro h hh
|
|
1063
|
+
simp only [${lid}, List.mem_cons, List.not_mem_nil, or_false] at hh
|
|
1064
|
+
rcases hh with ${helps.map(() => 'rfl').join(' | ')}
|
|
1065
|
+
${helps.map((_, i) => `· exact ${kstep('_', hAt(i))} (by ${checkBy})`).join('\n ')}
|
|
1066
|
+
/-- ${l.name}: along every run that takes each helpful request infinitely often, \`from\` is followed by \`to\`. -/
|
|
1067
|
+
theorem ${lid}_holds (run : SRun ${id}_machine)
|
|
1068
|
+
(fair : ∀ h ∈ ${lid}.helps, ∀ n, ∃ m, n ≤ m ∧ ∀ p ∈ h.2, (run.req m).1 p.1 = evalE (run.st m).1 (run.st m).2 p.2) :
|
|
1069
|
+
∀ n, evalC (run.st n).1 (run.st n).2 ${lid}.from_ = true → ∃ m, n ≤ m ∧ evalC (run.st m).1 (run.st m).2 ${lid}.to = true :=
|
|
1070
|
+
sleads_of_holds ${id}_machine ${lid} ${lid}_ok ${lid}_stay ${lid}_cover ${lid}_helps run fair${wfp}`;
|
|
1071
|
+
});
|
|
1072
|
+
const loopBlock = lp0 ? `/-- ${lp0.loop.name} halts: for params meeting its precondition, no run of turns goes on forever. -/
|
|
1073
|
+
theorem ${id}_halts : ∀ maps ρ0, evalC ρ0 maps ${id}_loop.pre = true →
|
|
1074
|
+
¬ ∃ f : Nat → Nat → Nat, f 0 = applyS ${id}_loop.init ρ0 maps ∧ ∀ n, Turn ${id}_loop maps (f n) (f (n + 1)) :=
|
|
1075
|
+
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 ${checkBy}) ${id}_live0_holds` : '';
|
|
1076
|
+
const ghostBlock = gv.length ? `-- The laws that read no ghost hold of the machine without its ghosts.
|
|
1077
|
+
theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈ ${id}_mlaws, avoids ${id}_ghosted.vars e.1.holds = true → holdsOver e.1 M e.2 ρ := by
|
|
1078
|
+
intro ρ M hr e he hav
|
|
1079
|
+
${laws.length ? `${oneOf('he')}
|
|
1080
|
+
rcases he with ${laws.map(() => 'rfl').join(' | ')}
|
|
1081
|
+
${laws.map((l, j) => `· exact cyc_machine_holds (cyc_ghost_holds ${id}_ghosted ${id}_ghostOk ${id}_law${j} ${keysL(l)} hav ${id}_law${j}_holds ρ M hr)`).join('\n ')}` : `simp [${id}_mlaws] at he`}` : '';
|
|
1082
|
+
const refineBlock = rf ? `theorem ${id}_allowed : SKStepHolds ${id}_machine (srStep ${id}_refined ${primesL()} ${primeMapsL()} noSProof) := ${kstep('_', stepAt[0])} (by ${checkBy})
|
|
1083
|
+
theorem ${id}_refines : ∀ ρ M, SReach ${id}_refined.impl ρ M → ∃ ρc Mc σ Ms, SReach ${id}_refined.full ρc Mc ∧ Agree ${id}_refined.SV ρc ρ
|
|
1084
|
+
∧ (∀ mp, mp ∉ ${id}_refined.SM → Mc mp = M mp) ∧ SReach ${id}_refined.spec σ Ms ∧ Agree ${id}_refined.X σ ρc
|
|
1085
|
+
∧ (∀ mp, mp ∉ ${id}_refined.IM → mp ∉ ${id}_refined.spec.carried → Ms mp = Mc mp) ∧ ∀ e ∈ ${id}_mlaws, holdsOver e.1 Mc e.2 ρc :=
|
|
1086
|
+
srefines_of_holds ${id}_refined (by ${checkBy}) (by ${checkBy}) _ ${id}_laws
|
|
1087
|
+
(fun ρ M h => by have hp := ${id}_law${rf.specPre}_holds ρ M h ρ (fun _ _ => rfl); exact hp)
|
|
1088
|
+
${primesL()} ${primeMapsL()} noSProof ${id}_allowed (by ${checkBy}) (by ${checkBy}) (by ${checkBy})` : '';
|
|
1089
|
+
const records = [...stepBlocks, ...(refineBlock ? [refineBlock] : []), ...liveBlocks, ...(loopBlock ? [loopBlock] : []), ...(ghostBlock ? [ghostBlock] : [])];
|
|
1090
|
+
cycTexts++;
|
|
1091
|
+
return { text: `-- ${m.name}: ${laws.map(l => l.name).join('; ')}, as one cyclic proof (its laws' nodes, then ${allSteps.length} step laws')
|
|
1092
|
+
-- (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')}; maps ${maps.map((v2, j) => `${j} ${v2}`).join(', ')})
|
|
1093
|
+
${machineDef}
|
|
1094
|
+
def ${id}_enc : CycEnc := ${enc}
|
|
1095
|
+
${cycProofText(id, p, [], vars, maps)}
|
|
1096
|
+
${lawBlocks}
|
|
1097
|
+
${lawsThm}
|
|
1098
|
+
${hm ? histBlock() : readable(id, laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
|
|
1099
|
+
have: j => `have hl : holdsOver ${id}_l${j} μ ${keys2(laws[j])} ρ := cyc_machine_holds (${id}_law${j}_holds ρ μ h)` })}${opts.noFacts ? '' : factsProof(id, m, maps, vars, opts.simp)}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}` };
|
|
1100
|
+
}
|
|
1101
|
+
// A fold from its cyclic proof (Bridge.lean's cyc_fold_sound): the fold numbered as foldToLean numbers it, each law's
|
|
1102
|
+
// theorem over trees, and all of them as the usual export's `<id>_laws` states them.
|
|
1103
|
+
function cycFoldToLean(id, f, laws) {
|
|
1104
|
+
const gs = foldObligations(f, laws), trees = {};
|
|
1105
|
+
for (const g of gs) {
|
|
1106
|
+
const o = grow(g);
|
|
1107
|
+
if (!('tree' in o))
|
|
1108
|
+
return { why: `${g.name}: not proven the usual way` };
|
|
1109
|
+
trees[g.name] = o.tree;
|
|
1110
|
+
}
|
|
1111
|
+
let p, why = 'no nodes';
|
|
1112
|
+
const until = performance.now() + certMs();
|
|
1113
|
+
for (const base of withDeadline(until, () => [...(routeOf() !== 'search' ? [foldNodes(f, laws, trees)] : []), foldNodes(f, laws)].filter(b => !b.why))) {
|
|
1114
|
+
if (performance.now() > until) {
|
|
1115
|
+
why = `past the certificate's deadline (${certMs()} ms)`;
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
const v = withDeadline(until, () => proveCyclic(base.facts, base.specs));
|
|
1119
|
+
if (v.proven && v.checked) {
|
|
1120
|
+
p = v.proof;
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
why = v.proven ? `not checked (${v.why})` : `${v.node}: ${v.goal}: ${v.stuck}`;
|
|
1124
|
+
}
|
|
1125
|
+
if (!p)
|
|
1126
|
+
return { why };
|
|
1127
|
+
const st = Object.keys(f.leaf), v = (name) => ({ op: 'var', name });
|
|
1128
|
+
const sc = cycScope([p], undefined, [...[...f.params, ...f.inputs, ...st, ...st.map(x => `l_${x}`), ...st.map(x => `r_${x}`), 'n$', 'N$'].map(v),
|
|
1129
|
+
...Object.values(f.leaf), ...Object.values(f.node), ...laws.map((l) => ({ op: 'if', c: l.holds, then: v('n$'), else: v('n$') })),
|
|
1130
|
+
...['Kind', 'Left', 'Right', ...st, ...f.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') }))]);
|
|
1131
|
+
const vars = [...new Set([...f.params, ...f.inputs, ...st, ...st.map(n2 => `l_${n2}`), ...st.map(n2 => `r_${n2}`), ...sc.vars])], maps = sc.maps;
|
|
1132
|
+
const t = leanTerms(vars, maps, id);
|
|
1133
|
+
cycTexts++;
|
|
1134
|
+
return { text: `-- ${f.name}: ${laws.map(law => law.name).join('; ')}, as one cyclic proof (variables ${vars.map((v2, j) => `${j} ${v2}`).join(', ')}; maps ${maps.map((v2, j) => `${j} ${v2}`).join(', ')})
|
|
1135
|
+
${cycProofText(id, p, [], vars, maps)}
|
|
1136
|
+
${cycFoldLean(id, f, laws, vars, maps, t)}
|
|
1137
|
+
def ${id}_flaws : List C := ${list(laws.map((_, j) => `${id}_law${j}`))}
|
|
1138
|
+
theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t, Allowed ${id}_fold ρ maps t →
|
|
1139
|
+
∀ l ∈ ${id}_flaws, evalC (foldT ${id}_fold ρ maps t) maps l = true := by
|
|
1140
|
+
intro maps ρ hp t ht l hl
|
|
1141
|
+
simp only [${id}_flaws, List.mem_cons, List.not_mem_nil, or_false] at hl
|
|
1142
|
+
rcases hl with ${laws.map(() => 'rfl').join(' | ')}
|
|
1143
|
+
${laws.map((_, j) => `· exact ${id}_law${j}_holds maps ρ hp t ht`).join('\n ')}` };
|
|
1144
|
+
}
|
|
817
1145
|
// A function defined by a recurrence (core/dsl.ts recur): its Lean definition by the same recurrence, and its lemmas
|
|
818
1146
|
// proven together by induction (each case by omega, its min, max and ifs split first), and its laws between two points
|
|
819
1147
|
// by induction on the distance. A function of several arguments is `<name>_rec`, its further arguments first, and the
|
|
@@ -900,8 +1228,10 @@ function recurKernel(lean, r, uses, which) {
|
|
|
900
1228
|
const cite = (f) => { const j = r.lemmas.findIndex(l => f.name === `${l.name} at x`); return two && j >= 0 ? `(${lean}_lemmas ${args.join(' ')} x)${proj(j, r.lemmas.length)}` : undefined; };
|
|
901
1229
|
const factsAt = factsProof(id, m, fnNames, vars, [`${id}_ρ`, `${id}_μ`, ...recurDefs], { binders, rho: `(${id}_ρ ${ps})`, mu: `${id}_μ`, cite });
|
|
902
1230
|
const unfold = [`${id}_machine`, `${id}_μ`, 'applyS', 'assoc', 'lookup', 'evalE', 'evalC', ...recurDefs].join(', ');
|
|
1231
|
+
// (with cyclic certificates, the machine's laws from its cyclic proof, where it has one)
|
|
1232
|
+
const cyc = certsOn ? cycMachineToLean(id, m, pr.laws, { noFacts: true }) : undefined;
|
|
903
1233
|
const pre = `
|
|
904
|
-
${smachineToLean(id, m, pr.laws, { noFacts: true })}
|
|
1234
|
+
${cyc && 'text' in cyc ? cyc.text : smachineToLean(id, m, pr.laws, { noFacts: true })}
|
|
905
1235
|
def ${id}_ρ ${binders} : Nat → Nat := fun i => ${chain(start.map(([n, e]) => [col(n), e]), '0', 'i')}
|
|
906
1236
|
noncomputable def ${id}_μ : Nat → Nat → Nat := fun j => ${chain(fnNames.map((n, j) => [j, `(${m.fns[n].lean})`]), 'fun _ => 0', 'j')}${factsAt}
|
|
907
1237
|
-- The run of ${m.name} that ${two ? 'counts y up from x' : 'counts x up from 0'}: reachable, and holding ${two ? 'y = x + n and f(y)' : 'x = n and f(x)'}.
|
|
@@ -923,7 +1253,9 @@ theorem ${id}_run ${binders} : ∀ n, ∃ ρ, SReach ${id}_machine ρ ${id}_μ
|
|
|
923
1253
|
all_goals simp [sNext, sView, decideFlags, decideQ, flagSub, withInputs, withCarried, ${hs.join(', ')}, ${unfold}]`;
|
|
924
1254
|
// The lemmas (or the laws between, after the lemmas at y): each the machine's law read on the run.
|
|
925
1255
|
const own = two ? r.between.map((_, j) => r.lemmas.length + j) : r.lemmas.map((_, j) => j);
|
|
926
|
-
const each = (j) =>
|
|
1256
|
+
const each = (j) => (cyc && 'text' in cyc
|
|
1257
|
+
? `by have h := ${id}_law${j}_holds ρ ${id}_μ hr ρ (fun _ _ => rfl); simp [${id}_law${j}, evalC, evalE, ${hs.join(', ')}] at h; simpa using h`
|
|
1258
|
+
: `by have h := ${id}_laws ρ ${id}_μ hr ${id}_e${j} (by simp [${id}_proof]); simp [SEntry.Holds, holdsOver, ${id}_e${j}, ${id}_l${j}, MLaw.Holds, evalC, evalE, ${hs.join(', ')}] at h; simpa using h`);
|
|
927
1259
|
const proof = (from) => `(obtain ⟨ρ, hr, ${hs.join(', ')}⟩ := ${id}_run ${from}; exact ${own.length === 1 ? `(${each(own[0])})` : `⟨${own.map(j => `(${each(j)})`).join(', ')}⟩`})`;
|
|
928
1260
|
return { pre, proof };
|
|
929
1261
|
}
|
|
@@ -1052,10 +1384,17 @@ export function hmachineToLean(id, m, laws, tampers = [], corollary) {
|
|
|
1052
1384
|
// `decide +kernel` (Lean's kernel alone). Every such theorem then also rests on an axiom of its own that `#print axioms`
|
|
1053
1385
|
// names (`…_native.native_decide.ax_…`): it trusts Lean's compiler, its runtime and the C compiler. Defaults to off, or
|
|
1054
1386
|
// to GDP_LEAN_NATIVE=1 (the suite's quick runs); a release's proofs stay on Lean's kernel and its core axioms.
|
|
1387
|
+
// `cyclic`: each machine, loop and fold the cyclic route covers from cyclic certificates (see cycMachineToLean); defaults
|
|
1388
|
+
// to GDP_CERT=cyclic. `certReport` then says which items went which way.
|
|
1055
1389
|
export function toLean(items, opts = {}) {
|
|
1056
|
-
const outer = checkBy;
|
|
1390
|
+
const outer = checkBy, outerCerts = certsOn;
|
|
1057
1391
|
const env = globalThis.process?.env;
|
|
1058
1392
|
checkBy = (opts.native ?? env?.GDP_LEAN_NATIVE === '1') ? 'native_decide' : 'decide +kernel';
|
|
1393
|
+
certsOn = opts.cyclic ?? cyclicCerts();
|
|
1394
|
+
certReport.cyclic = [];
|
|
1395
|
+
certReport.usual = [];
|
|
1396
|
+
certReport.translatedSteps = [];
|
|
1397
|
+
cycTexts = 0;
|
|
1059
1398
|
// The proofs found with only the instances their leaves use: the same laws, from fewer hypotheses for Lean to build.
|
|
1060
1399
|
const wasMinimal = minimal.on;
|
|
1061
1400
|
minimal.on = true;
|
|
@@ -1064,6 +1403,7 @@ export function toLean(items, opts = {}) {
|
|
|
1064
1403
|
}
|
|
1065
1404
|
finally {
|
|
1066
1405
|
checkBy = outer;
|
|
1406
|
+
certsOn = outerCerts;
|
|
1067
1407
|
minimal.on = wasMinimal;
|
|
1068
1408
|
}
|
|
1069
1409
|
}
|
|
@@ -1104,8 +1444,16 @@ function toLeanWith(items, opts) {
|
|
|
1104
1444
|
}
|
|
1105
1445
|
// One twin: every machine goes to Lean as a machine with sets (one with none, if it has none): its laws, step laws,
|
|
1106
1446
|
// liveness and ghosts in one export; a machine with a history as its plain machine, and its laws over histories.
|
|
1107
|
-
|
|
1108
|
-
|
|
1447
|
+
// With cyclic certificates, from one cyclic proof where the cyclic route covers it.
|
|
1448
|
+
const mopts = { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
|
|
1449
|
+
...(item.ghosts?.length ? { ghosts: item.ghosts } : {}), ...(item.lean?.simp ? { simp: item.lean.simp } : {}), ...(item.lean?.after ? { after: item.lean.after } : {}) };
|
|
1450
|
+
const defs = `${recurDefs(m)}${item.lean?.defs ? `${item.lean.defs}\n` : ''}`;
|
|
1451
|
+
const cyc = certsOn ? cycMachineToLean(id, m, item.laws, mopts) : undefined;
|
|
1452
|
+
if (cyc && 'text' in cyc)
|
|
1453
|
+
certReport.cyclic.push(item.id);
|
|
1454
|
+
else if (cyc)
|
|
1455
|
+
certReport.usual.push({ id: item.id, why: cyc.why });
|
|
1456
|
+
parts.push(`-- ${item.id}\n${defs}${cyc && 'text' in cyc ? cyc.text : smachineToLean(id, m, item.laws, mopts)}`);
|
|
1109
1457
|
theorems.push(`${id}_laws`, ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
|
|
1110
1458
|
if (item.lean?.after)
|
|
1111
1459
|
theorems.push(...[...item.lean.after(() => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1]));
|
|
@@ -1117,19 +1465,39 @@ function toLeanWith(items, opts) {
|
|
|
1117
1465
|
const laws = [...relation, { name: `${spec.name}'s precondition`, holds: spec.pre ?? atMost(n(0), n(0)) }];
|
|
1118
1466
|
const sl = R.stepLaws[0] ?? { name: `${spec.name} allows the request it is sent`, then: { op: 'or', xs: [stutter, atMost(n(0), n(0))] } };
|
|
1119
1467
|
const refined = { impl: S.impl, spec: S.spec, wit: witness, stutter, specPre: relation.length }, free = spec.inputs.map(i => `b_${i}`);
|
|
1120
|
-
|
|
1468
|
+
// With cyclic certificates: the composite's laws and its step law (Lean's SRefined.allowed, built here as Lean builds
|
|
1469
|
+
// it) from one cyclic proof, through BridgeRules.lean's srefines_of_holds.
|
|
1470
|
+
const cyc = certsOn ? cycMachineToLean(id, R.machine, laws, { free, refined: { ...refined, step: sl } }) : undefined;
|
|
1471
|
+
if (cyc && 'text' in cyc)
|
|
1472
|
+
certReport.cyclic.push(item.id);
|
|
1473
|
+
else if (cyc)
|
|
1474
|
+
certReport.usual.push({ id: item.id, why: cyc.why });
|
|
1475
|
+
parts.push(`-- ${item.id}\n${cyc && 'text' in cyc ? cyc.text : smachineToLean(id, R.machine, laws, { stepLaws: [sl], free, refined })}`);
|
|
1121
1476
|
theorems.push(`${id}_laws`, `${id}_refines`);
|
|
1122
1477
|
}
|
|
1123
1478
|
else if ('loop' in item) {
|
|
1124
|
-
|
|
1479
|
+
loopObligations(item.loop, item.laws ?? [], item.rank);
|
|
1480
|
+
const cyc = certsOn ? cycMachineToLean(id, loopMachine(item.loop), item.laws ?? [], { liveness: [loopLiveness(item.loop, item.rank)], loop: { loop: item.loop } }) : undefined;
|
|
1481
|
+
if (cyc && 'text' in cyc)
|
|
1482
|
+
certReport.cyclic.push(item.id);
|
|
1483
|
+
else if (cyc)
|
|
1484
|
+
certReport.usual.push({ id: item.id, why: cyc.why });
|
|
1485
|
+
parts.push(`-- ${item.id}\n${cyc && 'text' in cyc ? cyc.text : loopToLean(id, item.loop, item.laws ?? [], item.rank)}`);
|
|
1125
1486
|
theorems.push(`${id}_halts`);
|
|
1126
1487
|
}
|
|
1127
1488
|
else if ('cyclic' in item) {
|
|
1128
|
-
parts.push(`-- ${item.id}\n${cyclicToLean(id, item.cyclic, item.tampered ?? [], item.after)}`);
|
|
1129
|
-
theorems.push(`${id}_sound`, ...(item.
|
|
1489
|
+
parts.push(`-- ${item.id}\n${cyclicToLean(id, item.cyclic, item.tampered ?? [], item.after, item.bridge, item.recur, item.fold)}`);
|
|
1490
|
+
theorems.push(`${id}_sound`, ...(item.bridge?.laws ?? item.fold?.laws ?? []).map((_, j) => `${id}_law${j}_holds`), ...(item.recur ? [`${id}_values`] : []), ...(item.recur ? [...item.recur.lemmas.map((_, j) => `${id}_lemma${j}`), ...item.recur.between.map((_, j) => `${id}_between${j}`)] : []),
|
|
1491
|
+
// A datatype law's theorem is in a namespace of the item's (its datatypes and functions are named as declared).
|
|
1492
|
+
...(item.after ? (ts => (item.data ? ts.slice(-1).map(t => `${id}.${t}`) : ts))([...item.after(() => 0, () => 0).matchAll(/^theorem (\S+)/gm)].map(x2 => x2[1])) : []));
|
|
1130
1493
|
}
|
|
1131
1494
|
else if ('fold' in item) {
|
|
1132
|
-
|
|
1495
|
+
const cyc = certsOn ? cycFoldToLean(id, item.fold, item.laws) : undefined;
|
|
1496
|
+
if (cyc && 'text' in cyc)
|
|
1497
|
+
certReport.cyclic.push(item.id);
|
|
1498
|
+
else if (cyc)
|
|
1499
|
+
certReport.usual.push({ id: item.id, why: cyc.why });
|
|
1500
|
+
parts.push(`-- ${item.id}\n${cyc && 'text' in cyc ? cyc.text : foldToLean(id, item.fold, item.laws)}`);
|
|
1133
1501
|
theorems.push(`${id}_laws`);
|
|
1134
1502
|
}
|
|
1135
1503
|
else {
|
|
@@ -1141,7 +1509,7 @@ function toLeanWith(items, opts) {
|
|
|
1141
1509
|
}
|
|
1142
1510
|
// The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
|
|
1143
1511
|
return `import History
|
|
1144
|
-
${items.some(item => 'cyclic' in item) ? 'import Cyclic\n' : ''}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'}
|
|
1512
|
+
${cycTexts ? 'import BridgeRules\n' : ''}${items.some(item => 'cyclic' in item) ? (items.some(item => 'cyclic' in item && (item.bridge || item.recur || item.fold)) ? 'import Bridge\n' : 'import Cyclic\n') : ''}${items.some(item => 'cyclic' in item && item.data) ? 'import Data\n' : ''}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'}
|
|
1145
1513
|
open Kern Cert
|
|
1146
1514
|
set_option maxRecDepth 100000
|
|
1147
1515
|
set_option maxHeartbeats 0
|
|
@@ -1173,14 +1541,22 @@ lean_lib ${name} where
|
|
|
1173
1541
|
`,
|
|
1174
1542
|
};
|
|
1175
1543
|
}
|
|
1176
|
-
|
|
1177
|
-
// from the nodes), one theorem for every node; each tampered proof's check computed to false. `after` writes Lean
|
|
1178
|
-
// after it (a corollary in plain terms), given the variables' and maps' numbers.
|
|
1179
|
-
export function cyclicToLean(id, p, tampered = [], after) {
|
|
1544
|
+
export function cyclicToLean(id, p, tampered = [], after, bridge, recur, fold) {
|
|
1180
1545
|
const all = [p, ...tampered.map(t => t.proof)];
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1183
|
-
const maps = [...
|
|
1546
|
+
const v = (name) => ({ op: 'var', name });
|
|
1547
|
+
const fnames = fold ? Object.keys(fold.fold.leaf) : [];
|
|
1548
|
+
const { vars, maps } = cycScope(all, bridge, [...(recur ? [recur.zero, recur.next, v('x'), v('prev'), v('k$'), ...recur.args.map(v)] : []),
|
|
1549
|
+
...(fold ? [...fold.fold.params, ...fold.fold.inputs, ...fnames, ...fnames.map(x => `l_${x}`), ...fnames.map(x => `r_${x}`), 'n$', 'N$'].map(v) : []),
|
|
1550
|
+
...(fold ? [...Object.values(fold.fold.leaf), ...Object.values(fold.fold.node), ...fold.laws.map((l) => ({ op: 'if', c: l.holds, then: v('n$'), else: v('n$') }))] : []),
|
|
1551
|
+
...(fold ? ['Kind', 'Left', 'Right', ...fnames, ...fold.fold.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') })) : [])]);
|
|
1552
|
+
const { e, c } = leanTerms(vars, maps, id);
|
|
1553
|
+
const ix = (name) => vars.indexOf(name), mx = (map) => maps.indexOf(map);
|
|
1554
|
+
return `-- ${p.nodes.map(n => n.name).join('; ')} (variables ${vars.map((name, j) => `${j} ${name}`).join(', ')}; maps ${maps.map((name, j) => `${j} ${name}`).join(', ')})
|
|
1555
|
+
${cycProofText(id, p, tampered, vars, maps)}${bridge ? `\n${cycBridgeLean(id, p, bridge, vars, maps, { e, c })}` : ''}${recur ? `\n${cycRecurLean(id, p, recur, vars, maps, { e, c })}` : ''}${fold ? `\n${cycFoldLean(id, fold.fold, fold.laws, vars, maps, { e, c })}` : ''}${after ? `\n${after(ix, mx)}` : ''}`;
|
|
1556
|
+
}
|
|
1557
|
+
// A cyclic proof as Lean: its facts and nodes (each goal's table and tree, Lean building the goals from the nodes), each
|
|
1558
|
+
// node's membership, the check and cyclic_sound's theorem; each tampered proof's check computed to false.
|
|
1559
|
+
function cycProofText(id, p, tampered, vars, maps) {
|
|
1184
1560
|
const { e, c } = leanTerms(vars, maps, id);
|
|
1185
1561
|
const ix = (name) => vars.indexOf(name);
|
|
1186
1562
|
const facts = list(p.facts.map(f => `⟨${list(f.keys.map(key => String(ix(key))))}, ${c(f.holds)}⟩`));
|
|
@@ -1195,9 +1571,7 @@ export function cyclicToLean(id, p, tampered = [], after) {
|
|
|
1195
1571
|
return { hyps: list(n.hyps.map(c)), concl: c(n.concl), rest: `${list(n.rank.map(e))}, ${list(n.insts.map(t => `(${t.fact}, ${list(t.at.map(e))})`))},\n ${list(uses)},\n ${enc(mainGoal(p.facts, p.nodes, o), o.main)}` };
|
|
1196
1572
|
});
|
|
1197
1573
|
const nodes = (q) => list(nodeLits(q).map(x => `⟨${x.hyps}, ${x.concl}, ${x.rest}⟩`));
|
|
1198
|
-
|
|
1199
|
-
return `-- ${p.nodes.map(n => n.name).join('; ')} (variables ${vars.map((name, j) => `${j} ${name}`).join(', ')}; maps ${maps.map((name, j) => `${j} ${name}`).join(', ')})
|
|
1200
|
-
def ${id}_facts : List Fact := ${facts}
|
|
1574
|
+
return `def ${id}_facts : List Fact := ${facts}
|
|
1201
1575
|
${nodeLits(p).map((x, i) => `def ${id}_hyps${i} : List C := ${x.hyps}
|
|
1202
1576
|
def ${id}_concl${i} : C := ${x.concl}
|
|
1203
1577
|
def ${id}_node${i} : CNode := ⟨${id}_hyps${i}, ${id}_concl${i}, ${x.rest}⟩
|
|
@@ -1210,5 +1584,163 @@ theorem ${id}_sound : ∀ (maps : Nat → Nat → Nat) env, FactsHold ${id}_fact
|
|
|
1210
1584
|
cyclic_sound ${id}_facts ${id}_nodes ${id}_check
|
|
1211
1585
|
${tampered.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
|
|
1212
1586
|
def ${id}_tampered${j} : List CNode := ${nodes(t.proof)}
|
|
1213
|
-
example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}
|
|
1587
|
+
example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}`;
|
|
1588
|
+
}
|
|
1589
|
+
function cycScope(all, bridge, extra = []) {
|
|
1590
|
+
const names = (t) => [...JSON.stringify(t).matchAll(/"op":"var","name":"([^"]+)"/g)].map(m => m[1]);
|
|
1591
|
+
const ms = bridge ? [bridge.machine, ...(bridge.tampered ?? []).map(t => t.machine)] : [];
|
|
1592
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
1593
|
+
const traced = (m) => [...Object.keys(m.state), ...m.inputs], tracedMaps = (m) => [...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])];
|
|
1594
|
+
const machineVars = ms.flatMap(m => [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.step.flags ?? {}).map(f => `?${f}`),
|
|
1595
|
+
...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : [])), ...(m.facts ?? []).flatMap(keysOf), ...names([m.state, m.step, m.pre, m.assume, m.facts])]);
|
|
1596
|
+
const vars = [...new Set([...all.flatMap(q => [...q.facts.flatMap(f => [...f.keys, ...names(f.holds)]), ...q.nodes.flatMap(n => [...names([n.hyps, n.concl, n.rank, n.insts]), ...n.uses.flatMap(u => [...Object.keys(u.subst), ...names([u.subst, u.guard])])])]),
|
|
1597
|
+
...machineVars, ...(bridge ? [...bridge.laws.flatMap(l => [...keysOf(l), ...names(l.holds)]), 'u$', 'k$', 'T$', 'O$', 't$'] : []), ...names(extra)])];
|
|
1598
|
+
const maps = [...new Set([...[...JSON.stringify([all, ms, bridge?.laws ?? []]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]),
|
|
1599
|
+
...ms.flatMap(m => [...tracedMaps(m), ...Object.keys(m.fns ?? {}), ...traced(m).map(x => `${x}@`), ...tracedMaps(m).map(x => `${x}@`)])])];
|
|
1600
|
+
return { vars, maps };
|
|
1601
|
+
}
|
|
1602
|
+
// Bridge.lean's cycMOk alone, part by part, for each law of a machine (the sweep research/cyclic/bridge.ts runs over the
|
|
1603
|
+
// examples): the facts and nodes machineNodes makes, each node with no proof (cycMOk reads only its hypotheses and
|
|
1604
|
+
// conclusion), against the machine's encoding. Each part an example, named in a comment before it.
|
|
1605
|
+
export function cycEncodingLean(id, facts, nodes, b) {
|
|
1606
|
+
const shape = { facts, nodes: nodes.map(n => ({ hyps: n.hyps, concl: n.concl, rank: [], insts: [], uses: [] })) };
|
|
1607
|
+
const { vars, maps } = cycScope([shape], b);
|
|
1608
|
+
const t = leanTerms(vars, maps, id);
|
|
1609
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1610
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1611
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1612
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1613
|
+
const m = b.machine, keysOf = (l) => list([...(l.forall ? [l.forall] : []), ...(l.keys ?? [])].map(k2 => String(ix(k2))));
|
|
1614
|
+
const by = checkBy;
|
|
1615
|
+
return `-- ${id}: ${m.name}
|
|
1616
|
+
def ${id}_machine : SMachine := ${machineLit(m, t, ix, mx)}
|
|
1617
|
+
def ${id}_enc : CycEnc := ⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
|
|
1618
|
+
${list([...Object.keys(m.state), ...m.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
|
|
1619
|
+
${list([...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩
|
|
1620
|
+
${facts.map((f, i) => `-- CHECK ${id} fact ${i}: ${f.name}
|
|
1621
|
+
example : (natC (${t.c(f.holds)}) || (cycCanon ${id}_machine ${id}_enc).any (factBeq ⟨${list(f.keys.map(k2 => String(ix(k2))))}, ${t.c(f.holds)}⟩)) = true := by ${by}`).join('\n')}
|
|
1622
|
+
${b.laws.map((l, j) => `-- CHECK ${id} law ${j}: shape: ${l.name}
|
|
1623
|
+
example : cycShapeOk ${id}_machine ${id}_enc ${keysOf(l)} = true := by ${by}
|
|
1624
|
+
-- CHECK ${id} law ${j}: terms: ${l.name}
|
|
1625
|
+
example : cycTermsOk ${id}_machine ${id}_enc ${keysOf(l)} = true := by ${by}
|
|
1626
|
+
-- CHECK ${id} law ${j}: law: ${l.name}
|
|
1627
|
+
example : cycLawOk ${id}_machine ${id}_enc (${t.c(l.holds)}) = true := by ${by}
|
|
1628
|
+
-- CHECK ${id} law ${j}: node: ${l.name}
|
|
1629
|
+
example : cycNodeOk ${id}_machine ${id}_enc (${t.c(l.holds)}) ⟨${list(nodes[j].hyps.map(t.c))}, ${t.c(nodes[j].concl)}, [], [], [], ([], .leaf [] [] [] [])⟩ = true := by ${by}`).join('\n')}`;
|
|
1630
|
+
}
|
|
1631
|
+
// Bridge.lean's recOk and foldOk alone, for the sweep (research/cyclic/bridge.ts): recurNodes' and foldNodes' facts (and
|
|
1632
|
+
// a fold's nodes, with no proof: foldOk reads only their hypotheses and conclusions) against the recurrence's equations
|
|
1633
|
+
// and the fold's table. Each an example, named in a comment before it.
|
|
1634
|
+
export function cycRecurCheckLean(id, facts, r) {
|
|
1635
|
+
const v = (name) => ({ op: 'var', name });
|
|
1636
|
+
const { vars, maps } = cycScope([{ facts, nodes: [] }], undefined, [r.zero, r.next, v('x'), v('prev'), v('k$'), ...r.args.map(v), { op: 'get', map: `${r.name}$`, key: v('x') }]);
|
|
1637
|
+
const t = leanTerms(vars, maps, id), ix = (n2) => vars.indexOf(n2), mx = (n2) => maps.indexOf(n2);
|
|
1638
|
+
return `-- CHECK ${id} recurrence: ${r.name}
|
|
1639
|
+
example : recOk ⟨${mx(`${r.name}$`)}, ${ix('x')}, ${ix('prev')}, ${ix('k$')}⟩ (${t.e(r.zero)}) (${t.e(r.next)}) []
|
|
1640
|
+
${list(facts.map(f => `⟨${list(f.keys.map(k2 => String(ix(k2))))}, ${t.c(f.holds)}⟩`))} = true := by ${checkBy}`;
|
|
1641
|
+
}
|
|
1642
|
+
export function cycFoldCheckLean(id, facts, nodes, f, laws) {
|
|
1643
|
+
const v = (name) => ({ op: 'var', name }), st = Object.keys(f.leaf);
|
|
1644
|
+
const shape = { facts, nodes: nodes.map(n => ({ hyps: n.hyps, concl: n.concl, rank: [], insts: [], uses: [] })) };
|
|
1645
|
+
const { vars, maps } = cycScope([shape], undefined, [...[...f.params, ...f.inputs, ...st, ...st.map(x => `l_${x}`), ...st.map(x => `r_${x}`), 'n$', 'N$'].map(v),
|
|
1646
|
+
...Object.values(f.leaf), ...Object.values(f.node), ...['Kind', 'Left', 'Right', ...st, ...f.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') }))]);
|
|
1647
|
+
const t = leanTerms(vars, maps, id);
|
|
1648
|
+
return `${cycFoldLean(id, f, [], vars, maps, t)}
|
|
1649
|
+
def ${id}_facts : List Fact := ${list(facts.map(g => `⟨${list(g.keys.map(k2 => String(vars.indexOf(k2))))}, ${t.c(g.holds)}⟩`))}
|
|
1650
|
+
${laws.map((l, j) => `-- CHECK ${id} fold law ${j}: ${l.name}
|
|
1651
|
+
example : foldOk ${id}_fold ${id}_fenc ${id}_facts (${t.c(l.holds)}) ⟨${list(nodes[j].hyps.map(t.c))}, ${t.c(nodes[j].concl)}, [], [], [], ([], .leaf [] [] [] [])⟩ = true := by ${checkBy}`).join('\n')}`;
|
|
1652
|
+
}
|
|
1653
|
+
// Bridge.lean's cyc_fold_sound for each law of a cyclic proof made by foldNodes: the fold as Kernel.lean's Fold, the
|
|
1654
|
+
// table (its added variables and maps), each law's check and its theorem over trees, as fold_sound states the usual one.
|
|
1655
|
+
function cycFoldLean(id, f, laws, vars, maps, t) {
|
|
1656
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1657
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1658
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1659
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1660
|
+
const st = Object.keys(f.leaf);
|
|
1661
|
+
return `def ${id}_fold : Fold := ⟨${list(f.inputs.map(name => String(ix(name))))},
|
|
1662
|
+
${list(st.map(n2 => `(${ix(n2)}, ${ix(`l_${n2}`)}, ${ix(`r_${n2}`)})`))},
|
|
1663
|
+
${list(Object.entries(f.leaf).map(([name, e2]) => `(${ix(name)}, ${t.e(e2)})`))},
|
|
1664
|
+
${list(Object.entries(f.node).map(([name, e2]) => `(${ix(name)}, ${t.e(e2)})`))},
|
|
1665
|
+
${f.pre ? t.c(f.pre) : TRUE},
|
|
1666
|
+
${f.assume ? t.c(f.assume) : TRUE}⟩
|
|
1667
|
+
def ${id}_fenc : FoldEnc := ⟨${ix('n$')}, ${ix('N$')}, ${mx('Kind@')}, ${mx('Left@')}, ${mx('Right@')}, ${list([...st, ...f.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))}⟩
|
|
1668
|
+
${laws.map((l, j) => `-- ${l.name}
|
|
1669
|
+
def ${id}_law${j} : C := ${t.c(l.holds)}
|
|
1670
|
+
theorem ${id}_law${j}_ok : foldOk ${id}_fold ${id}_fenc ${id}_facts ${id}_law${j} ${id}_node${j} = true := by ${checkBy}
|
|
1671
|
+
theorem ${id}_law${j}_holds : ∀ (maps : Nat → Nat → Nat) ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t, Allowed ${id}_fold ρ maps t →
|
|
1672
|
+
evalC (foldT ${id}_fold ρ maps t) maps ${id}_law${j} = true :=
|
|
1673
|
+
cyc_fold_sound ${id}_fold ${id}_fenc ${id}_facts ${id}_nodes ${id}_check ${id}_law${j} ${id}_node${j} ${id}_node${j}_mem ${id}_law${j}_ok`).join('\n')}`;
|
|
1674
|
+
}
|
|
1675
|
+
// Bridge.lean's cyc_recur_sound for a cyclic proof made by recurNodes: the recurrence's equations checked against the
|
|
1676
|
+
// proof's facts, the nodes of its values (recF), its Lean definition (as recurToLean writes it) shown to be those values,
|
|
1677
|
+
// and each lemma and law between two points plainly of the definition.
|
|
1678
|
+
function cycRecurLean(id, p, r, vars, maps, t) {
|
|
1679
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1680
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1681
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1682
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1683
|
+
const fn = `${id}_fn`, args = r.args.map(recurArg), binders = args.map(a => `(${a} : Nat)`).join(' ');
|
|
1684
|
+
const call = (x) => `(${[fn, ...args, x].join(' ')})`;
|
|
1685
|
+
const surface = (names) => leanSurface({ ...Object.fromEntries(r.args.map((a, i) => [a, args[i]])), ...names }, id);
|
|
1686
|
+
const renc = `⟨${mx(`${r.name}$`)}, ${ix('x')}, ${ix('prev')}, ${ix('k$')}⟩`;
|
|
1687
|
+
const recF = `recF ${id}_zero ${id}_next ${ix('x')} ${ix('prev')}`;
|
|
1688
|
+
const envAt = (pts) => `(fun i => ${[...pts, ...r.args.map((a, i) => [a, args[i]])].map(([v, val]) => `if i = ${ix(v)} then ${val} else `).join('')}0)`;
|
|
1689
|
+
const node = (j) => `${id}_node${j}`;
|
|
1690
|
+
const n = r.lemmas.length;
|
|
1691
|
+
return `def ${id}_renc : RecEnc := ${renc}
|
|
1692
|
+
def ${id}_zero : E := ${t.e(r.zero)}
|
|
1693
|
+
def ${id}_next : E := ${t.e(r.next)}
|
|
1694
|
+
theorem ${id}_rok : recOk ${id}_renc ${id}_zero ${id}_next [] ${id}_facts = true := by ${checkBy}
|
|
1695
|
+
theorem ${id}_values : ∀ maps env, ∀ n ∈ ${id}_nodes, (∀ c ∈ n.hyps, evalC env (withMap maps ${mx(`${r.name}$`)} (${recF} env maps)) c = true) →
|
|
1696
|
+
evalC env (withMap maps ${mx(`${r.name}$`)} (${recF} env maps)) n.concl = true :=
|
|
1697
|
+
fun maps env => cyc_recur_sound ${id}_renc ${id}_zero ${id}_next [] ${id}_facts ${id}_nodes ${id}_check ${id}_rok maps env (by intro f hf; simp at hf)
|
|
1698
|
+
-- ${r.name} as Lean defines it (recurToLean's recursion), and the same as the values the proof is about.
|
|
1699
|
+
def ${fn} ${binders} : Nat → Nat
|
|
1700
|
+
| 0 => ${surface({}).e(r.zero)}
|
|
1701
|
+
| x + 1 => ${surface({ x: 'x', prev: call('x') }).e(r.next)}
|
|
1702
|
+
theorem ${fn}_eq (maps : Nat → Nat → Nat) (env : Nat → Nat) : ∀ n, ${recF} env maps n = ${[fn, ...r.args.map(a => `(env ${ix(a)})`)].join(' ')} n := by
|
|
1703
|
+
intro n
|
|
1704
|
+
induction n with
|
|
1705
|
+
| zero => simp [recF, ${id}_zero, ${fn}, evalE]
|
|
1706
|
+
| succ n ih => simp only [recF]; rw [ih]; simp [${id}_next, ${fn}, evalE, upd]
|
|
1707
|
+
${r.lemmas.map((l, j) => `/-- ${r.name}: ${l.name} -/
|
|
1708
|
+
theorem ${id}_lemma${j} ${binders} (x : Nat) : ${surface({ x: 'x', fx: call('x') }).c(l.holds)} := by
|
|
1709
|
+
have h := ${id}_values (fun _ _ => 0) ${envAt([['x', 'x']])} _ ${node(j)}_mem (by rw [${node(j)}_hyps]; simp [${id}_hyps${j}])
|
|
1710
|
+
rw [${node(j)}_concl] at h
|
|
1711
|
+
simpa [${id}_concl${j}, evalC, evalE, withMap, ${fn}_eq] using h`).join('\n')}
|
|
1712
|
+
${r.between.map((b, j) => `/-- ${r.name}: ${b.name}, for every x ≤ y -/
|
|
1713
|
+
theorem ${id}_between${j} ${binders} (x y : Nat) (hxy : x ≤ y) : ${surface({ x: 'x', fx: call('x'), y: 'y', fy: call('y') }).c(b.holds)} := by
|
|
1714
|
+
have h := ${id}_values (fun _ _ => 0) ${envAt([['x', 'x'], ['y', 'y']])} _ ${node(n + j)}_mem (by rw [${node(n + j)}_hyps]; simp [${id}_hyps${n + j}])
|
|
1715
|
+
rw [${node(n + j)}_concl] at h
|
|
1716
|
+
simp [${id}_concl${n + j}, evalC, evalE, withMap, ${fn}_eq] at h
|
|
1717
|
+
first | exact h hxy | omega | exact h.resolve_left (by omega)`).join('\n')}`;
|
|
1718
|
+
}
|
|
1719
|
+
// Bridge.lean's cyc_machine_sound for each law of a cyclic proof made by machineNodes: the machine as Sets.lean's
|
|
1720
|
+
// SMachine, the encoding (its added variables and maps over time), each law's check and theorem, and the laws plainly.
|
|
1721
|
+
function cycBridgeLean(id, p, b, vars, maps, t) {
|
|
1722
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1723
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1724
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1725
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1726
|
+
if (b.laws.length > p.nodes.length)
|
|
1727
|
+
throw new Error(`${id}: more laws than nodes`);
|
|
1728
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
1729
|
+
const enc = (m) => `⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
|
|
1730
|
+
${list([...Object.keys(m.state), ...m.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
|
|
1731
|
+
${list([...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩`;
|
|
1732
|
+
const keysL = (l) => list(keysOf(l).map(k2 => String(ix(k2))));
|
|
1733
|
+
return `def ${id}_machine : SMachine := ${machineLit(b.machine, t, ix, mx)}
|
|
1734
|
+
def ${id}_enc : CycEnc := ${enc(b.machine)}
|
|
1735
|
+
${b.laws.map((l, j) => `-- ${l.name}
|
|
1736
|
+
def ${id}_law${j} : C := ${t.c(l.holds)}
|
|
1737
|
+
def ${id}_l${j} : MLaw := ⟨${id}_law${j}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩
|
|
1738
|
+
theorem ${id}_law${j}_ok : cycMOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${id}_node${j} = true := by ${checkBy}
|
|
1739
|
+
theorem ${id}_law${j}_holds : ∀ ρ M, SReach ${id}_machine ρ M → ∀ ρ' : Nat → Nat, (∀ i, i ∉ ${keysL(l)} → ρ' i = ρ i) → evalC ρ' M ${id}_law${j} = true :=
|
|
1740
|
+
cyc_machine_sound ${id}_machine ${id}_enc ${id}_facts ${id}_nodes ${id}_check ${id}_law${j} ${keysL(l)} ${id}_node${j} ${id}_node${j}_mem ${id}_law${j}_ok`).join('\n')}
|
|
1741
|
+
${(b.tampered ?? []).map((tm, j) => `-- TAMPERED: ${tm.what}. Lean computes the check against the proof to false.
|
|
1742
|
+
def ${id}_tamperedm${j} : SMachine := ${machineLit(tm.machine, t, ix, mx)}
|
|
1743
|
+
example : cycMOk ${id}_tamperedm${j} ${id}_enc ${id}_facts ${id}_law0 ${keysL(b.laws[0])} ${id}_node0 = false := by ${checkBy}`).join('\n')}
|
|
1744
|
+
${readable(id, b.laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
|
|
1745
|
+
have: j => `have hl : holdsOver ${id}_l${j} μ ${list((b.laws[j].keys ?? []).map(k2 => String(ix(k2))))} ρ := cyc_machine_holds (${id}_law${j}_holds ρ μ h)` })}`;
|
|
1214
1746
|
}
|