gdp-wired 0.8.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 +12 -7
- package/dist/core/dsl.d.ts +30 -25
- package/dist/core/dsl.js +78 -32
- package/dist/core/gdp.d.ts +2 -2
- package/dist/core/gdp.js +2 -1
- package/dist/core/machine.d.ts +10 -0
- package/dist/lean-kernel/emit.d.ts +1 -0
- package/dist/lean-kernel/emit.js +130 -84
- package/dist/lean-kernel/library.js +5 -5
- package/package.json +1 -1
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -475,7 +475,7 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
|
|
|
475
475
|
export function smachineToLean(id, given, laws, opts = {}) {
|
|
476
476
|
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
477
477
|
const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
|
|
478
|
-
const
|
|
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,48 +760,11 @@ 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, vars, opts.simp
|
|
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
|
-
function specializeFacts(m, laws, stepLaws) {
|
|
750
|
-
const origins = new Map(), facts = m.facts ?? [];
|
|
751
|
-
if (!facts.some(f => f.keys?.length))
|
|
752
|
-
return { machine: m, origins };
|
|
753
|
-
const r = proveStepLaws(m, laws, stepLaws);
|
|
754
|
-
if (!r.proven || !r.proof)
|
|
755
|
-
throw new Error(`${m.name}: not proven (${r.failed?.stuck ?? ''})`);
|
|
756
|
-
const all = [...Object.values(r.proof.laws.instances), r.proof.instances, ...Object.values(r.proof.perStep ?? {})];
|
|
757
|
-
const overParams = (t) => varsOf({ op: '=', a: t, b: t }).every(x => m.params.includes(x)) && !JSON.stringify(t).includes('"op":"get"');
|
|
758
|
-
const out = [];
|
|
759
|
-
for (const f of facts) {
|
|
760
|
-
if (!f.keys?.length) {
|
|
761
|
-
out.push(f);
|
|
762
|
-
continue;
|
|
763
|
-
}
|
|
764
|
-
const vars = [f.forall, ...f.keys], seen = new Set();
|
|
765
|
-
for (const inst of all) {
|
|
766
|
-
const keys = inst[f.name] ?? [];
|
|
767
|
-
for (let t = 0; t + vars.length <= keys.length; t += vars.length) {
|
|
768
|
-
const tuple = keys.slice(t, t + vars.length), open = tuple.map((e2, i) => [e2, i]).filter(([e2]) => !overParams(e2)).map(([, i]) => i);
|
|
769
|
-
// A tuple with two keys over the state is left out (the prover offers more instances than a proof uses): if the
|
|
770
|
-
// proof needs it, proving the machine again without it fails, and says so.
|
|
771
|
-
if (open.length > 1)
|
|
772
|
-
continue;
|
|
773
|
-
const left = open[0] ?? 0, at = Object.fromEntries(vars.flatMap((x, i) => (i === left ? [] : [[x, tuple[i]]])));
|
|
774
|
-
const id = JSON.stringify([left, at]);
|
|
775
|
-
if (seen.has(id))
|
|
776
|
-
continue;
|
|
777
|
-
seen.add(id);
|
|
778
|
-
const name = `${f.name} #${seen.size}`;
|
|
779
|
-
out.push({ name, forall: vars[left], holds: substC(f.holds, { vars: at, maps: {}, flags: {} }) });
|
|
780
|
-
origins.set(name, { from: f, at });
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return { machine: { ...m, facts: out }, origins };
|
|
785
|
-
}
|
|
786
768
|
// A function defined by a recurrence (core/dsl.ts recur): its Lean definition by the same recurrence, and its lemmas
|
|
787
769
|
// proven together by induction (each case by omega, its min, max and ifs split first), and its laws between two points
|
|
788
770
|
// by induction on the distance. A function of several arguments is `<name>_rec`, its further arguments first, and the
|
|
@@ -838,6 +820,64 @@ const useLemmas = (uses, t, names, where) => {
|
|
|
838
820
|
walk(t);
|
|
839
821
|
return [...new Set(out)];
|
|
840
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
|
+
}
|
|
841
881
|
export function recurToLean(lean, r, uses = {}) {
|
|
842
882
|
const binders = r.args.map(a => `(${recurArg(a)} : Nat)`).join(' '), argNames = Object.fromEntries(r.args.map(a => [a, recurArg(a)]));
|
|
843
883
|
const self = r.args.length ? `${lean}_rec` : lean;
|
|
@@ -847,22 +887,28 @@ export function recurToLean(lean, r, uses = {}) {
|
|
|
847
887
|
const lemmaAt = (x) => r.lemmas.map(l => `(${surface({ x, fx: recurCall(lean, r, x) }).c(l.holds)})`).join(' ∧ ');
|
|
848
888
|
const close = (haves) => `${haves.map(h => `${h}; `).join('')}(repeat' split) <;> omega`;
|
|
849
889
|
const step = (n) => useLemmas(uses, r.next, { ...argNames, x: n, prev: recurCall(lean, r, n) }, lean);
|
|
850
|
-
|
|
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 ?? ''}
|
|
851
893
|
-- ${r.lemmas.map(l => l.name).join('; ')}
|
|
852
894
|
theorem ${lean}_lemmas ${binders} (x : Nat) : ${lemmaAt('x')} := by
|
|
853
|
-
|
|
854
|
-
|
|
|
855
|
-
|
|
|
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)])})` : '';
|
|
856
900
|
const args = r.args.map(recurArg).join(' ');
|
|
857
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(' ∧ ');
|
|
858
|
-
const between = r.between.length ?
|
|
902
|
+
const between = r.between.length ? `${kb?.pre ?? ''}
|
|
859
903
|
-- ${r.between.map(l => l.name).join('; ')}, for every x <= y
|
|
860
904
|
theorem ${lean}_between_by ${binders} (x d : Nat) : ${pairAt('x', '(x + d)')} := by
|
|
861
|
-
|
|
862
|
-
|
|
|
863
|
-
|
|
|
864
|
-
|
|
865
|
-
|
|
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)'))})
|
|
866
912
|
theorem ${lean}_between ${binders} (x y : Nat) (h : x ≤ y) : ${pairAt('x', 'y')} := by
|
|
867
913
|
obtain ⟨d, rfl⟩ := Nat.exists_eq_add_of_le h
|
|
868
914
|
exact ${lean}_between_by ${args} x d` : '';
|
|
@@ -899,45 +945,45 @@ theorem Recur.unpair_pair (a b : Nat) : Recur.unpair ((a + b) * (a + b) + b) = (
|
|
|
899
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⟩
|
|
900
946
|
obtain ⟨h1, h2⟩ := Recur.pair_inj h
|
|
901
947
|
exact Prod.ext h1 h2`;
|
|
902
|
-
|
|
903
|
-
// those functions starts where SReach asks the facts to hold. A recurrence's equations unfold its definition (a function
|
|
904
|
-
// of several arguments read at its key first); its lemmas and laws between two points are its theorems at the keys.
|
|
905
|
-
function factsProof(id, m, maps, vars, simp = [], origins = new Map()) {
|
|
948
|
+
function factsProof(id, m, maps, vars, simp = [], at) {
|
|
906
949
|
const fns = Object.entries(m.fns ?? {}), facts = m.facts ?? [];
|
|
907
|
-
if (!facts.length || !fns.length || fns.some(([, f]) => !f.lean))
|
|
950
|
+
if (!facts.length || (!at && !fns.length) || fns.some(([, f]) => !f.lean))
|
|
908
951
|
return '';
|
|
909
952
|
const recurs = fns.filter(([, f]) => f.recur);
|
|
910
953
|
simp = [...simp, ...recurs.flatMap(([, f]) => (f.recur.args.length ? [`${f.lean}_rec`, `${f.lean}_key`] : [f.lean]))];
|
|
911
|
-
const at = leanSurface(Object.fromEntries(vars.map((x, j) => [x, `(ρ ${j})`])), `${id}'s facts`);
|
|
912
954
|
// Each recurrence's key lemma rewrites the function at its key before anything else simplifies the key.
|
|
913
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.
|
|
914
959
|
const tactic = (fact) => {
|
|
915
|
-
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
// Each key: the one left (v), or its term, read at the columns.
|
|
923
|
-
const arg = keys.map(x => (x === fact.forall ? 'v' : `(${at.e(o?.at[x] ?? { op: 'var', name: x })})`));
|
|
924
|
-
if (r.lemmas.some(l => l.name === what))
|
|
925
|
-
return `${f.lean}_lemmas ${[...arg.slice(1), arg[0]].join(' ')}`;
|
|
926
|
-
if (r.between.some(l => l.name === what))
|
|
927
|
-
return `${f.lean}_between ${[...arg.slice(2), arg[0], arg[1]].join(' ')}`;
|
|
928
|
-
return undefined;
|
|
929
|
-
};
|
|
930
|
-
const c = cite(), hs = [...fns.map((_, j) => `h${j}`)];
|
|
931
|
-
return `simp only [MLaw.Holds]; (try intro v); ${c ? `have hc := ${c}; ` : ''}simp only [evalC, evalE, upd, ${hs.join(', ')}]${keyed.length ? `; (try simp only [${keyed.join(', ')}])` : ''}; (try simp [${[...hs, ...simp].join(', ')}]${c ? ' at hc ⊢' : ''}); all_goals omega`;
|
|
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'}`;
|
|
932
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')}`;
|
|
933
974
|
return `
|
|
934
|
-
-- The facts hold of the functions' Lean definitions: ${fns.map(([n, f]) => `${n} := ${f.lean}`).join('; ')}.
|
|
935
|
-
theorem ${id}
|
|
936
|
-
∀ l ∈ ${id}_machine.facts, l.Holds
|
|
937
|
-
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
|
|
938
979
|
simp only [${id}_machine, List.mem_cons, List.not_mem_nil, or_false] at hl
|
|
939
|
-
|
|
940
|
-
|
|
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')}` : ''}`;
|
|
941
987
|
}
|
|
942
988
|
// Machines with a history through Kern.hmachine_sound (History.lean): Lean builds the plain machine (sums as state,
|
|
943
989
|
// relations in the assumption) and each relation's record goal; the trees and the instance keys are the kernel's.
|