gdp-wired 0.10.0 → 0.11.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";
@@ -867,6 +867,7 @@ export class CellsRef {
867
867
  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
868
  }
869
869
  }
870
+ const fromOf = (d, p) => (typeof d.from === 'number' ? d.from : d.from(p));
870
871
  export class CarriedList {
871
872
  name;
872
873
  kind;
@@ -874,14 +875,20 @@ export class CarriedList {
874
875
  folds;
875
876
  map;
876
877
  lemmas;
878
+ params;
879
+ paramKinds;
877
880
  fold;
878
- constructor(name, kind, len, folds, map, lemmas = {}) {
881
+ // `params`: the params' values the folds read (by their kinds: terms for the kernel, constants to encode a request),
882
+ // undefined where none are given; `paramKinds`, the params themselves, for the lemmas' induction.
883
+ constructor(name, kind, len, folds, map, lemmas = {}, params, paramKinds = []) {
879
884
  this.name = name;
880
885
  this.kind = kind;
881
886
  this.len = len;
882
887
  this.folds = folds;
883
888
  this.map = map;
884
889
  this.lemmas = lemmas;
890
+ this.params = params;
891
+ this.paramKinds = paramKinds;
885
892
  this.fold = Object.fromEntries(Object.keys(folds).map(f => [f, map(`${name}$${f}`).at(len)]));
886
893
  }
887
894
  at(i) { return this.kind.build(this.name, part => this.map(part).at(i)); }
@@ -897,12 +904,21 @@ export class CarriedList {
897
904
  equations() {
898
905
  const folds = Object.entries(this.folds), lemmas = Object.values(this.lemmas);
899
906
  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)));
907
+ 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
908
  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))))] : []));
909
+ 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
910
  };
904
911
  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)] : [])];
912
+ return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(fromOf(d, this.p()))), ...(any ? [everyKey(at)] : [])];
913
+ }
914
+ // Whether a fold reads the params (its start a function, or its step taking them).
915
+ get readsParams() { return Object.values(this.folds).some(d => typeof d.from === 'function' || d.step.length > 2); }
916
+ p() {
917
+ if (this.params)
918
+ return this.params;
919
+ if (this.readsParams)
920
+ throw new RangeError(`${this.name}'s folds read the params: give them (encodeInput(values, params))`);
921
+ return {};
906
922
  }
907
923
  running(k) { return Object.fromEntries(Object.keys(this.folds).map(f => [f, this.after(f, k)])); }
908
924
  // 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 +932,11 @@ export class CarriedList {
916
932
  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
933
  const fns = Object.fromEntries(parts.map((p, j) => [fname(p), fn(1, 'x => 0', j === 0 ? { facts: () => kindFact } : {})]));
918
934
  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))])) }));
935
+ const r = rule(`${this.name}'s folds, by induction`).params(Object.fromEntries(this.paramKinds)).fns(fns).inputs({})
936
+ .state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
937
+ .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
938
  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))));
939
+ return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))));
923
940
  }
924
941
  // Every carried map it is kept in: the items' parts, then each fold's.
925
942
  get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
@@ -933,13 +950,13 @@ export class CarriedList {
933
950
  if (x !== 0)
934
951
  maps[part].set(i, x); });
935
952
  for (const [f, d] of Object.entries(this.folds)) {
936
- const acc = maps[`${this.name}$${f}`];
937
- let value = d.from;
953
+ const acc = maps[`${this.name}$${f}`], p = this.p();
954
+ let value = valOf(fromOf(d, p));
938
955
  if (value !== 0)
939
956
  acc.set(0, value);
940
957
  items.forEach((enc, i) => {
941
958
  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));
959
+ const next = valOf(d.step(new Term({ op: 'const', value }, value), item, p));
943
960
  if (next === undefined || !Number.isSafeInteger(next) || next < 0)
944
961
  throw new RangeError(`${this.name}'s fold ${f} reads more than its items`);
945
962
  value = next;
@@ -981,6 +998,66 @@ export class ItemsRef {
981
998
  within() { return forall(k => this.len.atMost(k).implies(all(...this.names.map(part => this.list(part).at(k).eq(0))))); }
982
999
  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
1000
  }
1001
+ export class NodeTable {
1002
+ name;
1003
+ kind;
1004
+ children;
1005
+ folds;
1006
+ size;
1007
+ map;
1008
+ tables;
1009
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1010
+ constructor(name, kind, children, folds, size, map, tables = {}) {
1011
+ this.name = name;
1012
+ this.kind = kind;
1013
+ this.children = children;
1014
+ this.folds = folds;
1015
+ this.size = size;
1016
+ this.map = map;
1017
+ this.tables = tables;
1018
+ }
1019
+ // The node at n (0, or past the end: the empty tree).
1020
+ node(n) {
1021
+ const index = termAt(n);
1022
+ return { index, empty: index.eq(0), value: this.kind.build(this.name, part => this.map(part).at(index)),
1023
+ fold: Object.fromEntries(Object.keys(this.folds).map(f => [f, this.map(`${this.name}$${f}`).at(index)])),
1024
+ kids: Object.fromEntries(this.children.map(c => [c, this.map(`${this.name}^${c}`).at(index)])) };
1025
+ }
1026
+ at(n) { return this.node(n).value; }
1027
+ // Whether nodes can be made from these children (each 0 or a node already there).
1028
+ fits(kids) { return all(...this.children.map(c => termAt(kids[c]).atMost(this.size))); }
1029
+ // Node size + 1, from a value and children already there, when `when` holds (and they fit): its value, children and
1030
+ // folds written, the size grown.
1031
+ make(value, kids, when = yes) {
1032
+ const n = this.size.plus(1), go = when.and(this.fits(kids)), parts = partsOf(this.kind, this.name, value);
1033
+ const val = this.kind.build(this.name, part => termAt(parts[part]));
1034
+ const kidRefs = Object.fromEntries(this.children.map(c => [c, this.node(termAt(kids[c]))]));
1035
+ const writes = {};
1036
+ const put = (part, x) => { writes[part] = this.map(part).set(n, when_(go, x, this.map(part).at(n))); };
1037
+ for (const part of this.kind.vars(this.name))
1038
+ put(part, parts[part]);
1039
+ for (const c of this.children)
1040
+ put(`${this.name}^${c}`, kids[c]);
1041
+ for (const [f, d] of Object.entries(this.folds))
1042
+ put(`${this.name}$${f}`, d.node(val, kidRefs, this.tables));
1043
+ return { [this.name]: { parts: writes }, [`${this.name}_size`]: when_(go, n, this.size) };
1044
+ }
1045
+ // The table's own laws: every node's children are earlier nodes and its folds are its children's, and node 0 and every
1046
+ // key past the end hold nothing (each fold's empty).
1047
+ laws() {
1048
+ const inside = (k) => k.atLeast(1).and(k.atMost(this.size));
1049
+ const folds = Object.entries(this.folds);
1050
+ return [
1051
+ ...(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)))))]] : []),
1052
+ ...(folds.length ? [[`every ${this.name} node's folds are its children's`, forall(k => inside(k).implies(all(...folds.map(([f, d]) => {
1053
+ const x = this.node(k);
1054
+ return x.fold[f].eq(d.node(x.value, Object.fromEntries(this.children.map(c => [c, this.node(x.kids[c])])), this.tables));
1055
+ }))))]] : []),
1056
+ [`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)))))],
1057
+ ];
1058
+ }
1059
+ }
1060
+ const termAt = (t) => (t instanceof Term ? t : lit(t));
984
1061
  // A law about every key of a map: proven at a fresh key, assumed at the keys a step reads.
985
1062
  // With two parameters or more, about every two keys or more: forall((i, j) => i.lt(j).implies(...)).
986
1063
  export function forall(f) { return { forall: f }; }
@@ -1194,6 +1271,8 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1194
1271
  if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
1195
1272
  throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
1196
1273
  distinctNames(`recur ${name}`, [...args, ...uses.map(([u]) => u)]);
1274
+ // A rule reads the function at its arguments paired, which the kernel multiplies out: with five further arguments that
1275
+ // is a polynomial of degree 32, past what it can hold.
1197
1276
  if (args.length > 4)
1198
1277
  throw new Error(`recur ${name}: at most four further arguments`);
1199
1278
  const named = (n) => new Term({ op: 'var', name: n });
@@ -1244,8 +1323,9 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1244
1323
  const keysOf = (n) => (f) => {
1245
1324
  if (n === 0)
1246
1325
  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];
1326
+ // A law's keys are counted by its callback's parameters: this one takes n.
1327
+ const by = (...ks) => f(...ks);
1328
+ Object.defineProperty(by, 'length', { value: n });
1249
1329
  return forall(by);
1250
1330
  };
1251
1331
  const facts = (withBetween) => (f, at = {}) => {
@@ -1279,7 +1359,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1279
1359
  };
1280
1360
  const distinctNames = (what, names) => { if (new Set(names).size !== names.length)
1281
1361
  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,
1362
+ // A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each called on its arguments,
1283
1363
  // as the runtimes call it).
1284
1364
  const recurJs = (name, args, zero, next, uses) => {
1285
1365
  const fns = new Map(uses.map(([u], i) => [u, `$f${i}`]));
@@ -1294,7 +1374,10 @@ const recurJs = (name, args, zero, next, uses) => {
1294
1374
  case 'min': return `Math.min(${js(e.a)}, ${js(e.b)})`;
1295
1375
  case 'max': return `Math.max(${js(e.a)}, ${js(e.b)})`;
1296
1376
  case 'if': return `(${jc(e.c)} ? ${js(e.then)} : ${js(e.else)})`;
1297
- case 'get': return `${fns.get(e.map)}(${js(e.key)})`;
1377
+ case 'get': {
1378
+ const i = uses.findIndex(([u]) => u === e.map), args = uses[i][1].arity > 1 ? pairedArgs(e.key, uses[i][1].arity) : undefined;
1379
+ return args ? `$g${i}(${args.map(js).join(', ')})` : `${fns.get(e.map)}(${js(e.key)})`;
1380
+ }
1298
1381
  }
1299
1382
  };
1300
1383
  const jc = (c) => {
@@ -1309,7 +1392,7 @@ const recurJs = (name, args, zero, next, uses) => {
1309
1392
  case 'all': throw new Error('a recurrence takes no flag or quantifier');
1310
1393
  }
1311
1394
  };
1312
- const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); `).join('');
1395
+ const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); ${d.arity > 1 ? `const $g${i} = (${jsOf(d)}); ` : ''}`).join('');
1313
1396
  // 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
1397
  // only the steps between (a rule that reads f at a count that grows costs one step a request).
1315
1398
  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 +1405,9 @@ const flatFns = (defs) => defs.flatMap(([n, d]) => {
1322
1405
  const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
1323
1406
  const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
1324
1407
  // A function's JavaScript as one function of its key: its arguments unpaired (left-nested, as argsKey pairs them).
1408
+ const jsOf = (d) => (typeof d.js === 'string' ? d.js : String(d.js));
1325
1409
  const fnSource = (name, d) => {
1326
- const src = typeof d.js === 'string' ? d.js : String(d.js);
1410
+ const src = jsOf(d);
1327
1411
  let f;
1328
1412
  try {
1329
1413
  f = compileFunction([], `return (${src})`)();
@@ -1446,6 +1530,19 @@ export class Rule {
1446
1530
  return this.with({ maps: [...this.spec.maps, ...entries.flatMap(([n, k]) => k.vars(n).map(part => [part, 0]))],
1447
1531
  state: [...this.spec.state, ...entries.map(([n]) => [`${n}_len`, 0])], items: [...(this.spec.items ?? []), ...entries] });
1448
1532
  }
1533
+ // A table of nodes, empty at the start (NodeTable): a tree in the state, made one node a step.
1534
+ nodes(name, of, children, folds = {}) {
1535
+ for (const c of children)
1536
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c) || c === 'value')
1537
+ throw new Error(`${name}: ${c} can't name a child`);
1538
+ for (const f of Object.keys(folds))
1539
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
1540
+ throw new Error(`${name}: ${f} is not a plain fold name`);
1541
+ distinctNames(name, [...children]);
1542
+ const maps = [...of.vars(name).map(part => [part, 0]), ...children.map(c => [`${name}^${c}`, 0]),
1543
+ ...Object.entries(folds).map(([f, d]) => [`${name}$${f}`, d.empty])];
1544
+ return this.with({ maps: [...this.spec.maps, ...maps], state: [...this.spec.state, [`${name}_size`, 0]], nodes: [...(this.spec.nodes ?? []), [name, of, [...children], folds]] });
1545
+ }
1449
1546
  // Functions defined outside the rule (fn, divBy, modBy), read like params that take arguments: f(x).
1450
1547
  fns(defs) {
1451
1548
  return this.with({ fns: [...(this.spec.fns ?? []), ...Object.entries(defs)] });
@@ -1456,6 +1553,7 @@ export class Rule {
1456
1553
  carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
1457
1554
  // A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
1458
1555
  // .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
1556
+ // Each fold's start and step, and each lemma, may read the params declared before it.
1459
1557
  carriesList(name, of, folds = {}, lemmas = {}) {
1460
1558
  const entries = [[name, of, folds, lemmas]];
1461
1559
  for (const [n, , fs] of entries)
@@ -1464,7 +1562,7 @@ export class Rule {
1464
1562
  throw new Error(`${n}: ${f} is not a plain fold name`);
1465
1563
  // The folds' lemmas, proven now (a lemma that isn't throws, with the verdict).
1466
1564
  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();
1565
+ const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, undefined, this.spec.params).prove();
1468
1566
  if (!(v2.proven && v2.checked))
1469
1567
  throw new Unproven(`${n}'s fold lemmas`, v2);
1470
1568
  }
@@ -1530,6 +1628,12 @@ export class Rule {
1530
1628
  if (k.invariant(k.value(n)).length) {
1531
1629
  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
1630
  }
1631
+ for (const [n, k] of this.spec.nodes ?? []) {
1632
+ if (k.invariant(k.value(n)).length)
1633
+ out.push(this.law(`every ${n} node is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
1634
+ for (const [name] of this.env({ flags: false, inputs: false, queries: false })[n].laws())
1635
+ out.push(this.law(name, env => of(env, n).laws().find(([m]) => m === name)[1]));
1636
+ }
1533
1637
  return out;
1534
1638
  }
1535
1639
  // A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
@@ -1547,7 +1651,7 @@ export class Rule {
1547
1651
  const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
1548
1652
  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
1653
  }
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
1654
+ // 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
1655
  // state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
1552
1656
  // j)` narrows the j-th step's request (j from 0), read at the state that step starts from.
1553
1657
  // meter.stepsLaw('two requests spend at most 2m', 2, ({spent, m}, after) => after.spent.atMost(spent.plus(m).plus(m)))
@@ -1649,8 +1753,9 @@ export class Rule {
1649
1753
  out[n] = k.value(n);
1650
1754
  for (const n of this.spec.carried ?? [])
1651
1755
  out[n] = new MapRef(n, 0);
1756
+ const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
1652
1757
  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);
1758
+ const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
1654
1759
  for (const part of l.maps)
1655
1760
  delete out[part];
1656
1761
  out[n] = l;
@@ -1697,6 +1802,15 @@ export class Rule {
1697
1802
  delete out[part];
1698
1803
  out[n] = new ItemsRef(n, k, len(n), part => new ListRef(at(part), len(n)));
1699
1804
  }
1805
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1806
+ const tables = {};
1807
+ for (const [n, k, cs, fs] of this.spec.nodes ?? []) {
1808
+ const t = new NodeTable(n, k, cs, fs, v(at(`${n}_size`)), part => new MapRef(at(part), init.get(part)), { ...tables });
1809
+ tables[n] = t;
1810
+ for (const part of [...k.vars(n), ...cs.map(c => `${n}^${c}`), ...Object.keys(fs).map(f => `${n}$${f}`)])
1811
+ delete out[part];
1812
+ out[n] = t;
1813
+ }
1700
1814
  }
1701
1815
  // Every name the rule declares is declared once (a flag named again replaces the earlier one, so flags are checked as
1702
1816
  // they are defined): an input and a state variable sharing a name would hand the callbacks only one of them.
@@ -1705,6 +1819,7 @@ export class Rule {
1705
1819
  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
1820
  ...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
1821
  ...[...(s.cells ?? []), ...(s.items ?? [])].filter(([n, k]) => !k.vars(n).includes(n)).map(([n]) => [n, 'a map of a kind']),
1822
+ ...(s.nodes ?? []).map(([n]) => [n, 'a table of nodes']),
1708
1823
  ];
1709
1824
  }
1710
1825
  whatIs(n) { return this.names().find(([m]) => m === n)?.[1] ?? 'something else'; }
@@ -1770,7 +1885,7 @@ export class Rule {
1770
1885
  // Functions, and their facts: each function's own (named after it), then the rule's.
1771
1886
  // The functions a function reads come first, each named <function>.<use>.
1772
1887
  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 } : {}) }]));
1888
+ 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
1889
  const facts = [
1775
1890
  ...flat.flatMap(([n, d, uses]) => Object.entries(d.facts?.(fnTerm(n, d), uses) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
1776
1891
  ...(s.facts ?? []).flatMap(f => Object.entries(f(pEnv)).map(([name, out]) => this.lawOf(name, out))),
@@ -1837,7 +1952,7 @@ export class Rule {
1837
1952
  }
1838
1953
  if (d === (opts.depth ?? 8))
1839
1954
  break;
1840
- const input = this.randomInput(rand, bound);
1955
+ const input = this.randomInput(rand, bound, params);
1841
1956
  if (s.prune || !allowed(m, run, params, st, input))
1842
1957
  continue;
1843
1958
  d++;
@@ -1961,7 +2076,7 @@ export class Rule {
1961
2076
  const edges = trial === 0 ? opts.edges ?? [] : [];
1962
2077
  for (let d = 0; d < (opts.depth ?? 10) + edges.length; d++) {
1963
2078
  const request = edges[d] ?? opts.draw?.(rand) ?? sample(s.inputs);
1964
- ours = gen.step(ps, ours, this.encodeInput(request));
2079
+ ours = gen.step(ps, ours, this.encodeInput(request, params));
1965
2080
  theirs = reference.step(params, theirs, request);
1966
2081
  const got = this.decode(ours);
1967
2082
  for (const [field, want] of Object.entries(theirs)) {
@@ -1977,10 +2092,12 @@ export class Rule {
1977
2092
  // Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
1978
2093
  encodeParams(values) { return encode(this.spec.params, values); }
1979
2094
  // 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).
2095
+ // A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
2096
+ // folds that read the params are computed with.
1981
2097
  // 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])]));
2098
+ encodeInput(values, params) {
2099
+ const at = params && this.paramValues(this.encodeParams(params));
2100
+ 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
2101
  const out = encode(this.spec.inputs, { ...values, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
1985
2102
  for (const l of Object.values(lists))
1986
2103
  Object.assign(out, l.maps);
@@ -1994,13 +2111,18 @@ export class Rule {
1994
2111
  }
1995
2112
  // Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
1996
2113
  randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
1997
- randomInput(rand, bound) {
2114
+ // The params' values by their kinds, as constants (what a fold that reads them is computed with).
2115
+ paramValues(ps) {
2116
+ return Object.fromEntries(this.spec.params.map(([n, k]) => [n, k.build(n, part => new Term({ op: 'const', value: ps[part] }, ps[part]))]));
2117
+ }
2118
+ // `ps`: the params (encoded), for folds that read them.
2119
+ randomInput(rand, bound, ps) {
1998
2120
  const out = Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound)));
1999
2121
  for (const n of this.spec.carried ?? [])
2000
2122
  out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
2001
2123
  // A carried list: items drawn by their kind, its length and folds as the encoding computes them.
2002
2124
  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)));
2125
+ 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
2126
  Object.assign(out, l.maps, { [`${n}_len`]: l.len });
2005
2127
  }
2006
2128
  return out;
@@ -2022,6 +2144,11 @@ export class Rule {
2022
2144
  }
2023
2145
  for (const [n, k] of this.spec.items ?? [])
2024
2146
  out[n] = Array.from({ length: st.scalars[`${n}_len`] }, (_, i) => cell(n, k, i));
2147
+ // A table of nodes: node 1 first, each its value and its children.
2148
+ for (const [n, k, cs] of this.spec.nodes ?? []) {
2149
+ const at = (part, key) => st.maps[part]?.get(key) ?? 0;
2150
+ 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)])) }));
2151
+ }
2025
2152
  return out;
2026
2153
  }
2027
2154
  // The prune order's variable, if the history is pruned (a runtime must send it nondecreasing).
@@ -2105,7 +2232,7 @@ export class Fun {
2105
2232
  this.compiledRule ??= (rule => ({ rule, gen: rule.compiled() }))(this.rule);
2106
2233
  const { rule, gen } = this.compiledRule, ps = rule.encodeParams(params);
2107
2234
  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)));
2235
+ const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values, params)));
2109
2236
  return Object.fromEntries(kinds.map(([k]) => [k, st[`out.${k}`]]));
2110
2237
  }
2111
2238
  }
@@ -2212,7 +2339,7 @@ export function crossCheckDecision(d, opts = {}) {
2212
2339
  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
2340
  let checked = 0;
2214
2341
  for (let i = 0; i < (opts.trials ?? 500); i++) {
2215
- const input = r.randomInput(rand, bound), values = { ...r.randomParams(rand, bound), ...numbersOf(input) };
2342
+ const ps = r.randomParams(rand, bound), input = r.randomInput(rand, bound, ps), values = { ...ps, ...numbersOf(input) };
2216
2343
  const st = { scalars: {}, maps: Object.fromEntries(Object.entries(input).filter((e) => typeof e[1] !== 'number').map(([n, cells]) => [n, new Map(cells)])) };
2217
2344
  const keys = exactKeys(keysOf(bound, st), values, st);
2218
2345
  const sc = { vars: values, maps: Object.fromEntries(Object.entries(st.maps).map(([n, cells]) => [n, { cells, dflt: 0 }])), keys, flags: {} };
@@ -2243,6 +2370,11 @@ export class FoldRule {
2243
2370
  assume(f) { return this.with({ assume: [...this.spec.assume, f] }); }
2244
2371
  leaf(f) { return this.with({ leaf: f }); }
2245
2372
  node(f) { return this.with({ node: f }); }
2373
+ // Nodes with any number of children (RoseFold): the children's values folded into `kids`, from `from` by `step` (which
2374
+ // reads the params), each lemma a law of every prefix of every node's children that meet the fold's laws.
2375
+ children(from, step, lemmas = {}) {
2376
+ return new RoseFold(this.spec, { from: from, step: step, lemmas: lemmas });
2377
+ }
2246
2378
  env(inputs = true) {
2247
2379
  const out = {};
2248
2380
  for (const [n, k] of this.spec.params)
@@ -2337,4 +2469,151 @@ export class FoldRule {
2337
2469
  }
2338
2470
  }
2339
2471
  export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
2340
- export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, laws));
2472
+ // A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
2473
+ export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, [...(f.lemmas ?? []), ...laws]));
2474
+ const asTerm = (t) => (t instanceof Term ? t : lit(t));
2475
+ // A fold over trees whose nodes have any number of children, as the kernel's binary fold (so the kernel and Lean's
2476
+ // fold_sound check it as they check any fold): a node's children become a chain of glue nodes, each folding one child
2477
+ // into the values of the rest (`kids`, from `from` at the chain's end), and the node reads the chain's values. Every
2478
+ // 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
2479
+ // 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
2480
+ // 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
2481
+ // node a child only, so the laws are proven of every binary tree, and of each rose tree's image in particular.
2482
+ // fold('org chart').params({cap: nat}).inputs({pay: nat})
2483
+ // .leaf(({pay}) => ({cost: pay, heads: 1}))
2484
+ // .children({cost: 0, heads: 0}, (acc, kid) => ({cost: acc.cost.plus(kid.cost), heads: acc.heads.plus(kid.heads)}))
2485
+ // .node(({pay}, kids) => ({cost: kids.cost.plus(pay), heads: kids.heads.plus(1)}))
2486
+ export class RoseFold {
2487
+ spec;
2488
+ kids;
2489
+ nodeOf;
2490
+ constructor(spec, kids, nodeOf) {
2491
+ this.spec = spec;
2492
+ this.kids = kids;
2493
+ this.nodeOf = nodeOf;
2494
+ }
2495
+ get name() { return this.spec.name; }
2496
+ node(f) { return new RoseFold(this.spec, this.kids, f); }
2497
+ env(inputs = true) {
2498
+ const out = {};
2499
+ for (const [n, k] of this.spec.params)
2500
+ out[n] = k.value(n);
2501
+ if (inputs)
2502
+ for (const [n, k] of this.spec.inputs)
2503
+ out[n] = k.value(n);
2504
+ return out;
2505
+ }
2506
+ names() {
2507
+ if (!this.spec.leaf)
2508
+ throw new Error(`${this.spec.name}: a fold needs a leaf`);
2509
+ return Object.keys(this.spec.leaf(this.env()));
2510
+ }
2511
+ side(prefix) { return Object.fromEntries(this.names().map(n => [n, v(`${prefix}${n}`)])); }
2512
+ accs(prefix) { return Object.fromEntries(Object.keys(this.kids.from).map(a => [a, v(`${prefix}kids$${a}`)])); }
2513
+ start(p) { return Object.fromEntries(Object.entries(this.kids.from).map(([a, x]) => [a, typeof x === 'number' ? lit(x) : asTerm(x(p))])); }
2514
+ // A law about the values and the params, claimed of every node and leaf of the tree.
2515
+ law(name, f) { return { name, holds: v('kind$').atLeast(1).or(f({ ...this.env(false), ...this.side('') })).c }; }
2516
+ // The children's lemmas (claimed of every glue node), and that kind$ is 0 or 1: proveFold adds them.
2517
+ get lemmas() {
2518
+ const p = this.env(false);
2519
+ return [{ name: `${this.spec.name}: kind$ is 0 or 1`, holds: v('kind$').atMost(1).c },
2520
+ ...Object.entries(this.kids.lemmas).map(([n, l]) => ({ name: n, holds: v('kind$').eq(0).or(l(this.accs(''), p)).c }))];
2521
+ }
2522
+ get fold() {
2523
+ const s = this.spec, node = this.nodeOf;
2524
+ if (!s.leaf || !node)
2525
+ throw new Error(`${s.name}: a fold needs a leaf and a node`);
2526
+ const env = this.env(), pEnv = this.env(false), names = this.names(), accNames = Object.keys(this.kids.from);
2527
+ const role = v('role$'), real = role.eq(0);
2528
+ const pre = [...s.params.flatMap(([n, k]) => k.assume(n)).map(c => c.c), ...s.pre.map(f => f(pEnv).c)];
2529
+ const own = all(...s.inputs.flatMap(([n, k]) => k.assume(n)), ...s.assume.map(f => f(env)));
2530
+ const assume = role.atMost(1).and(role.eq(1).or(own)).c;
2531
+ const start = this.start(pEnv);
2532
+ // A leaf of the tree, or the end of a chain (no children yet).
2533
+ const leafOut = s.leaf(env);
2534
+ const leaf = { ...Object.fromEntries(names.map(n => [n, when(real, leafOut[n], 0).e])),
2535
+ ...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, start[a]).e])), 'kind$': when(real, 0, 1).e };
2536
+ // A node reads the chain on its left (its children's values); a glue node folds its left child into the rest.
2537
+ const lAcc = this.accs('l_'), rAcc = this.accs('r_'), lk = v('l_kind$'), rk = v('r_kind$');
2538
+ const kidsIn = Object.fromEntries(accNames.map(a => [a, when(lk.eq(1), lAcc[a], start[a])]));
2539
+ const rest = Object.fromEntries(accNames.map(a => [a, when(rk.eq(1), rAcc[a], start[a])]));
2540
+ const nodeOut = node(env, kidsIn), stepped = this.kids.step(rest, this.side('l_'), pEnv);
2541
+ const nodeE = { ...Object.fromEntries(names.map(n => [n, when(real, nodeOut[n], 0).e])),
2542
+ ...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 };
2543
+ 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,
2544
+ ...(pre.length ? { pre: pre.length === 1 ? pre[0] : { op: 'and', xs: pre } } : {}), assume };
2545
+ }
2546
+ // The values of a rose tree, by the terms proven (the tree as the binary tree the fold reads it as).
2547
+ run(tree, params = {}) {
2548
+ const f = this.fold, ps = encode(this.spec.params, params), none = { name: f.name, params: [], inputs: [], state: {}, step: {} };
2549
+ const st = { scalars: {}, maps: {} };
2550
+ if (f.pre && !evalC(f.pre, ps, st, none))
2551
+ throw new RangeError(`${f.name}: the params don't meet its precondition`);
2552
+ const zero = Object.fromEntries(f.inputs.map(n => [n, 0]));
2553
+ const at = (exprs, env) => Object.fromEntries(Object.entries(exprs).map(([n, e]) => [n, evalE(e, env, st, none)]));
2554
+ const kidsOf = (vals, prefix) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [`${prefix}${n}`, x]));
2555
+ const chain = (cs) => {
2556
+ const env = { ...ps, ...zero, 'role$': 1 };
2557
+ if (!cs.length)
2558
+ return at(f.leaf, env);
2559
+ return at(f.node, { ...env, ...kidsOf(go(cs[0]), 'l_'), ...kidsOf(chain(cs.slice(1)), 'r_') });
2560
+ };
2561
+ const go = (t) => {
2562
+ const own = 'leaf' in t ? t.leaf : t.node, env = { ...ps, ...encode(this.spec.inputs, own), 'role$': 0 };
2563
+ if (f.assume && !evalC(f.assume, env, st, none))
2564
+ throw new RangeError(`${f.name}: ${JSON.stringify(own)} doesn't meet its assumption`);
2565
+ if ('leaf' in t)
2566
+ return at(f.leaf, env);
2567
+ return at(f.node, { ...env, ...kidsOf(chain(t.children), 'l_'), ...kidsOf(chain([]), 'r_') });
2568
+ };
2569
+ const out = go(tree);
2570
+ return Object.fromEntries(this.names().map(n => [n, out[n]]));
2571
+ }
2572
+ // The builders against what they built, on random rose trees: each value by the callbacks' own arithmetic (the
2573
+ // children folded by step, then the node) against the terms proven, and each law and lemma at the root.
2574
+ crossCheck(laws = [], opts = {}) {
2575
+ const s = this.spec, rand = opts.rand ?? seeded(17), bound = opts.bound ?? 6, f = this.fold;
2576
+ let checked = 0;
2577
+ const sample = (kinds) => Object.fromEntries(kinds.map(([n, k]) => [n, k.sample(rand, bound)]));
2578
+ 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)) });
2579
+ const known = (vals) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [n, new Term({ op: 'const', value: x }, x)]));
2580
+ const nums = (out) => { const vals = Object.entries(out).map(([n, x]) => [n, valOf(x)]); return vals.every(([, x]) => x !== undefined) ? Object.fromEntries(vals) : undefined; };
2581
+ for (let t = 0; t < (opts.trials ?? 200); t++) {
2582
+ const params = sample(s.params), ps = encode(s.params, params);
2583
+ const pv = known(ps), pEnv = Object.fromEntries(s.params.map(([n, k]) => [n, k.build(n, part => pv[part])]));
2584
+ const builder = (tr) => {
2585
+ 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])])) };
2586
+ if ('leaf' in tr)
2587
+ return nums(s.leaf(env));
2588
+ let acc = nums(this.start(pEnv));
2589
+ for (const c of [...tr.children].reverse()) {
2590
+ const kid = builder(c);
2591
+ acc = acc && kid && nums(this.kids.step(known(acc), known(kid), pEnv));
2592
+ }
2593
+ return acc && nums(this.nodeOf(env, known(acc)));
2594
+ };
2595
+ const tr = tree(opts.depth ?? 3);
2596
+ let ours;
2597
+ try {
2598
+ ours = this.run(tr, params);
2599
+ }
2600
+ catch {
2601
+ continue;
2602
+ }
2603
+ const theirs = builder(tr);
2604
+ if (!theirs)
2605
+ continue;
2606
+ for (const [n, x] of Object.entries(theirs)) {
2607
+ checked++;
2608
+ if (ours[n] !== x)
2609
+ return { checked, failure: `${f.name}: ${n} is ${x} by its callbacks, ${ours[n]} by the terms proven, for ${JSON.stringify(tr)}` };
2610
+ }
2611
+ for (const law of laws) {
2612
+ checked++;
2613
+ if (!evalC(law.holds, { ...ps, ...ours, 'kind$': 0 }, { scalars: {}, maps: {} }, { name: f.name, params: [], inputs: [], state: {}, step: {} }))
2614
+ return { checked, failure: `${f.name}: law ${law.name} fails for ${JSON.stringify(tr)} with params ${JSON.stringify(params)}` };
2615
+ }
2616
+ }
2617
+ return { checked };
2618
+ }
2619
+ }
package/dist/core/gdp.js CHANGED
@@ -21,7 +21,7 @@ export function guard(prover, fn, field, opts) {
21
21
  if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
22
22
  throw new Error(`${fn.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
23
23
  return (args, ...about) => {
24
- const input = r.encodeInput(args);
24
+ const input = r.encodeInput(args, params);
25
25
  if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
26
26
  return null;
27
27
  return atPath(fn.run(args, params), field) === true ? prover.prove(...about) : null;
@@ -46,7 +46,7 @@ export function gate(prover, r, flag, opts) {
46
46
  let st = opts.state ?? compiled.init(ps);
47
47
  return {
48
48
  request: (args, ...about) => {
49
- const input = r.encodeInput(args);
49
+ const input = r.encodeInput(args, params);
50
50
  if (!allowed(m, run, ps, st, input))
51
51
  return null;
52
52
  const granted = requestEnv(m, run, ps, st, input)[`?${flag}`] === 1;
@@ -71,6 +71,8 @@ export type Fn = {
71
71
  js: string;
72
72
  lean?: string;
73
73
  recur?: Recurrence;
74
+ arity?: number;
75
+ call?: string;
74
76
  };
75
77
  export type Recurrence = {
76
78
  args: string[];
@@ -162,6 +164,7 @@ export type Request = Record<string, number | ReadonlyMap<number, number>>;
162
164
  export declare const naturalIn: (v: unknown, what: string) => number;
163
165
  export declare function evalE(e: E, env: Env, st: State, m: Machine): number;
164
166
  export declare const fnValue: (fn: Fn, key: number) => number;
167
+ export declare function pairedArgs(key: E, n: number): E[] | undefined;
165
168
  export declare function evalC(c: C, env: Env, st: State, m: Machine): boolean;
166
169
  export declare function carriedView(m: Machine, st: State, input: Request, rename?: (map: string) => string): State;
167
170
  export declare const numbersOf: (input: Request) => Record<string, number>;