gdp-wired 0.10.1 → 0.12.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 +22 -8
- package/dist/core/compose.js +1 -1
- package/dist/core/dsl.d.ts +107 -9
- package/dist/core/dsl.js +328 -40
- package/dist/core/gdp.js +2 -2
- package/dist/core/machine.d.ts +4 -0
- package/dist/core/machine.js +93 -15
- package/dist/core/solver.d.ts +12 -0
- package/dist/core/solver.js +68 -17
- package/dist/kernel/kernel.d.ts +19 -3
- package/dist/kernel/kernel.js +106 -27
- package/dist/kernel/prove.js +73 -38
- package/dist/lean-kernel/emit.js +44 -19
- package/dist/lean-kernel/library.js +6 -6
- package/package.json +2 -2
package/dist/core/dsl.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//
|
|
16
16
|
// A step returns its updates as one object: every right-hand side reads the state before the step, as the kernel and
|
|
17
17
|
// both runtimes do.
|
|
18
|
-
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf } from "./machine.js";
|
|
18
|
+
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
|
|
19
19
|
import { allowed, keysOf, requestEnv, watch } from "./watch.js";
|
|
20
20
|
import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
|
|
21
21
|
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
|
|
@@ -774,8 +774,15 @@ export const decimal = (places) => {
|
|
|
774
774
|
};
|
|
775
775
|
let boundCounter = 0;
|
|
776
776
|
// A condition at every key: only as a whole flag (`flags: ({...}) => ({covered: everyKey(k => ...)})`), the key read
|
|
777
|
-
// only as a map's key. The kernel closes it (instances where it holds, a witness where it fails).
|
|
778
|
-
|
|
777
|
+
// only as a map's key. The kernel closes it (instances where it holds, a witness where it fails). With two parameters
|
|
778
|
+
// or more, at every two keys or more (`everyKey((i, j) => i.lt(j).implies(At.at(i).atMost(At.at(j))))`): each key read
|
|
779
|
+
// on its own, and compared with another only plainly (i < j, i <= j, i = j); the runtimes decide it on every tuple of
|
|
780
|
+
// a few keys per breakpoint, so it costs the square of the cells (the cube, over three keys).
|
|
781
|
+
export const everyKey = (f) => {
|
|
782
|
+
const names = Array.from({ length: Math.max(1, f.length) }, () => `k$${++boundCounter}`), g = f;
|
|
783
|
+
const ok = (picked) => (picked.length === names.length ? g(...picked).ok : everyOk(scope?.keys, k => ok([...picked, keyTerm(k)])));
|
|
784
|
+
return new Cond({ op: 'all', key: names[0], ...(names.length > 1 ? { keys: names.slice(1) } : {}), c: g(...names.map(v)).c }, ok([]));
|
|
785
|
+
};
|
|
779
786
|
// Some key where f holds, in a law or a step law's conclusion (¬∀k. ¬f(k)): proven through a witness, a term naming the
|
|
780
787
|
// key, given here or found by the prover among a few candidates (kernel/prove.ts).
|
|
781
788
|
export const someKey = (f, witness) => {
|
|
@@ -867,6 +874,7 @@ export class CellsRef {
|
|
|
867
874
|
return { parts: Object.fromEntries(this.kind.vars(this.name).map(part => [part, this.map(part).each(k => partsOf(this.kind, this.name, f(k))[part])])) };
|
|
868
875
|
}
|
|
869
876
|
}
|
|
877
|
+
const fromOf = (d, p) => (typeof d.from === 'number' ? d.from : d.from(p));
|
|
870
878
|
export class CarriedList {
|
|
871
879
|
name;
|
|
872
880
|
kind;
|
|
@@ -874,14 +882,20 @@ export class CarriedList {
|
|
|
874
882
|
folds;
|
|
875
883
|
map;
|
|
876
884
|
lemmas;
|
|
885
|
+
params;
|
|
886
|
+
paramKinds;
|
|
877
887
|
fold;
|
|
878
|
-
|
|
888
|
+
// `params`: the params' values the folds read (by their kinds: terms for the kernel, constants to encode a request),
|
|
889
|
+
// undefined where none are given; `paramKinds`, the params themselves, for the lemmas' induction.
|
|
890
|
+
constructor(name, kind, len, folds, map, lemmas = {}, params, paramKinds = []) {
|
|
879
891
|
this.name = name;
|
|
880
892
|
this.kind = kind;
|
|
881
893
|
this.len = len;
|
|
882
894
|
this.folds = folds;
|
|
883
895
|
this.map = map;
|
|
884
896
|
this.lemmas = lemmas;
|
|
897
|
+
this.params = params;
|
|
898
|
+
this.paramKinds = paramKinds;
|
|
885
899
|
this.fold = Object.fromEntries(Object.keys(folds).map(f => [f, map(`${name}$${f}`).at(len)]));
|
|
886
900
|
}
|
|
887
901
|
at(i) { return this.kind.build(this.name, part => this.map(part).at(i)); }
|
|
@@ -897,12 +911,21 @@ export class CarriedList {
|
|
|
897
911
|
equations() {
|
|
898
912
|
const folds = Object.entries(this.folds), lemmas = Object.values(this.lemmas);
|
|
899
913
|
const at = (k) => {
|
|
900
|
-
const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x)));
|
|
914
|
+
const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x, this.p())));
|
|
901
915
|
const below = [...this.kind.invariant(x), ...steps];
|
|
902
|
-
return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k))))] : []));
|
|
916
|
+
return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k, this.p()))))] : []));
|
|
903
917
|
};
|
|
904
918
|
const any = this.kind.invariant(this.kind.value(this.name)).length + folds.length + lemmas.length > 0;
|
|
905
|
-
return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(d.
|
|
919
|
+
return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(fromOf(d, this.p()))), ...(any ? [everyKey(at)] : [])];
|
|
920
|
+
}
|
|
921
|
+
// Whether a fold reads the params (its start a function, or its step taking them).
|
|
922
|
+
get readsParams() { return Object.values(this.folds).some(d => typeof d.from === 'function' || d.step.length > 2); }
|
|
923
|
+
p() {
|
|
924
|
+
if (this.params)
|
|
925
|
+
return this.params;
|
|
926
|
+
if (this.readsParams)
|
|
927
|
+
throw new RangeError(`${this.name}'s folds read the params: give them (encodeInput(values, params))`);
|
|
928
|
+
return {};
|
|
906
929
|
}
|
|
907
930
|
running(k) { return Object.fromEntries(Object.keys(this.folds).map(f => [f, this.after(f, k)])); }
|
|
908
931
|
// The lemmas, proven: the folds as a machine counting i up from 0 over items that are functions of i known only by
|
|
@@ -916,10 +939,11 @@ export class CarriedList {
|
|
|
916
939
|
const kindFact = this.kind.invariant(this.kind.value(this.name)).length ? { 'every item is a value of its kind': forall(k => all(...this.kind.invariant(item(k)))) } : {};
|
|
917
940
|
const fns = Object.fromEntries(parts.map((p, j) => [fname(p), fn(1, 'x => 0', j === 0 ? { facts: () => kindFact } : {})]));
|
|
918
941
|
const folds = Object.entries(this.folds);
|
|
919
|
-
const r = rule(`${this.name}'s folds, by induction`).
|
|
920
|
-
.
|
|
942
|
+
const r = rule(`${this.name}'s folds, by induction`).params(Object.fromEntries(this.paramKinds)).fns(fns).inputs({})
|
|
943
|
+
.state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
|
|
944
|
+
.step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i), env)])) }));
|
|
921
945
|
const accs = (env) => Object.fromEntries(folds.map(([f]) => [f, env[`acc$${f}`]]));
|
|
922
|
-
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i))));
|
|
946
|
+
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))));
|
|
923
947
|
}
|
|
924
948
|
// Every carried map it is kept in: the items' parts, then each fold's.
|
|
925
949
|
get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
|
|
@@ -933,13 +957,13 @@ export class CarriedList {
|
|
|
933
957
|
if (x !== 0)
|
|
934
958
|
maps[part].set(i, x); });
|
|
935
959
|
for (const [f, d] of Object.entries(this.folds)) {
|
|
936
|
-
const acc = maps[`${this.name}$${f}`];
|
|
937
|
-
let value = d
|
|
960
|
+
const acc = maps[`${this.name}$${f}`], p = this.p();
|
|
961
|
+
let value = valOf(fromOf(d, p));
|
|
938
962
|
if (value !== 0)
|
|
939
963
|
acc.set(0, value);
|
|
940
964
|
items.forEach((enc, i) => {
|
|
941
965
|
const item = this.kind.build(this.name, part => new Term({ op: 'const', value: enc[part] }, enc[part]));
|
|
942
|
-
const next = valOf(d.step(new Term({ op: 'const', value }, value), item));
|
|
966
|
+
const next = valOf(d.step(new Term({ op: 'const', value }, value), item, p));
|
|
943
967
|
if (next === undefined || !Number.isSafeInteger(next) || next < 0)
|
|
944
968
|
throw new RangeError(`${this.name}'s fold ${f} reads more than its items`);
|
|
945
969
|
value = next;
|
|
@@ -981,6 +1005,66 @@ export class ItemsRef {
|
|
|
981
1005
|
within() { return forall(k => this.len.atMost(k).implies(all(...this.names.map(part => this.list(part).at(k).eq(0))))); }
|
|
982
1006
|
increasing(by) { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(by(this.at(i)).lt(by(this.at(j))))); }
|
|
983
1007
|
}
|
|
1008
|
+
export class NodeTable {
|
|
1009
|
+
name;
|
|
1010
|
+
kind;
|
|
1011
|
+
children;
|
|
1012
|
+
folds;
|
|
1013
|
+
size;
|
|
1014
|
+
map;
|
|
1015
|
+
tables;
|
|
1016
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1017
|
+
constructor(name, kind, children, folds, size, map, tables = {}) {
|
|
1018
|
+
this.name = name;
|
|
1019
|
+
this.kind = kind;
|
|
1020
|
+
this.children = children;
|
|
1021
|
+
this.folds = folds;
|
|
1022
|
+
this.size = size;
|
|
1023
|
+
this.map = map;
|
|
1024
|
+
this.tables = tables;
|
|
1025
|
+
}
|
|
1026
|
+
// The node at n (0, or past the end: the empty tree).
|
|
1027
|
+
node(n) {
|
|
1028
|
+
const index = termAt(n);
|
|
1029
|
+
return { index, empty: index.eq(0), value: this.kind.build(this.name, part => this.map(part).at(index)),
|
|
1030
|
+
fold: Object.fromEntries(Object.keys(this.folds).map(f => [f, this.map(`${this.name}$${f}`).at(index)])),
|
|
1031
|
+
kids: Object.fromEntries(this.children.map(c => [c, this.map(`${this.name}^${c}`).at(index)])) };
|
|
1032
|
+
}
|
|
1033
|
+
at(n) { return this.node(n).value; }
|
|
1034
|
+
// Whether nodes can be made from these children (each 0 or a node already there).
|
|
1035
|
+
fits(kids) { return all(...this.children.map(c => termAt(kids[c]).atMost(this.size))); }
|
|
1036
|
+
// Node size + 1, from a value and children already there, when `when` holds (and they fit): its value, children and
|
|
1037
|
+
// folds written, the size grown.
|
|
1038
|
+
make(value, kids, when = yes) {
|
|
1039
|
+
const n = this.size.plus(1), go = when.and(this.fits(kids)), parts = partsOf(this.kind, this.name, value);
|
|
1040
|
+
const val = this.kind.build(this.name, part => termAt(parts[part]));
|
|
1041
|
+
const kidRefs = Object.fromEntries(this.children.map(c => [c, this.node(termAt(kids[c]))]));
|
|
1042
|
+
const writes = {};
|
|
1043
|
+
const put = (part, x) => { writes[part] = this.map(part).set(n, when_(go, x, this.map(part).at(n))); };
|
|
1044
|
+
for (const part of this.kind.vars(this.name))
|
|
1045
|
+
put(part, parts[part]);
|
|
1046
|
+
for (const c of this.children)
|
|
1047
|
+
put(`${this.name}^${c}`, kids[c]);
|
|
1048
|
+
for (const [f, d] of Object.entries(this.folds))
|
|
1049
|
+
put(`${this.name}$${f}`, d.node(val, kidRefs, this.tables));
|
|
1050
|
+
return { [this.name]: { parts: writes }, [`${this.name}_size`]: when_(go, n, this.size) };
|
|
1051
|
+
}
|
|
1052
|
+
// The table's own laws: every node's children are earlier nodes and its folds are its children's, and node 0 and every
|
|
1053
|
+
// key past the end hold nothing (each fold's empty).
|
|
1054
|
+
laws() {
|
|
1055
|
+
const inside = (k) => k.atLeast(1).and(k.atMost(this.size));
|
|
1056
|
+
const folds = Object.entries(this.folds);
|
|
1057
|
+
return [
|
|
1058
|
+
...(this.children.length ? [[`every ${this.name} node's children are earlier nodes`, forall(k => inside(k).implies(all(...this.children.map(c => this.node(k).kids[c].lt(k)))))]] : []),
|
|
1059
|
+
...(folds.length ? [[`every ${this.name} node's folds are its children's`, forall(k => inside(k).implies(all(...folds.map(([f, d]) => {
|
|
1060
|
+
const x = this.node(k);
|
|
1061
|
+
return x.fold[f].eq(d.node(x.value, Object.fromEntries(this.children.map(c => [c, this.node(x.kids[c])])), this.tables));
|
|
1062
|
+
}))))]] : []),
|
|
1063
|
+
[`nothing in ${this.name} past its end`, forall(k => k.eq(0).or(this.size.lt(k)).implies(all(...this.kind.vars(this.name).map(part => this.map(part).at(k).eq(0)), ...this.children.map(c => this.node(k).kids[c].eq(0)), ...folds.map(([f, d]) => this.node(k).fold[f].eq(d.empty)))))],
|
|
1064
|
+
];
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
const termAt = (t) => (t instanceof Term ? t : lit(t));
|
|
984
1068
|
// A law about every key of a map: proven at a fresh key, assumed at the keys a step reads.
|
|
985
1069
|
// With two parameters or more, about every two keys or more: forall((i, j) => i.lt(j).implies(...)).
|
|
986
1070
|
export function forall(f) { return { forall: f }; }
|
|
@@ -1002,7 +1086,7 @@ function swapC(c, from, to) {
|
|
|
1002
1086
|
case 'not': return { op: 'not', x: swapC(c.x, from, to) };
|
|
1003
1087
|
case 'and':
|
|
1004
1088
|
case 'or': return { op: c.op, xs: c.xs.map(y => swapC(y, from, to)) };
|
|
1005
|
-
case 'all': return {
|
|
1089
|
+
case 'all': return { ...c, c: swapC(c.c, from, to) };
|
|
1006
1090
|
case 'flag': return c;
|
|
1007
1091
|
case '<':
|
|
1008
1092
|
case '<=':
|
|
@@ -1093,15 +1177,15 @@ class Hoister {
|
|
|
1093
1177
|
quantifier(q, bound, name) {
|
|
1094
1178
|
const outer = bound.filter(key => namesIn(q).includes(key));
|
|
1095
1179
|
if (outer.length)
|
|
1096
|
-
throw new Error(`a condition at every key reads ${outer[0]}, a key bound outside it: two keys at once are
|
|
1097
|
-
const inner = perStep(q.c);
|
|
1180
|
+
throw new Error(`a condition at every key reads ${outer[0]}, a key bound outside it: two keys at once are named together (everyKey((i, j) => ...))`);
|
|
1181
|
+
const inner = perStep(q.c), keys = q.keys?.length ? { keys: q.keys } : {};
|
|
1098
1182
|
if (!inner) {
|
|
1099
1183
|
const f = name ?? `${this.prefix}${++this.n}`;
|
|
1100
|
-
this.flags[f] = { op: 'all', key: q.key, c: q.c };
|
|
1184
|
+
this.flags[f] = { op: 'all', key: q.key, ...keys, c: q.c };
|
|
1101
1185
|
return { op: 'flag', name: f };
|
|
1102
1186
|
}
|
|
1103
|
-
const truth = inner.op === 'flag' ? inner : this.quantifier(inner, [...bound, q.key]);
|
|
1104
|
-
const split = (by) => this.quantifier({ op: 'all', key: q.key, c: swapC(q.c, inner, by) }, bound);
|
|
1187
|
+
const truth = inner.op === 'flag' ? inner : this.quantifier(inner, [...bound, q.key, ...(q.keys ?? [])]);
|
|
1188
|
+
const split = (by) => this.quantifier({ op: 'all', key: q.key, ...keys, c: swapC(q.c, inner, by) }, bound);
|
|
1105
1189
|
return { op: 'or', xs: [{ op: 'and', xs: [truth, split(yes.c)] }, { op: 'and', xs: [{ op: 'not', x: truth }, split(no.c)] }] };
|
|
1106
1190
|
}
|
|
1107
1191
|
}
|
|
@@ -1134,6 +1218,8 @@ function prenex(c, what, key) {
|
|
|
1134
1218
|
const walk = (d, positive) => {
|
|
1135
1219
|
switch (d.op) {
|
|
1136
1220
|
case 'all':
|
|
1221
|
+
if (d.keys?.length)
|
|
1222
|
+
throw new Error(`${what}: a condition at every two keys in a law is the law's own keys (forall((i, j) => ...))`);
|
|
1137
1223
|
if (!positive)
|
|
1138
1224
|
return d;
|
|
1139
1225
|
found = true;
|
|
@@ -1194,6 +1280,8 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1194
1280
|
if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
|
|
1195
1281
|
throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
|
|
1196
1282
|
distinctNames(`recur ${name}`, [...args, ...uses.map(([u]) => u)]);
|
|
1283
|
+
// A rule reads the function at its arguments paired, which the kernel multiplies out: with five further arguments that
|
|
1284
|
+
// is a polynomial of degree 32, past what it can hold.
|
|
1197
1285
|
if (args.length > 4)
|
|
1198
1286
|
throw new Error(`recur ${name}: at most four further arguments`);
|
|
1199
1287
|
const named = (n) => new Term({ op: 'var', name: n });
|
|
@@ -1244,8 +1332,9 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1244
1332
|
const keysOf = (n) => (f) => {
|
|
1245
1333
|
if (n === 0)
|
|
1246
1334
|
return f();
|
|
1247
|
-
|
|
1248
|
-
|
|
1335
|
+
// A law's keys are counted by its callback's parameters: this one takes n.
|
|
1336
|
+
const by = (...ks) => f(...ks);
|
|
1337
|
+
Object.defineProperty(by, 'length', { value: n });
|
|
1249
1338
|
return forall(by);
|
|
1250
1339
|
};
|
|
1251
1340
|
const facts = (withBetween) => (f, at = {}) => {
|
|
@@ -1279,7 +1368,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1279
1368
|
};
|
|
1280
1369
|
const distinctNames = (what, names) => { if (new Set(names).size !== names.length)
|
|
1281
1370
|
throw new Error(`${what}: a name given twice`); };
|
|
1282
|
-
// A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each
|
|
1371
|
+
// A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each called on its arguments,
|
|
1283
1372
|
// as the runtimes call it).
|
|
1284
1373
|
const recurJs = (name, args, zero, next, uses) => {
|
|
1285
1374
|
const fns = new Map(uses.map(([u], i) => [u, `$f${i}`]));
|
|
@@ -1294,7 +1383,10 @@ const recurJs = (name, args, zero, next, uses) => {
|
|
|
1294
1383
|
case 'min': return `Math.min(${js(e.a)}, ${js(e.b)})`;
|
|
1295
1384
|
case 'max': return `Math.max(${js(e.a)}, ${js(e.b)})`;
|
|
1296
1385
|
case 'if': return `(${jc(e.c)} ? ${js(e.then)} : ${js(e.else)})`;
|
|
1297
|
-
case 'get':
|
|
1386
|
+
case 'get': {
|
|
1387
|
+
const i = uses.findIndex(([u]) => u === e.map), args = uses[i][1].arity > 1 ? pairedArgs(e.key, uses[i][1].arity) : undefined;
|
|
1388
|
+
return args ? `$g${i}(${args.map(js).join(', ')})` : `${fns.get(e.map)}(${js(e.key)})`;
|
|
1389
|
+
}
|
|
1298
1390
|
}
|
|
1299
1391
|
};
|
|
1300
1392
|
const jc = (c) => {
|
|
@@ -1309,7 +1401,7 @@ const recurJs = (name, args, zero, next, uses) => {
|
|
|
1309
1401
|
case 'all': throw new Error('a recurrence takes no flag or quantifier');
|
|
1310
1402
|
}
|
|
1311
1403
|
};
|
|
1312
|
-
const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); `).join('');
|
|
1404
|
+
const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); ${d.arity > 1 ? `const $g${i} = (${jsOf(d)}); ` : ''}`).join('');
|
|
1313
1405
|
// The last value computed at each tuple of further arguments is kept, so reading f at x after f at a smaller x takes
|
|
1314
1406
|
// only the steps between (a rule that reads f at a count that grows costs one step a request).
|
|
1315
1407
|
return `(() => { ${inlined}const $memo = new Map(); return (x, ${args.map(a => `${a}, `).join('')}) => { const $k = ${args.length ? `[${args.join(', ')}].join()` : "''"}; let $at = $memo.get($k); if (!$at || $at[0] > x) $at = [0, ${js(zero)}]; let x0 = $at[0], prev = $at[1]; for (; x0 < x; x0++) { prev = ((x, prev) => ${js(next)})(x0, prev); if (!Number.isSafeInteger(prev)) throw new RangeError(${JSON.stringify(`${name} passes the safe integers`)}); } if ($memo.size > 4096) $memo.clear(); $memo.set($k, [x, prev]); return prev; }; })()`;
|
|
@@ -1322,8 +1414,9 @@ const flatFns = (defs) => defs.flatMap(([n, d]) => {
|
|
|
1322
1414
|
const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
|
|
1323
1415
|
const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
|
|
1324
1416
|
// A function's JavaScript as one function of its key: its arguments unpaired (left-nested, as argsKey pairs them).
|
|
1417
|
+
const jsOf = (d) => (typeof d.js === 'string' ? d.js : String(d.js));
|
|
1325
1418
|
const fnSource = (name, d) => {
|
|
1326
|
-
const src =
|
|
1419
|
+
const src = jsOf(d);
|
|
1327
1420
|
let f;
|
|
1328
1421
|
try {
|
|
1329
1422
|
f = compileFunction([], `return (${src})`)();
|
|
@@ -1372,7 +1465,7 @@ const markedE = (e) => {
|
|
|
1372
1465
|
case 'prod': return { ...e, a: markedE(e.a), b: markedE(e.b) };
|
|
1373
1466
|
}
|
|
1374
1467
|
};
|
|
1375
|
-
const marked = (c) => c.op === 'flag' ? c : c.op === 'all' ? {
|
|
1468
|
+
const marked = (c) => c.op === 'flag' ? c : c.op === 'all' ? { ...c, c: marked(c.c) } : c.op === 'not' ? { op: 'not', x: marked(c.x) } : c.op === 'and' || c.op === 'or' ? { op: c.op, xs: c.xs.map(marked) } : { ...c, a: markedE(c.a), b: markedE(c.b) };
|
|
1376
1469
|
// P params, I inputs, S state, M maps, F flags, R a history's record, Su its sums, Q its queries: each a record of the
|
|
1377
1470
|
// values the builders hand out, grown by each call.
|
|
1378
1471
|
export class Rule {
|
|
@@ -1446,6 +1539,19 @@ export class Rule {
|
|
|
1446
1539
|
return this.with({ maps: [...this.spec.maps, ...entries.flatMap(([n, k]) => k.vars(n).map(part => [part, 0]))],
|
|
1447
1540
|
state: [...this.spec.state, ...entries.map(([n]) => [`${n}_len`, 0])], items: [...(this.spec.items ?? []), ...entries] });
|
|
1448
1541
|
}
|
|
1542
|
+
// A table of nodes, empty at the start (NodeTable): a tree in the state, made one node a step.
|
|
1543
|
+
nodes(name, of, children, folds = {}) {
|
|
1544
|
+
for (const c of children)
|
|
1545
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c) || c === 'value')
|
|
1546
|
+
throw new Error(`${name}: ${c} can't name a child`);
|
|
1547
|
+
for (const f of Object.keys(folds))
|
|
1548
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
|
|
1549
|
+
throw new Error(`${name}: ${f} is not a plain fold name`);
|
|
1550
|
+
distinctNames(name, [...children]);
|
|
1551
|
+
const maps = [...of.vars(name).map(part => [part, 0]), ...children.map(c => [`${name}^${c}`, 0]),
|
|
1552
|
+
...Object.entries(folds).map(([f, d]) => [`${name}$${f}`, d.empty])];
|
|
1553
|
+
return this.with({ maps: [...this.spec.maps, ...maps], state: [...this.spec.state, [`${name}_size`, 0]], nodes: [...(this.spec.nodes ?? []), [name, of, [...children], folds]] });
|
|
1554
|
+
}
|
|
1449
1555
|
// Functions defined outside the rule (fn, divBy, modBy), read like params that take arguments: f(x).
|
|
1450
1556
|
fns(defs) {
|
|
1451
1557
|
return this.with({ fns: [...(this.spec.fns ?? []), ...Object.entries(defs)] });
|
|
@@ -1456,6 +1562,7 @@ export class Rule {
|
|
|
1456
1562
|
carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
|
|
1457
1563
|
// A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
|
|
1458
1564
|
// .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
|
|
1565
|
+
// Each fold's start and step, and each lemma, may read the params declared before it.
|
|
1459
1566
|
carriesList(name, of, folds = {}, lemmas = {}) {
|
|
1460
1567
|
const entries = [[name, of, folds, lemmas]];
|
|
1461
1568
|
for (const [n, , fs] of entries)
|
|
@@ -1464,7 +1571,7 @@ export class Rule {
|
|
|
1464
1571
|
throw new Error(`${n}: ${f} is not a plain fold name`);
|
|
1465
1572
|
// The folds' lemmas, proven now (a lemma that isn't throws, with the verdict).
|
|
1466
1573
|
for (const [n, k, fs, ls] of entries) {
|
|
1467
|
-
const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls).prove();
|
|
1574
|
+
const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, undefined, this.spec.params).prove();
|
|
1468
1575
|
if (!(v2.proven && v2.checked))
|
|
1469
1576
|
throw new Unproven(`${n}'s fold lemmas`, v2);
|
|
1470
1577
|
}
|
|
@@ -1530,6 +1637,12 @@ export class Rule {
|
|
|
1530
1637
|
if (k.invariant(k.value(n)).length) {
|
|
1531
1638
|
out.push(this.law(`every ${n} cell is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
|
|
1532
1639
|
}
|
|
1640
|
+
for (const [n, k] of this.spec.nodes ?? []) {
|
|
1641
|
+
if (k.invariant(k.value(n)).length)
|
|
1642
|
+
out.push(this.law(`every ${n} node is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
|
|
1643
|
+
for (const [name] of this.env({ flags: false, inputs: false, queries: false })[n].laws())
|
|
1644
|
+
out.push(this.law(name, env => of(env, n).laws().find(([m]) => m === name)[1]));
|
|
1645
|
+
}
|
|
1533
1646
|
return out;
|
|
1534
1647
|
}
|
|
1535
1648
|
// A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
|
|
@@ -1547,7 +1660,7 @@ export class Rule {
|
|
|
1547
1660
|
const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
|
|
1548
1661
|
return withSource(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }, src);
|
|
1549
1662
|
}
|
|
1550
|
-
// A law about k steps in a row (k from 2 to
|
|
1663
|
+
// A law about k steps in a row (k from 2 to 64): `then(first, last, requests)` relates what the first step reads to the
|
|
1551
1664
|
// state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
|
|
1552
1665
|
// j)` narrows the j-th step's request (j from 0), read at the state that step starts from.
|
|
1553
1666
|
// meter.stepsLaw('two requests spend at most 2m', 2, ({spent, m}, after) => after.spent.atMost(spent.plus(m).plus(m)))
|
|
@@ -1649,8 +1762,9 @@ export class Rule {
|
|
|
1649
1762
|
out[n] = k.value(n);
|
|
1650
1763
|
for (const n of this.spec.carried ?? [])
|
|
1651
1764
|
out[n] = new MapRef(n, 0);
|
|
1765
|
+
const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
|
|
1652
1766
|
for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
|
|
1653
|
-
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls);
|
|
1767
|
+
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
|
|
1654
1768
|
for (const part of l.maps)
|
|
1655
1769
|
delete out[part];
|
|
1656
1770
|
out[n] = l;
|
|
@@ -1697,6 +1811,15 @@ export class Rule {
|
|
|
1697
1811
|
delete out[part];
|
|
1698
1812
|
out[n] = new ItemsRef(n, k, len(n), part => new ListRef(at(part), len(n)));
|
|
1699
1813
|
}
|
|
1814
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1815
|
+
const tables = {};
|
|
1816
|
+
for (const [n, k, cs, fs] of this.spec.nodes ?? []) {
|
|
1817
|
+
const t = new NodeTable(n, k, cs, fs, v(at(`${n}_size`)), part => new MapRef(at(part), init.get(part)), { ...tables });
|
|
1818
|
+
tables[n] = t;
|
|
1819
|
+
for (const part of [...k.vars(n), ...cs.map(c => `${n}^${c}`), ...Object.keys(fs).map(f => `${n}$${f}`)])
|
|
1820
|
+
delete out[part];
|
|
1821
|
+
out[n] = t;
|
|
1822
|
+
}
|
|
1700
1823
|
}
|
|
1701
1824
|
// Every name the rule declares is declared once (a flag named again replaces the earlier one, so flags are checked as
|
|
1702
1825
|
// they are defined): an input and a state variable sharing a name would hand the callbacks only one of them.
|
|
@@ -1705,6 +1828,7 @@ export class Rule {
|
|
|
1705
1828
|
return [...s.params.map(([n]) => [n, 'a param']), ...(s.fns ?? []).map(([n]) => [n, 'a function']), ...s.inputs.map(([n]) => [n, 'an input']), ...(s.carried ?? []).map(n => [n, 'a carried map']),
|
|
1706
1829
|
...s.state.map(([n]) => [n, 'a state variable']), ...s.maps.map(([n]) => [n, 'a map']), ...s.sums.map(([n]) => [n, 'a sum']), ...s.queries.map(([n]) => [n, 'a query']),
|
|
1707
1830
|
...[...(s.cells ?? []), ...(s.items ?? [])].filter(([n, k]) => !k.vars(n).includes(n)).map(([n]) => [n, 'a map of a kind']),
|
|
1831
|
+
...(s.nodes ?? []).map(([n]) => [n, 'a table of nodes']),
|
|
1708
1832
|
];
|
|
1709
1833
|
}
|
|
1710
1834
|
whatIs(n) { return this.names().find(([m]) => m === n)?.[1] ?? 'something else'; }
|
|
@@ -1770,7 +1894,7 @@ export class Rule {
|
|
|
1770
1894
|
// Functions, and their facts: each function's own (named after it), then the rule's.
|
|
1771
1895
|
// The functions a function reads come first, each named <function>.<use>.
|
|
1772
1896
|
const flat = flatFns(s.fns ?? []);
|
|
1773
|
-
const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
|
|
1897
|
+
const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.arity > 1 ? { arity: d.arity, call: jsOf(d) } : {}), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
|
|
1774
1898
|
const facts = [
|
|
1775
1899
|
...flat.flatMap(([n, d, uses]) => Object.entries(d.facts?.(fnTerm(n, d), uses) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
|
|
1776
1900
|
...(s.facts ?? []).flatMap(f => Object.entries(f(pEnv)).map(([name, out]) => this.lawOf(name, out))),
|
|
@@ -1837,7 +1961,7 @@ export class Rule {
|
|
|
1837
1961
|
}
|
|
1838
1962
|
if (d === (opts.depth ?? 8))
|
|
1839
1963
|
break;
|
|
1840
|
-
const input = this.randomInput(rand, bound);
|
|
1964
|
+
const input = this.randomInput(rand, bound, params);
|
|
1841
1965
|
if (s.prune || !allowed(m, run, params, st, input))
|
|
1842
1966
|
continue;
|
|
1843
1967
|
d++;
|
|
@@ -1961,7 +2085,7 @@ export class Rule {
|
|
|
1961
2085
|
const edges = trial === 0 ? opts.edges ?? [] : [];
|
|
1962
2086
|
for (let d = 0; d < (opts.depth ?? 10) + edges.length; d++) {
|
|
1963
2087
|
const request = edges[d] ?? opts.draw?.(rand) ?? sample(s.inputs);
|
|
1964
|
-
ours = gen.step(ps, ours, this.encodeInput(request));
|
|
2088
|
+
ours = gen.step(ps, ours, this.encodeInput(request, params));
|
|
1965
2089
|
theirs = reference.step(params, theirs, request);
|
|
1966
2090
|
const got = this.decode(ours);
|
|
1967
2091
|
for (const [field, want] of Object.entries(theirs)) {
|
|
@@ -1977,10 +2101,12 @@ export class Rule {
|
|
|
1977
2101
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
1978
2102
|
encodeParams(values) { return encode(this.spec.params, values); }
|
|
1979
2103
|
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural.
|
|
1980
|
-
// A carried list is an array of its items' runtime values (its length and folds computed from them)
|
|
2104
|
+
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
2105
|
+
// folds that read the params are computed with.
|
|
1981
2106
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1982
|
-
encodeInput(values) {
|
|
1983
|
-
const
|
|
2107
|
+
encodeInput(values, params) {
|
|
2108
|
+
const at = params && this.paramValues(this.encodeParams(params));
|
|
2109
|
+
const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, at).encode(values[n])]));
|
|
1984
2110
|
const out = encode(this.spec.inputs, { ...values, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
|
|
1985
2111
|
for (const l of Object.values(lists))
|
|
1986
2112
|
Object.assign(out, l.maps);
|
|
@@ -1994,13 +2120,18 @@ export class Rule {
|
|
|
1994
2120
|
}
|
|
1995
2121
|
// Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
|
|
1996
2122
|
randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
|
|
1997
|
-
|
|
2123
|
+
// The params' values by their kinds, as constants (what a fold that reads them is computed with).
|
|
2124
|
+
paramValues(ps) {
|
|
2125
|
+
return Object.fromEntries(this.spec.params.map(([n, k]) => [n, k.build(n, part => new Term({ op: 'const', value: ps[part] }, ps[part]))]));
|
|
2126
|
+
}
|
|
2127
|
+
// `ps`: the params (encoded), for folds that read them.
|
|
2128
|
+
randomInput(rand, bound, ps) {
|
|
1998
2129
|
const out = Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound)));
|
|
1999
2130
|
for (const n of this.spec.carried ?? [])
|
|
2000
2131
|
out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
|
|
2001
2132
|
// A carried list: items drawn by their kind, its length and folds as the encoding computes them.
|
|
2002
2133
|
for (const [n, k, fs] of this.spec.lists2 ?? []) {
|
|
2003
|
-
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
|
|
2134
|
+
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, ps && this.paramValues(ps)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
|
|
2004
2135
|
Object.assign(out, l.maps, { [`${n}_len`]: l.len });
|
|
2005
2136
|
}
|
|
2006
2137
|
return out;
|
|
@@ -2022,6 +2153,11 @@ export class Rule {
|
|
|
2022
2153
|
}
|
|
2023
2154
|
for (const [n, k] of this.spec.items ?? [])
|
|
2024
2155
|
out[n] = Array.from({ length: st.scalars[`${n}_len`] }, (_, i) => cell(n, k, i));
|
|
2156
|
+
// A table of nodes: node 1 first, each its value and its children.
|
|
2157
|
+
for (const [n, k, cs] of this.spec.nodes ?? []) {
|
|
2158
|
+
const at = (part, key) => st.maps[part]?.get(key) ?? 0;
|
|
2159
|
+
out[n] = Array.from({ length: st.scalars[`${n}_size`] }, (_, i) => ({ value: cell(n, k, i + 1), ...Object.fromEntries(cs.map(c => [c, at(`${n}^${c}`, i + 1)])) }));
|
|
2160
|
+
}
|
|
2025
2161
|
return out;
|
|
2026
2162
|
}
|
|
2027
2163
|
// The prune order's variable, if the history is pruned (a runtime must send it nondecreasing).
|
|
@@ -2105,7 +2241,7 @@ export class Fun {
|
|
|
2105
2241
|
this.compiledRule ??= (rule => ({ rule, gen: rule.compiled() }))(this.rule);
|
|
2106
2242
|
const { rule, gen } = this.compiledRule, ps = rule.encodeParams(params);
|
|
2107
2243
|
const kinds = Object.entries(this.result(this.r['env']({ flags: false, state: false, queries: false })));
|
|
2108
|
-
const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values)));
|
|
2244
|
+
const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values, params)));
|
|
2109
2245
|
return Object.fromEntries(kinds.map(([k]) => [k, st[`out.${k}`]]));
|
|
2110
2246
|
}
|
|
2111
2247
|
}
|
|
@@ -2212,7 +2348,7 @@ export function crossCheckDecision(d, opts = {}) {
|
|
|
2212
2348
|
const r = d.rule, m = r.machine, bound = opts.bound ?? 4, rand = opts.rand ?? seeded(11), own = d.given.slice(d.given.length - src.own);
|
|
2213
2349
|
let checked = 0;
|
|
2214
2350
|
for (let i = 0; i < (opts.trials ?? 500); i++) {
|
|
2215
|
-
const
|
|
2351
|
+
const ps = r.randomParams(rand, bound), input = r.randomInput(rand, bound, ps), values = { ...ps, ...numbersOf(input) };
|
|
2216
2352
|
const st = { scalars: {}, maps: Object.fromEntries(Object.entries(input).filter((e) => typeof e[1] !== 'number').map(([n, cells]) => [n, new Map(cells)])) };
|
|
2217
2353
|
const keys = exactKeys(keysOf(bound, st), values, st);
|
|
2218
2354
|
const sc = { vars: values, maps: Object.fromEntries(Object.entries(st.maps).map(([n, cells]) => [n, { cells, dflt: 0 }])), keys, flags: {} };
|
|
@@ -2243,6 +2379,11 @@ export class FoldRule {
|
|
|
2243
2379
|
assume(f) { return this.with({ assume: [...this.spec.assume, f] }); }
|
|
2244
2380
|
leaf(f) { return this.with({ leaf: f }); }
|
|
2245
2381
|
node(f) { return this.with({ node: f }); }
|
|
2382
|
+
// Nodes with any number of children (RoseFold): the children's values folded into `kids`, from `from` by `step` (which
|
|
2383
|
+
// reads the params), each lemma a law of every prefix of every node's children that meet the fold's laws.
|
|
2384
|
+
children(from, step, lemmas = {}) {
|
|
2385
|
+
return new RoseFold(this.spec, { from: from, step: step, lemmas: lemmas });
|
|
2386
|
+
}
|
|
2246
2387
|
env(inputs = true) {
|
|
2247
2388
|
const out = {};
|
|
2248
2389
|
for (const [n, k] of this.spec.params)
|
|
@@ -2337,4 +2478,151 @@ export class FoldRule {
|
|
|
2337
2478
|
}
|
|
2338
2479
|
}
|
|
2339
2480
|
export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
|
|
2340
|
-
|
|
2481
|
+
// A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
|
|
2482
|
+
export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, [...(f.lemmas ?? []), ...laws]));
|
|
2483
|
+
const asTerm = (t) => (t instanceof Term ? t : lit(t));
|
|
2484
|
+
// A fold over trees whose nodes have any number of children, as the kernel's binary fold (so the kernel and Lean's
|
|
2485
|
+
// fold_sound check it as they check any fold): a node's children become a chain of glue nodes, each folding one child
|
|
2486
|
+
// into the values of the rest (`kids`, from `from` at the chain's end), and the node reads the chain's values. Every
|
|
2487
|
+
// tree node carries `kind$` (0 a node or leaf of the tree, 1 a glue node or the chain's end) and its role as an input; a
|
|
2488
|
+
// law of the fold is claimed where kind$ is 0, each lemma where it is 1. A binary tree no rose tree maps to (a child
|
|
2489
|
+
// where a chain belongs, or a chain where a child does) is read as one that does: a node reads a chain only, a glue
|
|
2490
|
+
// node a child only, so the laws are proven of every binary tree, and of each rose tree's image in particular.
|
|
2491
|
+
// fold('org chart').params({cap: nat}).inputs({pay: nat})
|
|
2492
|
+
// .leaf(({pay}) => ({cost: pay, heads: 1}))
|
|
2493
|
+
// .children({cost: 0, heads: 0}, (acc, kid) => ({cost: acc.cost.plus(kid.cost), heads: acc.heads.plus(kid.heads)}))
|
|
2494
|
+
// .node(({pay}, kids) => ({cost: kids.cost.plus(pay), heads: kids.heads.plus(1)}))
|
|
2495
|
+
export class RoseFold {
|
|
2496
|
+
spec;
|
|
2497
|
+
kids;
|
|
2498
|
+
nodeOf;
|
|
2499
|
+
constructor(spec, kids, nodeOf) {
|
|
2500
|
+
this.spec = spec;
|
|
2501
|
+
this.kids = kids;
|
|
2502
|
+
this.nodeOf = nodeOf;
|
|
2503
|
+
}
|
|
2504
|
+
get name() { return this.spec.name; }
|
|
2505
|
+
node(f) { return new RoseFold(this.spec, this.kids, f); }
|
|
2506
|
+
env(inputs = true) {
|
|
2507
|
+
const out = {};
|
|
2508
|
+
for (const [n, k] of this.spec.params)
|
|
2509
|
+
out[n] = k.value(n);
|
|
2510
|
+
if (inputs)
|
|
2511
|
+
for (const [n, k] of this.spec.inputs)
|
|
2512
|
+
out[n] = k.value(n);
|
|
2513
|
+
return out;
|
|
2514
|
+
}
|
|
2515
|
+
names() {
|
|
2516
|
+
if (!this.spec.leaf)
|
|
2517
|
+
throw new Error(`${this.spec.name}: a fold needs a leaf`);
|
|
2518
|
+
return Object.keys(this.spec.leaf(this.env()));
|
|
2519
|
+
}
|
|
2520
|
+
side(prefix) { return Object.fromEntries(this.names().map(n => [n, v(`${prefix}${n}`)])); }
|
|
2521
|
+
accs(prefix) { return Object.fromEntries(Object.keys(this.kids.from).map(a => [a, v(`${prefix}kids$${a}`)])); }
|
|
2522
|
+
start(p) { return Object.fromEntries(Object.entries(this.kids.from).map(([a, x]) => [a, typeof x === 'number' ? lit(x) : asTerm(x(p))])); }
|
|
2523
|
+
// A law about the values and the params, claimed of every node and leaf of the tree.
|
|
2524
|
+
law(name, f) { return { name, holds: v('kind$').atLeast(1).or(f({ ...this.env(false), ...this.side('') })).c }; }
|
|
2525
|
+
// The children's lemmas (claimed of every glue node), and that kind$ is 0 or 1: proveFold adds them.
|
|
2526
|
+
get lemmas() {
|
|
2527
|
+
const p = this.env(false);
|
|
2528
|
+
return [{ name: `${this.spec.name}: kind$ is 0 or 1`, holds: v('kind$').atMost(1).c },
|
|
2529
|
+
...Object.entries(this.kids.lemmas).map(([n, l]) => ({ name: n, holds: v('kind$').eq(0).or(l(this.accs(''), p)).c }))];
|
|
2530
|
+
}
|
|
2531
|
+
get fold() {
|
|
2532
|
+
const s = this.spec, node = this.nodeOf;
|
|
2533
|
+
if (!s.leaf || !node)
|
|
2534
|
+
throw new Error(`${s.name}: a fold needs a leaf and a node`);
|
|
2535
|
+
const env = this.env(), pEnv = this.env(false), names = this.names(), accNames = Object.keys(this.kids.from);
|
|
2536
|
+
const role = v('role$'), real = role.eq(0);
|
|
2537
|
+
const pre = [...s.params.flatMap(([n, k]) => k.assume(n)).map(c => c.c), ...s.pre.map(f => f(pEnv).c)];
|
|
2538
|
+
const own = all(...s.inputs.flatMap(([n, k]) => k.assume(n)), ...s.assume.map(f => f(env)));
|
|
2539
|
+
const assume = role.atMost(1).and(role.eq(1).or(own)).c;
|
|
2540
|
+
const start = this.start(pEnv);
|
|
2541
|
+
// A leaf of the tree, or the end of a chain (no children yet).
|
|
2542
|
+
const leafOut = s.leaf(env);
|
|
2543
|
+
const leaf = { ...Object.fromEntries(names.map(n => [n, when(real, leafOut[n], 0).e])),
|
|
2544
|
+
...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, start[a]).e])), 'kind$': when(real, 0, 1).e };
|
|
2545
|
+
// A node reads the chain on its left (its children's values); a glue node folds its left child into the rest.
|
|
2546
|
+
const lAcc = this.accs('l_'), rAcc = this.accs('r_'), lk = v('l_kind$'), rk = v('r_kind$');
|
|
2547
|
+
const kidsIn = Object.fromEntries(accNames.map(a => [a, when(lk.eq(1), lAcc[a], start[a])]));
|
|
2548
|
+
const rest = Object.fromEntries(accNames.map(a => [a, when(rk.eq(1), rAcc[a], start[a])]));
|
|
2549
|
+
const nodeOut = node(env, kidsIn), stepped = this.kids.step(rest, this.side('l_'), pEnv);
|
|
2550
|
+
const nodeE = { ...Object.fromEntries(names.map(n => [n, when(real, nodeOut[n], 0).e])),
|
|
2551
|
+
...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, when(lk.eq(0), stepped[a], rest[a])).e])), 'kind$': when(real, 0, 1).e };
|
|
2552
|
+
return { name: s.name, params: s.params.flatMap(([n, k]) => k.vars(n)), inputs: [...s.inputs.flatMap(([n, k]) => k.vars(n)), 'role$'], leaf, node: nodeE,
|
|
2553
|
+
...(pre.length ? { pre: pre.length === 1 ? pre[0] : { op: 'and', xs: pre } } : {}), assume };
|
|
2554
|
+
}
|
|
2555
|
+
// The values of a rose tree, by the terms proven (the tree as the binary tree the fold reads it as).
|
|
2556
|
+
run(tree, params = {}) {
|
|
2557
|
+
const f = this.fold, ps = encode(this.spec.params, params), none = { name: f.name, params: [], inputs: [], state: {}, step: {} };
|
|
2558
|
+
const st = { scalars: {}, maps: {} };
|
|
2559
|
+
if (f.pre && !evalC(f.pre, ps, st, none))
|
|
2560
|
+
throw new RangeError(`${f.name}: the params don't meet its precondition`);
|
|
2561
|
+
const zero = Object.fromEntries(f.inputs.map(n => [n, 0]));
|
|
2562
|
+
const at = (exprs, env) => Object.fromEntries(Object.entries(exprs).map(([n, e]) => [n, evalE(e, env, st, none)]));
|
|
2563
|
+
const kidsOf = (vals, prefix) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [`${prefix}${n}`, x]));
|
|
2564
|
+
const chain = (cs) => {
|
|
2565
|
+
const env = { ...ps, ...zero, 'role$': 1 };
|
|
2566
|
+
if (!cs.length)
|
|
2567
|
+
return at(f.leaf, env);
|
|
2568
|
+
return at(f.node, { ...env, ...kidsOf(go(cs[0]), 'l_'), ...kidsOf(chain(cs.slice(1)), 'r_') });
|
|
2569
|
+
};
|
|
2570
|
+
const go = (t) => {
|
|
2571
|
+
const own = 'leaf' in t ? t.leaf : t.node, env = { ...ps, ...encode(this.spec.inputs, own), 'role$': 0 };
|
|
2572
|
+
if (f.assume && !evalC(f.assume, env, st, none))
|
|
2573
|
+
throw new RangeError(`${f.name}: ${JSON.stringify(own)} doesn't meet its assumption`);
|
|
2574
|
+
if ('leaf' in t)
|
|
2575
|
+
return at(f.leaf, env);
|
|
2576
|
+
return at(f.node, { ...env, ...kidsOf(chain(t.children), 'l_'), ...kidsOf(chain([]), 'r_') });
|
|
2577
|
+
};
|
|
2578
|
+
const out = go(tree);
|
|
2579
|
+
return Object.fromEntries(this.names().map(n => [n, out[n]]));
|
|
2580
|
+
}
|
|
2581
|
+
// The builders against what they built, on random rose trees: each value by the callbacks' own arithmetic (the
|
|
2582
|
+
// children folded by step, then the node) against the terms proven, and each law and lemma at the root.
|
|
2583
|
+
crossCheck(laws = [], opts = {}) {
|
|
2584
|
+
const s = this.spec, rand = opts.rand ?? seeded(17), bound = opts.bound ?? 6, f = this.fold;
|
|
2585
|
+
let checked = 0;
|
|
2586
|
+
const sample = (kinds) => Object.fromEntries(kinds.map(([n, k]) => [n, k.sample(rand, bound)]));
|
|
2587
|
+
const tree = (d) => (d === 0 || rand(3) === 0 ? { leaf: sample(s.inputs) } : { node: sample(s.inputs), children: Array.from({ length: rand(4) }, () => tree(d - 1)) });
|
|
2588
|
+
const known = (vals) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [n, new Term({ op: 'const', value: x }, x)]));
|
|
2589
|
+
const nums = (out) => { const vals = Object.entries(out).map(([n, x]) => [n, valOf(x)]); return vals.every(([, x]) => x !== undefined) ? Object.fromEntries(vals) : undefined; };
|
|
2590
|
+
for (let t = 0; t < (opts.trials ?? 200); t++) {
|
|
2591
|
+
const params = sample(s.params), ps = encode(s.params, params);
|
|
2592
|
+
const pv = known(ps), pEnv = Object.fromEntries(s.params.map(([n, k]) => [n, k.build(n, part => pv[part])]));
|
|
2593
|
+
const builder = (tr) => {
|
|
2594
|
+
const own = 'leaf' in tr ? tr.leaf : tr.node, iv = known(encode(s.inputs, own)), env = { ...pEnv, ...Object.fromEntries(s.inputs.map(([n, k]) => [n, k.build(n, part => iv[part])])) };
|
|
2595
|
+
if ('leaf' in tr)
|
|
2596
|
+
return nums(s.leaf(env));
|
|
2597
|
+
let acc = nums(this.start(pEnv));
|
|
2598
|
+
for (const c of [...tr.children].reverse()) {
|
|
2599
|
+
const kid = builder(c);
|
|
2600
|
+
acc = acc && kid && nums(this.kids.step(known(acc), known(kid), pEnv));
|
|
2601
|
+
}
|
|
2602
|
+
return acc && nums(this.nodeOf(env, known(acc)));
|
|
2603
|
+
};
|
|
2604
|
+
const tr = tree(opts.depth ?? 3);
|
|
2605
|
+
let ours;
|
|
2606
|
+
try {
|
|
2607
|
+
ours = this.run(tr, params);
|
|
2608
|
+
}
|
|
2609
|
+
catch {
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
const theirs = builder(tr);
|
|
2613
|
+
if (!theirs)
|
|
2614
|
+
continue;
|
|
2615
|
+
for (const [n, x] of Object.entries(theirs)) {
|
|
2616
|
+
checked++;
|
|
2617
|
+
if (ours[n] !== x)
|
|
2618
|
+
return { checked, failure: `${f.name}: ${n} is ${x} by its callbacks, ${ours[n]} by the terms proven, for ${JSON.stringify(tr)}` };
|
|
2619
|
+
}
|
|
2620
|
+
for (const law of laws) {
|
|
2621
|
+
checked++;
|
|
2622
|
+
if (!evalC(law.holds, { ...ps, ...ours, 'kind$': 0 }, { scalars: {}, maps: {} }, { name: f.name, params: [], inputs: [], state: {}, step: {} }))
|
|
2623
|
+
return { checked, failure: `${f.name}: law ${law.name} fails for ${JSON.stringify(tr)} with params ${JSON.stringify(params)}` };
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
return { checked };
|
|
2627
|
+
}
|
|
2628
|
+
}
|