gdp-wired 0.7.0 → 0.9.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 +51 -6
- package/dist/core/dsl.d.ts +164 -13
- package/dist/core/dsl.js +525 -27
- package/dist/core/gdp.d.ts +4 -8
- package/dist/core/gdp.js +4 -11
- package/dist/core/machine.d.ts +28 -0
- package/dist/kernel/prove.js +39 -2
- package/dist/lean-kernel/emit.d.ts +9 -1
- package/dist/lean-kernel/emit.js +264 -20
- package/dist/lean-kernel/lean-terms.d.ts +4 -0
- package/dist/lean-kernel/lean-terms.js +38 -0
- package/dist/lean-kernel/library.js +5 -5
- package/package.json +2 -2
package/dist/core/gdp.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { Named, NamesOf, Proof, Prover } from '@gdp-ts/core';
|
|
2
|
-
import { type Decision, type Fun, type Rule, type StepLaw,
|
|
2
|
+
import { type Decision, type Fun, type Rule, type StepLaw, Unproven } from './dsl.js';
|
|
3
3
|
import { type Law, type State } from './machine.js';
|
|
4
4
|
type AnyNamed = Named<any, any>;
|
|
5
5
|
export type Mint<K extends string, A> = <S extends AnyNamed[]>(args: A, ...about: S) => Proof<K, NamesOf<S>> | null;
|
|
6
|
-
export
|
|
7
|
-
readonly verdict: Verdict;
|
|
8
|
-
constructor(what: string, verdict: Verdict);
|
|
9
|
-
}
|
|
6
|
+
export { Unproven };
|
|
10
7
|
type AnyFun = Fun<any, any, any>;
|
|
11
8
|
type Conditions<O> = {
|
|
12
|
-
[k in keyof O]: O[k] extends boolean ? k : never;
|
|
13
|
-
}[keyof O
|
|
9
|
+
[k in keyof O & string]: O[k] extends boolean ? k : O[k] extends Record<string, unknown> ? `${k}.${Conditions<O[k]>}` : never;
|
|
10
|
+
}[keyof O & string];
|
|
14
11
|
type ArgsOf<F extends AnyFun> = Parameters<F['run']>[0];
|
|
15
12
|
type ParamsOf<F extends AnyFun> = NonNullable<Parameters<F['run']>[1]>;
|
|
16
13
|
export declare function guard<K extends string, F extends AnyFun>(prover: Prover<K>, fn: F, field: Conditions<ReturnType<F['run']>>, opts: {
|
|
@@ -32,4 +29,3 @@ export declare function gate<K extends string, R extends AnyRule>(prover: Prover
|
|
|
32
29
|
params?: RuleParams<R>;
|
|
33
30
|
state?: State;
|
|
34
31
|
}): Gate<K, InputOf<R>>;
|
|
35
|
-
export {};
|
package/dist/core/gdp.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import { funOf, proveDecision, proveSteps } from "./dsl.js";
|
|
1
|
+
import { Unproven, funOf, proveDecision, proveSteps } from "./dsl.js";
|
|
2
2
|
import { evalC, interpret } from "./machine.js";
|
|
3
3
|
import { allowed, requestEnv } from "./watch.js";
|
|
4
|
-
|
|
5
|
-
export class Unproven extends Error {
|
|
6
|
-
verdict;
|
|
7
|
-
constructor(what, verdict) {
|
|
8
|
-
const found = [verdict.goal && `goal ${verdict.goal}`, verdict.stuck, verdict.witness && `a counterexample: ${JSON.stringify(verdict.witness)}`, verdict.why].filter(Boolean).join('; ');
|
|
9
|
-
super(`${what}: ${verdict.proven ? 'proven, but the kernel did not check the proof' : `not proven${found ? ` (${found})` : ''}`}`);
|
|
10
|
-
this.verdict = verdict;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
4
|
+
export { Unproven };
|
|
13
5
|
const proven = (what, v) => { if (!(v.proven && v.checked))
|
|
14
6
|
throw new Unproven(what, v); };
|
|
7
|
+
const atPath = (o, path) => path.split('.').reduce((x, f) => (typeof x === 'object' && x !== null ? x[f] : undefined), o);
|
|
15
8
|
// A proof wherever `fn`'s result `field` holds. Every law in `laws` must have been stated about `fn` (fn.law or
|
|
16
9
|
// fn.relate) and is proven now; the arguments it is later called with must meet the function's kinds and `assume`.
|
|
17
10
|
// const access = fun('document access').inputs({...}).returns(env => ({permit: permit(env)}))
|
|
@@ -31,7 +24,7 @@ export function guard(prover, fn, field, opts) {
|
|
|
31
24
|
const input = r.encodeInput(args);
|
|
32
25
|
if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
|
|
33
26
|
return null;
|
|
34
|
-
return fn.run(args, params)
|
|
27
|
+
return atPath(fn.run(args, params), field) === true ? prover.prove(...about) : null;
|
|
35
28
|
};
|
|
36
29
|
}
|
|
37
30
|
// A proof for each request whose step takes `flag`, the rule's laws (and step laws) proven now. One gate holds one
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -70,6 +70,34 @@ export type Machine = {
|
|
|
70
70
|
export type Fn = {
|
|
71
71
|
js: string;
|
|
72
72
|
lean?: string;
|
|
73
|
+
recur?: Recurrence;
|
|
74
|
+
};
|
|
75
|
+
export type Recurrence = {
|
|
76
|
+
args: string[];
|
|
77
|
+
zero: E;
|
|
78
|
+
next: E;
|
|
79
|
+
lemmas: {
|
|
80
|
+
name: string;
|
|
81
|
+
holds: C;
|
|
82
|
+
}[];
|
|
83
|
+
between: {
|
|
84
|
+
name: string;
|
|
85
|
+
holds: C;
|
|
86
|
+
}[];
|
|
87
|
+
uses: Record<string, {
|
|
88
|
+
arity: number;
|
|
89
|
+
lean?: string;
|
|
90
|
+
}>;
|
|
91
|
+
proofs?: {
|
|
92
|
+
lemmas?: {
|
|
93
|
+
machine: Machine;
|
|
94
|
+
laws: Law[];
|
|
95
|
+
};
|
|
96
|
+
between?: {
|
|
97
|
+
machine: Machine;
|
|
98
|
+
laws: Law[];
|
|
99
|
+
};
|
|
100
|
+
};
|
|
73
101
|
};
|
|
74
102
|
export type History = {
|
|
75
103
|
keep: C;
|
package/dist/kernel/prove.js
CHANGED
|
@@ -448,13 +448,50 @@ function fnKeys(m, goals) {
|
|
|
448
448
|
// value that makes one of its reads one of these (by matching: a function of several arguments reads a paired key), and
|
|
449
449
|
// at the keys themselves.
|
|
450
450
|
const withFacts = (m, inst, keys) => ({ ...inst, ...Object.fromEntries((m.facts ?? []).filter(f => f.forall).map(f => {
|
|
451
|
-
if (f.keys?.length)
|
|
452
|
-
|
|
451
|
+
if (f.keys?.length) {
|
|
452
|
+
// A fact about several keys reads a function of several arguments (at a paired key): each read matched against the
|
|
453
|
+
// keys the goals read binds some of the fact's keys; one match, or two that agree, binding every key is an instance.
|
|
454
|
+
const vars = [f.forall, ...f.keys], reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
|
|
455
|
+
if (!reads.length)
|
|
456
|
+
return [f.name, tuples(keys, vars.length)];
|
|
457
|
+
const partial = reads.flatMap(pattern => keys.flatMap(key => { const b = matchMany(pattern, key, vars); return b ? [b] : []; }));
|
|
458
|
+
const whole = (b) => vars.every(x => x in b);
|
|
459
|
+
const agree = (a, b) => Object.entries(b).every(([x, e]) => !(x in a) || JSON.stringify(a[x]) === JSON.stringify(e));
|
|
460
|
+
const found = [...partial.filter(whole), ...partial.flatMap((a, i) => partial.slice(i + 1).filter(b => agree(a, b)).map(b => ({ ...a, ...b })).filter(whole))];
|
|
461
|
+
const had = inst[f.name] ?? [], out = [...had], seen = new Set();
|
|
462
|
+
for (let t = 0; t < had.length / vars.length; t++)
|
|
463
|
+
seen.add(JSON.stringify(had.slice(t * vars.length, (t + 1) * vars.length)));
|
|
464
|
+
for (const b of found) {
|
|
465
|
+
const tuple = vars.map(x => b[x]), id = JSON.stringify(tuple);
|
|
466
|
+
if (!seen.has(id)) {
|
|
467
|
+
seen.add(id);
|
|
468
|
+
out.push(...tuple);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return [f.name, out];
|
|
472
|
+
}
|
|
453
473
|
const had = inst[f.name] ?? [], seen = new Set(had.map(e => JSON.stringify(e)));
|
|
454
474
|
const reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
|
|
455
475
|
const at = [...keys, ...reads.flatMap(pattern => keys.flatMap(key => { const b = match(pattern, key, f.forall); return b ? [b] : []; }))];
|
|
456
476
|
return [f.name, [...had, ...at.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
|
|
457
477
|
})) });
|
|
478
|
+
// The values of the names in xs that make pattern the term, for those it reads, if some do (structurally).
|
|
479
|
+
function matchMany(pattern, term, xs) {
|
|
480
|
+
const bound = {};
|
|
481
|
+
const go = (p, t) => {
|
|
482
|
+
if (p.op === 'var' && xs.includes(p.name)) {
|
|
483
|
+
if (p.name in bound)
|
|
484
|
+
return JSON.stringify(bound[p.name]) === JSON.stringify(t);
|
|
485
|
+
bound[p.name] = t;
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
if (p.op !== t.op)
|
|
489
|
+
return false;
|
|
490
|
+
const pe = Object.entries(p), te = t;
|
|
491
|
+
return pe.every(([k, val]) => k === 'op' || (typeof val === 'object' && val !== null ? (Array.isArray(val) ? Array.isArray(te[k]) && te[k].length === val.length && val.every((y, i) => go(y, te[k][i])) : go(val, te[k])) : val === te[k]));
|
|
492
|
+
};
|
|
493
|
+
return go(pattern, term) ? bound : undefined;
|
|
494
|
+
}
|
|
458
495
|
// The value of x that makes pattern the term, if one does (structurally).
|
|
459
496
|
function match(pattern, term, x) {
|
|
460
497
|
let bound;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { C, E, Law, Machine } from '../core/machine.js';
|
|
1
|
+
import type { C, E, Law, Machine, Recurrence } from '../core/machine.js';
|
|
2
2
|
import { 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
4
|
import { leanLibrary } from './library.js';
|
|
@@ -92,7 +92,15 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
|
|
|
92
92
|
loop: Loop;
|
|
93
93
|
};
|
|
94
94
|
};
|
|
95
|
+
noFacts?: boolean;
|
|
95
96
|
}): string;
|
|
97
|
+
type Uses = Record<string, {
|
|
98
|
+
lean?: string;
|
|
99
|
+
recur?: Recurrence;
|
|
100
|
+
arity: number;
|
|
101
|
+
}>;
|
|
102
|
+
export declare function recurToLean(lean: string, r: Recurrence, uses?: Uses): string;
|
|
103
|
+
export declare const recurPairing = "theorem Recur.pair_inj {a b c d : Nat} (h : (a + b) * (a + b) + b = (c + d) * (c + d) + d) : a = c \u2227 b = d := by\n have sq : \u2200 s t : Nat, s < t \u2192 s * s + s < t * t := by\n intro s t hst\n have h1 : (s + 1) * (s + 1) \u2264 t * t := Nat.mul_le_mul hst hst\n have h2 : (s + 1) * (s + 1) = s * s + s + s + 1 := by simp only [Nat.add_mul, Nat.mul_add, Nat.mul_one, Nat.one_mul]; omega\n omega\n have hb : b \u2264 a + b := Nat.le_add_left b a\n have hd : d \u2264 c + d := Nat.le_add_left d c\n rcases Nat.lt_trichotomy (a + b) (c + d) with hlt | heq | hgt\n \u00B7 have := sq _ _ hlt; omega\n \u00B7 rw [heq] at h; omega\n \u00B7 have := sq _ _ hgt; omega\nnoncomputable def Recur.unpair (k : Nat) : Nat \u00D7 Nat :=\n Classical.epsilon fun p : Nat \u00D7 Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = k\ntheorem Recur.unpair_pair (a b : Nat) : Recur.unpair ((a + b) * (a + b) + b) = (a, b) := by\n have h := Classical.epsilon_spec (p := fun p : Nat \u00D7 Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = (a + b) * (a + b) + b) \u27E8(a, b), rfl\u27E9\n obtain \u27E8h1, h2\u27E9 := Recur.pair_inj h\n exact Prod.ext h1 h2";
|
|
96
104
|
export declare function hmachineToLean(id: string, m: Machine, laws: Law[], tampers?: {
|
|
97
105
|
what: string;
|
|
98
106
|
relations: {
|
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -3,7 +3,7 @@ import { add, k, scale, sub } from "../core/solver.js";
|
|
|
3
3
|
import { canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
4
4
|
import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
5
5
|
import { refinement, refinementSides } from "../core/compose.js";
|
|
6
|
-
import { leanTerms } from "./lean-terms.js";
|
|
6
|
+
import { leanSurface, leanTerms } from "./lean-terms.js";
|
|
7
7
|
import { leanLibrary } from "./library.js";
|
|
8
8
|
export { leanLibrary };
|
|
9
9
|
// --- to Lean ------------------------------------------------------------------------------------------------------------
|
|
@@ -474,8 +474,8 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
|
|
|
474
474
|
// `historyTampers`: the history with other relations, each refused.
|
|
475
475
|
export function smachineToLean(id, given, laws, opts = {}) {
|
|
476
476
|
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
477
|
-
const hplain = hm ? historyMachine(hm) : undefined,
|
|
478
|
-
const
|
|
477
|
+
const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
|
|
478
|
+
const m = hplain ? hplain.machine : given;
|
|
479
479
|
const claims = [];
|
|
480
480
|
const claimSteps = () => claims.flatMap(cl => cl.steps);
|
|
481
481
|
// The machine's own step laws, one step or k (Sets.lean's skstep_sound).
|
|
@@ -497,6 +497,7 @@ def ${id}_step${j} : SKStep := ⟨⟨${list(Array.from({ length: kk }, (_, s2) =
|
|
|
497
497
|
${list(qinsts(inst))},
|
|
498
498
|
${list(finsts(inst))},
|
|
499
499
|
${list(pinsts(inst))},
|
|
500
|
+
${list(pfinsts(inst))},
|
|
500
501
|
[],
|
|
501
502
|
${list(Array.from({ length: kk - 1 }, (_, s2) => list(Object.keys(m.state).map(n2 => `(${ix(n2)}, ${ix(stateName(n2, s2 + 1))})`))))}⟩
|
|
502
503
|
theorem ${id}_step${j}_holds : SKStepHolds ${id}_machine ${id}_step${j} :=
|
|
@@ -608,7 +609,15 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
608
609
|
const factsF = m.facts ?? [];
|
|
609
610
|
const base = [m.pre ?? trueC,
|
|
610
611
|
...factsF.filter(f => !f.forall).map(f => f.holds),
|
|
611
|
-
...Object.entries(inst).flatMap(([name, keys]) => { const f = factsF.find(f2 => f2.name === name); return f?.forall ? keys.map(key => sub1(f.holds, { [f.forall]: key })) : []; }),
|
|
612
|
+
...Object.entries(inst).flatMap(([name, keys]) => { const f = factsF.find(f2 => f2.name === name); return f?.forall && !f.keys?.length ? keys.map(key => sub1(f.holds, { [f.forall]: key })) : []; }),
|
|
613
|
+
// A fact about several keys at each tuple (kept flattened), as Lean reads it: the further keys, then the first.
|
|
614
|
+
...Object.entries(inst).flatMap(([name, keys]) => {
|
|
615
|
+
const f = factsF.find(f2 => f2.name === name);
|
|
616
|
+
if (!f?.keys?.length)
|
|
617
|
+
return [];
|
|
618
|
+
const k = f.keys.length + 1;
|
|
619
|
+
return Array.from({ length: Math.floor(keys.length / k) }, (_, t) => sub1(sub1(f.holds, Object.fromEntries(f.keys.map((kn, i) => [kn, keys[t * k + 1 + i]]))), { [f.forall]: keys[t * k] }));
|
|
620
|
+
}),
|
|
612
621
|
...laws.filter(lw => !lw.forall).map(lw => lw.holds),
|
|
613
622
|
...Object.entries(inst).flatMap(([name, keys]) => { const lw = laws.find(l2 => l2.name === name); return lw?.forall && !lw.keys?.length ? keys.map(key => sub1(lw.holds, { [lw.forall]: key })) : []; }),
|
|
614
623
|
...Object.entries(inst).flatMap(([name, keys]) => {
|
|
@@ -658,7 +667,8 @@ theorem ${id}_orig_laws : ∀ ρ M, SReach ${id}_ghosted.orig ρ M → ∀ e ∈
|
|
|
658
667
|
${list(ws.flatMap(([name, w]) => ('each' in w ? [`(${mx(name)}, ${ix(w.each)}, ${e(w.value)})`] : [])))},
|
|
659
668
|
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(c2.key)}, ${c(c2.c)})`] : [])))},
|
|
660
669
|
${list(fs.flatMap(([f, c2]) => (c2.op === 'all' ? [`(${ix(`?${f}`)}, ${ix(`w?${f}`)})`] : [])))},
|
|
661
|
-
${list((mm.facts ?? []).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))}
|
|
670
|
+
${list((mm.facts ?? []).filter(l => !l.keys?.length).map(l => `⟨${c(l.holds)}, ${l.forall ? `some ${ix(l.forall)}` : 'none'}⟩`))},
|
|
671
|
+
${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))))})`))}⟩`;
|
|
662
672
|
};
|
|
663
673
|
// A loop as Live.lean's record: inputs, start, body, while, precondition, assumption.
|
|
664
674
|
const loopLit = (lp) => `⟨${list(lp.inputs.map(name => String(ix(name))))},
|
|
@@ -700,12 +710,21 @@ ${readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat
|
|
|
700
710
|
const n = laws[lawIx(name)].keys.length + 1;
|
|
701
711
|
return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => `(${lawIx(name)}, ${e(keys[t * n])}, ${list(keys.slice(t * n + 1, t * n + n).map(e))})`);
|
|
702
712
|
});
|
|
703
|
-
|
|
713
|
+
// The facts about one key, and those about several (each instance a tuple, kept flattened: its first key, the rest).
|
|
714
|
+
const facts1 = facts.filter(f => !f.keys?.length), factsN = facts.filter(f => f.keys?.length);
|
|
715
|
+
const finsts = (inst) => Object.entries(inst).flatMap(([name, keys]) => { const j = facts1.findIndex(f => f.name === name); return j < 0 ? [] : keys.map(key => `(${j}, ${e(key)})`); });
|
|
716
|
+
const pfinsts = (inst) => Object.entries(inst).flatMap(([name, keys]) => {
|
|
717
|
+
const j = factsN.findIndex(f => f.name === name);
|
|
718
|
+
if (j < 0)
|
|
719
|
+
return [];
|
|
720
|
+
const n = factsN[j].keys.length + 1;
|
|
721
|
+
return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => `(${j}, ${e(keys[t * n])}, ${list(keys.slice(t * n + 1, t * n + n).map(e))})`);
|
|
722
|
+
});
|
|
704
723
|
const qinsts = (inst) => Object.entries(inst).filter(([name]) => name.startsWith('?')).flatMap(([f, keys]) => keys.map(key => `(${ix(f)}, ${e(key)})`));
|
|
705
724
|
const entries = laws.map((law, j) => {
|
|
706
725
|
const g0 = goalsL[2 * j], g1 = goalsL[2 * j + 1], a = enc(g0, lp.trees[g0.name]), b = enc(g1, lp.trees[g1.name]), inst = lp.instances[law.name] ?? {};
|
|
707
726
|
return `def ${id}_l${j} : MLaw := ⟨${c(law.holds)}, ${law.forall ? `some ${ix(law.forall)}` : 'none'}⟩
|
|
708
|
-
def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list((law.keys ?? []).map(key => String(ix(key))))}, ${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))},
|
|
727
|
+
def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` : '#')}, ${list((law.keys ?? []).map(key => String(ix(key))))}, ${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))},
|
|
709
728
|
(${a.tab}, [], ${a.tree}),
|
|
710
729
|
(${b.tab}, [], ${b.tree})⟩`;
|
|
711
730
|
});
|
|
@@ -714,13 +733,13 @@ def ${id}_e${j} : SEntry := ⟨${id}_l${j}, ${ix(law.forall ? `${law.forall}#` :
|
|
|
714
733
|
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))) } };
|
|
715
734
|
const h = {
|
|
716
735
|
step: sl => { const g = goalsS[allSteps.indexOf(sl)]; return { pr: enc(g, r.proof.trees[g.name]), inst: instOf(sl) }; },
|
|
717
|
-
sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, [], ${pr.tree}))`; },
|
|
736
|
+
sproof: sl => { const { pr, inst } = h.step(sl); return `(${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))}, (${pr.tab}, [], ${pr.tree}))`; },
|
|
718
737
|
allow: a => {
|
|
719
738
|
const f2 = allowed.get(a);
|
|
720
739
|
if (!f2)
|
|
721
740
|
return undefined;
|
|
722
741
|
const ar = f2.out, pr = enc(ar.goal, ar.tree), inst = ar.instances;
|
|
723
|
-
return `(${list(f2.wit.map(([inp, t]) => `(${ix(inp)}, ${e(t)})`))}, (${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, (${pr.tab}, ${list(ar.goal.hyps.map(c))}, ${pr.tree})), ${list(ar.goal.zkeys.map(e))})`;
|
|
742
|
+
return `(${list(f2.wit.map(([inp, t]) => `(${ix(inp)}, ${e(t)})`))}, (${list(insts(inst))}, ${list(qinsts(inst))}, ${list(finsts(inst))}, ${list(pinsts(inst))}, ${list(pfinsts(inst))}, (${pr.tab}, ${list(ar.goal.hyps.map(c))}, ${pr.tree})), ${list(ar.goal.zkeys.map(e))})`;
|
|
724
743
|
},
|
|
725
744
|
};
|
|
726
745
|
const records = claims.map(cl => cl.render(h)).filter(t => t.length);
|
|
@@ -741,24 +760,230 @@ def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
|
|
|
741
760
|
theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by decide +kernel
|
|
742
761
|
theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
|
|
743
762
|
smachine_sound ${id}_machine ${id}_proof ${id}_check
|
|
744
|
-
${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, opts.simp)}${opts.tamper ? `
|
|
763
|
+
${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${opts.noFacts ? '' : factsProof(id, m, mapNames, vars, opts.simp)}${opts.tamper ? `
|
|
745
764
|
-- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
|
|
746
765
|
def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
|
|
747
766
|
example : checkSMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
|
|
748
767
|
}
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
function
|
|
768
|
+
// A function defined by a recurrence (core/dsl.ts recur): its Lean definition by the same recurrence, and its lemmas
|
|
769
|
+
// proven together by induction (each case by omega, its min, max and ifs split first), and its laws between two points
|
|
770
|
+
// by induction on the distance. A function of several arguments is `<name>_rec`, its further arguments first, and the
|
|
771
|
+
// function the machine reads is its key's (`<name>`, through Recur.unpair: the arguments paired as the builders pair
|
|
772
|
+
// them, (a + b)² + b, which Recur.pair_inj proves injective). The kernel proved the lemmas by the same inductions; Lean
|
|
773
|
+
// proves them again of the definition.
|
|
774
|
+
const recurArg = (a) => `«${a}»`;
|
|
775
|
+
// The arguments a key was paired from (the builders' argsKey: left-nested (k + a)² + a), if it was.
|
|
776
|
+
const unpairKey = (key, n) => {
|
|
777
|
+
if (n === 1)
|
|
778
|
+
return [key];
|
|
779
|
+
if (key.op !== 'add' || key.a.op !== 'prod' || key.a.a.op !== 'add')
|
|
780
|
+
return undefined;
|
|
781
|
+
const s = key.a.a, b = key.b;
|
|
782
|
+
if (JSON.stringify(key.a.b) !== JSON.stringify(s) || JSON.stringify(s.b) !== JSON.stringify(b))
|
|
783
|
+
return undefined;
|
|
784
|
+
const rest = unpairKey(s.a, n - 1);
|
|
785
|
+
return rest ? [...rest, b] : undefined;
|
|
786
|
+
};
|
|
787
|
+
// f at x (and the further arguments), as Lean reads it.
|
|
788
|
+
const recurCall = (lean, r, x, args = r.args.map(recurArg)) => (r.args.length ? `(${lean}_rec ${[...args, x].join(' ')})` : `(${lean} ${x})`);
|
|
789
|
+
// A read of a function the recurrence uses: a recurrence by its definition at the unpaired arguments, any other by its
|
|
790
|
+
// Lean term at the key.
|
|
791
|
+
const useRead = (uses, where) => (map, key, e) => {
|
|
792
|
+
const u = uses[map];
|
|
793
|
+
if (!u?.lean)
|
|
794
|
+
throw new Error(`${where}: ${map} has no Lean definition`);
|
|
795
|
+
if (u.recur) {
|
|
796
|
+
const args = unpairKey(key, u.arity);
|
|
797
|
+
if (!args)
|
|
798
|
+
throw new Error(`${where}: ${map} read at a key not paired by its arguments`);
|
|
799
|
+
return recurCall(u.lean, u.recur, e(args[0]), args.slice(1).map(e));
|
|
800
|
+
}
|
|
801
|
+
return `((${u.lean}) ${e(key)})`;
|
|
802
|
+
};
|
|
803
|
+
// The lemmas of the recurrences a term reads, at the keys it reads them, with the names as given.
|
|
804
|
+
const useLemmas = (uses, t, names, where) => {
|
|
805
|
+
const out = [];
|
|
806
|
+
const walk = (x) => {
|
|
807
|
+
if (!x || typeof x !== 'object')
|
|
808
|
+
return;
|
|
809
|
+
const o = x;
|
|
810
|
+
if (o.op === 'get' && o.map) {
|
|
811
|
+
const u = uses[o.map];
|
|
812
|
+
if (u?.recur?.lemmas.length) {
|
|
813
|
+
const args = unpairKey(o.key, u.arity), s = leanSurface(names, where, useRead(uses, where)).e;
|
|
814
|
+
out.push(`have := ${u.lean}_lemmas ${[...args.slice(1).map(s), s(args[0])].join(' ')}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
for (const v of Object.values(o))
|
|
818
|
+
walk(v);
|
|
819
|
+
};
|
|
820
|
+
walk(t);
|
|
821
|
+
return [...new Set(out)];
|
|
822
|
+
};
|
|
823
|
+
// The kernel's own proof of a recurrence's lemmas (or of its laws between two points), for Lean to check: the machine
|
|
824
|
+
// it was proven by, exported with its trees (smachine_sound gives its laws at every reachable state), and the run of
|
|
825
|
+
// that machine that reaches x and f(x) (or y = x + d and f(y) from x and f(x)), so each law, read there, is the lemma.
|
|
826
|
+
// The proof needs no tactic beyond simp's arithmetic of the machine's terms: what the kernel proved (products of two
|
|
827
|
+
// terms among it) Lean checks as the kernel's own tree.
|
|
828
|
+
function recurKernel(lean, r, uses, which) {
|
|
829
|
+
const pr = r.proofs?.[which];
|
|
830
|
+
if (!pr)
|
|
831
|
+
return undefined;
|
|
832
|
+
const m = pr.machine, two = which === 'between', id = `${lean.replace(/[^A-Za-z0-9_]/g, '_')}_${two ? 'two' : 'ind'}`;
|
|
833
|
+
const vars = [...m.params, ...m.inputs, ...Object.keys(m.state)], col = (n) => { const j = vars.indexOf(n); if (j < 0)
|
|
834
|
+
throw new Error(`${id}: no column for ${n}`); return j; };
|
|
835
|
+
const fnNames = Object.keys(m.fns ?? {}), args = r.args.map(recurArg), argNames = Object.fromEntries(r.args.map(a => [a, recurArg(a)]));
|
|
836
|
+
const F = (x) => recurCall(lean, r, x);
|
|
837
|
+
const surface = leanSurface(argNames, lean, useRead(uses, lean));
|
|
838
|
+
const binders = [...r.args.map(a => `(${recurArg(a)} : Nat)`), ...(two ? ['(x : Nat)'] : [])].join(' '), ps = [...args, ...(two ? ['x'] : [])].join(' ');
|
|
839
|
+
const start = [...r.args.map(a => [a, recurArg(a)]), ...(m.params.includes('fx0') ? [['fx0', surface.e(r.zero)]] : []),
|
|
840
|
+
...(two ? [['x', 'x'], ['fx', F('x')]] : [])];
|
|
841
|
+
const chain = (pairs, dflt, v) => pairs.reduceRight((acc, [c, e]) => `if ${v} = ${c} then ${e} else ${acc}`, dflt);
|
|
842
|
+
// What the run holds after n steps, column by column.
|
|
843
|
+
const at = (n) => two
|
|
844
|
+
? [[col('y'), `x + ${n}`], [col('fy'), F(`(x + ${n})`)], [col('x'), 'x'], [col('fx'), F('x')], ...r.args.map(a => [col(a), recurArg(a)])]
|
|
845
|
+
: [[col('x'), n], [col('fx'), F(n)], ...r.args.map(a => [col(a), recurArg(a)])];
|
|
846
|
+
const hs = at('n').map((_, i) => `h${i}`);
|
|
847
|
+
const recurDefs = [r.args.length ? `${lean}_rec` : lean, ...Object.values(uses).flatMap(u => (u.recur ? (u.recur.args.length ? [`${u.lean}_rec`, `${u.lean}_key`] : [u.lean]) : []))];
|
|
848
|
+
const one = (m.facts ?? []).some(f => !f.keys?.length), several = (m.facts ?? []).some(f => f.keys?.length);
|
|
849
|
+
// A fact of the params at the start of the laws between: the lemma at x and f(x), its own conjunct.
|
|
850
|
+
const proj = (j, k) => (k === 1 ? '' : j < k - 1 ? `${'.2'.repeat(j)}.1` : '.2'.repeat(k - 1));
|
|
851
|
+
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; };
|
|
852
|
+
const factsAt = factsProof(id, m, fnNames, vars, [`${id}_ρ`, `${id}_μ`, ...recurDefs], { binders, rho: `(${id}_ρ ${ps})`, mu: `${id}_μ`, cite });
|
|
853
|
+
const unfold = [`${id}_machine`, `${id}_μ`, 'applyS', 'assoc', 'lookup', 'evalE', 'evalC', ...recurDefs].join(', ');
|
|
854
|
+
const pre = `
|
|
855
|
+
${smachineToLean(id, m, pr.laws, { noFacts: true })}
|
|
856
|
+
def ${id}_ρ ${binders} : Nat → Nat := fun i => ${chain(start.map(([n, e]) => [col(n), e]), '0', 'i')}
|
|
857
|
+
noncomputable def ${id}_μ : Nat → Nat → Nat := fun j => ${chain(fnNames.map((n, j) => [j, `(${m.fns[n].lean})`]), 'fun _ => 0', 'j')}${factsAt}
|
|
858
|
+
-- 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)'}.
|
|
859
|
+
theorem ${id}_run ${binders} : ∀ n, ∃ ρ, SReach ${id}_machine ρ ${id}_μ ∧ ${at('n').map(([c, e]) => `ρ ${c} = ${e}`).join(' ∧ ')} := by
|
|
860
|
+
intro n
|
|
861
|
+
induction n with
|
|
862
|
+
| zero =>
|
|
863
|
+
refine ⟨_, SReach.start (${id}_ρ ${ps}) ${id}_μ ?_ ?_ ?_ ?_, ${at('0').map(() => '?_').join(', ')}⟩
|
|
864
|
+
· intro p hp; simp [${id}_machine] at hp
|
|
865
|
+
· simp [${id}_machine, evalC, evalE]
|
|
866
|
+
· ${one ? `exact ${id}_facts_at ${ps}` : `intro l hl; simp [${id}_machine] at hl`}
|
|
867
|
+
· ${several ? `exact ${id}_pfacts_at ${ps}` : `intro p hp; simp [${id}_machine] at hp`}
|
|
868
|
+
all_goals simp [${id}_ρ, ${unfold}]
|
|
869
|
+
| succ n ih =>
|
|
870
|
+
obtain ⟨ρ, hr, ${hs.join(', ')}⟩ := ih
|
|
871
|
+
refine ⟨_, SReach.step ρ ${id}_μ (fun _ => 0) (fun _ _ => 0) hr ?_, ${at('0').map(() => '?_').join(', ')}⟩
|
|
872
|
+
· simp [sView, ${id}_machine, evalC, evalE]
|
|
873
|
+
all_goals (try rw [show x + (n + 1) = x + n + 1 from by omega])
|
|
874
|
+
all_goals simp [sNext, sView, decideFlags, decideQ, flagSub, withInputs, withCarried, ${hs.join(', ')}, ${unfold}]`;
|
|
875
|
+
// The lemmas (or the laws between, after the lemmas at y): each the machine's law read on the run.
|
|
876
|
+
const own = two ? r.between.map((_, j) => r.lemmas.length + j) : r.lemmas.map((_, j) => j);
|
|
877
|
+
const each = (j) => `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`;
|
|
878
|
+
const proof = (from) => `(obtain ⟨ρ, hr, ${hs.join(', ')}⟩ := ${id}_run ${from}; exact ${own.length === 1 ? `(${each(own[0])})` : `⟨${own.map(j => `(${each(j)})`).join(', ')}⟩`})`;
|
|
879
|
+
return { pre, proof };
|
|
880
|
+
}
|
|
881
|
+
export function recurToLean(lean, r, uses = {}) {
|
|
882
|
+
const binders = r.args.map(a => `(${recurArg(a)} : Nat)`).join(' '), argNames = Object.fromEntries(r.args.map(a => [a, recurArg(a)]));
|
|
883
|
+
const self = r.args.length ? `${lean}_rec` : lean;
|
|
884
|
+
const read = useRead(uses, lean);
|
|
885
|
+
const surface = (names) => leanSurface({ ...argNames, ...names }, lean, read);
|
|
886
|
+
const text = JSON.stringify(r), unfold = [self, ...(text.includes('"op":"min"') ? ['Nat.min_def'] : []), ...(text.includes('"op":"max"') ? ['Nat.max_def'] : [])].join(', ');
|
|
887
|
+
const lemmaAt = (x) => r.lemmas.map(l => `(${surface({ x, fx: recurCall(lean, r, x) }).c(l.holds)})`).join(' ∧ ');
|
|
888
|
+
const close = (haves) => `${haves.map(h => `${h}; `).join('')}(repeat' split) <;> omega`;
|
|
889
|
+
const step = (n) => useLemmas(uses, r.next, { ...argNames, x: n, prev: recurCall(lean, r, n) }, lean);
|
|
890
|
+
// Each theorem by the kernel's proof (its machine and the run), or by induction with omega where that fails.
|
|
891
|
+
const kl = recurKernel(lean, r, uses, 'lemmas'), kb = recurKernel(lean, r, uses, 'between');
|
|
892
|
+
const lemmas = r.lemmas.length ? `${kl?.pre ?? ''}
|
|
893
|
+
-- ${r.lemmas.map(l => l.name).join('; ')}
|
|
894
|
+
theorem ${lean}_lemmas ${binders} (x : Nat) : ${lemmaAt('x')} := by
|
|
895
|
+
first
|
|
896
|
+
| ${kl ? kl.proof(`${[...r.args.map(recurArg), 'x'].join(' ')}`) : 'fail'}
|
|
897
|
+
| (induction x with
|
|
898
|
+
| zero => (try simp only [${unfold}]); ${close(useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '0', fx: recurCall(lean, r, '0') }, lean))}
|
|
899
|
+
| succ n ih => (try simp only [${unfold}] at ih ⊢); ${close([...step('n'), ...useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '(n + 1)', fx: recurCall(lean, r, '(n + 1)') }, lean)])})` : '';
|
|
900
|
+
const args = r.args.map(recurArg).join(' ');
|
|
901
|
+
const pairAt = (x, y) => r.between.map(l => `(${surface({ x, fx: recurCall(lean, r, x), y, fy: recurCall(lean, r, y) }).c(l.holds)})`).join(' ∧ ');
|
|
902
|
+
const between = r.between.length ? `${kb?.pre ?? ''}
|
|
903
|
+
-- ${r.between.map(l => l.name).join('; ')}, for every x <= y
|
|
904
|
+
theorem ${lean}_between_by ${binders} (x d : Nat) : ${pairAt('x', '(x + d)')} := by
|
|
905
|
+
first
|
|
906
|
+
| ${kb ? kb.proof(`${[...r.args.map(recurArg), 'x', 'd'].join(' ')}`) : 'fail'}
|
|
907
|
+
| (induction d with
|
|
908
|
+
| zero => simp only [Nat.add_zero]; (repeat' split) <;> omega
|
|
909
|
+
| succ n ih =>
|
|
910
|
+
rw [show x + (n + 1) = (x + n) + 1 from by omega]
|
|
911
|
+
${r.lemmas.length ? `have := ${lean}_lemmas ${args} (x + n); ` : ''}(try simp only [${unfold}] at ih this ⊢); ${close(step('(x + n)'))})
|
|
912
|
+
theorem ${lean}_between ${binders} (x y : Nat) (h : x ≤ y) : ${pairAt('x', 'y')} := by
|
|
913
|
+
obtain ⟨d, rfl⟩ := Nat.exists_eq_add_of_le h
|
|
914
|
+
exact ${lean}_between_by ${args} x d` : '';
|
|
915
|
+
const zero = surface({}).e(r.zero), next = surface({ x: 'x', prev: recurCall(lean, r, 'x') }).e(r.next);
|
|
916
|
+
if (!r.args.length)
|
|
917
|
+
return `def ${lean} : Nat → Nat\n | 0 => ${zero}\n | x + 1 => ${next}${lemmas}${between}`;
|
|
918
|
+
// The key's function: the arguments unpaired, innermost first.
|
|
919
|
+
const u = (i) => (i === r.args.length ? '(Recur.unpair k)' : `(Recur.unpair ${u(i + 1)}.1)`);
|
|
920
|
+
const keyOf = (x) => r.args.map(recurArg).reduce((k, a) => `((${k} + ${a}) * (${k} + ${a}) + ${a})`, x);
|
|
921
|
+
return `def ${self} ${binders} : Nat → Nat
|
|
922
|
+
| 0 => ${zero}
|
|
923
|
+
| x + 1 => ${next}
|
|
924
|
+
noncomputable def ${lean} (k : Nat) : Nat := ${self} ${r.args.map((_, i) => `${u(i + 1)}.2`).join(' ')} ${u(1)}.1
|
|
925
|
+
theorem ${lean}_key ${binders} (x : Nat) : ${lean} ${keyOf('x')} = ${self} ${args} x := by
|
|
926
|
+
simp only [${lean}, Recur.unpair_pair]${lemmas}${between}`;
|
|
927
|
+
}
|
|
928
|
+
// The pairing the builders read a function of several arguments by, and Lean's inverse of it (choice over the pairs:
|
|
929
|
+
// Recur.pair_inj makes it the pair).
|
|
930
|
+
export const recurPairing = `theorem Recur.pair_inj {a b c d : Nat} (h : (a + b) * (a + b) + b = (c + d) * (c + d) + d) : a = c ∧ b = d := by
|
|
931
|
+
have sq : ∀ s t : Nat, s < t → s * s + s < t * t := by
|
|
932
|
+
intro s t hst
|
|
933
|
+
have h1 : (s + 1) * (s + 1) ≤ t * t := Nat.mul_le_mul hst hst
|
|
934
|
+
have h2 : (s + 1) * (s + 1) = s * s + s + s + 1 := by simp only [Nat.add_mul, Nat.mul_add, Nat.mul_one, Nat.one_mul]; omega
|
|
935
|
+
omega
|
|
936
|
+
have hb : b ≤ a + b := Nat.le_add_left b a
|
|
937
|
+
have hd : d ≤ c + d := Nat.le_add_left d c
|
|
938
|
+
rcases Nat.lt_trichotomy (a + b) (c + d) with hlt | heq | hgt
|
|
939
|
+
· have := sq _ _ hlt; omega
|
|
940
|
+
· rw [heq] at h; omega
|
|
941
|
+
· have := sq _ _ hgt; omega
|
|
942
|
+
noncomputable def Recur.unpair (k : Nat) : Nat × Nat :=
|
|
943
|
+
Classical.epsilon fun p : Nat × Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = k
|
|
944
|
+
theorem Recur.unpair_pair (a b : Nat) : Recur.unpair ((a + b) * (a + b) + b) = (a, b) := by
|
|
945
|
+
have h := Classical.epsilon_spec (p := fun p : Nat × Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = (a + b) * (a + b) + b) ⟨(a, b), rfl⟩
|
|
946
|
+
obtain ⟨h1, h2⟩ := Recur.pair_inj h
|
|
947
|
+
exact Prod.ext h1 h2`;
|
|
948
|
+
function factsProof(id, m, maps, vars, simp = [], at) {
|
|
752
949
|
const fns = Object.entries(m.fns ?? {}), facts = m.facts ?? [];
|
|
753
|
-
if (!facts.length || !fns.length || fns.some(([, f]) => !f.lean))
|
|
950
|
+
if (!facts.length || (!at && !fns.length) || fns.some(([, f]) => !f.lean))
|
|
754
951
|
return '';
|
|
952
|
+
const recurs = fns.filter(([, f]) => f.recur);
|
|
953
|
+
simp = [...simp, ...recurs.flatMap(([, f]) => (f.recur.args.length ? [`${f.lean}_rec`, `${f.lean}_key`] : [f.lean]))];
|
|
954
|
+
// Each recurrence's key lemma rewrites the function at its key before anything else simplifies the key.
|
|
955
|
+
const keyed = recurs.filter(([, f]) => f.recur.args.length).map(([, f]) => `${f.lean}_key`);
|
|
956
|
+
const hs = fns.map((_, j) => `h${j}`);
|
|
957
|
+
// A fact's proof, its keys named: the first `v`, the further ones `w1`, `w2`...: the recurrence theorem it is an
|
|
958
|
+
// instance of cited first (so that it and the goal simplify alike), then the facts' terms read and omega.
|
|
959
|
+
const tactic = (fact) => {
|
|
960
|
+
const keys = fact.forall ? [fact.forall, ...(fact.keys ?? [])] : [], arg = keys.map((_, i) => (i === 0 ? 'v' : `w${i}`));
|
|
961
|
+
const owner = recurs.find(([n]) => fact.name.startsWith(`${n}: `)), what = owner ? fact.name.slice(owner[0].length + 2) : '';
|
|
962
|
+
const r = owner?.[1].recur, lean = owner?.[1].lean;
|
|
963
|
+
const cite = at?.cite?.(fact) ?? (!r || !keys.length ? undefined : r.lemmas.some(l => l.name === what) ? `${lean}_lemmas ${[...arg.slice(1), arg[0]].join(' ')}`
|
|
964
|
+
: r.between.some(l => l.name === what) ? `${lean}_between ${[...arg.slice(2), arg[0], arg[1]].join(' ')}` : undefined);
|
|
965
|
+
const intro = keys.length > 1 ? `simp only [holdsOver]; intro ${arg.slice(1).join(' ')}; ` : '';
|
|
966
|
+
return `${intro}simp only [MLaw.Holds]; (try intro v); ${cite ? `have hc := ${cite}; ` : ''}simp only [${['evalC', 'evalE', 'upd', ...hs].join(', ')}]${keyed.length ? `; (try simp only [${keyed.join(', ')}])` : ''}; (try simp [${[...hs, ...simp].join(', ')}]${cite ? ' at hc ⊢' : ''}); all_goals ${cite ? 'first | omega | (simpa using hc)' : 'omega'}`;
|
|
967
|
+
};
|
|
968
|
+
const one = facts.filter(f => !f.keys?.length), several = facts.filter(f => f.keys?.length);
|
|
969
|
+
const binders = at ? at.binders : `(ρ : Nat → Nat) (μ : Nat → Nat → Nat) ${fns.map(([n, f], j) => `(h${j} : μ ${maps.indexOf(n)} = ${f.lean})`).join(' ')}`;
|
|
970
|
+
const rho = at ? at.rho : 'ρ', mu = at ? at.mu : 'μ', name = at ? 'at' : 'hold';
|
|
971
|
+
// At one state and maps, each function's equation is the maps' own definition.
|
|
972
|
+
const given = at ? fns.map(([n, f], j) => `have h${j} : ${mu} ${maps.indexOf(n)} = (${f.lean}) := rfl\n `).join('') : '';
|
|
973
|
+
const cases = (fs, h) => `rcases ${h} with ${fs.map(() => 'rfl').join(' | ')}\n${fs.map(f => ` · ${tactic(f)}`).join('\n')}`;
|
|
755
974
|
return `
|
|
756
|
-
-- The facts hold of the functions' Lean definitions: ${fns.map(([n, f]) => `${n} := ${f.lean}`).join('; ')}.
|
|
757
|
-
theorem ${id}
|
|
758
|
-
∀ l ∈ ${id}_machine.facts, l.Holds
|
|
759
|
-
intro l hl
|
|
975
|
+
-- The facts hold of the functions' Lean definitions: ${fns.map(([n, f]) => `${n} := ${f.lean}`).join('; ')}.${one.length ? `
|
|
976
|
+
theorem ${id}_facts_${name} ${binders} :
|
|
977
|
+
∀ l ∈ ${id}_machine.facts, l.Holds ${rho} ${mu} := by
|
|
978
|
+
${given}intro l hl
|
|
760
979
|
simp only [${id}_machine, List.mem_cons, List.not_mem_nil, or_false] at hl
|
|
761
|
-
|
|
980
|
+
${cases(one, 'hl')}` : ''}${several.length ? `
|
|
981
|
+
-- The facts about several keys, at every value of their further keys.
|
|
982
|
+
theorem ${id}_pfacts_${name} ${binders} :
|
|
983
|
+
${id}_machine.PFacts ${rho} ${mu} := by
|
|
984
|
+
${given}intro p hp
|
|
985
|
+
simp only [${id}_machine, List.mem_cons, List.not_mem_nil, or_false] at hp
|
|
986
|
+
${cases(several, 'hp')}` : ''}`;
|
|
762
987
|
}
|
|
763
988
|
// Machines with a history through Kern.hmachine_sound (History.lean): Lean builds the plain machine (sums as state,
|
|
764
989
|
// relations in the assumption) and each relation's record goal; the trees and the instance keys are the kernel's.
|
|
@@ -777,6 +1002,25 @@ export function toLean(items, opts = {}) {
|
|
|
777
1002
|
const ident = (id) => { const s = id.replace(/[^A-Za-z0-9_]/g, '_'); return /^[A-Za-z]/.test(s) ? s : `x_${s}`; };
|
|
778
1003
|
const parts = [], theorems = [];
|
|
779
1004
|
let goals = 0;
|
|
1005
|
+
// Each recurrence defined once, before the first item that reads it.
|
|
1006
|
+
const recurs = new Map();
|
|
1007
|
+
const recurDefs = (m) => Object.entries(m.fns ?? {}).filter(([, f]) => f.recur).map(([n, f]) => {
|
|
1008
|
+
// The functions it reads are the machine's <n>.<use>, defined before it (flatFns puts them first).
|
|
1009
|
+
const uses = Object.fromEntries(Object.entries(f.recur.uses).map(([u, d]) => { const g = m.fns?.[`${n}.${u}`]; return [u, { ...d, ...(g?.recur ? { recur: g.recur } : {}) }]; }));
|
|
1010
|
+
const text = recurToLean(f.lean, f.recur, uses), had = recurs.get(f.lean);
|
|
1011
|
+
if (had !== undefined) {
|
|
1012
|
+
if (had !== text)
|
|
1013
|
+
throw new Error(`${f.lean}: two recurrences of one name`);
|
|
1014
|
+
return '';
|
|
1015
|
+
}
|
|
1016
|
+
const pairing = f.recur.args.length && !recurs.has('') ? (recurs.set('', recurPairing), `${recurPairing}\n`) : '';
|
|
1017
|
+
recurs.set(f.lean, text);
|
|
1018
|
+
if (f.recur.lemmas.length)
|
|
1019
|
+
theorems.push(`${f.lean}_lemmas`);
|
|
1020
|
+
if (f.recur.between.length)
|
|
1021
|
+
theorems.push(`${f.lean}_between`);
|
|
1022
|
+
return `${pairing}${text}\n`;
|
|
1023
|
+
}).join('');
|
|
780
1024
|
for (let item of items) {
|
|
781
1025
|
const id = ident(item.id);
|
|
782
1026
|
if ('machine' in item) {
|
|
@@ -791,7 +1035,7 @@ export function toLean(items, opts = {}) {
|
|
|
791
1035
|
}
|
|
792
1036
|
// One twin: every machine goes to Lean as a machine with sets (one with none, if it has none): its laws, step laws,
|
|
793
1037
|
// liveness and ghosts in one export; a machine with a history as its plain machine, and its laws over histories.
|
|
794
|
-
parts.push(`-- ${item.id}\n${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
|
|
1038
|
+
parts.push(`-- ${item.id}\n${recurDefs(m)}${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
|
|
795
1039
|
...(item.ghosts?.length ? { ghosts: item.ghosts } : {}), ...(item.lean?.simp ? { simp: item.lean.simp } : {}), ...(item.lean?.after ? { after: item.lean.after } : {}) })}`);
|
|
796
1040
|
theorems.push(`${id}_laws`, ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
|
|
797
1041
|
if (item.lean?.after)
|
|
@@ -3,3 +3,7 @@ export declare function leanTerms(vars: readonly string[], maps: readonly string
|
|
|
3
3
|
e: (t: E) => string;
|
|
4
4
|
c: (t: C) => string;
|
|
5
5
|
};
|
|
6
|
+
export declare function leanSurface(names: Record<string, string>, where: string, get?: (map: string, key: E, e: (t: E) => string) => string): {
|
|
7
|
+
e: (t: E) => string;
|
|
8
|
+
c: (t: C) => string;
|
|
9
|
+
};
|
|
@@ -42,3 +42,41 @@ export function leanTerms(vars, maps, where) {
|
|
|
42
42
|
};
|
|
43
43
|
return { e, c };
|
|
44
44
|
}
|
|
45
|
+
// Terms and conditions as plain Lean over Nat (a recurrence's definition and its lemmas): each variable as the Lean
|
|
46
|
+
// expression `names` gives it.
|
|
47
|
+
export function leanSurface(names, where, get) {
|
|
48
|
+
const name = (n) => { const s = names[n]; if (s === undefined)
|
|
49
|
+
throw new Error(`${where}: no Lean expression for ${n}`); return s; };
|
|
50
|
+
const e = (t) => {
|
|
51
|
+
switch (t.op) {
|
|
52
|
+
case 'var': return name(t.name);
|
|
53
|
+
case 'const': return String(t.value);
|
|
54
|
+
case 'mul': return `(${t.k} * ${e(t.a)})`;
|
|
55
|
+
case 'add': return `(${e(t.a)} + ${e(t.b)})`;
|
|
56
|
+
case 'sub': return `(${e(t.a)} - ${e(t.b)})`;
|
|
57
|
+
case 'prod': return `(${e(t.a)} * ${e(t.b)})`;
|
|
58
|
+
case 'min':
|
|
59
|
+
case 'max': return `(${t.op} ${e(t.a)} ${e(t.b)})`;
|
|
60
|
+
case 'if': return `(if ${c(t.c)} then ${e(t.then)} else ${e(t.else)})`;
|
|
61
|
+
case 'get':
|
|
62
|
+
if (!get)
|
|
63
|
+
throw new Error(`${where}: reads ${t.map}, a function or a map`);
|
|
64
|
+
return get(t.map, t.key, e);
|
|
65
|
+
default: return unreachable(t);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const c = (t) => {
|
|
69
|
+
switch (t.op) {
|
|
70
|
+
case '<=': return `${e(t.a)} ≤ ${e(t.b)}`;
|
|
71
|
+
case '<': return `${e(t.a)} < ${e(t.b)}`;
|
|
72
|
+
case '=': return `${e(t.a)} = ${e(t.b)}`;
|
|
73
|
+
case 'not': return `¬(${c(t.x)})`;
|
|
74
|
+
case 'and':
|
|
75
|
+
case 'or': return t.xs.length === 0 ? (t.op === 'and' ? 'True' : 'False') : `(${t.xs.map(c).join(t.op === 'and' ? ' ∧ ' : ' ∨ ')})`;
|
|
76
|
+
case 'flag':
|
|
77
|
+
case 'all': throw new Error(`${where}: takes no flag or quantifier`);
|
|
78
|
+
default: return unreachable(t);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return { e, c };
|
|
82
|
+
}
|