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/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
- export const everyKey = (f) => { const key = `k$${++boundCounter}`; return new Cond({ op: 'all', key, c: f(v(key)).c }, everyOk(scope?.keys, k => f(keyTerm(k)).ok)); };
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
- constructor(name, kind, len, folds, map, lemmas = {}) {
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.from)), ...(any ? [everyKey(at)] : [])];
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`).fns(fns).inputs({}).state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.from])) })
920
- .step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i))])) }));
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.from;
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 { op: 'all', key: c.key, c: swapC(c.c, from, to) };
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 outside the kernel`);
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
- const by = [(a) => f(a), (a, b) => f(a, b), (a, b, c) => f(a, b, c), (a, b, c, d) => f(a, b, c, d),
1248
- (a, b, c, d, e) => f(a, b, c, d, e), (a, b, c, d, e, g) => f(a, b, c, d, e, g)][n - 1];
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 as a function of its key,
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': return `${fns.get(e.map)}(${js(e.key)})`;
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 = typeof d.js === 'string' ? d.js : String(d.js);
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' ? { op: 'all', key: c.key, 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) };
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 16): `then(first, last, requests)` relates what the first step reads to the
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 lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(values[n])]));
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
- randomInput(rand, bound) {
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 input = r.randomInput(rand, bound), values = { ...r.randomParams(rand, bound), ...numbersOf(input) };
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
- export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, laws));
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
+ }