gdp-wired 0.16.0 → 0.18.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 -2
- package/dist/core/cycles.d.ts +73 -0
- package/dist/core/cycles.js +899 -0
- package/dist/core/cyclic.d.ts +67 -0
- package/dist/core/cyclic.js +740 -0
- package/dist/core/data.d.ts +258 -0
- package/dist/core/data.js +1858 -0
- package/dist/core/discover.js +7 -2
- package/dist/core/dsl.d.ts +7 -1
- package/dist/core/dsl.js +39 -6
- package/dist/core/infer.js +5 -1
- package/dist/kernel/cyclic.d.ts +45 -0
- package/dist/kernel/cyclic.js +54 -0
- package/dist/kernel/prove.d.ts +15 -0
- package/dist/kernel/prove.js +485 -5
- package/dist/lean-kernel/emit.d.ts +73 -0
- package/dist/lean-kernel/emit.js +250 -26
- package/dist/lean-kernel/library.js +3 -0
- package/package.json +14 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { C, E, Law, Machine, Recurrence } from '../core/machine.js';
|
|
2
2
|
import { type Lin, type Step } from '../core/solver.js';
|
|
3
3
|
import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
|
|
4
|
+
import { type Cyclic } from '../kernel/cyclic.js';
|
|
4
5
|
import { leanLibrary } from './library.js';
|
|
5
6
|
export { leanLibrary };
|
|
6
7
|
export type Exported = {
|
|
@@ -36,6 +37,7 @@ export declare function readable(id: string, laws: Law[], vars: string[], maps:
|
|
|
36
37
|
binders: string;
|
|
37
38
|
hyp: string;
|
|
38
39
|
call: string;
|
|
40
|
+
have?: (j: number) => string;
|
|
39
41
|
}): string;
|
|
40
42
|
export type ProgressCase = {
|
|
41
43
|
id: string;
|
|
@@ -60,6 +62,10 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
|
|
|
60
62
|
stepLaws?: StepLaw[];
|
|
61
63
|
}): string;
|
|
62
64
|
export declare const hasSets: (m: Machine) => boolean;
|
|
65
|
+
export declare function machineLit(mm: Machine, t: {
|
|
66
|
+
e: (x: E) => string;
|
|
67
|
+
c: (x: C) => string;
|
|
68
|
+
}, ix: (name: string) => number, mx: (name: string) => number): string;
|
|
63
69
|
export declare function smachineToLean(id: string, given: Machine, laws: Law[], opts?: {
|
|
64
70
|
stepLaws?: StepLaw[];
|
|
65
71
|
liveness?: Liveness[];
|
|
@@ -143,6 +149,21 @@ export type LeanItem = {
|
|
|
143
149
|
} | {
|
|
144
150
|
id: string;
|
|
145
151
|
goal: Goal;
|
|
152
|
+
} | {
|
|
153
|
+
id: string;
|
|
154
|
+
cyclic: Cyclic;
|
|
155
|
+
tampered?: {
|
|
156
|
+
what: string;
|
|
157
|
+
proof: Cyclic;
|
|
158
|
+
}[];
|
|
159
|
+
after?: (ix: (name: string) => number, mx: (map: string) => number) => string;
|
|
160
|
+
bridge?: CycBridge;
|
|
161
|
+
recur?: CycRecur;
|
|
162
|
+
fold?: {
|
|
163
|
+
fold: Fold;
|
|
164
|
+
laws: Law[];
|
|
165
|
+
};
|
|
166
|
+
data?: true;
|
|
146
167
|
};
|
|
147
168
|
export declare function toLean(items: LeanItem[], opts?: {
|
|
148
169
|
title?: string;
|
|
@@ -151,3 +172,55 @@ export declare function toLean(items: LeanItem[], opts?: {
|
|
|
151
172
|
export declare function leanProject(file: string, opts?: {
|
|
152
173
|
name?: string;
|
|
153
174
|
}): Record<string, string>;
|
|
175
|
+
export type CycBridge = {
|
|
176
|
+
machine: Machine;
|
|
177
|
+
laws: Law[];
|
|
178
|
+
tampered?: {
|
|
179
|
+
what: string;
|
|
180
|
+
machine: Machine;
|
|
181
|
+
}[];
|
|
182
|
+
};
|
|
183
|
+
export type CycRecur = {
|
|
184
|
+
name: string;
|
|
185
|
+
args: string[];
|
|
186
|
+
zero: E;
|
|
187
|
+
next: E;
|
|
188
|
+
lemmas: {
|
|
189
|
+
name: string;
|
|
190
|
+
holds: C;
|
|
191
|
+
}[];
|
|
192
|
+
between: {
|
|
193
|
+
name: string;
|
|
194
|
+
holds: C;
|
|
195
|
+
}[];
|
|
196
|
+
};
|
|
197
|
+
export declare function cyclicToLean(id: string, p: Cyclic, tampered?: {
|
|
198
|
+
what: string;
|
|
199
|
+
proof: Cyclic;
|
|
200
|
+
}[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string, bridge?: CycBridge, recur?: CycRecur, fold?: {
|
|
201
|
+
fold: Fold;
|
|
202
|
+
laws: Law[];
|
|
203
|
+
}): string;
|
|
204
|
+
export declare function cycEncodingLean(id: string, facts: {
|
|
205
|
+
name: string;
|
|
206
|
+
keys: string[];
|
|
207
|
+
holds: C;
|
|
208
|
+
}[], nodes: {
|
|
209
|
+
name: string;
|
|
210
|
+
hyps: C[];
|
|
211
|
+
concl: C;
|
|
212
|
+
}[], b: CycBridge): string;
|
|
213
|
+
export declare function cycRecurCheckLean(id: string, facts: {
|
|
214
|
+
name: string;
|
|
215
|
+
keys: string[];
|
|
216
|
+
holds: C;
|
|
217
|
+
}[], r: CycRecur): string;
|
|
218
|
+
export declare function cycFoldCheckLean(id: string, facts: {
|
|
219
|
+
name: string;
|
|
220
|
+
keys: string[];
|
|
221
|
+
holds: C;
|
|
222
|
+
}[], nodes: {
|
|
223
|
+
name: string;
|
|
224
|
+
hyps: C[];
|
|
225
|
+
concl: C;
|
|
226
|
+
}[], f: Fold, laws: Law[]): string;
|
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
4
|
+
import { grow, minimal, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
5
5
|
import { refinement, refinementSides } from "../core/compose.js";
|
|
6
6
|
import { leanSurface, leanTerms } from "./lean-terms.js";
|
|
7
|
+
import { descentGoal, mainGoal, premiseGoal } from "../kernel/cyclic.js";
|
|
7
8
|
import { leanLibrary } from "./library.js";
|
|
8
9
|
export { leanLibrary };
|
|
9
10
|
// --- to Lean ------------------------------------------------------------------------------------------------------------
|
|
@@ -130,12 +131,24 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
|
|
|
130
131
|
// monomial with a cell among its factors after the cells.
|
|
131
132
|
const degree = (a, b) => factorsOf(a).length - factorsOf(b).length;
|
|
132
133
|
const plainMonos = [...monos].filter(m => factorsOf(m).every(f => !isCell(f))).sort(degree), cellMonos = [...monos].filter(m => factorsOf(m).some(isCell)).sort(degree);
|
|
133
|
-
|
|
134
|
+
// 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)
|
|
135
|
+
// comes after that product, and the product after its cells: each column placed after the columns it reads.
|
|
136
|
+
const late = [], placed = new Set();
|
|
137
|
+
const place = (name) => {
|
|
138
|
+
if (placed.has(name))
|
|
139
|
+
return;
|
|
140
|
+
placed.add(name);
|
|
141
|
+
for (const d of isMonomial(name) ? factorsOf(name) : Object.keys(cellKey(name)))
|
|
142
|
+
if (cells.has(d) || cellMonos.includes(d))
|
|
143
|
+
place(d);
|
|
144
|
+
late.push(name);
|
|
145
|
+
};
|
|
146
|
+
for (const cell of [...cells].sort((a, b) => depth(a) - depth(b)))
|
|
147
|
+
place(cell);
|
|
148
|
+
for (const mono of cellMonos)
|
|
149
|
+
place(mono);
|
|
150
|
+
const cols = [...vars, ...plainMonos, ...[...terms].sort((a, b) => Number(a.slice(1, -1)) - Number(b.slice(1, -1))), ...late];
|
|
134
151
|
const col = new Map(cols.map((name, j) => [name, j]));
|
|
135
|
-
for (const cell of cells)
|
|
136
|
-
for (const key of Object.keys(cellKey(cell)))
|
|
137
|
-
if (cellMonos.includes(key))
|
|
138
|
-
throw new Error(`${ex.name}: the key of ${cell} reads a product of cells`);
|
|
139
152
|
const maps = globalMaps ?? [...new Set([...[...cells].map(cellMap), ...goal.hyps.flatMap(h => [...JSON.stringify(h).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]))])];
|
|
140
153
|
const dense = (lin) => {
|
|
141
154
|
const coefs = new Array(cols.length).fill(0);
|
|
@@ -320,6 +333,7 @@ theorem ${id}_laws : ∀ maps ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t,
|
|
|
320
333
|
// Readable corollaries: each law as a plain Lean statement over named variables (ρ the state, μ the maps, each name an
|
|
321
334
|
// abbreviation for its column), proven from the machine's theorem by unfolding the law's own definition. The statement
|
|
322
335
|
// is what a Lean development cites; the column numbers stay inside the namespace.
|
|
336
|
+
// `have`: the line that gives each law as a hypothesis hl (an SEntry's Holds by default; a cyclic proof's, holdsOver).
|
|
323
337
|
export function readable(id, laws, vars, maps, reach) {
|
|
324
338
|
for (const name of [...vars, ...maps])
|
|
325
339
|
if (name === 'ρ' || name === 'μ' || name.includes('«') || name.includes('»'))
|
|
@@ -362,8 +376,8 @@ export function readable(id, laws, vars, maps, reach) {
|
|
|
362
376
|
${[...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')}
|
|
363
377
|
${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, '- /')} -/
|
|
364
378
|
theorem law${j} ${reach.binders} (h : ${reach.hyp}) : ${statement(law)} := by
|
|
365
|
-
${law.keys?.length ? `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])
|
|
366
|
-
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])
|
|
379
|
+
${law.keys?.length ? `${reach.have ? reach.have(j) : `have hl : ${id}_e${j}.Holds ρ μ := ${reach.call} ${id}_e${j} (by simp [${id}_proof])`}
|
|
380
|
+
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])`}
|
|
367
381
|
${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')}
|
|
368
382
|
end ${id}`;
|
|
369
383
|
}
|
|
@@ -510,6 +524,25 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
|
|
|
510
524
|
// the relations assumed), built in Lean from the machine and its history (History.lean's HMachine.plain), so every claim
|
|
511
525
|
// below is about it; with the relations' record goals, its laws hold over histories too (hmachine_sound).
|
|
512
526
|
// `historyTampers`: the history with other relations, each refused.
|
|
527
|
+
// A machine as Sets.lean's SMachine literal: its variables and maps numbered by `ix` and `mx`, its terms by `t`.
|
|
528
|
+
export function machineLit(mm, t, ix, mx) {
|
|
529
|
+
const { e, c } = t;
|
|
530
|
+
const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
|
|
531
|
+
return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
|
|
532
|
+
${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
533
|
+
${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
|
|
534
|
+
${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
535
|
+
${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
|
|
536
|
+
${mm.pre ? c(mm.pre) : TRUE},
|
|
537
|
+
${mm.assume ? c(mm.assume) : TRUE}⟩,
|
|
538
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
|
|
539
|
+
${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
|
|
540
|
+
${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
|
|
541
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
|
|
542
|
+
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
|
|
543
|
+
${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
|
|
544
|
+
${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))))})`))}⟩`;
|
|
545
|
+
}
|
|
513
546
|
export function smachineToLean(id, given, laws, opts = {}) {
|
|
514
547
|
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
515
548
|
const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
|
|
@@ -697,23 +730,7 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
697
730
|
const mx = (name) => { const j = mapNames.indexOf(name); if (j < 0)
|
|
698
731
|
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
699
732
|
const inline = (c2) => substC(c2, { vars: {}, maps: {}, flags: flagsOfM(m) });
|
|
700
|
-
const lit = (mm) => {
|
|
701
|
-
const fl = flagsOfM(mm), fs = Object.entries(mm.step.flags ?? {}), ws = Object.entries(mm.step.maps ?? {});
|
|
702
|
-
return `⟨⟨⟨${list(mm.inputs.map(name => String(ix(name))))},
|
|
703
|
-
${list(Object.entries(mm.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
704
|
-
${list(Object.entries(mm.maps ?? {}).map(([name, c2]) => `(${mx(name)}, ${c2})`))},
|
|
705
|
-
${list(Object.entries(mm.step.state ?? {}).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
706
|
-
${list(ws.flatMap(([name, w]) => ('each' in w ? [] : [`(${mx(name)}, ${e(w.key)}, ${e(w.value)})`])))},
|
|
707
|
-
${mm.pre ? c(mm.pre) : TRUE},
|
|
708
|
-
${mm.assume ? c(mm.assume) : TRUE}⟩,
|
|
709
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [] : [`(${ix(`?${f}`)}, ${c(fl[f])})`])))}⟩,
|
|
710
|
-
${list((mm.inputMaps ?? []).map(mp => String(mx(mp))))},
|
|
711
|
-
${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
|
|
712
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(boundOf(c2).map(key => String(ix(key))))}, ${c(c2.c)})`] : [])))},
|
|
713
|
-
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${list(witnessesOf(f, c2).map(w => String(ix(w))))})`] : [])))},
|
|
714
|
-
${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
|
|
715
|
-
${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))))})`))}⟩`;
|
|
716
|
-
};
|
|
733
|
+
const lit = (mm) => machineLit(mm, { e, c }, ix, mx);
|
|
717
734
|
// A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
|
|
718
735
|
const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
|
|
719
736
|
${list(Object.entries(lp.state).map(([name, e2]) => `(${ix(name)}, ${e(e2)})`))},
|
|
@@ -1055,11 +1072,15 @@ export function toLean(items, opts = {}) {
|
|
|
1055
1072
|
const outer = checkBy;
|
|
1056
1073
|
const env = globalThis.process?.env;
|
|
1057
1074
|
checkBy = (opts.native ?? env?.GDP_LEAN_NATIVE === '1') ? 'native_decide' : 'decide +kernel';
|
|
1075
|
+
// The proofs found with only the instances their leaves use: the same laws, from fewer hypotheses for Lean to build.
|
|
1076
|
+
const wasMinimal = minimal.on;
|
|
1077
|
+
minimal.on = true;
|
|
1058
1078
|
try {
|
|
1059
1079
|
return toLeanWith(items, opts);
|
|
1060
1080
|
}
|
|
1061
1081
|
finally {
|
|
1062
1082
|
checkBy = outer;
|
|
1083
|
+
minimal.on = wasMinimal;
|
|
1063
1084
|
}
|
|
1064
1085
|
}
|
|
1065
1086
|
function toLeanWith(items, opts) {
|
|
@@ -1119,6 +1140,12 @@ function toLeanWith(items, opts) {
|
|
|
1119
1140
|
parts.push(`-- ${item.id}\n${loopToLean(id, item.loop, item.laws ?? [], item.rank)}`);
|
|
1120
1141
|
theorems.push(`${id}_halts`);
|
|
1121
1142
|
}
|
|
1143
|
+
else if ('cyclic' in item) {
|
|
1144
|
+
parts.push(`-- ${item.id}\n${cyclicToLean(id, item.cyclic, item.tampered ?? [], item.after, item.bridge, item.recur, item.fold)}`);
|
|
1145
|
+
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}`)] : []),
|
|
1146
|
+
// A datatype law's theorem is in a namespace of the item's (its datatypes and functions are named as declared).
|
|
1147
|
+
...(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])) : []));
|
|
1148
|
+
}
|
|
1122
1149
|
else if ('fold' in item) {
|
|
1123
1150
|
parts.push(`-- ${item.id}\n${foldToLean(id, item.fold, item.laws)}`);
|
|
1124
1151
|
theorems.push(`${id}_laws`);
|
|
@@ -1132,7 +1159,7 @@ function toLeanWith(items, opts) {
|
|
|
1132
1159
|
}
|
|
1133
1160
|
// The file the items need: Live.lean for a liveness or a loop, Sets.lean (which imports Steps.lean) for any machine.
|
|
1134
1161
|
return `import History
|
|
1135
|
-
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'}
|
|
1162
|
+
${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'}
|
|
1136
1163
|
open Kern Cert
|
|
1137
1164
|
set_option maxRecDepth 100000
|
|
1138
1165
|
set_option maxHeartbeats 0
|
|
@@ -1164,3 +1191,200 @@ lean_lib ${name} where
|
|
|
1164
1191
|
`,
|
|
1165
1192
|
};
|
|
1166
1193
|
}
|
|
1194
|
+
export function cyclicToLean(id, p, tampered = [], after, bridge, recur, fold) {
|
|
1195
|
+
const all = [p, ...tampered.map(t => t.proof)];
|
|
1196
|
+
const v = (name) => ({ op: 'var', name });
|
|
1197
|
+
const fnames = fold ? Object.keys(fold.fold.leaf) : [];
|
|
1198
|
+
const { vars, maps } = cycScope(all, bridge, [...(recur ? [recur.zero, recur.next, v('x'), v('prev'), v('k$'), ...recur.args.map(v)] : []),
|
|
1199
|
+
...(fold ? [...fold.fold.params, ...fold.fold.inputs, ...fnames, ...fnames.map(x => `l_${x}`), ...fnames.map(x => `r_${x}`), 'n$', 'N$'].map(v) : []),
|
|
1200
|
+
...(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$') }))] : []),
|
|
1201
|
+
...(fold ? ['Kind', 'Left', 'Right', ...fnames, ...fold.fold.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') })) : [])]);
|
|
1202
|
+
const { e, c } = leanTerms(vars, maps, id);
|
|
1203
|
+
const ix = (name) => vars.indexOf(name);
|
|
1204
|
+
const facts = list(p.facts.map(f => `⟨${list(f.keys.map(key => String(ix(key))))}, ${c(f.holds)}⟩`));
|
|
1205
|
+
// A tampered proof keeps the proof's tables and trees (made for the proof's own goals) under its changed nodes.
|
|
1206
|
+
const enc = (g, tree) => { const x = encode({ name: `${id}: ${g.name}`, goal: g, tree }, false, true, vars, maps); return `(${x.tab}, ${x.tree})`; };
|
|
1207
|
+
const nodeLits = (q) => q.nodes.map((n, i) => {
|
|
1208
|
+
const o = p.nodes[i];
|
|
1209
|
+
const uses = n.uses.map((u, j) => {
|
|
1210
|
+
const ou = o.uses[j], om = p.nodes[ou.node];
|
|
1211
|
+
return `⟨${u.node}, ${list(Object.entries(u.subst).map(([name, t]) => `(${ix(name)}, ${e(t)})`))}, ${c(u.guard)},\n ${list(om.hyps.map((h, k2) => enc(premiseGoal(p.facts, o, ou, h, k2), ou.premises[k2])))},\n ${enc(descentGoal(p.facts, o, ou, om), ou.descent)}⟩`;
|
|
1212
|
+
});
|
|
1213
|
+
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)}` };
|
|
1214
|
+
});
|
|
1215
|
+
const nodes = (q) => list(nodeLits(q).map(x => `⟨${x.hyps}, ${x.concl}, ${x.rest}⟩`));
|
|
1216
|
+
const mx = (map) => maps.indexOf(map);
|
|
1217
|
+
return `-- ${p.nodes.map(n => n.name).join('; ')} (variables ${vars.map((name, j) => `${j} ${name}`).join(', ')}; maps ${maps.map((name, j) => `${j} ${name}`).join(', ')})
|
|
1218
|
+
def ${id}_facts : List Fact := ${facts}
|
|
1219
|
+
${nodeLits(p).map((x, i) => `def ${id}_hyps${i} : List C := ${x.hyps}
|
|
1220
|
+
def ${id}_concl${i} : C := ${x.concl}
|
|
1221
|
+
def ${id}_node${i} : CNode := ⟨${id}_hyps${i}, ${id}_concl${i}, ${x.rest}⟩
|
|
1222
|
+
theorem ${id}_node${i}_hyps : ${id}_node${i}.hyps = ${id}_hyps${i} := rfl
|
|
1223
|
+
theorem ${id}_node${i}_concl : ${id}_node${i}.concl = ${id}_concl${i} := rfl`).join('\n')}
|
|
1224
|
+
def ${id}_nodes : List CNode := ${list(p.nodes.map((_, i) => `${id}_node${i}`))}
|
|
1225
|
+
${p.nodes.map((_, i) => `theorem ${id}_node${i}_mem : ${id}_node${i} ∈ ${id}_nodes := by simp only [${id}_nodes, List.mem_cons, true_or, or_true]`).join('\n')}
|
|
1226
|
+
theorem ${id}_check : checkCyclic ${id}_facts ${id}_nodes = true := by ${checkBy}
|
|
1227
|
+
theorem ${id}_sound : ∀ (maps : Nat → Nat → Nat) env, FactsHold ${id}_facts env maps → ∀ n ∈ ${id}_nodes, (∀ c ∈ n.hyps, evalC env maps c = true) → evalC env maps n.concl = true :=
|
|
1228
|
+
cyclic_sound ${id}_facts ${id}_nodes ${id}_check
|
|
1229
|
+
${tampered.map((t, j) => `-- TAMPERED: ${t.what}. Lean computes the check to false.
|
|
1230
|
+
def ${id}_tampered${j} : List CNode := ${nodes(t.proof)}
|
|
1231
|
+
example : checkCyclic ${id}_facts ${id}_tampered${j} = false := by ${checkBy}`).join('\n')}${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)}` : ''}`;
|
|
1232
|
+
}
|
|
1233
|
+
function cycScope(all, bridge, extra = []) {
|
|
1234
|
+
const names = (t) => [...JSON.stringify(t).matchAll(/"op":"var","name":"([^"]+)"/g)].map(m => m[1]);
|
|
1235
|
+
const ms = bridge ? [bridge.machine, ...(bridge.tampered ?? []).map(t => t.machine)] : [];
|
|
1236
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
1237
|
+
const traced = (m) => [...Object.keys(m.state), ...m.inputs], tracedMaps = (m) => [...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])];
|
|
1238
|
+
const machineVars = ms.flatMap(m => [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.step.flags ?? {}).map(f => `?${f}`),
|
|
1239
|
+
...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])]);
|
|
1240
|
+
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])])])]),
|
|
1241
|
+
...machineVars, ...(bridge ? [...bridge.laws.flatMap(l => [...keysOf(l), ...names(l.holds)]), 'u$', 'k$', 'T$', 'O$', 't$'] : []), ...names(extra)])];
|
|
1242
|
+
const maps = [...new Set([...[...JSON.stringify([all, ms, bridge?.laws ?? []]).matchAll(/"op":"get","map":"([^"]+)"/g)].map(m => m[1]),
|
|
1243
|
+
...ms.flatMap(m => [...tracedMaps(m), ...Object.keys(m.fns ?? {}), ...traced(m).map(x => `${x}@`), ...tracedMaps(m).map(x => `${x}@`)])])];
|
|
1244
|
+
return { vars, maps };
|
|
1245
|
+
}
|
|
1246
|
+
// Bridge.lean's cycMOk alone, part by part, for each law of a machine (the sweep research/cyclic/bridge.ts runs over the
|
|
1247
|
+
// examples): the facts and nodes machineNodes makes, each node with no proof (cycMOk reads only its hypotheses and
|
|
1248
|
+
// conclusion), against the machine's encoding. Each part an example, named in a comment before it.
|
|
1249
|
+
export function cycEncodingLean(id, facts, nodes, b) {
|
|
1250
|
+
const shape = { facts, nodes: nodes.map(n => ({ hyps: n.hyps, concl: n.concl, rank: [], insts: [], uses: [] })) };
|
|
1251
|
+
const { vars, maps } = cycScope([shape], b);
|
|
1252
|
+
const t = leanTerms(vars, maps, id);
|
|
1253
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1254
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1255
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1256
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1257
|
+
const m = b.machine, keysOf = (l) => list([...(l.forall ? [l.forall] : []), ...(l.keys ?? [])].map(k2 => String(ix(k2))));
|
|
1258
|
+
const by = checkBy;
|
|
1259
|
+
return `-- ${id}: ${m.name}
|
|
1260
|
+
def ${id}_machine : SMachine := ${machineLit(m, t, ix, mx)}
|
|
1261
|
+
def ${id}_enc : CycEnc := ⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
|
|
1262
|
+
${list([...Object.keys(m.state), ...m.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
|
|
1263
|
+
${list([...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩
|
|
1264
|
+
${facts.map((f, i) => `-- CHECK ${id} fact ${i}: ${f.name}
|
|
1265
|
+
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')}
|
|
1266
|
+
${b.laws.map((l, j) => `-- CHECK ${id} law ${j}: shape: ${l.name}
|
|
1267
|
+
example : cycShapeOk ${id}_machine ${id}_enc ${keysOf(l)} = true := by ${by}
|
|
1268
|
+
-- CHECK ${id} law ${j}: terms: ${l.name}
|
|
1269
|
+
example : cycTermsOk ${id}_machine ${id}_enc ${keysOf(l)} = true := by ${by}
|
|
1270
|
+
-- CHECK ${id} law ${j}: law: ${l.name}
|
|
1271
|
+
example : cycLawOk ${id}_machine ${id}_enc (${t.c(l.holds)}) = true := by ${by}
|
|
1272
|
+
-- CHECK ${id} law ${j}: node: ${l.name}
|
|
1273
|
+
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')}`;
|
|
1274
|
+
}
|
|
1275
|
+
// Bridge.lean's recOk and foldOk alone, for the sweep (research/cyclic/bridge.ts): recurNodes' and foldNodes' facts (and
|
|
1276
|
+
// a fold's nodes, with no proof: foldOk reads only their hypotheses and conclusions) against the recurrence's equations
|
|
1277
|
+
// and the fold's table. Each an example, named in a comment before it.
|
|
1278
|
+
export function cycRecurCheckLean(id, facts, r) {
|
|
1279
|
+
const v = (name) => ({ op: 'var', name });
|
|
1280
|
+
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') }]);
|
|
1281
|
+
const t = leanTerms(vars, maps, id), ix = (n2) => vars.indexOf(n2), mx = (n2) => maps.indexOf(n2);
|
|
1282
|
+
return `-- CHECK ${id} recurrence: ${r.name}
|
|
1283
|
+
example : recOk ⟨${mx(`${r.name}$`)}, ${ix('x')}, ${ix('prev')}, ${ix('k$')}⟩ (${t.e(r.zero)}) (${t.e(r.next)}) []
|
|
1284
|
+
${list(facts.map(f => `⟨${list(f.keys.map(k2 => String(ix(k2))))}, ${t.c(f.holds)}⟩`))} = true := by ${checkBy}`;
|
|
1285
|
+
}
|
|
1286
|
+
export function cycFoldCheckLean(id, facts, nodes, f, laws) {
|
|
1287
|
+
const v = (name) => ({ op: 'var', name }), st = Object.keys(f.leaf);
|
|
1288
|
+
const shape = { facts, nodes: nodes.map(n => ({ hyps: n.hyps, concl: n.concl, rank: [], insts: [], uses: [] })) };
|
|
1289
|
+
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),
|
|
1290
|
+
...Object.values(f.leaf), ...Object.values(f.node), ...['Kind', 'Left', 'Right', ...st, ...f.inputs].map((x) => ({ op: 'get', map: `${x}@`, key: v('n$') }))]);
|
|
1291
|
+
const t = leanTerms(vars, maps, id);
|
|
1292
|
+
return `${cycFoldLean(id, f, [], vars, maps, t)}
|
|
1293
|
+
def ${id}_facts : List Fact := ${list(facts.map(g => `⟨${list(g.keys.map(k2 => String(vars.indexOf(k2))))}, ${t.c(g.holds)}⟩`))}
|
|
1294
|
+
${laws.map((l, j) => `-- CHECK ${id} fold law ${j}: ${l.name}
|
|
1295
|
+
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')}`;
|
|
1296
|
+
}
|
|
1297
|
+
// Bridge.lean's cyc_fold_sound for each law of a cyclic proof made by foldNodes: the fold as Kernel.lean's Fold, the
|
|
1298
|
+
// table (its added variables and maps), each law's check and its theorem over trees, as fold_sound states the usual one.
|
|
1299
|
+
function cycFoldLean(id, f, laws, vars, maps, t) {
|
|
1300
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1301
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1302
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1303
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1304
|
+
const st = Object.keys(f.leaf);
|
|
1305
|
+
return `def ${id}_fold : Fold := ⟨${list(f.inputs.map(name => String(ix(name))))},
|
|
1306
|
+
${list(st.map(n2 => `(${ix(n2)}, ${ix(`l_${n2}`)}, ${ix(`r_${n2}`)})`))},
|
|
1307
|
+
${list(Object.entries(f.leaf).map(([name, e2]) => `(${ix(name)}, ${t.e(e2)})`))},
|
|
1308
|
+
${list(Object.entries(f.node).map(([name, e2]) => `(${ix(name)}, ${t.e(e2)})`))},
|
|
1309
|
+
${f.pre ? t.c(f.pre) : TRUE},
|
|
1310
|
+
${f.assume ? t.c(f.assume) : TRUE}⟩
|
|
1311
|
+
def ${id}_fenc : FoldEnc := ⟨${ix('n$')}, ${ix('N$')}, ${mx('Kind@')}, ${mx('Left@')}, ${mx('Right@')}, ${list([...st, ...f.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))}⟩
|
|
1312
|
+
${laws.map((l, j) => `-- ${l.name}
|
|
1313
|
+
def ${id}_law${j} : C := ${t.c(l.holds)}
|
|
1314
|
+
theorem ${id}_law${j}_ok : foldOk ${id}_fold ${id}_fenc ${id}_facts ${id}_law${j} ${id}_node${j} = true := by ${checkBy}
|
|
1315
|
+
theorem ${id}_law${j}_holds : ∀ (maps : Nat → Nat → Nat) ρ, evalC ρ maps ${id}_fold.pre = true → ∀ t, Allowed ${id}_fold ρ maps t →
|
|
1316
|
+
evalC (foldT ${id}_fold ρ maps t) maps ${id}_law${j} = true :=
|
|
1317
|
+
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')}`;
|
|
1318
|
+
}
|
|
1319
|
+
// Bridge.lean's cyc_recur_sound for a cyclic proof made by recurNodes: the recurrence's equations checked against the
|
|
1320
|
+
// proof's facts, the nodes of its values (recF), its Lean definition (as recurToLean writes it) shown to be those values,
|
|
1321
|
+
// and each lemma and law between two points plainly of the definition.
|
|
1322
|
+
function cycRecurLean(id, p, r, vars, maps, t) {
|
|
1323
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1324
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1325
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1326
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1327
|
+
const fn = `${id}_fn`, args = r.args.map(recurArg), binders = args.map(a => `(${a} : Nat)`).join(' ');
|
|
1328
|
+
const call = (x) => `(${[fn, ...args, x].join(' ')})`;
|
|
1329
|
+
const surface = (names) => leanSurface({ ...Object.fromEntries(r.args.map((a, i) => [a, args[i]])), ...names }, id);
|
|
1330
|
+
const renc = `⟨${mx(`${r.name}$`)}, ${ix('x')}, ${ix('prev')}, ${ix('k$')}⟩`;
|
|
1331
|
+
const recF = `recF ${id}_zero ${id}_next ${ix('x')} ${ix('prev')}`;
|
|
1332
|
+
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)`;
|
|
1333
|
+
const node = (j) => `${id}_node${j}`;
|
|
1334
|
+
const n = r.lemmas.length;
|
|
1335
|
+
return `def ${id}_renc : RecEnc := ${renc}
|
|
1336
|
+
def ${id}_zero : E := ${t.e(r.zero)}
|
|
1337
|
+
def ${id}_next : E := ${t.e(r.next)}
|
|
1338
|
+
theorem ${id}_rok : recOk ${id}_renc ${id}_zero ${id}_next [] ${id}_facts = true := by ${checkBy}
|
|
1339
|
+
theorem ${id}_values : ∀ maps env, ∀ n ∈ ${id}_nodes, (∀ c ∈ n.hyps, evalC env (withMap maps ${mx(`${r.name}$`)} (${recF} env maps)) c = true) →
|
|
1340
|
+
evalC env (withMap maps ${mx(`${r.name}$`)} (${recF} env maps)) n.concl = true :=
|
|
1341
|
+
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)
|
|
1342
|
+
-- ${r.name} as Lean defines it (recurToLean's recursion), and the same as the values the proof is about.
|
|
1343
|
+
def ${fn} ${binders} : Nat → Nat
|
|
1344
|
+
| 0 => ${surface({}).e(r.zero)}
|
|
1345
|
+
| x + 1 => ${surface({ x: 'x', prev: call('x') }).e(r.next)}
|
|
1346
|
+
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
|
|
1347
|
+
intro n
|
|
1348
|
+
induction n with
|
|
1349
|
+
| zero => simp [recF, ${id}_zero, ${fn}, evalE]
|
|
1350
|
+
| succ n ih => simp only [recF]; rw [ih]; simp [${id}_next, ${fn}, evalE, upd]
|
|
1351
|
+
${r.lemmas.map((l, j) => `/-- ${r.name}: ${l.name} -/
|
|
1352
|
+
theorem ${id}_lemma${j} ${binders} (x : Nat) : ${surface({ x: 'x', fx: call('x') }).c(l.holds)} := by
|
|
1353
|
+
have h := ${id}_values (fun _ _ => 0) ${envAt([['x', 'x']])} _ ${node(j)}_mem (by rw [${node(j)}_hyps]; simp [${id}_hyps${j}])
|
|
1354
|
+
rw [${node(j)}_concl] at h
|
|
1355
|
+
simpa [${id}_concl${j}, evalC, evalE, withMap, ${fn}_eq] using h`).join('\n')}
|
|
1356
|
+
${r.between.map((b, j) => `/-- ${r.name}: ${b.name}, for every x ≤ y -/
|
|
1357
|
+
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
|
|
1358
|
+
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}])
|
|
1359
|
+
rw [${node(n + j)}_concl] at h
|
|
1360
|
+
simp [${id}_concl${n + j}, evalC, evalE, withMap, ${fn}_eq] at h
|
|
1361
|
+
first | exact h hxy | omega | exact h.resolve_left (by omega)`).join('\n')}`;
|
|
1362
|
+
}
|
|
1363
|
+
// Bridge.lean's cyc_machine_sound for each law of a cyclic proof made by machineNodes: the machine as Sets.lean's
|
|
1364
|
+
// SMachine, the encoding (its added variables and maps over time), each law's check and theorem, and the laws plainly.
|
|
1365
|
+
function cycBridgeLean(id, p, b, vars, maps, t) {
|
|
1366
|
+
const ix = (name) => { const j = vars.indexOf(name); if (j < 0)
|
|
1367
|
+
throw new Error(`${id}: no variable for ${name}`); return j; };
|
|
1368
|
+
const mx = (name) => { const j = maps.indexOf(name); if (j < 0)
|
|
1369
|
+
throw new Error(`${id}: no map for ${name}`); return j; };
|
|
1370
|
+
if (b.laws.length > p.nodes.length)
|
|
1371
|
+
throw new Error(`${id}: more laws than nodes`);
|
|
1372
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
1373
|
+
const enc = (m) => `⟨${['u$', 'k$', 'T$', 'O$', 't$'].map(ix).join(', ')},
|
|
1374
|
+
${list([...Object.keys(m.state), ...m.inputs].map(x => `(${ix(x)}, ${mx(`${x}@`)})`))},
|
|
1375
|
+
${list([...Object.keys(m.maps ?? {}), ...(m.inputMaps ?? [])].map(x => `(${mx(x)}, ${mx(`${x}@`)})`))}⟩`;
|
|
1376
|
+
const keysL = (l) => list(keysOf(l).map(k2 => String(ix(k2))));
|
|
1377
|
+
return `def ${id}_machine : SMachine := ${machineLit(b.machine, t, ix, mx)}
|
|
1378
|
+
def ${id}_enc : CycEnc := ${enc(b.machine)}
|
|
1379
|
+
${b.laws.map((l, j) => `-- ${l.name}
|
|
1380
|
+
def ${id}_law${j} : C := ${t.c(l.holds)}
|
|
1381
|
+
def ${id}_l${j} : MLaw := ⟨${id}_law${j}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩
|
|
1382
|
+
theorem ${id}_law${j}_ok : cycMOk ${id}_machine ${id}_enc ${id}_facts ${id}_law${j} ${keysL(l)} ${id}_node${j} = true := by ${checkBy}
|
|
1383
|
+
theorem ${id}_law${j}_holds : ∀ ρ M, SReach ${id}_machine ρ M → ∀ ρ' : Nat → Nat, (∀ i, i ∉ ${keysL(l)} → ρ' i = ρ i) → evalC ρ' M ${id}_law${j} = true :=
|
|
1384
|
+
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')}
|
|
1385
|
+
${(b.tampered ?? []).map((tm, j) => `-- TAMPERED: ${tm.what}. Lean computes the check against the proof to false.
|
|
1386
|
+
def ${id}_tamperedm${j} : SMachine := ${machineLit(tm.machine, t, ix, mx)}
|
|
1387
|
+
example : cycMOk ${id}_tamperedm${j} ${id}_enc ${id}_facts ${id}_law0 ${keysL(b.laws[0])} ${id}_node0 = false := by ${checkBy}`).join('\n')}
|
|
1388
|
+
${readable(id, b.laws, vars, maps, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: '',
|
|
1389
|
+
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)` })}`;
|
|
1390
|
+
}
|