gdp-wired 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core/dsl.js CHANGED
@@ -84,6 +84,16 @@ export const max = (a, b) => new Term({ op: 'max', a: termOf(a), b: termOf(b) },
84
84
  export function when(c, then, otherwise) {
85
85
  if (then instanceof Cond && otherwise instanceof Cond)
86
86
  return choose(c, then, otherwise);
87
+ if (then instanceof Variant && otherwise instanceof Variant) {
88
+ if (then.kind.names.join() !== otherwise.kind.names.join())
89
+ throw new Error('when: variants of other cases');
90
+ return new Variant(when(c, then.tag, otherwise.tag), Object.fromEntries(then.kind.names.map(k => [k, when(c, then.cases[k], otherwise.cases[k])])), then.kind);
91
+ }
92
+ if (isRecord(then) && isRecord(otherwise)) {
93
+ if (Object.keys(then).sort().join() !== Object.keys(otherwise).sort().join())
94
+ throw new Error('when: records of other fields');
95
+ return Object.fromEntries(Object.keys(then).map(f => [f, when(c, then[f], otherwise[f])]));
96
+ }
87
97
  if (then instanceof Choice && otherwise instanceof Choice)
88
98
  return new Choice(when(c, then.n, otherwise.n), then.values);
89
99
  if (then instanceof Str && otherwise instanceof Str)
@@ -105,12 +115,53 @@ function pickNumbers(c, then, otherwise) {
105
115
  }
106
116
  // A condition by cases: `then` where c holds, `otherwise` elsewhere.
107
117
  export const choose = (c, then, otherwise) => any(all(c, then), all(c.not(), otherwise));
118
+ // A record's value: a plain object of its fields' values (not a term, a condition or another value of a kind).
119
+ const isRecord = (x) => typeof x === 'object' && x !== null && Object.getPrototypeOf(x) === Object.prototype;
120
+ // The same value, part by part: numbers equal, conditions both or neither, choices and strings alike, an optional value
121
+ // missing in both or present in both and the same, a variant in the same case with the same fields, a record field by
122
+ // field.
123
+ export function same(a, b) {
124
+ if (a instanceof Cond && b instanceof Cond)
125
+ return a.iff(b);
126
+ if (a instanceof Choice && b instanceof Choice)
127
+ return a.n.eq(b.n);
128
+ if (a instanceof Str && b instanceof Str)
129
+ return a.eq(b);
130
+ if (a instanceof Decimal)
131
+ return a.eq(b);
132
+ if (b instanceof Decimal)
133
+ return b.eq(a);
134
+ if (a instanceof Int)
135
+ return a.eq(b);
136
+ if (b instanceof Int)
137
+ return b.eq(a);
138
+ if (a instanceof Maybe && b instanceof Maybe)
139
+ return all(a.present.eq(b.present), a.isNone.or(same(a.inner, b.inner)));
140
+ if (a instanceof Variant && b instanceof Variant) {
141
+ if (a.kind.names.join() !== b.kind.names.join())
142
+ throw new Error('same: variants of other cases');
143
+ return all(a.tag.eq(b.tag), ...a.kind.names.filter(k => Object.keys(a.cases[k]).length).map(k => a.is(k).implies(same(a.cases[k], b.cases[k]))));
144
+ }
145
+ if (isRecord(a) && isRecord(b)) {
146
+ if (Object.keys(a).sort().join() !== Object.keys(b).sort().join())
147
+ throw new Error('same: records of other fields');
148
+ return all(...Object.keys(a).map(f => same(a[f], b[f])));
149
+ }
150
+ if ((a instanceof Term || typeof a === 'number') && (b instanceof Term || typeof b === 'number'))
151
+ return (typeof a === 'number' ? lit(a) : a).eq(b);
152
+ throw new Error('same: two values of one kind');
153
+ }
108
154
  // 1 where c holds, else 0: what a sum over a history adds per record.
109
155
  export const count = (c) => when(c, 1, 0);
110
156
  export const sum = (first, ...rest) => rest.reduce((acc, t) => acc.plus(t), typeof first === 'number' ? lit(first) : first);
111
157
  const named = (at, n) => (typeof at === 'string' ? at + n : at(n));
112
158
  const primed = (n) => `${n}'`;
113
159
  export class Kind {
160
+ value(name, at = '') { return this.build(name, part => v(named(at, part))); }
161
+ // What every value a step writes keeps true of its variables (a flag is 0 or 1, a choice or a case in range, a
162
+ // presence bit 0 or 1), unlike a bound such as upTo's, which a step may pass: the laws a rule's state and cells of the
163
+ // kind are proven to keep (Rule.kindLaws).
164
+ invariant(_value) { return []; }
114
165
  // A runtime value of the kind (what encode takes), for comparing a rule with the code it describes (compare).
115
166
  sample(_rand, _bound) { throw new Error('this kind draws no runtime values'); }
116
167
  // A state variable of this kind starting at a runtime value, or at a value over the params: `.state({open: bool.from(true)})`.
@@ -235,7 +286,7 @@ export class Decimal {
235
286
  }
236
287
  const asNat = (name, raw) => {
237
288
  if (typeof raw !== 'number' || !Number.isSafeInteger(raw) || raw < 0)
238
- throw new RangeError(`${name} must be a natural, not ${String(raw)}`);
289
+ throw new RangeError(`${name} must be a natural, not ${shown(raw)}`);
239
290
  return raw;
240
291
  };
241
292
  class NatKind extends Kind {
@@ -246,7 +297,7 @@ class NatKind extends Kind {
246
297
  }
247
298
  vars(name) { return [name]; }
248
299
  assume(name) { return this.maxValue === undefined ? [] : [v(name).atMost(this.maxValue)]; }
249
- value(name, at = '') { return v(named(at, name)); }
300
+ build(name, get) { return get(name); }
250
301
  store(name, value) { return { [name]: value }; }
251
302
  decode(name, vars) { return vars[name]; }
252
303
  empty() { return 0; }
@@ -273,7 +324,8 @@ export class Bool extends Cond {
273
324
  class BoolKind extends Kind {
274
325
  vars(name) { return [name]; }
275
326
  assume(name) { return [v(name).atMost(1)]; }
276
- value(name, at = '') { return new Bool(v(named(at, name))); }
327
+ build(name, get) { return new Bool(get(name)); }
328
+ invariant(b) { return [b.n.atMost(1)]; }
277
329
  store(name, value) { return { [name]: typeof value === 'boolean' ? (value ? 1 : 0) : count(value) }; }
278
330
  decode(name, vars) { return vars[name] === 1; }
279
331
  empty() { return false; }
@@ -282,7 +334,7 @@ class BoolKind extends Kind {
282
334
  return { [name]: raw ? 1 : 0 };
283
335
  if (raw === 0 || raw === 1)
284
336
  return { [name]: raw };
285
- throw new RangeError(`${name} must be a boolean, not ${String(raw)}`);
337
+ throw new RangeError(`${name} must be a boolean, not ${shown(raw)}`);
286
338
  }
287
339
  random(name, rand) { return { [name]: rand(2) }; }
288
340
  sample(rand) { return rand(2) === 1; }
@@ -322,14 +374,15 @@ class OneOfKind extends Kind {
322
374
  }
323
375
  vars(name) { return [name]; }
324
376
  assume(name) { return [v(name).lt(this.values.length)]; }
325
- value(name, at = '') { return new Choice(v(named(at, name)), this.values); }
377
+ build(name, get) { return new Choice(get(name), this.values); }
378
+ invariant(c) { return [c.n.lt(this.values.length)]; }
326
379
  store(name, value) { return { [name]: value instanceof Choice ? value.n : this.encode(name, value)[name] }; }
327
380
  decode(name, vars) { return this.values[vars[name]]; }
328
381
  empty() { return this.values[0]; }
329
382
  encode(name, raw) {
330
383
  const i = this.values.indexOf(raw);
331
384
  if (i < 0)
332
- throw new RangeError(`${name} is one of ${this.values.join(', ')}, not ${String(raw)}`);
385
+ throw new RangeError(`${name} is one of ${this.values.join(', ')}, not ${shown(raw)}`);
333
386
  return { [name]: i };
334
387
  }
335
388
  random(name, rand) { return { [name]: rand(this.values.length) }; }
@@ -366,7 +419,8 @@ class MaybeKind extends Kind {
366
419
  has(name) { return `${name}$some`; }
367
420
  vars(name) { return [this.has(name), ...this.of.vars(name)]; }
368
421
  assume(name) { return [v(this.has(name)).atMost(1), ...this.of.assume(name)]; }
369
- value(name, at = '') { return new Maybe(v(named(at, this.has(name))), this.of.value(name, at)); }
422
+ build(name, get) { return new Maybe(get(this.has(name)), this.of.build(name, get)); }
423
+ invariant(m) { return [m.present.atMost(1), ...this.of.invariant(m.inner)]; }
370
424
  // null for none, a Maybe as it is, any other value as some.
371
425
  store(name, value) {
372
426
  if (value === null || value === undefined)
@@ -388,6 +442,168 @@ class MaybeKind extends Kind {
388
442
  sample(rand, bound) { return rand(4) === 0 ? null : this.of.sample(rand, bound); }
389
443
  }
390
444
  export const maybe = (of) => new MaybeKind(of);
445
+ // A value present where c holds and missing elsewhere (a function's result that may be missing, say).
446
+ export const optional = (c, value) => new Maybe(count(c), value);
447
+ const fieldName = (name, f) => `${name}.${f}`;
448
+ const plainFields = (what, names) => { for (const f of names)
449
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
450
+ throw new Error(`${what}: ${f} is not a plain field name`); };
451
+ const rawObject = (name, raw, what) => {
452
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
453
+ throw new RangeError(`${name} must be ${what}, not ${String(raw)}`);
454
+ return raw;
455
+ };
456
+ // Runtime values alike: objects field by field.
457
+ const sameRaw = (a, b) => {
458
+ if (a === b)
459
+ return true;
460
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
461
+ return false;
462
+ const ka = Object.keys(a), kb = Object.keys(b);
463
+ return ka.length === kb.length && ka.every(k => sameRaw(a[k], b[k]));
464
+ };
465
+ const shown = (x) => (typeof x === 'object' && x !== null ? JSON.stringify(x) : String(x));
466
+ // r.<field> is a history record's field to the runtimes.
467
+ const composite = (name) => { if (name === 'r')
468
+ throw new Error('r names a history record; give the record or variant another name'); };
469
+ const merged = (parts) => Object.assign({}, ...parts);
470
+ class RecordKind extends Kind {
471
+ fields;
472
+ names;
473
+ constructor(fields) {
474
+ super();
475
+ this.fields = fields;
476
+ this.names = Object.keys(fields);
477
+ if (this.names.length === 0)
478
+ throw new Error('a record has a field');
479
+ plainFields('record', this.names);
480
+ }
481
+ each(f) { return this.names.map(n => f(n, this.fields[n])); }
482
+ vars(name) { composite(name); return this.each((f, k) => k.vars(fieldName(name, f))).flat(); }
483
+ assume(name) { return this.each((f, k) => k.assume(fieldName(name, f))).flat(); }
484
+ build(name, get) { return Object.fromEntries(this.each((f, k) => [f, k.build(fieldName(name, f), get)])); }
485
+ invariant(r) { return this.each((f, k) => k.invariant(r[f])).flat(); }
486
+ store(name, value) {
487
+ const o = value;
488
+ if (o === null || typeof o !== 'object')
489
+ throw new Error(`${name}: a record is written as an object of its fields`);
490
+ for (const f of Object.keys(o))
491
+ if (!(f in this.fields))
492
+ throw new Error(`${name}: the record has no field ${f}`);
493
+ return Object.assign({}, ...this.each((f, k) => { if (!(f in o))
494
+ throw new Error(`${name}: the write leaves out the field ${f}`); return k.store(fieldName(name, f), o[f]); }));
495
+ }
496
+ decode(name, vars) { return Object.fromEntries(this.each((f, k) => [f, k.decode(fieldName(name, f), vars)])); }
497
+ empty() { return Object.fromEntries(this.each((f, k) => [f, k.empty()])); }
498
+ encode(name, raw) {
499
+ const o = rawObject(name, raw, `an object with the fields ${this.names.join(', ')}`);
500
+ for (const f of Object.keys(o))
501
+ if (!(f in this.fields))
502
+ throw new RangeError(`${name} has no field ${f}`);
503
+ return merged(this.each((f, k) => k.encode(fieldName(name, f), o[f])));
504
+ }
505
+ random(name, rand, bound) { return merged(this.each((f, k) => k.random(fieldName(name, f), rand, bound))); }
506
+ sample(rand, bound) { return Object.fromEntries(this.each((f, k) => [f, k.sample(rand, bound)])); }
507
+ }
508
+ export const record = (fields) => new RecordKind(fields);
509
+ export class Variant {
510
+ tag;
511
+ cases;
512
+ kind;
513
+ constructor(tag, cases, kind) {
514
+ this.tag = tag;
515
+ this.cases = cases;
516
+ this.kind = kind;
517
+ }
518
+ is(k) { return this.tag.eq(this.kind.index(k)); }
519
+ match(cases) {
520
+ const results = this.kind.names.map(k => [k, cases[k](this.cases[k])]);
521
+ if (results.every(([, r]) => r instanceof Cond))
522
+ return any(...results.map(([k, r]) => all(this.is(k), r)));
523
+ // The last case is what's left: the kind's assumption says the index is below the number of cases.
524
+ return results.slice(0, -1).reduceRight((acc, [k, r]) => when(this.is(k), r, acc), results.at(-1)[1]);
525
+ }
526
+ }
527
+ export class VariantKind extends Kind {
528
+ cases;
529
+ tagKey;
530
+ names;
531
+ constructor(cases, tagKey) {
532
+ super();
533
+ this.cases = cases;
534
+ this.tagKey = tagKey;
535
+ this.names = Object.keys(cases);
536
+ if (this.names.length === 0)
537
+ throw new Error('a variant has a case');
538
+ plainFields('variant', this.names);
539
+ for (const k of this.names) {
540
+ plainFields(`variant case ${k}`, Object.keys(cases[k]));
541
+ if (tagKey in cases[k])
542
+ throw new Error(`variant case ${k}: ${tagKey} names the case, not a field`);
543
+ }
544
+ }
545
+ index(k) {
546
+ const i = this.names.indexOf(k);
547
+ if (i < 0)
548
+ throw new Error(`${k} is not a case of ${this.names.join(', ')}`);
549
+ return i;
550
+ }
551
+ payload(k) { return new RecordKind(this.cases[k]); }
552
+ filled(k) { return Object.keys(this.cases[k]).length > 0; }
553
+ at(name, k) { return fieldName(name, k); }
554
+ vars(name) { composite(name); return [name, ...this.names.filter(k => this.filled(k)).flatMap(k => this.payload(k).vars(this.at(name, k)))]; }
555
+ assume(name) { return [v(name).lt(this.names.length), ...this.names.filter(k => this.filled(k)).flatMap(k => this.payload(k).assume(this.at(name, k)))]; }
556
+ build(name, get) {
557
+ return new Variant(get(name), Object.fromEntries(this.names.map(k => [k, this.filled(k) ? this.payload(k).build(this.at(name, k), get) : {}])), this);
558
+ }
559
+ // The value of a case with these fields (terms, values or runtime values), to compare, match or pass to `when`:
560
+ // when(held, At.make({tag: 'holding', amount, since: now}), at)
561
+ invariant(x) { return [x.tag.lt(this.names.length), ...this.names.filter(k => this.filled(k)).flatMap(k => this.payload(k).invariant(x.cases[k]))]; }
562
+ make(write) {
563
+ const parts = this.store('$', write);
564
+ return this.build('$', part => { const t = parts[part]; return typeof t === 'number' ? lit(t) : t; });
565
+ }
566
+ // A Variant of this kind as it is, or {tag: case, ...fields} (fields as values or runtime values): the other cases'
567
+ // fields are written empty.
568
+ store(name, value) {
569
+ if (value instanceof Variant) {
570
+ if (value.kind.names.join() !== this.names.join())
571
+ throw new Error(`${name}: a variant of other cases is written`);
572
+ return { [name]: value.tag, ...Object.assign({}, ...this.names.filter(k => this.filled(k)).map(k => this.payload(k).store(this.at(name, k), value.cases[k]))) };
573
+ }
574
+ const o = value, k = o?.[this.tagKey];
575
+ if (typeof k !== 'string')
576
+ throw new Error(`${name}: a variant is written as {${this.tagKey}: case, ...fields}`);
577
+ const { [this.tagKey]: _, ...fields } = o;
578
+ return { [name]: this.index(k), ...Object.assign({}, ...this.names.filter(c => this.filled(c)).map(c => this.payload(c).store(this.at(name, c), c === k ? fields : this.payload(c).empty()))) };
579
+ }
580
+ decode(name, vars) {
581
+ const k = this.names[vars[name]];
582
+ return { [this.tagKey]: k, ...(this.filled(k) ? this.payload(k).decode(this.at(name, k), vars) : {}) };
583
+ }
584
+ empty() { const k = this.names[0]; return { [this.tagKey]: k, ...(this.filled(k) ? this.payload(k).empty() : {}) }; }
585
+ encode(name, raw) {
586
+ const o = rawObject(name, raw, `an object whose ${this.tagKey} is one of ${this.names.join(', ')}`), k = o[this.tagKey];
587
+ if (typeof k !== 'string' || !this.names.includes(k))
588
+ throw new RangeError(`${name}.${this.tagKey} is one of ${this.names.join(', ')}, not ${shown(k)}`);
589
+ const { [this.tagKey]: _, ...fields } = o;
590
+ if (!this.filled(k) && Object.keys(fields).length)
591
+ throw new RangeError(`${name}: the case ${k} has no fields`);
592
+ const zeros = (c) => Object.fromEntries(this.payload(c).vars(this.at(name, c)).map(n => [n, 0]));
593
+ return { [name]: this.index(k), ...merged(this.names.filter(c => this.filled(c)).map(c => (c === k ? this.payload(c).encode(this.at(name, c), fields) : zeros(c)))) };
594
+ }
595
+ random(name, rand, bound) {
596
+ const i = rand(this.names.length);
597
+ return { [name]: i, ...merged(this.names.filter(c => this.filled(c)).map(c => this.payload(c).random(this.at(name, c), rand, bound))) };
598
+ }
599
+ sample(rand, bound) { const k = this.names[rand(this.names.length)]; return { [this.tagKey]: k, ...(this.filled(k) ? this.payload(k).sample(rand, bound) : {}) }; }
600
+ }
601
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
602
+ export function variant(cases, opts = {}) {
603
+ if (opts.tag !== undefined && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(opts.tag))
604
+ throw new Error(`variant: ${opts.tag} is not a plain key`);
605
+ return new VariantKind(cases, opts.tag ?? 'tag');
606
+ }
391
607
  // A string, encoded as its interned name, its length and one bit per pattern the kind declares (`like`, Cedar's glob:
392
608
  // `*` any run of characters, `\*` a star): all computed from the string itself when the input is encoded, so the bits
393
609
  // are the string's, not an answer someone supplies. Names come from one table per process, the empty string 0; a law
@@ -480,7 +696,7 @@ class StrKind extends Kind {
480
696
  return [v(name).eq(0).iff(v(`${name}$len`).eq(0)), ...(this.maxLength === undefined ? [] : [v(`${name}$len`).atMost(this.maxLength)]),
481
697
  ...this.patterns.map((p, i) => (p.replaceAll('\\*', '').includes('*') ? v(this.bit(name, i)).atMost(1) : v(this.bit(name, i)).eq(1).iff(v(name).eq(literal(p.replaceAll('\\*', '*'))))))];
482
698
  }
483
- value(name, at = '') { return new Str(v(named(at, name)), v(named(at, `${name}$len`)), new Map(this.patterns.map((p, i) => [p, v(named(at, this.bit(name, i)))]))); }
699
+ build(name, get) { return new Str(get(name), get(`${name}$len`), new Map(this.patterns.map((p, i) => [p, get(this.bit(name, i))]))); }
484
700
  store(name, value) {
485
701
  if (typeof value === 'string')
486
702
  return { [name]: literal(value), [`${name}$len`]: value.length, ...Object.fromEntries(this.patterns.map((p, i) => [this.bit(name, i), globMatch(p, value) ? 1 : 0])) };
@@ -493,7 +709,7 @@ class StrKind extends Kind {
493
709
  empty() { return ''; }
494
710
  encode(name, raw) {
495
711
  if (typeof raw !== 'string')
496
- throw new RangeError(`${name} must be a string, not ${String(raw)}`);
712
+ throw new RangeError(`${name} must be a string, not ${shown(raw)}`);
497
713
  if (this.maxLength !== undefined && raw.length > this.maxLength)
498
714
  throw new RangeError(`${name} is at most ${this.maxLength} characters`);
499
715
  return { [name]: intern(raw), [`${name}$len`]: raw.length, ...Object.fromEntries(this.patterns.map((p, i) => [this.bit(name, i), globMatch(p, raw) ? 1 : 0])) };
@@ -513,13 +729,13 @@ export const str = (opts = {}) => new StrKind(opts.like ?? [], opts.maxLength);
513
729
  class IntKind extends Kind {
514
730
  vars(name) { return [name, `${name}$neg`]; }
515
731
  assume() { return []; }
516
- value(name, at = '') { return new Int(v(named(at, name)), v(named(at, `${name}$neg`))); }
732
+ build(name, get) { return new Int(get(name), get(`${name}$neg`)); }
517
733
  store(name, value) { const i = normal(Int.of(value)); return { [name]: i.pos, [`${name}$neg`]: i.neg }; }
518
734
  decode(name, vars) { return vars[name] - vars[`${name}$neg`]; }
519
735
  empty() { return 0; }
520
736
  encode(name, raw) {
521
737
  if (typeof raw !== 'number' || !Number.isSafeInteger(raw))
522
- throw new RangeError(`${name} must be an integer, not ${String(raw)}`);
738
+ throw new RangeError(`${name} must be an integer, not ${shown(raw)}`);
523
739
  return { [name]: Math.max(raw, 0), [`${name}$neg`]: Math.max(-raw, 0) };
524
740
  }
525
741
  random(name, rand, bound) { const x = rand(2 * bound + 1) - bound; return { [name]: Math.max(x, 0), [`${name}$neg`]: Math.max(-x, 0) }; }
@@ -535,7 +751,7 @@ class DecimalKind extends Kind {
535
751
  }
536
752
  vars(name) { return [name, `${name}$neg`]; }
537
753
  assume() { return []; }
538
- value(name, at = '') { return new Decimal(new Int(v(named(at, name)), v(named(at, `${name}$neg`))), this.places); }
754
+ build(name, get) { return new Decimal(new Int(get(name), get(`${name}$neg`)), this.places); }
539
755
  store(name, value) {
540
756
  const i = normal(typeof value === 'string' ? Int.of(scaledOf(value, this.places)) : new Decimal(Int.of(0), this.places).lift(value));
541
757
  return { [name]: i.pos, [`${name}$neg`]: i.neg };
@@ -544,7 +760,7 @@ class DecimalKind extends Kind {
544
760
  empty() { return 0; }
545
761
  encode(name, raw) {
546
762
  if (typeof raw !== 'number' && typeof raw !== 'string')
547
- throw new RangeError(`${name} must be a decimal, not ${String(raw)}`);
763
+ throw new RangeError(`${name} must be a decimal, not ${shown(raw)}`);
548
764
  const x = scaledOf(raw, this.places, `a decimal (${name})`);
549
765
  return { [name]: Math.max(x, 0), [`${name}$neg`]: Math.max(-x, 0) };
550
766
  }
@@ -624,6 +840,64 @@ export class ListRef {
624
840
  increasing() { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(this.at(i).lt(this.at(j)))); }
625
841
  }
626
842
  const when_ = (c, then, otherwise) => new Term({ op: 'if', c: c.c, then: termOf(then), else: termOf(otherwise) }, c.ok === undefined ? undefined : valOf(c.ok ? then : otherwise));
843
+ const isPartsWrite = (u) => typeof u === 'object' && u !== null && 'parts' in u;
844
+ // The parts of a value written at a cell, by the names of the maps they go to.
845
+ const partsOf = (kind, name, value) => kind.store(name, value);
846
+ // A map whose cells hold values of a kind (a record, a variant, a choice...): one map per variable of the kind
847
+ // (<name>.<field>, and the case's index as <name> for a variant), every cell starting at the kind's value given by
848
+ // `from`. `at(k)` is the value at k, read as the kind's value is; `set` and `each` write whole values.
849
+ // .maps({Acct: record({limit: nat, blocked: bool}).from({limit: 0, blocked: false})})
850
+ // .step(({who, Acct}) => ({Acct: Acct.set(who, {limit: 10, blocked: false})}))
851
+ export class CellsRef {
852
+ name;
853
+ kind;
854
+ map;
855
+ constructor(name, kind, map) {
856
+ this.name = name;
857
+ this.kind = kind;
858
+ this.map = map;
859
+ }
860
+ at(key) { return this.kind.build(this.name, part => this.map(part).at(key)); }
861
+ set(key, value) {
862
+ const parts = partsOf(this.kind, this.name, value);
863
+ return { parts: Object.fromEntries(this.kind.vars(this.name).map(part => [part, this.map(part).set(key, parts[part])])) };
864
+ }
865
+ // Every key at once (the key read only as a map's key).
866
+ each(f) {
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
+ }
869
+ }
870
+ export class ItemsRef {
871
+ name;
872
+ kind;
873
+ len;
874
+ list;
875
+ constructor(name, kind, len, list) {
876
+ this.name = name;
877
+ this.kind = kind;
878
+ this.len = len;
879
+ this.list = list;
880
+ }
881
+ get names() { return this.kind.vars(this.name); }
882
+ at(i) { return this.kind.build(this.name, part => this.list(part).at(i)); }
883
+ // x at some position.
884
+ has(x) { return everyKey(k => k.lt(this.len).implies(same(this.at(k), x).not())).not(); }
885
+ op(when, len, f) { return { when, len, parts: Object.fromEntries(this.names.map(part => [part, f(this.list(part), part)])) }; }
886
+ push(x, when = yes) { const p = partsOf(this.kind, this.name, x); return this.op(when, this.len.plus(1), (l, part) => l.push(p[part], when)); }
887
+ pop(when = yes) { return this.op(when, this.len.minus(1), l => l.pop(when)); }
888
+ set(i, x, when = yes) { const p = partsOf(this.kind, this.name, x); return this.op(when, this.len, (l, part) => l.set(i, p[part], when)); }
889
+ insertAt(i, x, when = yes) { const p = partsOf(this.kind, this.name, x); return this.op(when, this.len.plus(1), (l, part) => l.insertAt(i, p[part], when)); }
890
+ removeAt(i, when = yes) { return this.op(when, this.len.minus(1), l => l.removeAt(i, when)); }
891
+ apply(...ops) {
892
+ const parts = {};
893
+ for (const part of this.names)
894
+ parts[part] = this.list(part).apply(...ops.map(op => op.parts[part]))[part];
895
+ return { [this.name]: { parts }, [`${this.name}_len`]: ops.reduceRight((acc, op) => when_(op.when, op.len, acc), this.len) };
896
+ }
897
+ // Laws: nothing past the end; the items in strictly increasing order of `by`.
898
+ within() { return forall(k => this.len.atMost(k).implies(all(...this.names.map(part => this.list(part).at(k).eq(0))))); }
899
+ increasing(by) { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(by(this.at(i)).lt(by(this.at(j))))); }
900
+ }
627
901
  // A law about every key of a map: proven at a fresh key, assumed at the keys a step reads.
628
902
  // With two parameters or more, about every two keys or more: forall((i, j) => i.lt(j).implies(...)).
629
903
  export function forall(f) { return { forall: f }; }
@@ -827,7 +1101,142 @@ export const modBy = (c) => {
827
1101
  [`r(x + ${c}) = r(x)`]: forall(x => f(x.plus(c)).eq(f(x))),
828
1102
  }) });
829
1103
  };
830
- 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 } });
1104
+ const reservedInRecur = ['x', 'fx', 'y', 'fy', 'prev', 'x0', 'fx0'];
1105
+ export const recur = (name, def, lemmas = {}, between = {}) => {
1106
+ const plain = /^[A-Za-z][A-Za-z0-9_]*$/;
1107
+ if (!plain.test(name))
1108
+ throw new Error(`recur: ${name} is not a plain name (it names the Lean definition)`);
1109
+ const args = [...(def.args ?? [])], uses = Object.entries(def.fns ?? {});
1110
+ for (const n of [...args, ...uses.map(([u]) => u)])
1111
+ if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
1112
+ throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
1113
+ distinctNames(`recur ${name}`, [...args, ...uses.map(([u]) => u)]);
1114
+ if (args.length > 4)
1115
+ throw new Error(`recur ${name}: at most four further arguments`);
1116
+ const named = (n) => new Term({ op: 'var', name: n });
1117
+ const argsAt = (ts) => Object.fromEntries(args.map((a, i) => [a, ts[i]]));
1118
+ const usesAt = (at) => Object.fromEntries(uses.map(([u, d]) => [u, fnTerm(at(u), d)]));
1119
+ const envAt = (ts, at = u => u) => ({ ...argsAt(ts), ...usesAt(at) });
1120
+ const zeroAt = (ts, at = u => u) => (typeof def.zero === 'function' ? def.zero(envAt(ts, at)) : def.zero);
1121
+ // The recurrence over named variables (x, prev, the arguments; lemmas over x and fx; laws between over x, fx, y, fy),
1122
+ // the functions it reads by their own names: what the runtimes and the Lean export are made from.
1123
+ const own = args.map(named);
1124
+ const zero = termOf(zeroAt(own)), next = termOf(def.next(named('x'), named('prev'), envAt(own)));
1125
+ const only = (what, t, names, maps) => {
1126
+ if (JSON.stringify(t).includes('"op":"all"') || JSON.stringify(t).includes('"op":"flag"'))
1127
+ throw new Error(`recur ${name}: ${what} takes no quantifier`);
1128
+ for (const n of namesIn(t))
1129
+ if (!names.includes(n))
1130
+ throw new Error(`recur ${name}: ${what} reads ${n}`);
1131
+ for (const m of [...JSON.stringify(t).matchAll(/"op":"get","map":"([^"]+)"/g)].map(x => x[1]))
1132
+ if (!maps.includes(m))
1133
+ throw new Error(`recur ${name}: ${what} reads a function or a map it isn't given (${m})`);
1134
+ };
1135
+ only('zero', zero, args, uses.map(([u]) => u));
1136
+ only('next', next, ['x', 'prev', ...args], uses.map(([u]) => u));
1137
+ const stated = Object.entries(lemmas).map(([n, f]) => { const holds = f(named('x'), named('fx'), envAt(own)).c; only(`lemma "${n}"`, holds, ['x', 'fx', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
1138
+ const pairs = Object.entries(between).map(([n, f]) => { const holds = f(named('x'), named('fx'), named('y'), named('fy'), envAt(own)).c; only(`law between "${n}"`, holds, ['x', 'fx', 'y', 'fy', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
1139
+ const argKinds = Object.fromEntries(args.map(a => [a, nat]));
1140
+ const failed = (what, v) => { if (!(v.proven && v.checked))
1141
+ throw new Unproven(`${name}'s ${what}`, v); };
1142
+ // The lemmas: x counting up from 0 and fx its value, each lemma a law of that machine.
1143
+ let verdict = { proven: true, checked: true };
1144
+ const proofs = {};
1145
+ if (stated.length) {
1146
+ // A start that reads a function is a param given as that start by a fact (a start reads the params alone).
1147
+ const zeroReads = JSON.stringify(zero).includes('"op":"get"');
1148
+ let r0 = rule(`${name}, by induction`).params({ ...argKinds, ...(zeroReads ? { fx0: nat } : {}) }).fns(def.fns ?? {}).inputs({});
1149
+ if (zeroReads)
1150
+ r0 = r0.facts((p) => ({ [`fx0 is ${name}(0)`]: p.fx0.eq(zeroAt(args.map(a => p[a]))) }));
1151
+ const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
1152
+ .step((env) => ({ x: env.x.plus(1), fx: def.next(env.x, env.fx, envAt(args.map(a => env[a]))) }));
1153
+ const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
1154
+ verdict = proveRule(r, laws);
1155
+ failed('lemmas', verdict);
1156
+ proofs.lemmas = { machine: r.machine, laws };
1157
+ }
1158
+ const js = recurJs(name, args, zero, next, uses);
1159
+ const recurrence = { args, zero, next, lemmas: stated, between: pairs, uses: Object.fromEntries(uses.map(([u, d]) => [u, { arity: d.arity, ...(d.lean ? { lean: d.lean } : {}) }])), proofs };
1160
+ // A law about every n keys (forall counts them by the callback's own parameters).
1161
+ const keysOf = (n) => (f) => {
1162
+ if (n === 0)
1163
+ return f();
1164
+ 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),
1165
+ (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];
1166
+ return forall(by);
1167
+ };
1168
+ const facts = (withBetween) => (f, at = {}) => {
1169
+ const env = (ts) => ({ ...argsAt(ts), ...Object.fromEntries(uses.map(([u, d]) => [u, at[u] ?? fnTerm(u, d)])) });
1170
+ return {
1171
+ [`${name}(0)`]: keysOf(args.length)((...as) => f(0, ...as).eq(typeof def.zero === 'function' ? def.zero(env(as)) : def.zero)),
1172
+ [`${name}(x + 1)`]: keysOf(1 + args.length)((k, ...as) => f(k.plus(1), ...as).eq(def.next(k, f(k, ...as), env(as)))),
1173
+ ...Object.fromEntries(Object.entries(lemmas).map(([n, l]) => [n, keysOf(1 + args.length)((k, ...as) => l(k, f(k, ...as), env(as)))])),
1174
+ ...(withBetween ? Object.fromEntries(Object.entries(between).map(([n, l]) => [n, keysOf(2 + args.length)((k, j, ...as) => k.atMost(j).implies(l(k, f(k, ...as), j, f(j, ...as), env(as))))])) : {}),
1175
+ };
1176
+ };
1177
+ const base = { arity: 1 + args.length, js, lean: `Recur.${name}`, recur: recurrence, ...(uses.length ? { uses: def.fns } : {}) };
1178
+ // The laws between two points: y counting up from x and fy from fx, a value at x that meets the lemmas (as f(x)
1179
+ // does: they are given of it, as facts of the params), each law a law of that machine beside the lemmas at y. What
1180
+ // holds there for every such fx holds for f(x), and fy is f(y) by the recurrence; the machine reads no f at all.
1181
+ if (pairs.length) {
1182
+ const at = (env) => args.map(a => env[a]);
1183
+ let r0 = rule(`${name}, between`).params({ ...argKinds, x: nat, fx: nat }).fns(def.fns ?? {}).inputs({});
1184
+ if (stated.length)
1185
+ r0 = r0.facts((p) => Object.fromEntries(Object.entries(lemmas).map(([n, l]) => [`${n} at x`, l(p.x, p.fx, envAt(at(p)))])));
1186
+ const r = r0.state({ y: ((p) => p.x), fy: ((p) => p.fx) })
1187
+ .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
1188
+ const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
1189
+ ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
1190
+ const v = proveRule(r, laws);
1191
+ failed('laws between two points', v);
1192
+ proofs.between = { machine: r.machine, laws };
1193
+ verdict = { proven: true, checked: verdict.checked && v.checked };
1194
+ }
1195
+ return { ...base, verdict, facts: facts(true) };
1196
+ };
1197
+ const distinctNames = (what, names) => { if (new Set(names).size !== names.length)
1198
+ throw new Error(`${what}: a name given twice`); };
1199
+ // A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each as a function of its key,
1200
+ // as the runtimes call it).
1201
+ const recurJs = (name, args, zero, next, uses) => {
1202
+ const fns = new Map(uses.map(([u], i) => [u, `$f${i}`]));
1203
+ const js = (e) => {
1204
+ switch (e.op) {
1205
+ case 'var': return e.name;
1206
+ case 'const': return String(e.value);
1207
+ case 'add': return `(${js(e.a)} + ${js(e.b)})`;
1208
+ case 'sub': return `Math.max(0, ${js(e.a)} - ${js(e.b)})`;
1209
+ case 'mul': return `(${e.k} * ${js(e.a)})`;
1210
+ case 'prod': return `(${js(e.a)} * ${js(e.b)})`;
1211
+ case 'min': return `Math.min(${js(e.a)}, ${js(e.b)})`;
1212
+ case 'max': return `Math.max(${js(e.a)}, ${js(e.b)})`;
1213
+ case 'if': return `(${jc(e.c)} ? ${js(e.then)} : ${js(e.else)})`;
1214
+ case 'get': return `${fns.get(e.map)}(${js(e.key)})`;
1215
+ }
1216
+ };
1217
+ const jc = (c) => {
1218
+ switch (c.op) {
1219
+ case '<': return `(${js(c.a)} < ${js(c.b)})`;
1220
+ case '<=': return `(${js(c.a)} <= ${js(c.b)})`;
1221
+ case '=': return `(${js(c.a)} === ${js(c.b)})`;
1222
+ case 'and': return c.xs.length ? `(${c.xs.map(jc).join(' && ')})` : 'true';
1223
+ case 'or': return c.xs.length ? `(${c.xs.map(jc).join(' || ')})` : 'false';
1224
+ case 'not': return `(!${jc(c.x)})`;
1225
+ case 'flag':
1226
+ case 'all': throw new Error('a recurrence takes no flag or quantifier');
1227
+ }
1228
+ };
1229
+ const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); `).join('');
1230
+ // The last value computed at each tuple of further arguments is kept, so reading f at x after f at a smaller x takes
1231
+ // only the steps between (a rule that reads f at a count that grows costs one step a request).
1232
+ 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; }; })()`;
1233
+ };
1234
+ // Functions with the functions they read, those first (named <function>.<use>), each with the terms its facts read them by.
1235
+ const flatFns = (defs) => defs.flatMap(([n, d]) => {
1236
+ const uses = Object.entries(d.uses ?? {}).map(([u, ud]) => [`${n}.${u}`, ud]);
1237
+ return [...flatFns(uses), [n, d, Object.fromEntries(uses.map(([un, ud]) => [un.slice(n.length + 1), fnTerm(un, ud)]))]];
1238
+ });
1239
+ const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
831
1240
  const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
832
1241
  // A function's JavaScript as one function of its key: its arguments unpaired (left-nested, as argsKey pairs them).
833
1242
  const fnSource = (name, d) => {
@@ -842,7 +1251,7 @@ const fnSource = (name, d) => {
842
1251
  }
843
1252
  if (d.arity === 1)
844
1253
  return src;
845
- 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); }`;
1254
+ return `(() => { const $fn = (${src}); return k => { const u = z => { let s = Math.floor(Math.sqrt(z)); while (s * s > z) s--; while ((s + 1) * (s + 1) <= z) s++; return [s - (z - s * s), z - 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 $fn(...a); }; })()`;
846
1255
  };
847
1256
  const fnTerm = (name, d) => (...args) => {
848
1257
  if (args.length !== d.arity)
@@ -916,7 +1325,24 @@ export class Rule {
916
1325
  }
917
1326
  return this.with({ state: [...this.spec.state, ...entries], kinded: [...(this.spec.kinded ?? []), ...kinded] });
918
1327
  }
919
- maps(init) { return this.with({ maps: [...this.spec.maps, ...Object.entries(init)] }); }
1328
+ // A map of naturals starts every cell at a natural; a map of a kind at the kind's value: `record({...}).from({...})`.
1329
+ maps(init) {
1330
+ const maps = [], cells = [];
1331
+ for (const [n, x] of Object.entries(init)) {
1332
+ if (typeof x === 'number') {
1333
+ maps.push([n, x]);
1334
+ continue;
1335
+ }
1336
+ const { kind, init: start } = x;
1337
+ if (typeof start === 'function')
1338
+ throw new Error(`map ${n}: every cell starts at one value (not a term over the params)`);
1339
+ const enc = kind.encode(n, start);
1340
+ cells.push([n, kind]);
1341
+ for (const part of kind.vars(n))
1342
+ maps.push([part, enc[part]]);
1343
+ }
1344
+ return this.with({ maps: [...this.spec.maps, ...maps], cells: [...(this.spec.cells ?? []), ...cells] });
1345
+ }
920
1346
  // Integers (Int), each starting at an integer or at one over the params; kept as two naturals, <name> and <name>$neg.
921
1347
  ints(init) {
922
1348
  return this.state(Object.fromEntries(Object.entries(init).map(([n, x]) => [n, int.from(x)])));
@@ -930,6 +1356,13 @@ export class Rule {
930
1356
  lists(...names) {
931
1357
  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] });
932
1358
  }
1359
+ // Lists whose items are values of a kind, each empty at the start: one list per variable of the kind, of one length
1360
+ // <name>_len (ItemsRef).
1361
+ items(kinds) {
1362
+ const entries = Object.entries(kinds);
1363
+ return this.with({ maps: [...this.spec.maps, ...entries.flatMap(([n, k]) => k.vars(n).map(part => [part, 0]))],
1364
+ state: [...this.spec.state, ...entries.map(([n]) => [`${n}_len`, 0])], items: [...(this.spec.items ?? []), ...entries] });
1365
+ }
933
1366
  // Functions defined outside the rule (fn, divBy, modBy), read like params that take arguments: f(x).
934
1367
  fns(defs) {
935
1368
  return this.with({ fns: [...(this.spec.fns ?? []), ...Object.entries(defs)] });
@@ -979,6 +1412,22 @@ export class Rule {
979
1412
  const [k1, more] = keysFor(out), holds = out.forall(v(k1), ...more.map(v)).c;
980
1413
  return more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds };
981
1414
  }
1415
+ // What the kinds keep true of the state and of the maps and lists of a kind (Kind.invariant: a flag 0 or 1, a choice or
1416
+ // a case in range, a presence bit 0 or 1), as laws: proveRule, proveSteps and proveLeadsTo prove them beside the laws
1417
+ // they are given, so a law may lean on "the hold is idle, holding or released" without stating it; give them to
1418
+ // toLean (or the kernel) with the rest.
1419
+ kindLaws() {
1420
+ const out = [];
1421
+ const of = (env, n) => env[n];
1422
+ for (const [n, k] of this.spec.kinded ?? [])
1423
+ if (k.invariant(k.value(n)).length)
1424
+ out.push(this.law(`${n} is a value of its kind`, env => all(...k.invariant(of(env, n)))));
1425
+ for (const [n, k] of [...this.spec.cells ?? [], ...this.spec.items ?? []])
1426
+ if (k.invariant(k.value(n)).length) {
1427
+ out.push(this.law(`every ${n} cell is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
1428
+ }
1429
+ return out;
1430
+ }
982
1431
  // A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
983
1432
  // the request, state, maps, flags, sums, queries) to the state, maps and sums after it. `when` narrows the requests.
984
1433
  // counter.stepLaw('never goes back', ({used}, after) => used.atMost(after.used))
@@ -1073,6 +1522,7 @@ export class Rule {
1073
1522
  after[n] = new MapRef(`${n}'`, init);
1074
1523
  for (const n of this.spec.lists ?? [])
1075
1524
  after[n] = new ListRef(`${n}'`, v(`${n}_len'`));
1525
+ this.composites(after, part => `${part}'`, n => v(`${n}_len'`));
1076
1526
  for (const [n, k] of this.spec.kinded ?? []) {
1077
1527
  for (const part of k.vars(n))
1078
1528
  delete after[part];
@@ -1101,6 +1551,7 @@ export class Rule {
1101
1551
  out[n] = new MapRef(n, init);
1102
1552
  for (const n of this.spec.lists ?? [])
1103
1553
  out[n] = new ListRef(n, v(`${n}_len`));
1554
+ this.composites(out, part => part, n => v(`${n}_len`));
1104
1555
  for (const [n] of this.spec.sums)
1105
1556
  out[n] = v(n);
1106
1557
  for (const [n, k] of this.spec.kinded ?? []) {
@@ -1121,12 +1572,27 @@ export class Rule {
1121
1572
  }
1122
1573
  return out;
1123
1574
  }
1575
+ // Maps and lists of a kind in place of their parts' maps (and a list's length), each part's map named by `at`.
1576
+ composites(out, at, len) {
1577
+ const init = new Map(this.spec.maps);
1578
+ for (const [n, k] of this.spec.cells ?? []) {
1579
+ for (const part of k.vars(n))
1580
+ delete out[part];
1581
+ out[n] = new CellsRef(n, k, part => new MapRef(at(part), init.get(part)));
1582
+ }
1583
+ for (const [n, k] of this.spec.items ?? []) {
1584
+ for (const part of k.vars(n))
1585
+ delete out[part];
1586
+ out[n] = new ItemsRef(n, k, len(n), part => new ListRef(at(part), len(n)));
1587
+ }
1588
+ }
1124
1589
  // Every name the rule declares is declared once (a flag named again replaces the earlier one, so flags are checked as
1125
1590
  // they are defined): an input and a state variable sharing a name would hand the callbacks only one of them.
1126
1591
  names() {
1127
1592
  const s = this.spec;
1128
1593
  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']),
1129
1594
  ...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']),
1595
+ ...[...(s.cells ?? []), ...(s.items ?? [])].filter(([n, k]) => !k.vars(n).includes(n)).map(([n]) => [n, 'a map of a kind']),
1130
1596
  ];
1131
1597
  }
1132
1598
  whatIs(n) { return this.names().find(([m]) => m === n)?.[1] ?? 'something else'; }
@@ -1173,6 +1639,7 @@ export class Rule {
1173
1639
  delete updates[n];
1174
1640
  Object.assign(updates, k.store(n, u));
1175
1641
  }
1642
+ expandParts(updates);
1176
1643
  const state = {}, maps = {};
1177
1644
  for (const [n, u] of Object.entries(updates)) {
1178
1645
  if (u === undefined)
@@ -1189,9 +1656,11 @@ export class Rule {
1189
1656
  const assume = [...this.kindAssumptions('inputs'), ...s.assume.map(f => hoist.cond(f(env).c))];
1190
1657
  placed();
1191
1658
  // Functions, and their facts: each function's own (named after it), then the rule's.
1192
- const fns = Object.fromEntries((s.fns ?? []).map(([n, d]) => [n, { js: fnSource(n, d), ...(d.lean ? { lean: d.lean } : {}) }]));
1659
+ // The functions a function reads come first, each named <function>.<use>.
1660
+ const flat = flatFns(s.fns ?? []);
1661
+ const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
1193
1662
  const facts = [
1194
- ...(s.fns ?? []).flatMap(([n, d]) => Object.entries(d.facts?.(fnTerm(n, d)) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
1663
+ ...flat.flatMap(([n, d, uses]) => Object.entries(d.facts?.(fnTerm(n, d), uses) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
1195
1664
  ...(s.facts ?? []).flatMap(f => Object.entries(f(pEnv)).map(([name, out]) => this.lawOf(name, out))),
1196
1665
  ];
1197
1666
  const m = {
@@ -1311,7 +1780,7 @@ export class Rule {
1311
1780
  }
1312
1781
  // The updates: every state variable as its value is written (a kinded one through its kind's `store`, as the
1313
1782
  // machine is built) or kept, and each map write.
1314
- const updates = inScope(sc, () => s.step?.(this.env()) ?? {});
1783
+ const updates = inScope(sc, () => expandParts(s.step?.(this.env()) ?? {}));
1315
1784
  const written = { ...updates };
1316
1785
  inScope(sc, () => { for (const [n, k] of s.kinded ?? [])
1317
1786
  if (updates[n] !== undefined) {
@@ -1385,8 +1854,8 @@ export class Rule {
1385
1854
  const got = this.decode(ours);
1386
1855
  for (const [field, want] of Object.entries(theirs)) {
1387
1856
  checked++;
1388
- if (got[field] !== want)
1389
- return { checked, failure: `${s.name}, params ${JSON.stringify(params)}, after ${JSON.stringify(request)}: ${field} is ${got[field]} here, ${want} in the code it describes` };
1857
+ if (!sameRaw(got[field], want))
1858
+ return { checked, failure: `${s.name}, params ${JSON.stringify(params)}, after ${JSON.stringify(request)}: ${field} is ${shown(got[field])} here, ${shown(want)} in the code it describes` };
1390
1859
  }
1391
1860
  }
1392
1861
  }
@@ -1421,6 +1890,14 @@ export class Rule {
1421
1890
  delete out[part];
1422
1891
  out[n] = k.decode(n, st.scalars);
1423
1892
  }
1893
+ // A map of a kind: the keys any of its parts holds a cell at, each with its value; a list of a kind: its items.
1894
+ const cell = (n, k, key) => k.decode(n, Object.fromEntries(k.vars(n).map(part => [part, st.maps[part]?.get(key) ?? st.defaults?.[part] ?? new Map(this.spec.maps).get(part)])));
1895
+ for (const [n, k] of this.spec.cells ?? []) {
1896
+ const keys = [...new Set(k.vars(n).flatMap(part => [...(st.maps[part]?.keys() ?? [])]))].sort((a, b) => a - b);
1897
+ out[n] = Object.fromEntries(keys.map(key => [key, cell(n, k, key)]));
1898
+ }
1899
+ for (const [n, k] of this.spec.items ?? [])
1900
+ out[n] = Array.from({ length: st.scalars[`${n}_len`] }, (_, i) => cell(n, k, i));
1424
1901
  return out;
1425
1902
  }
1426
1903
  // The prune order's variable, if the history is pruned (a runtime must send it nondecreasing).
@@ -1430,10 +1907,20 @@ export class Rule {
1430
1907
  return loadJs(this.machine);
1431
1908
  }
1432
1909
  }
1910
+ // A step's writes to maps and lists of a kind, as the writes to their parts' maps.
1911
+ const expandParts = (updates) => {
1912
+ for (const [n, u] of Object.entries(updates))
1913
+ if (isPartsWrite(u)) {
1914
+ delete updates[n];
1915
+ Object.assign(updates, u.parts);
1916
+ }
1917
+ return updates;
1918
+ };
1433
1919
  const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.encode(n, values[n])));
1434
1920
  export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
1435
1921
  // The kind a result field is kept in.
1436
- const kindOf = (t) => (t instanceof Cond ? bool : t instanceof Int ? int : t instanceof Decimal ? decimal(t.places) : t instanceof Choice ? oneOf(...t.values) : t instanceof Str ? str({ like: t.patterns }) : nat);
1922
+ const kindOf = (t) => (t instanceof Cond ? bool : t instanceof Int ? int : t instanceof Decimal ? decimal(t.places) : t instanceof Choice ? oneOf(...t.values) : t instanceof Str ? str({ like: t.patterns })
1923
+ : t instanceof Variant ? t.kind : t instanceof Maybe ? maybe(kindOf(t.inner)) : isRecord(t) ? record(Object.fromEntries(Object.entries(t).map(([f, x]) => [f, kindOf(x)]))) : nat);
1437
1924
  export class Fun {
1438
1925
  r;
1439
1926
  out;
@@ -1475,8 +1962,8 @@ export class Fun {
1475
1962
  const ours = this.run(values, params), theirs = reference(values, params);
1476
1963
  for (const [field, want] of Object.entries(theirs)) {
1477
1964
  checked++;
1478
- if (ours[field] !== want)
1479
- return { checked, failure: `${this.name} at ${JSON.stringify(values)}: ${field} is ${String(ours[field])} here, ${String(want)} in the code it describes` };
1965
+ if (!sameRaw(ours[field], want))
1966
+ return { checked, failure: `${this.name} at ${JSON.stringify(values)}: ${field} is ${shown(ours[field])} here, ${shown(want)} in the code it describes` };
1480
1967
  }
1481
1968
  }
1482
1969
  return { checked };
@@ -1557,6 +2044,15 @@ export class LoopRule {
1557
2044
  randomInput(rand, bound) { return Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound))); }
1558
2045
  }
1559
2046
  export const loop = (name) => new LoopRule({ name, params: [], inputs: [], state: [], pre: [], assume: [] });
2047
+ // A verdict that isn't a checked proof, as the error a lemma, a guard or a gate is refused with (the verdict on `.verdict`).
2048
+ export class Unproven extends Error {
2049
+ verdict;
2050
+ constructor(what, verdict) {
2051
+ const found = [verdict.goal && `goal ${verdict.goal}`, verdict.stuck, verdict.witness && `a counterexample: ${JSON.stringify(verdict.witness)}`, verdict.why].filter(Boolean).join('; ');
2052
+ super(`${what}: ${verdict.proven ? 'proven, but the kernel did not check the proof' : `not proven${found ? ` (${found})` : ''}`}`);
2053
+ this.verdict = verdict;
2054
+ }
2055
+ }
1560
2056
  const goalsVerdict = (goals) => {
1561
2057
  const trees = {};
1562
2058
  for (const g of goals) {
@@ -1567,16 +2063,18 @@ const goalsVerdict = (goals) => {
1567
2063
  }
1568
2064
  return { proven: true, checked: goals.every(g => check(g, trees[g.name])), trees };
1569
2065
  };
2066
+ // The kinds' laws first, those the caller didn't pass already (by name).
2067
+ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.name === k.name)), ...laws];
1570
2068
  export function proveRule(r, laws) {
1571
- const out = proveLaws(r.machine, laws);
2069
+ const out = proveLaws(r.machine, withKinds(r, laws));
1572
2070
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
1573
2071
  }
1574
2072
  export function proveSteps(r, laws, stepLaws) {
1575
- const out = proveStepLaws(r.machine, laws, stepLaws);
2073
+ const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
1576
2074
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
1577
2075
  }
1578
2076
  export function proveLeadsTo(r, laws, l) {
1579
- const out = proveLiveness(r.machine, laws, l);
2077
+ const out = proveLiveness(r.machine, withKinds(r, laws), l);
1580
2078
  return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
1581
2079
  }
1582
2080
  export const proveDecision = (d) => goalsVerdict([d.goal]);