gdp-wired 0.2.0 → 0.3.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,9 +15,9 @@
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 { interpret, loadJs } from "./machine.js";
18
+ import { compileFunction, interpret, loadJs } from "./machine.js";
19
19
  import { check, loopObligations } from "../kernel/kernel.js";
20
- import { grow, proveLaws, proveStepLaws } from "../kernel/prove.js";
20
+ import { grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
21
21
  // --- terms and conditions -------------------------------------------------------------------------------------------
22
22
  const naturalConst = (v) => {
23
23
  if (!Number.isSafeInteger(v) || v < 0)
@@ -205,6 +205,16 @@ class MaybeKind extends Kind {
205
205
  }
206
206
  }
207
207
  export const maybe = (of) => new MaybeKind(of);
208
+ let boundCounter = 0;
209
+ // A condition at every key: only as a whole flag (`flags: ({...}) => ({covered: everyKey(k => ...)})`), the key read
210
+ // only as a map's key. The kernel closes it (instances where it holds, a witness where it fails).
211
+ export const everyKey = (f) => { const key = `k$${++boundCounter}`; return new Cond({ op: 'all', key, c: f(v(key)).c }); };
212
+ // 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
213
+ // key, given here or found by the prover among a few candidates (kernel/prove.ts).
214
+ export const someKey = (f, witness) => {
215
+ const key = `s$${++boundCounter}`;
216
+ return new Cond({ op: 'not', x: { op: 'all', key, c: f(v(key)).not().c, ...(witness === undefined ? {} : { witness: termOf(witness) }) } });
217
+ };
208
218
  // A map from naturals to naturals, every cell starting at `init`. One write per map per step.
209
219
  export class MapRef {
210
220
  name;
@@ -217,11 +227,272 @@ export class MapRef {
217
227
  set(key, value) { return { map: this.name, key: termOf(key), value: termOf(value) }; }
218
228
  // key's cell plus `by` where `c` holds (the counter every cap keeps).
219
229
  bump(key, c = yes, by = 1) { return this.set(key, when(c, this.at(key).plus(by), this.at(key))); }
230
+ // Every key at once: the value at each key, the key read only as a map's key (Other.at(k)).
231
+ each(f) { const each = `k$${++boundCounter}`; return { map: this.name, each, value: termOf(f(v(each))) }; }
232
+ // As a set (cells 0 or 1): a member, and, as whole flags, every member of this one in another, none shared, the same.
233
+ has(key) { return this.at(key).atLeast(1); }
234
+ subsetOf(other) { return everyKey(k => this.at(k).atMost(other.at(k))); }
235
+ disjointFrom(other) { return everyKey(k => this.at(k).plus(other.at(k)).atMost(1)); }
236
+ sameAs(other) { return everyKey(k => this.at(k).eq(other.at(k))); }
237
+ // Over a universe of n names (keys 0 .. n − 1): every key past it holds 0 (a law, or a whole flag for a set a request
238
+ // carries), and the set's size, the sum of its cells there (exact, given `within`).
239
+ within(n) { naturalConst(n); return everyKey(k => k.atLeast(n).implies(this.at(k).eq(0))); }
240
+ sizeBelow(n) { naturalConst(n); return sum(0, ...Array.from({ length: n }, (_, key) => this.at(key))); }
241
+ }
242
+ export class ListRef {
243
+ name;
244
+ len;
245
+ constructor(name, len) {
246
+ this.name = name;
247
+ this.len = len;
248
+ }
249
+ at(i) { return new Term({ op: 'get', map: this.name, key: termOf(i) }); }
250
+ // Conditions: x at some position; each position below the next.
251
+ has(x) { return everyKey(k => k.lt(this.len).implies(this.at(k).ne(x))).not(); }
252
+ sorted() { return everyKey(k => k.plus(1).lt(this.len).implies(this.at(k).lt(this.at(k.plus(1))))); }
253
+ // Operations.
254
+ push(x, when = yes) { return { when, at: k => when_(k.eq(this.len), x, this.at(k)), len: this.len.plus(1) }; }
255
+ pop(when = yes) { return { when, at: k => when_(k.plus(1).eq(this.len), 0, this.at(k)), len: this.len.minus(1) }; }
256
+ set(i, x, when = yes) { return { when, at: k => when_(k.eq(i), x, this.at(k)), len: this.len }; }
257
+ insertAt(i, x, when = yes) { return { when, at: k => when_(k.lt(i), this.at(k), when_(k.eq(i), x, this.at(k.minus(1)))), len: this.len.plus(1) }; }
258
+ removeAt(i, when = yes) { return { when, at: k => when_(k.lt(i), this.at(k), this.at(k.plus(1))), len: this.len.minus(1) }; }
259
+ // The updates for the step: the first operation whose condition holds, else the list as it was.
260
+ apply(...ops) {
261
+ const each = `k$${++boundCounter}`, k = v(each);
262
+ const at = ops.reduceRight((acc, op) => when_(op.when, op.at(k), acc), this.at(k));
263
+ const len = ops.reduceRight((acc, op) => when_(op.when, op.len, acc), this.len);
264
+ return { [this.name]: { map: this.name, each, value: at.e }, [`${this.name}_len`]: len };
265
+ }
266
+ // Laws: nothing past the end; positions strictly increasing (so no name twice), as one law about every two keys.
267
+ within() { return forall(k => this.len.atMost(k).implies(this.at(k).eq(0))); }
268
+ increasing() { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(this.at(i).lt(this.at(j)))); }
220
269
  }
270
+ const when_ = (c, then, otherwise) => new Term({ op: 'if', c: c.c, then: termOf(then), else: termOf(otherwise) });
221
271
  // A law about every key of a map: proven at a fresh key, assumed at the keys a step reads.
222
- export const forall = (f) => ({ forall: f });
272
+ // With two parameters or more, about every two keys or more: forall((i, j) => i.lt(j).implies(...)).
273
+ export function forall(f) { return { forall: f }; }
274
+ // The keys a law's callback names: one, or two when it takes two.
275
+ const keysFor = (out) => [`key$${++keyCounter}`, Array.from({ length: Math.max(0, out.forall.length - 1) }, () => `key$${++keyCounter}`)];
223
276
  // For histories: `holds` (a <= b, homogeneous over sums and queries) wherever `when` (params and plain inputs) holds.
224
277
  export const relation = (holds, whenever = yes) => ({ when: whenever, holds });
278
+ // --- quantifiers anywhere ---------------------------------------------------------------------------------------------
279
+ // The kernel takes a condition at every key as a whole flag only (decided once per step, closed both ways). Written
280
+ // anywhere else in a step (inside another flag, a term's if, a write, the assumption), it is named here as a flag of its
281
+ // own, so every part downstream sees only whole flags. A per-step truth inside such a condition (another quantifier,
282
+ // or a flag) is split out by cases: ∀k. φ(k, q) is (q ∧ ∀k. φ(k, yes)) ∨ (¬q ∧ ∀k. φ(k, no)). A quantifier that reads a
283
+ // key bound outside it is two keys at once, which no flag is.
284
+ const namesIn = (t) => [...JSON.stringify(t).matchAll(/"op":"var","name":"([^"]+)"/g)].map(m => m[1]);
285
+ function swapC(c, from, to) {
286
+ if (c === from)
287
+ return to;
288
+ switch (c.op) {
289
+ case 'not': return { op: 'not', x: swapC(c.x, from, to) };
290
+ case 'and':
291
+ case 'or': return { op: c.op, xs: c.xs.map(y => swapC(y, from, to)) };
292
+ case 'all': return { op: 'all', key: c.key, c: swapC(c.c, from, to) };
293
+ case 'flag': return c;
294
+ case '<':
295
+ case '<=':
296
+ case '=': return { ...c, a: swapE(c.a, from, to), b: swapE(c.b, from, to) };
297
+ }
298
+ }
299
+ function swapE(e, from, to) {
300
+ switch (e.op) {
301
+ case 'var':
302
+ case 'const': return e;
303
+ case 'mul': return { ...e, a: swapE(e.a, from, to) };
304
+ case 'if': return { op: 'if', c: swapC(e.c, from, to), then: swapE(e.then, from, to), else: swapE(e.else, from, to) };
305
+ case 'get': return { ...e, key: swapE(e.key, from, to) };
306
+ case 'add':
307
+ case 'sub':
308
+ case 'min':
309
+ case 'max':
310
+ case 'prod': return { ...e, a: swapE(e.a, from, to), b: swapE(e.b, from, to) };
311
+ }
312
+ }
313
+ function perStep(c) {
314
+ switch (c.op) {
315
+ case 'flag':
316
+ case 'all': return c;
317
+ case 'not': return perStep(c.x);
318
+ case 'and':
319
+ case 'or':
320
+ for (const y of c.xs) {
321
+ const p = perStep(y);
322
+ if (p)
323
+ return p;
324
+ }
325
+ return undefined;
326
+ case '<':
327
+ case '<=':
328
+ case '=': return perStepE(c.a) ?? perStepE(c.b);
329
+ }
330
+ }
331
+ function perStepE(e) {
332
+ switch (e.op) {
333
+ case 'var':
334
+ case 'const': return undefined;
335
+ case 'mul': return perStepE(e.a);
336
+ case 'if': return perStep(e.c) ?? perStepE(e.then) ?? perStepE(e.else);
337
+ case 'get': return perStepE(e.key);
338
+ case 'add':
339
+ case 'sub':
340
+ case 'min':
341
+ case 'max':
342
+ case 'prod': return perStepE(e.a) ?? perStepE(e.b);
343
+ }
344
+ }
345
+ class Hoister {
346
+ prefix;
347
+ flags = {};
348
+ constructor(prefix) {
349
+ this.prefix = prefix;
350
+ }
351
+ n = 0;
352
+ // c with every quantifier inside it named as a flag; `bound` the keys bound outside c.
353
+ cond(c, bound = []) {
354
+ switch (c.op) {
355
+ case 'all': return this.quantifier(c, bound);
356
+ case 'flag': return c;
357
+ case 'not': return { op: 'not', x: this.cond(c.x, bound) };
358
+ case 'and':
359
+ case 'or': return { op: c.op, xs: c.xs.map(y => this.cond(y, bound)) };
360
+ case '<':
361
+ case '<=':
362
+ case '=': return { ...c, a: this.term(c.a, bound), b: this.term(c.b, bound) };
363
+ }
364
+ }
365
+ term(e, bound = []) {
366
+ switch (e.op) {
367
+ case 'var':
368
+ case 'const': return e;
369
+ case 'mul': return { ...e, a: this.term(e.a, bound) };
370
+ case 'if': return { op: 'if', c: this.cond(e.c, bound), then: this.term(e.then, bound), else: this.term(e.else, bound) };
371
+ case 'get': return { ...e, key: this.term(e.key, bound) };
372
+ case 'add':
373
+ case 'sub':
374
+ case 'min':
375
+ case 'max':
376
+ case 'prod': return { ...e, a: this.term(e.a, bound), b: this.term(e.b, bound) };
377
+ }
378
+ }
379
+ // A quantifier as a flag (or, with per-step truths inside, a condition over flags).
380
+ quantifier(q, bound, name) {
381
+ const outer = bound.filter(key => namesIn(q).includes(key));
382
+ if (outer.length)
383
+ throw new Error(`a condition at every key reads ${outer[0]}, a key bound outside it: two keys at once are outside the kernel`);
384
+ const inner = perStep(q.c);
385
+ if (!inner) {
386
+ const f = name ?? `${this.prefix}${++this.n}`;
387
+ this.flags[f] = { op: 'all', key: q.key, c: q.c };
388
+ return { op: 'flag', name: f };
389
+ }
390
+ const truth = inner.op === 'flag' ? inner : this.quantifier(inner, [...bound, q.key]);
391
+ const split = (by) => this.quantifier({ op: 'all', key: q.key, c: swapC(q.c, inner, by) }, bound);
392
+ return { op: 'or', xs: [{ op: 'and', xs: [truth, split(yes.c)] }, { op: 'and', xs: [{ op: 'not', x: truth }, split(no.c)] }] };
393
+ }
394
+ }
395
+ // A machine built by hand (or by a combinator) with quantifiers inside its conditions, terms or writes: each named as a
396
+ // flag of its own, as the builders do.
397
+ export function nameQuantifiers(m) {
398
+ const hoist = new Hoister('all$'), flags = {};
399
+ const placed = () => { for (const [n, c] of Object.entries(hoist.flags))
400
+ if (!(n in flags))
401
+ flags[n] = c; };
402
+ for (const [n, c] of Object.entries(m.step.flags ?? {})) {
403
+ const out = c.op === 'all' ? hoist.quantifier(c, [], n) : hoist.cond(c);
404
+ placed();
405
+ if (!(n in flags))
406
+ flags[n] = out;
407
+ }
408
+ const state = Object.fromEntries(Object.entries(m.step.state ?? {}).map(([n, e]) => [n, hoist.term(e)]));
409
+ const maps = Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([n, w]) => [n, 'each' in w ? { each: w.each, value: hoist.term(w.value, [w.each]) } : { key: hoist.term(w.key), value: hoist.term(w.value) }]));
410
+ const assume = m.assume ? hoist.cond(m.assume) : undefined;
411
+ placed();
412
+ return { ...m, step: { ...(Object.keys(flags).length ? { flags } : {}), ...(m.step.state ? { state } : {}), ...(m.step.maps ? { maps } : {}) }, ...(assume ? { assume } : {}) };
413
+ }
414
+ // A law or a step law's conclusion with quantifiers inside, each pulled out as the law's one key: ∀k. A ∧ ∀k. B is
415
+ // ∀k. A ∧ B, and p ∨ ∀k. A is ∀k. p ∨ A when p doesn't read the key. One under an or beside a part that reads the key
416
+ // is two keys at once. A quantifier under a not is some key (someKey), left for the prover's witness.
417
+ const hasForall = (c, positive = true) => c.op === 'all' ? positive || hasForall(c.c, positive) : c.op === 'not' ? hasForall(c.x, !positive) : c.op === 'and' || c.op === 'or' ? c.xs.some(y => hasForall(y, positive)) : false;
418
+ const hasAll = (c) => JSON.stringify(c).includes('"op":"all"');
419
+ function prenex(c, what, key) {
420
+ let found = false;
421
+ const walk = (d, positive) => {
422
+ switch (d.op) {
423
+ case 'all':
424
+ if (!positive)
425
+ return d;
426
+ found = true;
427
+ return walk(substKey(d.c, d.key, key), positive);
428
+ case 'not': return { op: 'not', x: walk(d.x, !positive) };
429
+ case 'and':
430
+ case 'or': {
431
+ if ((d.op === 'or') === positive && d.xs.some(y => hasForall(y, positive)) && d.xs.filter(y => hasForall(y, positive) || namesIn(y).includes(key)).length > 1)
432
+ throw new Error(`${what}: a condition at every key in an or beside another part that reads a key is two keys at once`);
433
+ return { op: d.op, xs: d.xs.map(y => walk(y, positive)) };
434
+ }
435
+ case 'flag': return d;
436
+ case '<':
437
+ case '<=':
438
+ case '=':
439
+ if (hasAll(d))
440
+ throw new Error(`${what}: a condition at every key inside a term is a flag of the rule`);
441
+ return d;
442
+ }
443
+ };
444
+ const out = walk(c, true);
445
+ return found ? out : undefined;
446
+ }
447
+ const substKey = (c, from, to) => JSON.parse(JSON.stringify(c).replaceAll(`"op":"var","name":"${from}"`, `"op":"var","name":"${to}"`));
448
+ export const fn = (arity, js, opts = {}) => {
449
+ if (!Number.isInteger(arity) || arity < 1)
450
+ throw new Error('a function takes one argument or more');
451
+ return { arity, js, ...opts };
452
+ };
453
+ // x / c and x mod c, rounded down, for a constant c >= 1: each with its two facts (c·q <= x < c·q + c), which Lean's omega
454
+ // proves of `x / c`.
455
+ export const divBy = (c) => {
456
+ if (!Number.isSafeInteger(c) || c < 1)
457
+ throw new RangeError('divBy takes a whole number at least 1');
458
+ return fn(1, `x => Math.floor(x / ${c})`, { lean: `fun x => x / ${c}`, facts: f => ({
459
+ [`${c}·q(x) <= x`]: forall(x => f(x).times(c).atMost(x)),
460
+ [`x < ${c}·q(x) + ${c}`]: forall(x => x.lt(f(x).times(c).plus(c))),
461
+ }) });
462
+ };
463
+ export const modBy = (c) => {
464
+ if (!Number.isSafeInteger(c) || c < 1)
465
+ throw new RangeError('modBy takes a whole number at least 1');
466
+ // x mod c is below c, and x less it is a multiple of c: x ∸ r = c·q for the q that divBy(c) gives; as facts about r
467
+ // alone, r <= x, r < c, and r(x + c) = r(x).
468
+ return fn(1, `x => x % ${c}`, { lean: `fun x => x % ${c}`, facts: f => ({
469
+ [`r(x) < ${c}`]: forall(x => f(x).lt(c)),
470
+ [`r(x) <= x`]: forall(x => f(x).atMost(x)),
471
+ [`r(x + ${c}) = r(x)`]: forall(x => f(x.plus(c)).eq(f(x))),
472
+ }) });
473
+ };
474
+ const pairE = (a, b) => ({ op: 'if', c: { op: '<', a, b }, then: { op: 'add', a: { op: 'prod', a: b, b }, b: a }, else: { op: 'add', a: { op: 'add', a: { op: 'prod', a, b: a }, b: a }, b } });
475
+ const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
476
+ // A function's JavaScript as one function of its key: its arguments unpaired (left-nested, as argsKey pairs them).
477
+ const fnSource = (name, d) => {
478
+ const src = typeof d.js === 'string' ? d.js : String(d.js);
479
+ let f;
480
+ try {
481
+ f = compileFunction([], `return (${src})`)();
482
+ f(...Array.from({ length: d.arity }, () => 0));
483
+ }
484
+ catch (e) {
485
+ throw new Error(`function ${name}: its js must be a function of its arguments that reads nothing else (${e.message})`);
486
+ }
487
+ if (d.arity === 1)
488
+ return src;
489
+ return `k => { const u = z => { const s = Math.floor(Math.sqrt(z)); return z - s * s < s ? [z - s * s, s] : [s, z - s * s - s]; }; const a = []; let z = k; for (let i = 1; i < ${d.arity}; i++) { const [p, q] = u(z); a.unshift(q); z = p; } a.unshift(z); return (${src})(...a); }`;
490
+ };
491
+ const fnTerm = (name, d) => (...args) => {
492
+ if (args.length !== d.arity)
493
+ throw new Error(`function ${name} takes ${d.arity} argument${d.arity === 1 ? '' : 's'}`);
494
+ return new Term({ op: 'get', map: name, key: argsKey(args.map(termOf)) });
495
+ };
225
496
  let keyCounter = 0;
226
497
  // A k-step law's later requests are built over marked names (j:name, which no rule can declare: ':' is reserved); the
227
498
  // kernel reads them as name@j.
@@ -240,7 +511,7 @@ const markedE = (e) => {
240
511
  case 'prod': return { ...e, a: markedE(e.a), b: markedE(e.b) };
241
512
  }
242
513
  };
243
- const marked = (c) => c.op === 'flag' ? 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) };
514
+ 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) };
244
515
  // 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
245
516
  // values the builders hand out, grown by each call.
246
517
  export class Rule {
@@ -257,6 +528,18 @@ export class Rule {
257
528
  return this.with({ state: [...this.spec.state, ...Object.entries(init)] });
258
529
  }
259
530
  maps(init) { return this.with({ maps: [...this.spec.maps, ...Object.entries(init)] }); }
531
+ // Lists, each empty at the start: a map of positions and its length <name>_len (ListRef).
532
+ lists(...names) {
533
+ return this.with({ maps: [...this.spec.maps, ...names.map(n => [n, 0])], state: [...this.spec.state, ...names.map(n => [`${n}_len`, 0])], lists: [...(this.spec.lists ?? []), ...names] });
534
+ }
535
+ // Functions defined outside the rule (fn, divBy, modBy), read like params that take arguments: f(x).
536
+ fns(defs) {
537
+ return this.with({ fns: [...(this.spec.fns ?? []), ...Object.entries(defs)] });
538
+ }
539
+ // What is given about the functions (with their own facts): laws over the params and functions, each by name.
540
+ facts(f) { return this.with({ facts: [...(this.spec.facts ?? []), f] }); }
541
+ // Maps each request carries whole (a set of names, say: cells 0 or 1), every cell past them 0; read like any map.
542
+ carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
260
543
  // Named conditions of the step, decided once per step before it; each call's flags may read the earlier calls'. A flag
261
544
  // named again replaces the earlier definition in place (what a variant needs: one call, the step unchanged); the new
262
545
  // definition may read only the flags defined before the original's place, as the kernel checks.
@@ -288,12 +571,15 @@ export class Rule {
288
571
  // (the kernel proves it adds nothing to any query).
289
572
  prune(order, after) { return this.with({ prune: { order, after: after } }); }
290
573
  // A law about the params, the state, the maps and the sums (not a request): `forall` for every key of a map.
291
- law(name, f) {
292
- const out = f(this.env({ flags: false, inputs: false, queries: false }));
293
- if (out instanceof Cond)
294
- return { name, holds: out.c };
574
+ law(name, f) { return this.lawOf(name, f(this.env({ flags: false, inputs: false, queries: false }))); }
575
+ lawOf(name, out) {
295
576
  const key = `key$${++keyCounter}`;
296
- return { name, forall: key, holds: out.forall(v(key)).c };
577
+ if (out instanceof Cond) {
578
+ const p = prenex(out.c, `law ${name}`, key);
579
+ return p ? { name, forall: key, holds: p } : { name, holds: out.c };
580
+ }
581
+ const [k1, more] = keysFor(out), holds = out.forall(v(k1), ...more.map(v)).c;
582
+ return more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds };
297
583
  }
298
584
  // A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
299
585
  // the request, state, maps, flags, sums, queries) to the state, maps and sums after it. `when` narrows the requests.
@@ -307,11 +593,16 @@ export class Rule {
307
593
  after[n] = v(`${n}'`);
308
594
  for (const [n, init] of this.spec.maps)
309
595
  after[n] = new MapRef(`${n}'`, init);
596
+ for (const n of this.spec.lists ?? [])
597
+ after[n] = new ListRef(`${n}'`, v(`${n}_len'`));
310
598
  const out = then(env, after), w = when ? { when: when(env).c } : {};
311
- if (out instanceof Cond)
312
- return { name, then: out.c, ...w };
313
599
  const key = `key$${++keyCounter}`;
314
- return { name, forall: key, then: out.forall(v(key)).c, ...w };
600
+ if (out instanceof Cond) {
601
+ const p = prenex(out.c, `step law ${name}`, key);
602
+ return p ? { name, forall: key, then: p, ...w } : { name, then: out.c, ...w };
603
+ }
604
+ const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
605
+ return more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w };
315
606
  }
316
607
  // 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
317
608
  // state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
@@ -326,6 +617,8 @@ export class Rule {
326
617
  after[n] = v(`${n}'`);
327
618
  for (const [n, init] of this.spec.maps)
328
619
  after[n] = new MapRef(`${n}'`, init);
620
+ for (const n of this.spec.lists ?? [])
621
+ after[n] = new ListRef(`${n}'`, v(`${n}_len'`));
329
622
  // The j-th request's values over marked names, renamed to the kernel's input@j once built.
330
623
  const mark = (j) => `${j}:`;
331
624
  const requests = Array.from({ length: k }, (_, j) => {
@@ -338,10 +631,29 @@ export class Rule {
338
631
  while (whens.length && whens.at(-1) === undefined)
339
632
  whens.pop();
340
633
  const w = whens.length ? { when: whens.map(c => (c ?? lit(0).atMost(0)).c) } : {};
341
- if (out instanceof Cond)
342
- return { name, steps: k, then: marked(out.c), ...w };
343
634
  const key = `key$${++keyCounter}`;
344
- return { name, steps: k, forall: key, then: marked(out.forall(v(key)).c), ...w };
635
+ if (out instanceof Cond) {
636
+ const p = prenex(out.c, `step law ${name}`, key);
637
+ return p ? { name, steps: k, forall: key, then: marked(p), ...w } : { name, steps: k, then: marked(out.c), ...w };
638
+ }
639
+ const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
640
+ return more.length ? { name, steps: k, forall: k1, keys: more, then: marked(c), ...w } : { name, steps: k, forall: k1, then: marked(prenex(c, `step law ${name}`, k1) ?? c), ...w };
641
+ }
642
+ // "Whenever `from` holds, `to` holds later", in every run that takes each helpful request infinitely often: proven by
643
+ // a rank (a natural over the state and params, or several compared in order) that no step raises while `from` holds
644
+ // and `to` doesn't, except by reaching `to`, and that each helpful request lowers (or it reaches `to`). A helpful
645
+ // request names some inputs by terms over the state and params, the rest left free; with several, each comes with the
646
+ // region where it helps (`when`), some region always holds, and a step that keeps the rank keeps its region.
647
+ // queue.leadsTo('every ticket is served', {from: ({t, next, served}) => t.lt(next).and(served.atMost(t)), to: ({t, served}) => served.gt(t),
648
+ // rank: ({t, served}) => t.plus(1).minus(served), helpful: () => ({op: 1})})
649
+ // batches.leadsTo('every batch is done', {from: () => yes, to: ({waiting, items}) => waiting.eq(0).and(items.eq(0)),
650
+ // rank: ({waiting, items}) => [waiting, items], helpful: [{when: ({items}) => items.gt(0), request: () => ({op: 1})}, {when: ({items}) => items.eq(0), request: () => ({op: 0})}]})
651
+ leadsTo(name, spec) {
652
+ const env = this.env({ flags: false, inputs: false, queries: false });
653
+ const req = (f) => Object.fromEntries(Object.entries(f(env)).map(([n, t]) => [n, termOf(t)]));
654
+ const r = spec.rank(env), rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
655
+ const base = { name, from: spec.from(env).c, to: spec.to(env).c, rank };
656
+ return typeof spec.helpful === 'function' ? { ...base, request: req(spec.helpful) } : { ...base, helpful: spec.helpful.map(h => ({ ...(h.when ? { when: h.when(env).c } : {}), request: req(h.request) })) };
345
657
  }
346
658
  // A decision: whenever the params and the request meet `given`, `then` holds. Decisions don't read the state.
347
659
  decide(name, given, then) {
@@ -361,14 +673,21 @@ export class Rule {
361
673
  const out = {};
362
674
  for (const [n, k] of this.spec.params)
363
675
  out[n] = k.value(n);
364
- if (parts.inputs !== false)
676
+ for (const [n, d] of this.spec.fns ?? [])
677
+ out[n] = fnTerm(n, d);
678
+ if (parts.inputs !== false) {
365
679
  for (const [n, k] of this.spec.inputs)
366
680
  out[n] = k.value(n);
681
+ for (const n of this.spec.carried ?? [])
682
+ out[n] = new MapRef(n, 0);
683
+ }
367
684
  if (parts.state !== false) {
368
685
  for (const [n] of this.spec.state)
369
686
  out[n] = v(n);
370
687
  for (const [n, init] of this.spec.maps)
371
688
  out[n] = new MapRef(n, init);
689
+ for (const n of this.spec.lists ?? [])
690
+ out[n] = new ListRef(n, v(`${n}_len`));
372
691
  for (const [n] of this.spec.sums)
373
692
  out[n] = v(n);
374
693
  }
@@ -387,12 +706,23 @@ export class Rule {
387
706
  const inputs = [...s.inputs.flatMap(([n, k]) => k.vars(n)), ...queries];
388
707
  const pEnv = this.env({ inputs: false, state: false, flags: false, queries: false });
389
708
  const env = this.env({ flags: false });
390
- const flags = {};
709
+ const declared = {};
391
710
  for (const f of s.flags)
392
711
  for (const [n, c] of Object.entries(f(env))) {
393
- flags[n] = c.c;
712
+ declared[n] = c.c;
394
713
  env[n] = new Cond({ op: 'flag', name: n });
395
714
  } // a name again: replaced in place
715
+ // Quantifiers anywhere, named as flags: each placed before the flag that reads it.
716
+ const hoist = new Hoister('all$'), flags = {};
717
+ const placed = () => { for (const [n, c] of Object.entries(hoist.flags))
718
+ if (!(n in flags))
719
+ flags[n] = c; };
720
+ for (const [n, c] of Object.entries(declared)) {
721
+ const out = c.op === 'all' ? hoist.quantifier(c, [], n) : hoist.cond(c);
722
+ placed();
723
+ if (!(n in flags))
724
+ flags[n] = out;
725
+ }
396
726
  const updates = s.step?.(env) ?? {};
397
727
  const state = {}, maps = {};
398
728
  for (const [n, u] of Object.entries(updates)) {
@@ -401,20 +731,30 @@ export class Rule {
401
731
  if (typeof u === 'object' && 'map' in u) {
402
732
  if (u.map !== n)
403
733
  throw new Error(`${s.name}: the step writes map ${u.map} under the name ${n}`);
404
- maps[n] = { key: u.key, value: u.value };
734
+ maps[n] = 'each' in u ? { each: u.each, value: hoist.term(u.value, [u.each]) } : { key: hoist.term(u.key), value: hoist.term(u.value) };
405
735
  }
406
736
  else
407
- state[n] = termOf(u);
737
+ state[n] = hoist.term(termOf(u));
408
738
  }
409
739
  const pre = [...this.kindAssumptions('params'), ...s.pre.map(f => f(pEnv).c)];
410
- const assume = [...this.kindAssumptions('inputs'), ...s.assume.map(f => f(env).c)];
740
+ const assume = [...this.kindAssumptions('inputs'), ...s.assume.map(f => hoist.cond(f(env).c))];
741
+ placed();
742
+ // Functions, and their facts: each function's own (named after it), then the rule's.
743
+ const fns = Object.fromEntries((s.fns ?? []).map(([n, d]) => [n, { js: fnSource(n, d), ...(d.lean ? { lean: d.lean } : {}) }]));
744
+ const facts = [
745
+ ...(s.fns ?? []).flatMap(([n, d]) => Object.entries(d.facts?.(fnTerm(n, d)) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
746
+ ...(s.facts ?? []).flatMap(f => Object.entries(f(pEnv)).map(([name, out]) => this.lawOf(name, out))),
747
+ ];
411
748
  const m = {
412
749
  name: s.name, params, inputs,
413
750
  state: Object.fromEntries(s.state.map(([n, init]) => [n, typeof init === 'number' ? termOf(init) : termOf(init(pEnv))])),
414
751
  ...(s.maps.length ? { maps: Object.fromEntries(s.maps) } : {}),
752
+ ...(s.carried?.length ? { inputMaps: s.carried } : {}),
415
753
  step: { ...(Object.keys(flags).length ? { flags } : {}), ...(Object.keys(state).length ? { state } : {}), ...(Object.keys(maps).length ? { maps } : {}) },
416
754
  ...(pre.length ? { pre: pre.length === 1 ? pre[0] : { op: 'and', xs: pre } } : {}),
417
755
  ...(assume.length ? { assume: assume.length === 1 ? assume[0] : { op: 'and', xs: assume } } : {}),
756
+ ...(Object.keys(fns).length ? { fns } : {}),
757
+ ...(facts.length ? { facts } : {}),
418
758
  };
419
759
  if (s.record) {
420
760
  const kinds = new Map(s.inputs);
@@ -438,10 +778,23 @@ export class Rule {
438
778
  // --- running it -----------------------------------------------------------------------------------------------------
439
779
  // Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
440
780
  encodeParams(values) { return encode(this.spec.params, values); }
441
- encodeInput(values) { return encode(this.spec.inputs, values); }
781
+ // A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural.
782
+ encodeInput(values) {
783
+ const out = encode(this.spec.inputs, values);
784
+ for (const n of this.spec.carried ?? []) {
785
+ const given = values[n];
786
+ out[n] = given instanceof Map ? new Map(given) : new Map([...given].map(key => [key, 1]));
787
+ }
788
+ return out;
789
+ }
442
790
  // Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
443
791
  randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
444
- randomInput(rand, bound) { return Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound))); }
792
+ randomInput(rand, bound) {
793
+ const out = Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound)));
794
+ for (const n of this.spec.carried ?? [])
795
+ out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
796
+ return out;
797
+ }
445
798
  // The prune order's variable, if the history is pruned (a runtime must send it nondecreasing).
446
799
  get orderedBy() { return this.spec.prune ? new Map(this.spec.inputs).get(this.spec.prune.order).vars(this.spec.prune.order)[0] : undefined; }
447
800
  interpreter() { return interpret(this.machine); }
@@ -522,5 +875,9 @@ export function proveSteps(r, laws, stepLaws) {
522
875
  const out = proveStepLaws(r.machine, laws, stepLaws);
523
876
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}) } : {}) };
524
877
  }
878
+ export function proveLeadsTo(r, laws, l) {
879
+ const out = proveLiveness(r.machine, laws, l);
880
+ return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}) } : {}) };
881
+ }
525
882
  export const proveDecision = (d) => goalsVerdict([d.goal]);
526
883
  export const proveLoop = (l, laws) => goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
@@ -43,6 +43,11 @@ export type C = {
43
43
  } | {
44
44
  op: 'flag';
45
45
  name: string;
46
+ } | {
47
+ op: 'all';
48
+ key: string;
49
+ c: C;
50
+ witness?: E;
46
51
  };
47
52
  export type Machine = {
48
53
  name: string;
@@ -50,17 +55,21 @@ export type Machine = {
50
55
  inputs: string[];
51
56
  state: Record<string, E>;
52
57
  maps?: Record<string, number>;
58
+ inputMaps?: string[];
53
59
  step: {
54
60
  flags?: Record<string, C>;
55
61
  state?: Record<string, E>;
56
- maps?: Record<string, {
57
- key: E;
58
- value: E;
59
- }>;
62
+ maps?: Record<string, MapUpdate>;
60
63
  };
61
64
  pre?: C;
62
65
  assume?: C;
63
66
  history?: History;
67
+ fns?: Record<string, Fn>;
68
+ facts?: Law[];
69
+ };
70
+ export type Fn = {
71
+ js: string;
72
+ lean?: string;
64
73
  };
65
74
  export type History = {
66
75
  keep: C;
@@ -76,10 +85,24 @@ export type History = {
76
85
  after: E;
77
86
  };
78
87
  };
88
+ export type MapUpdate = {
89
+ key: E;
90
+ value: E;
91
+ } | {
92
+ each: string;
93
+ value: E;
94
+ };
95
+ export declare const pointWrite: (w: MapUpdate, where: string) => {
96
+ key: E;
97
+ value: E;
98
+ };
99
+ export declare const noQuantifier: (where: string) => never;
79
100
  export type Law = {
80
101
  name: string;
81
102
  holds: C;
82
103
  forall?: string;
104
+ keys?: string[];
105
+ stated?: C;
83
106
  };
84
107
  export { unreachable } from '../kernel/kernel.js';
85
108
  export declare const x: (name: string) => E;
@@ -102,17 +125,22 @@ export declare const bit: (name: string) => C;
102
125
  export type State = {
103
126
  scalars: Record<string, number>;
104
127
  maps: Record<string, Map<number, number>>;
128
+ defaults?: Record<string, number>;
105
129
  history?: Env[];
106
130
  last?: number;
107
131
  };
108
132
  type Env = Record<string, number>;
133
+ export type Request = Record<string, number | ReadonlyMap<number, number>>;
109
134
  export declare const naturalIn: (v: unknown, what: string) => number;
110
135
  export declare function evalE(e: E, env: Env, st: State, m: Machine): number;
136
+ export declare const fnValue: (fn: Fn, key: number) => number;
111
137
  export declare function evalC(c: C, env: Env, st: State, m: Machine): boolean;
138
+ export declare function carriedView(m: Machine, st: State, input: Request, rename?: (map: string) => string): State;
139
+ export declare const numbersOf: (input: Request) => Record<string, number>;
112
140
  export declare function interpret(m: Machine): {
113
141
  init: (params: Env) => State;
114
- queries: (params: Env, st: State, input: Env) => Env;
115
- step: (params: Env, st: State, input: Env) => State;
142
+ queries: (params: Env, st: State, input: Request) => Env;
143
+ step: (params: Env, given: State, input: Request) => State;
116
144
  };
117
145
  export declare function compileJs(m: Machine): string;
118
146
  export declare function compileFunction<F>(params: string[], body: string): F;