gdp-wired 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -148,6 +148,9 @@ const failsClosed = trial.decide('a missing fact means no trial', ({spent}) => s
148
148
  - **Kinds**: `nat`, `upTo(k)`, `bool`, `oneOf(...)`, `maybe(kind)`, `int`, `decimal(places)`, `str({like, maxLength})`;
149
149
  their assumptions (a bit is 0 or 1, a choice is in range) go to the kernel by themselves, and
150
150
  `encodeInput`/`encodeParams` turn runtime values into the numbers the machine reads.
151
+ - **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
152
+ a step writes each kind's own values, `when(c, a, b)` chooses between any two values of a kind, and `rule.decode(state)`
153
+ reads them back as runtime values.
151
154
  - **Integers and decimals**: `int` and `decimal(places)` inputs, `.ints({...})` and `.decimals(places, {...})` state;
152
155
  `plus`, `minus`, `negate`, `times(k)` (k may be negative), `min`, `max`, `abs`, `isNegative`, comparisons with numbers
153
156
  (a decimal's literals are scaled: `price.atMost(19.99)`). Each is two naturals underneath, so the kernel and the Lean
@@ -226,7 +229,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
226
229
  | `core/` | Machines, the interpreter and JS compiler, the builders, composition (`compose.ts`), the linear solver and its checker, Houdini invariants, models, the first Lean export |
227
230
  | `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
228
231
  | `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
229
- | `examples/` | The builders at work: `domains/` (92 rules across nine domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
232
+ | `examples/` | The builders at work: `domains/` (92 rules across nine domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 16 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
230
233
  | `docs/` | The comparison with Bend and Lean, and the full scope |
231
234
  | `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
232
235
  | `research/` | Everything around the library ([`research/README.md`](research/README.md)): the GPU searches (`search/`, `hw/`, `modal-gpu/`), invariant and rank synthesis (`synth/`), relational laws and guard synthesis (`next/`), bit-vector laws (`sat/`), the enumeration and benchmarks (`enum/`, `gpu/`, `perf/`), the first Lean checker (`lean/`) and the first prototype, the audit path (`audit/`) |
@@ -39,19 +39,32 @@ export declare function when(c: Cond, then: TermLike, otherwise: TermLike): Term
39
39
  export declare function when(c: Cond, then: Decimal, otherwise: Decimal | number): Decimal;
40
40
  export declare function when(c: Cond, then: Decimal | number, otherwise: Decimal): Decimal;
41
41
  export declare function when(c: Cond, then: IntLike, otherwise: IntLike): Int;
42
+ export declare function when<K extends string>(c: Cond, then: Choice<K>, otherwise: Choice<K>): Choice<K>;
43
+ export declare function when(c: Cond, then: Str, otherwise: Str): Str;
44
+ export declare function when<V>(c: Cond, then: Maybe<V>, otherwise: Maybe<V>): Maybe<V>;
45
+ export declare function when(c: Cond, then: Cond, otherwise: Cond): Cond;
42
46
  export declare const choose: (c: Cond, then: Cond, otherwise: Cond) => Cond;
43
47
  export declare const count: (c: Cond) => Term;
44
48
  export declare const sum: (first: TermLike, ...rest: TermLike[]) => Term;
45
49
  type Rand = (k: number) => number;
46
50
  type Raw = number | boolean | string | null | undefined;
51
+ export type At = string | ((name: string) => string);
52
+ export type Init<V> = {
53
+ readonly kind: Kind<V>;
54
+ readonly init: Raw | ((p: never) => unknown);
55
+ };
47
56
  export declare abstract class Kind<V> {
48
57
  readonly _value: V;
49
58
  abstract vars(name: string): string[];
50
59
  abstract assume(name: string): Cond[];
51
- abstract value(name: string, prefix?: string): V;
60
+ abstract value(name: string, at?: At): V;
52
61
  abstract encode(name: string, raw: Raw): Record<string, number>;
53
62
  abstract random(name: string, rand: Rand, bound: number): Record<string, number>;
63
+ abstract store(name: string, value: unknown): Record<string, TermLike>;
64
+ abstract decode(name: string, vars: Record<string, number>): Raw;
65
+ abstract empty(): Raw;
54
66
  sample(_rand: Rand, _bound: number): Raw;
67
+ from(init: Raw | ((p: never) => unknown)): Init<V>;
55
68
  }
56
69
  export type IntLike = Int | Term | number;
57
70
  export declare class Int {
@@ -117,8 +130,8 @@ export declare class Choice<K extends string> {
117
130
  }
118
131
  export declare const oneOf: <const K extends string>(...values: K[]) => Kind<Choice<K>>;
119
132
  export declare class Maybe<V> {
120
- private readonly present;
121
- private readonly inner;
133
+ readonly present: Term;
134
+ readonly inner: V;
122
135
  constructor(present: Term, inner: V);
123
136
  get isSome(): Cond;
124
137
  get isNone(): Cond;
@@ -142,8 +155,11 @@ export declare class Str {
142
155
  readonly length: Term;
143
156
  private readonly bits;
144
157
  constructor(id: Term, length: Term, bits: ReadonlyMap<string, Term>);
158
+ get patterns(): string[];
159
+ choose(c: Cond, other: Str): Str;
145
160
  like(pattern: string): Cond;
146
161
  get isEmpty(): Cond;
162
+ bit(pattern: string): Term;
147
163
  eq(b: Str | string): Cond;
148
164
  ne(b: Str | string): Cond;
149
165
  }
@@ -236,8 +252,9 @@ type ValuesOf<K extends Kinds> = {
236
252
  [n in keyof K]: K[n] extends Kind<infer V> ? V : never;
237
253
  };
238
254
  type Obj = object;
255
+ type Writes<V> = V extends Decimal ? DecimalLike | string : V extends Int ? IntLike : V extends Cond ? Cond | boolean : V extends Choice<infer K> ? Choice<K> | K : V extends Maybe<infer W> ? Maybe<W> | Writes<W> | null : V extends Str ? Str | string : TermLike;
239
256
  type Update<S extends Obj, M extends Obj> = Partial<{
240
- [k in keyof S]: S[k] extends Decimal ? DecimalLike : S[k] extends Int ? IntLike : TermLike;
257
+ [k in keyof S]: Writes<S[k]>;
241
258
  } & {
242
259
  [k in keyof M]: MapWrite;
243
260
  }>;
@@ -248,7 +265,7 @@ type Spec = {
248
265
  state: [string, number | ((p: never) => TermLike)][];
249
266
  maps: [string, number][];
250
267
  lists?: string[];
251
- signed?: [string, number | undefined][];
268
+ kinded?: [string, Kind<unknown>][];
252
269
  carried?: string[];
253
270
  fns?: [string, FnDef][];
254
271
  facts?: ((env: never) => Record<string, Cond | Forall>)[];
@@ -273,8 +290,8 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
273
290
  get name(): string;
274
291
  params<K extends Kinds>(kinds: K): Rule<P & ValuesOf<K>, I, S, M, F, R, Su, Q>;
275
292
  inputs<K extends Kinds>(kinds: K): Rule<P, I & ValuesOf<K>, S, M, F, R, Su, Q>;
276
- state<T extends Record<string, number | ((p: P) => TermLike)>>(init: T): Rule<P, I, S & {
277
- [k in keyof T]: Term;
293
+ state<T extends Record<string, number | ((p: P) => TermLike) | Init<unknown>>>(init: T): Rule<P, I, S & {
294
+ [k in keyof T]: T[k] extends Init<infer V> ? V : Term;
278
295
  }, M, F, R, Su, Q>;
279
296
  maps<T extends Record<string, number>>(init: T): Rule<P, I, S, M & {
280
297
  [k in keyof T]: MapRef;
@@ -285,7 +302,6 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
285
302
  decimals<T extends Record<string, number | string | ((p: P) => DecimalLike)>>(places: number, init: T): Rule<P, I, S & {
286
303
  [k in keyof T]: Decimal;
287
304
  }, M, F, R, Su, Q>;
288
- private signedState;
289
305
  lists<const N extends string>(...names: N[]): Rule<P, I, S & {
290
306
  [k in N as `${k}_len`]: Term;
291
307
  }, M & {
@@ -330,7 +346,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
330
346
  decide(name: string, given: (env: P & I) => Cond | Cond[], then: (env: P & I) => Cond): Decision;
331
347
  relate(name: string, given: (a: P & I, b: P & I) => Cond | Cond[], then: (a: P & I, b: P & I) => Cond): Decision;
332
348
  private kindAssumptions;
333
- private signedValue;
349
+ private afterEnv;
334
350
  private env;
335
351
  private names;
336
352
  private whatIs;
@@ -372,7 +388,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
372
388
  }): Request;
373
389
  randomParams(rand: Rand, bound: number): Record<string, number>;
374
390
  randomInput(rand: Rand, bound: number): Request;
375
- decode(st: State): Record<string, number>;
391
+ decode(st: State): Record<string, Raw>;
376
392
  get orderedBy(): string | undefined;
377
393
  interpreter(): {
378
394
  init: (params: {
@@ -410,15 +426,16 @@ export type Decision = {
410
426
  goal: Goal;
411
427
  };
412
428
  type Result<T> = {
413
- [k in keyof T]: T[k] extends Cond ? Cond : T[k] extends Decimal ? Decimal : T[k] extends Int ? Int : Term;
429
+ [k in keyof T]: T[k] extends Cond ? Cond : T[k] extends Decimal ? Decimal : T[k] extends Int ? Int : T[k] extends Choice<infer K> ? Choice<K> : T[k] extends Str ? Str : Term;
414
430
  };
415
431
  type Value<T> = {
416
- [k in keyof T]: T[k] extends Cond ? boolean : number;
432
+ [k in keyof T]: T[k] extends Cond ? boolean : T[k] extends Choice<infer K> ? K : T[k] extends Str ? string : number;
417
433
  };
434
+ type Field = Term | Cond | Int | Decimal | Choice<string> | Str;
418
435
  export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj = {}> {
419
436
  private readonly r;
420
437
  private readonly out?;
421
- constructor(r: Rule<P, I>, out?: ((env: never) => Record<string, TermLike | Cond | Int | Decimal>) | undefined);
438
+ constructor(r: Rule<P, I>, out?: ((env: never) => Record<string, TermLike | Field>) | undefined);
422
439
  get name(): string;
423
440
  params<K extends Kinds>(kinds: K): Fun<P & ValuesOf<K>, I, O>;
424
441
  inputs<K extends Kinds>(kinds: K): Fun<P, I & ValuesOf<K>, O>;
@@ -428,7 +445,7 @@ export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj =
428
445
  facts(f: (p: P) => Record<string, Cond | Forall>): Fun<P, I, O>;
429
446
  pre(f: (p: P) => Cond): Fun<P, I, O>;
430
447
  assume(f: (env: P & I) => Cond): Fun<P, I, O>;
431
- returns<T extends Record<string, TermLike | Cond | Int | Decimal>>(f: (env: P & I) => T): Fun<P, I, Result<T>>;
448
+ returns<T extends Record<string, TermLike | Field>>(f: (env: P & I) => T): Fun<P, I, Result<T>>;
432
449
  private result;
433
450
  law(name: string, given: (env: P & I, out: O) => Cond | Cond[], then: (out: O, env: P & I) => Cond): Decision;
434
451
  relate(name: string, given: (a: P & I, b: P & I) => Cond | Cond[], then: (outA: O, outB: O, a: P & I, b: P & I) => Cond): Decision;
package/dist/core/dsl.js CHANGED
@@ -82,6 +82,17 @@ export const not = (c) => c.not();
82
82
  export const min = (a, b) => new Term({ op: 'min', a: termOf(a), b: termOf(b) }, both(valOf(a), valOf(b), Math.min));
83
83
  export const max = (a, b) => new Term({ op: 'max', a: termOf(a), b: termOf(b) }, both(valOf(a), valOf(b), Math.max));
84
84
  export function when(c, then, otherwise) {
85
+ if (then instanceof Cond && otherwise instanceof Cond)
86
+ return choose(c, then, otherwise);
87
+ if (then instanceof Choice && otherwise instanceof Choice)
88
+ return new Choice(when(c, then.n, otherwise.n), then.values);
89
+ if (then instanceof Str && otherwise instanceof Str)
90
+ return then.choose(c, otherwise);
91
+ if (then instanceof Maybe && otherwise instanceof Maybe)
92
+ return new Maybe(when(c, then.present, otherwise.present), when(c, then.inner, otherwise.inner));
93
+ return pickNumbers(c, then, otherwise);
94
+ }
95
+ function pickNumbers(c, then, otherwise) {
85
96
  if (then instanceof Decimal || otherwise instanceof Decimal) {
86
97
  const places = (then instanceof Decimal ? then : otherwise).places, d = new Decimal(Int.of(0), places);
87
98
  return new Decimal(when(c, d.lift(then), d.lift(otherwise)), places);
@@ -97,11 +108,13 @@ export const choose = (c, then, otherwise) => any(all(c, then), all(c.not(), oth
97
108
  // 1 where c holds, else 0: what a sum over a history adds per record.
98
109
  export const count = (c) => when(c, 1, 0);
99
110
  export const sum = (first, ...rest) => rest.reduce((acc, t) => acc.plus(t), typeof first === 'number' ? lit(first) : first);
100
- // A kind names the variables a value is encoded in, what is assumed of them, the value the builders hand out, and how
101
- // a runtime value is encoded and drawn at random (the examples' harness draws inputs this way).
111
+ const named = (at, n) => (typeof at === 'string' ? at + n : at(n));
112
+ const primed = (n) => `${n}'`;
102
113
  export class Kind {
103
114
  // A runtime value of the kind (what encode takes), for comparing a rule with the code it describes (compare).
104
115
  sample(_rand, _bound) { throw new Error('this kind draws no runtime values'); }
116
+ // A state variable of this kind starting at a runtime value, or at a value over the params: `.state({open: bool.from(true)})`.
117
+ from(init) { return { kind: this, init }; }
105
118
  }
106
119
  const v = (name) => new Term({ op: 'var', name }, scope?.vars[name]);
107
120
  let scope;
@@ -177,7 +190,6 @@ export class Int {
177
190
  }
178
191
  // The same value with at most one part nonzero (what a state variable holds).
179
192
  const normal = (i) => (isZero(i.neg) || isZero(i.pos) ? i : new Int(i.pos.minus(i.neg), i.neg.minus(i.pos)));
180
- const termValue = (t) => (t.e.op === 'const' ? t.e.value : 0);
181
193
  const scaledOf = (x, places, what = 'a decimal') => {
182
194
  const text = typeof x === 'number' ? x.toFixed(places + 3) : x.trim();
183
195
  const m = /^(-?)(\d+)(?:\.(\d+))?$/.exec(text);
@@ -234,7 +246,10 @@ class NatKind extends Kind {
234
246
  }
235
247
  vars(name) { return [name]; }
236
248
  assume(name) { return this.maxValue === undefined ? [] : [v(name).atMost(this.maxValue)]; }
237
- value(name, prefix = '') { return v(prefix + name); }
249
+ value(name, at = '') { return v(named(at, name)); }
250
+ store(name, value) { return { [name]: value }; }
251
+ decode(name, vars) { return vars[name]; }
252
+ empty() { return 0; }
238
253
  encode(name, raw) {
239
254
  const n = asNat(name, raw);
240
255
  if (this.maxValue !== undefined && n > this.maxValue)
@@ -258,7 +273,10 @@ export class Bool extends Cond {
258
273
  class BoolKind extends Kind {
259
274
  vars(name) { return [name]; }
260
275
  assume(name) { return [v(name).atMost(1)]; }
261
- value(name, prefix = '') { return new Bool(v(prefix + name)); }
276
+ value(name, at = '') { return new Bool(v(named(at, name))); }
277
+ store(name, value) { return { [name]: typeof value === 'boolean' ? (value ? 1 : 0) : count(value) }; }
278
+ decode(name, vars) { return vars[name] === 1; }
279
+ empty() { return false; }
262
280
  encode(name, raw) {
263
281
  if (typeof raw === 'boolean')
264
282
  return { [name]: raw ? 1 : 0 };
@@ -304,7 +322,10 @@ class OneOfKind extends Kind {
304
322
  }
305
323
  vars(name) { return [name]; }
306
324
  assume(name) { return [v(name).lt(this.values.length)]; }
307
- value(name, prefix = '') { return new Choice(v(prefix + name), this.values); }
325
+ value(name, at = '') { return new Choice(v(named(at, name)), this.values); }
326
+ store(name, value) { return { [name]: value instanceof Choice ? value.n : this.encode(name, value)[name] }; }
327
+ decode(name, vars) { return this.values[vars[name]]; }
328
+ empty() { return this.values[0]; }
308
329
  encode(name, raw) {
309
330
  const i = this.values.indexOf(raw);
310
331
  if (i < 0)
@@ -345,7 +366,17 @@ class MaybeKind extends Kind {
345
366
  has(name) { return `${name}$some`; }
346
367
  vars(name) { return [this.has(name), ...this.of.vars(name)]; }
347
368
  assume(name) { return [v(this.has(name)).atMost(1), ...this.of.assume(name)]; }
348
- value(name, prefix = '') { return new Maybe(v(prefix + this.has(name)), this.of.value(name, prefix)); }
369
+ value(name, at = '') { return new Maybe(v(named(at, this.has(name))), this.of.value(name, at)); }
370
+ // null for none, a Maybe as it is, any other value as some.
371
+ store(name, value) {
372
+ if (value === null || value === undefined)
373
+ return { [this.has(name)]: 0, ...this.of.store(name, this.of.empty()) };
374
+ if (value instanceof Maybe)
375
+ return { [this.has(name)]: value.present, ...this.of.store(name, value.inner) };
376
+ return { [this.has(name)]: 1, ...this.of.store(name, value) };
377
+ }
378
+ decode(name, vars) { return vars[this.has(name)] === 1 ? this.of.decode(name, vars) : null; }
379
+ empty() { return null; }
349
380
  encode(name, raw) {
350
381
  if (raw === null || raw === undefined)
351
382
  return { [this.has(name)]: 0, ...Object.fromEntries(this.of.vars(name).map(n => [n, 0])) };
@@ -414,6 +445,9 @@ export class Str {
414
445
  this.length = length;
415
446
  this.bits = bits;
416
447
  }
448
+ get patterns() { return [...this.bits.keys()]; }
449
+ // This string where c holds, else `other` (the patterns both declare).
450
+ choose(c, other) { return new Str(when(c, this.id, other.id), when(c, this.length, other.length), new Map([...this.bits].filter(([p]) => other.bits.has(p)).map(([p, b]) => [p, when(c, b, other.bits.get(p))]))); }
417
451
  // The string matches `pattern`, one the kind declared.
418
452
  like(pattern) {
419
453
  const b = this.bits.get(pattern);
@@ -422,6 +456,9 @@ export class Str {
422
456
  return b.eq(1);
423
457
  }
424
458
  get isEmpty() { return this.id.eq(0); }
459
+ // A declared pattern's bit (0 or 1).
460
+ bit(pattern) { const b = this.bits.get(pattern); if (!b)
461
+ throw new Error(`no pattern "${pattern}" on this string`); return b; }
425
462
  eq(b) {
426
463
  if (typeof b === 'string')
427
464
  return all(this.id.eq(literal(b)), this.length.eq(b.length), ...[...this.bits].map(([p, bit]) => bit.eq(globMatch(p, b) ? 1 : 0)));
@@ -443,7 +480,17 @@ class StrKind extends Kind {
443
480
  return [v(name).eq(0).iff(v(`${name}$len`).eq(0)), ...(this.maxLength === undefined ? [] : [v(`${name}$len`).atMost(this.maxLength)]),
444
481
  ...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('\\*', '*'))))))];
445
482
  }
446
- value(name, prefix = '') { return new Str(v(prefix + name), v(`${prefix}${name}$len`), new Map(this.patterns.map((p, i) => [p, v(prefix + this.bit(name, i))]))); }
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)))]))); }
484
+ store(name, value) {
485
+ if (typeof value === 'string')
486
+ return { [name]: literal(value), [`${name}$len`]: value.length, ...Object.fromEntries(this.patterns.map((p, i) => [this.bit(name, i), globMatch(p, value) ? 1 : 0])) };
487
+ const t = value;
488
+ return { [name]: t.id, [`${name}$len`]: t.length, ...Object.fromEntries(this.patterns.map((p, i) => [this.bit(name, i), t.bit(p)])) };
489
+ }
490
+ decode(name, vars) { const id = vars[name]; for (const [text, n] of internTable)
491
+ if (n === id)
492
+ return text; throw new Error(`${name}: no string is interned as ${id}`); }
493
+ empty() { return ''; }
447
494
  encode(name, raw) {
448
495
  if (typeof raw !== 'string')
449
496
  throw new RangeError(`${name} must be a string, not ${String(raw)}`);
@@ -466,7 +513,10 @@ export const str = (opts = {}) => new StrKind(opts.like ?? [], opts.maxLength);
466
513
  class IntKind extends Kind {
467
514
  vars(name) { return [name, `${name}$neg`]; }
468
515
  assume() { return []; }
469
- value(name, prefix = '') { return new Int(v(prefix + name), v(`${prefix}${name}$neg`)); }
516
+ value(name, at = '') { return new Int(v(named(at, name)), v(named(at, `${name}$neg`))); }
517
+ store(name, value) { const i = normal(Int.of(value)); return { [name]: i.pos, [`${name}$neg`]: i.neg }; }
518
+ decode(name, vars) { return vars[name] - vars[`${name}$neg`]; }
519
+ empty() { return 0; }
470
520
  encode(name, raw) {
471
521
  if (typeof raw !== 'number' || !Number.isSafeInteger(raw))
472
522
  throw new RangeError(`${name} must be an integer, not ${String(raw)}`);
@@ -485,7 +535,13 @@ class DecimalKind extends Kind {
485
535
  }
486
536
  vars(name) { return [name, `${name}$neg`]; }
487
537
  assume() { return []; }
488
- value(name, prefix = '') { return new Decimal(new Int(v(prefix + name), v(`${prefix}${name}$neg`)), this.places); }
538
+ value(name, at = '') { return new Decimal(new Int(v(named(at, name)), v(named(at, `${name}$neg`))), this.places); }
539
+ store(name, value) {
540
+ const i = normal(typeof value === 'string' ? Int.of(scaledOf(value, this.places)) : new Decimal(Int.of(0), this.places).lift(value));
541
+ return { [name]: i.pos, [`${name}$neg`]: i.neg };
542
+ }
543
+ decode(name, vars) { return (vars[name] - vars[`${name}$neg`]) / 10 ** this.places; }
544
+ empty() { return 0; }
489
545
  encode(name, raw) {
490
546
  if (typeof raw !== 'number' && typeof raw !== 'string')
491
547
  throw new RangeError(`${name} must be a decimal, not ${String(raw)}`);
@@ -836,28 +892,39 @@ export class Rule {
836
892
  get name() { return this.spec.name; }
837
893
  params(kinds) { return this.with({ params: [...this.spec.params, ...Object.entries(kinds)] }); }
838
894
  inputs(kinds) { return this.with({ inputs: [...this.spec.inputs, ...Object.entries(kinds)] }); }
839
- // Each variable starts at a natural or at a term over the params.
895
+ // Each variable starts at a natural or at a term over the params, or is declared by a kind and starts where `from`
896
+ // says: `.state({balance: int.from(-5), status: oneOf('open', 'closed').from('open'), note: maybe(nat).from(null)})`.
840
897
  state(init) {
841
- return this.with({ state: [...this.spec.state, ...Object.entries(init)] });
898
+ const entries = [], kinded = [];
899
+ for (const [n, x] of Object.entries(init)) {
900
+ if (typeof x === 'number' || typeof x === 'function') {
901
+ entries.push([n, x]);
902
+ continue;
903
+ }
904
+ const { kind, init: start } = x;
905
+ kinded.push([n, kind]);
906
+ if (typeof start === 'function') {
907
+ const parts = (p) => kind.store(n, start(p));
908
+ for (const part of kind.vars(n))
909
+ entries.push([part, p => parts(p)[part]]);
910
+ }
911
+ else {
912
+ const enc = kind.encode(n, start);
913
+ for (const part of kind.vars(n))
914
+ entries.push([part, enc[part]]);
915
+ }
916
+ }
917
+ return this.with({ state: [...this.spec.state, ...entries], kinded: [...(this.spec.kinded ?? []), ...kinded] });
842
918
  }
843
919
  maps(init) { return this.with({ maps: [...this.spec.maps, ...Object.entries(init)] }); }
844
920
  // Integers (Int), each starting at an integer or at one over the params; kept as two naturals, <name> and <name>$neg.
845
- ints(init) { return this.signedState(init, undefined); }
921
+ ints(init) {
922
+ return this.state(Object.fromEntries(Object.entries(init).map(([n, x]) => [n, int.from(x)])));
923
+ }
846
924
  // Decimals with `places` places (Decimal), each starting at a number, a string ("12.50") or one over the params.
847
925
  decimals(places, init) {
848
- decimal(places);
849
- return this.signedState(init, places);
850
- }
851
- signedState(init, places) {
852
- const parts = (n, x) => {
853
- if (typeof x !== 'function') {
854
- const i = places === undefined ? Int.of(x) : Int.of(scaledOf(x, places));
855
- return [[n, termValue(i.pos)], [`${n}$neg`, termValue(i.neg)]];
856
- }
857
- const at = (p) => normal(places === undefined ? Int.of(x(p)) : new Decimal(Int.of(0), places).lift(x(p)));
858
- return [[n, p => at(p).pos], [`${n}$neg`, p => at(p).neg]];
859
- };
860
- return this.with({ state: [...this.spec.state, ...Object.entries(init).flatMap(([n, x]) => parts(n, x))], signed: [...(this.spec.signed ?? []), ...Object.keys(init).map(n => [n, places])] });
926
+ const kind = decimal(places);
927
+ return this.state(Object.fromEntries(Object.entries(init).map(([n, x]) => [n, kind.from(x)])));
861
928
  }
862
929
  // Lists, each empty at the start: a map of positions and its length <name>_len (ListRef).
863
930
  lists(...names) {
@@ -917,19 +984,7 @@ export class Rule {
917
984
  // counter.stepLaw('never goes back', ({used}, after) => used.atMost(after.used))
918
985
  stepLaw(name, then, when) {
919
986
  const env = this.env();
920
- const after = {};
921
- for (const [n] of this.spec.state)
922
- after[n] = v(`${n}'`);
923
- for (const [n] of this.spec.sums)
924
- after[n] = v(`${n}'`);
925
- for (const [n, init] of this.spec.maps)
926
- after[n] = new MapRef(`${n}'`, init);
927
- for (const n of this.spec.lists ?? [])
928
- after[n] = new ListRef(`${n}'`, v(`${n}_len'`));
929
- for (const [n, places] of this.spec.signed ?? []) {
930
- after[n] = this.signedValue(n, places, `${n}'`, `${n}$neg'`);
931
- delete after[`${n}$neg`];
932
- }
987
+ const after = this.afterEnv();
933
988
  const out = then(env, after), w = when ? { when: when(env).c } : {};
934
989
  const key = `key$${++keyCounter}`, src = { step: { then: then, ...(when ? { when: when } : {}) } };
935
990
  if (out instanceof Cond) {
@@ -945,19 +1000,7 @@ export class Rule {
945
1000
  // meter.stepsLaw('two requests spend at most 2m', 2, ({spent, m}, after) => after.spent.atMost(spent.plus(m).plus(m)))
946
1001
  stepsLaw(name, k, then, when) {
947
1002
  const env = this.env();
948
- const after = {};
949
- for (const [n] of this.spec.state)
950
- after[n] = v(`${n}'`);
951
- for (const [n] of this.spec.sums)
952
- after[n] = v(`${n}'`);
953
- for (const [n, init] of this.spec.maps)
954
- after[n] = new MapRef(`${n}'`, init);
955
- for (const n of this.spec.lists ?? [])
956
- after[n] = new ListRef(`${n}'`, v(`${n}_len'`));
957
- for (const [n, places] of this.spec.signed ?? []) {
958
- after[n] = this.signedValue(n, places, `${n}'`, `${n}$neg'`);
959
- delete after[`${n}$neg`];
960
- }
1003
+ const after = this.afterEnv();
961
1004
  // The j-th request's values over marked names, renamed to the kernel's input@j once built.
962
1005
  const mark = (j) => `${j}:`;
963
1006
  const requests = Array.from({ length: k }, (_, j) => {
@@ -1019,10 +1062,23 @@ export class Rule {
1019
1062
  const is = which === 'params' ? [] : this.spec.inputs.flatMap(([n, k]) => k.assume(n));
1020
1063
  return [...ps, ...is].map(c => c.c);
1021
1064
  }
1022
- // A signed state variable's value over these names of its two parts.
1023
- signedValue(n, places, pos, neg) {
1024
- const i = new Int(v(pos), v(neg));
1025
- return places === undefined ? i : new Decimal(i, places);
1065
+ // The state after a step, as a step law reads it: every state variable, sum, map and list, primed.
1066
+ afterEnv() {
1067
+ const after = {};
1068
+ for (const [n] of this.spec.state)
1069
+ after[n] = v(`${n}'`);
1070
+ for (const [n] of this.spec.sums)
1071
+ after[n] = v(`${n}'`);
1072
+ for (const [n, init] of this.spec.maps)
1073
+ after[n] = new MapRef(`${n}'`, init);
1074
+ for (const n of this.spec.lists ?? [])
1075
+ after[n] = new ListRef(`${n}'`, v(`${n}_len'`));
1076
+ for (const [n, k] of this.spec.kinded ?? []) {
1077
+ for (const part of k.vars(n))
1078
+ delete after[part];
1079
+ after[n] = k.value(n, primed);
1080
+ }
1081
+ return after;
1026
1082
  }
1027
1083
  // The values handed to the builders' callbacks.
1028
1084
  env(parts = {}) {
@@ -1047,9 +1103,10 @@ export class Rule {
1047
1103
  out[n] = new ListRef(n, v(`${n}_len`));
1048
1104
  for (const [n] of this.spec.sums)
1049
1105
  out[n] = v(n);
1050
- for (const [n, places] of this.spec.signed ?? []) {
1051
- out[n] = this.signedValue(n, places, n, `${n}$neg`);
1052
- delete out[`${n}$neg`];
1106
+ for (const [n, k] of this.spec.kinded ?? []) {
1107
+ for (const part of k.vars(n))
1108
+ delete out[part];
1109
+ out[n] = k.value(n);
1053
1110
  }
1054
1111
  }
1055
1112
  if (parts.queries !== false)
@@ -1108,14 +1165,13 @@ export class Rule {
1108
1165
  flags[n] = out;
1109
1166
  }
1110
1167
  const updates = { ...s.step?.(env) };
1111
- // A signed variable's update, as its two parts, normalized.
1112
- for (const [n, places] of s.signed ?? []) {
1168
+ // A kinded variable's update, as its variables' terms.
1169
+ for (const [n, k] of s.kinded ?? []) {
1113
1170
  const u = updates[n];
1114
1171
  if (u === undefined)
1115
1172
  continue;
1116
- const i = normal(places === undefined ? Int.of(u) : new Decimal(Int.of(0), places).lift(u));
1117
- updates[n] = i.pos;
1118
- updates[`${n}$neg`] = i.neg;
1173
+ delete updates[n];
1174
+ Object.assign(updates, k.store(n, u));
1119
1175
  }
1120
1176
  const state = {}, maps = {};
1121
1177
  for (const [n, u] of Object.entries(updates)) {
@@ -1253,22 +1309,17 @@ export class Rule {
1253
1309
  if (sc.flags[n] !== (req[`?${n}`] === 1))
1254
1310
  return `flag ${n}: its callback says ${sc.flags[n]}, the step decides ${req[`?${n}`] === 1}`;
1255
1311
  }
1256
- // The updates: each written value (an integer's or a decimal's as its count), every scalar not written kept, each map write.
1312
+ // The updates: every state variable as its value is written (a kinded one through its kind's `store`, as the
1313
+ // machine is built) or kept, and each map write.
1257
1314
  const updates = inScope(sc, () => s.step?.(this.env()) ?? {});
1258
- const signed = new Map(s.signed ?? []);
1315
+ const written = { ...updates };
1316
+ inScope(sc, () => { for (const [n, k] of s.kinded ?? [])
1317
+ if (updates[n] !== undefined) {
1318
+ delete written[n];
1319
+ Object.assign(written, k.store(n, updates[n]));
1320
+ } });
1259
1321
  for (const n of Object.keys(st.scalars)) {
1260
- if (n.endsWith('$neg') && signed.has(n.slice(0, -4)))
1261
- continue;
1262
- const u = updates[n], got = signed.has(n) ? next.scalars[n] - next.scalars[`${n}$neg`] : next.scalars[n];
1263
- let want;
1264
- if (u === undefined)
1265
- want = signed.has(n) ? st.scalars[n] - st.scalars[`${n}$neg`] : st.scalars[n];
1266
- else if (signed.has(n)) {
1267
- const i = signed.get(n) === undefined ? Int.of(u) : new Decimal(Int.of(0), signed.get(n)).lift(u);
1268
- want = both(i.pos.val, i.neg.val, (a, b) => a - b);
1269
- }
1270
- else
1271
- want = valOf(u);
1322
+ const u = written[n], got = next.scalars[n], want = u === undefined ? st.scalars[n] : valOf(u);
1272
1323
  if (want === undefined)
1273
1324
  continue;
1274
1325
  count();
@@ -1304,19 +1355,7 @@ export class Rule {
1304
1355
  const after = { ...sc, keys, vars: { ...sc.vars, ...Object.fromEntries(Object.entries(next.scalars).map(([n, x]) => [`${n}'`, x])) },
1305
1356
  maps: { ...sc.maps, ...Object.fromEntries(Object.entries(next.maps).map(([n, cells]) => [`${n}'`, { cells, dflt: next.defaults?.[n] ?? m.maps?.[n] ?? 0 }])) } };
1306
1357
  const ok = inScope(after, () => {
1307
- const env = this.env(), afterEnv = {};
1308
- for (const [n] of s.state)
1309
- afterEnv[n] = v(`${n}'`);
1310
- for (const [n] of s.sums)
1311
- afterEnv[n] = v(`${n}'`);
1312
- for (const [n, init] of s.maps)
1313
- afterEnv[n] = new MapRef(`${n}'`, init);
1314
- for (const n of s.lists ?? [])
1315
- afterEnv[n] = new ListRef(`${n}'`, v(`${n}_len'`));
1316
- for (const [n, places] of s.signed ?? []) {
1317
- afterEnv[n] = this.signedValue(n, places, `${n}'`, `${n}$neg'`);
1318
- delete afterEnv[`${n}$neg`];
1319
- }
1358
+ const env = this.env(), afterEnv = this.afterEnv();
1320
1359
  const w = src.when ? src.when(env).ok : true;
1321
1360
  return w === undefined ? undefined : w ? this.verdict(src.then(env, afterEnv), keys) : true;
1322
1361
  });
@@ -1373,13 +1412,14 @@ export class Rule {
1373
1412
  out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
1374
1413
  return out;
1375
1414
  }
1376
- // The state's scalars as values: an integer's two parts as one signed number, a decimal as its number.
1415
+ // The state's scalars as runtime values: each kinded variable as its kind decodes it (an integer as one signed
1416
+ // number, a decimal as its number, a choice by name, a string as itself, none as null).
1377
1417
  decode(st) {
1378
1418
  const out = { ...st.scalars };
1379
- for (const [n, places] of this.spec.signed ?? []) {
1380
- const x = (st.scalars[n] ?? 0) - (st.scalars[`${n}$neg`] ?? 0);
1381
- out[n] = places === undefined ? x : x / 10 ** places;
1382
- delete out[`${n}$neg`];
1419
+ for (const [n, k] of this.spec.kinded ?? []) {
1420
+ for (const part of k.vars(n))
1421
+ delete out[part];
1422
+ out[n] = k.decode(n, st.scalars);
1383
1423
  }
1384
1424
  return out;
1385
1425
  }
@@ -1392,6 +1432,8 @@ export class Rule {
1392
1432
  }
1393
1433
  const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.encode(n, values[n])));
1394
1434
  export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
1435
+ // 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);
1395
1437
  export class Fun {
1396
1438
  r;
1397
1439
  out;
@@ -1411,7 +1453,7 @@ export class Fun {
1411
1453
  result(env) {
1412
1454
  if (!this.out)
1413
1455
  throw new Error(`${this.name}: no result (returns)`);
1414
- return Object.fromEntries(Object.entries(this.out(env)).map(([k, t]) => [k, t instanceof Cond || t instanceof Int || t instanceof Decimal ? t : new Term(termOf(t))]));
1456
+ return Object.fromEntries(Object.entries(this.out(env)).map(([k, t]) => [k, typeof t === 'number' ? lit(t) : t]));
1415
1457
  }
1416
1458
  // Whenever the arguments (and the result) meet `given`, the result meets `then`.
1417
1459
  law(name, given, then) {
@@ -1442,11 +1484,9 @@ export class Fun {
1442
1484
  // The one-step rule it compiles to: the result's fields are its state (named out.<field>, apart from the arguments).
1443
1485
  get rule() {
1444
1486
  const fields = Object.entries(this.result(this.r['env']({ flags: false, state: false, queries: false })));
1445
- const name = (k) => `out.${k}`, of = (pick) => Object.fromEntries(fields.filter(([, t]) => pick(t)).map(([k]) => [name(k), 0]));
1446
- let r = this.r.state(of(t => t instanceof Term || t instanceof Cond)).ints(of(t => t instanceof Int));
1447
- for (const places of new Set(fields.flatMap(([, t]) => (t instanceof Decimal ? [t.places] : []))))
1448
- r = r.decimals(places, of(t => t instanceof Decimal && t.places === places));
1449
- return r.step(env => Object.fromEntries(Object.entries(this.result(env)).map(([k, t]) => [name(k), t instanceof Cond ? count(t) : t])));
1487
+ const name = (k) => `out.${k}`;
1488
+ return this.r.state(Object.fromEntries(fields.map(([k, t]) => { const kind = kindOf(t); return [name(k), kind.from(kind.empty())]; })))
1489
+ .step(env => Object.fromEntries(Object.entries(this.result(env)).map(([k, t]) => [name(k), t])));
1450
1490
  }
1451
1491
  compiledRule;
1452
1492
  // The result at these arguments (and params), from the compiled rule: numbers, and booleans for the conditions.
@@ -1455,7 +1495,7 @@ export class Fun {
1455
1495
  const { rule, gen } = this.compiledRule, ps = rule.encodeParams(params);
1456
1496
  const kinds = Object.entries(this.result(this.r['env']({ flags: false, state: false, queries: false })));
1457
1497
  const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values)));
1458
- return Object.fromEntries(kinds.map(([k, t]) => [k, t instanceof Cond ? st[`out.${k}`] === 1 : st[`out.${k}`]]));
1498
+ return Object.fromEntries(kinds.map(([k]) => [k, st[`out.${k}`]]));
1459
1499
  }
1460
1500
  }
1461
1501
  export const fun = (name) => new Fun(rule(name));
@@ -20,6 +20,10 @@ export type Path = Map<string, {
20
20
  export declare const canon: (lin: Lin) => string;
21
21
  export declare const atomOf: (lin: Lin, holds: boolean) => Constraint;
22
22
  export declare function linear(e: E, path: Path): Lin | undefined;
23
+ export declare const termColumn: (e: E) => string;
24
+ export declare const columnTerm: (name: string) => E | undefined;
25
+ export declare function decided(e: E, path: Path): boolean;
26
+ export declare function termRows(name: string, path: Path): Lin[];
23
27
  export declare function truth(c: C, path: Path): boolean | undefined;
24
28
  export declare function facts(c: C, path: Path, positive?: boolean): Lin[];
25
29
  export declare function leafConstraints(goal: Goal, path: Path): Constraint[];
@@ -33,6 +37,7 @@ export type Subst = {
33
37
  export declare function substE(e: E, s: Subst): E;
34
38
  export declare function substC(c: C, s: Subst): C;
35
39
  export type Instances = Record<string, E[]>;
40
+ export declare const stateName: (n: string, j: number) => string;
36
41
  export declare const assumed: (laws: Law[], instances: Instances) => C[];
37
42
  export declare function scope(m: Machine, laws?: Law[]): void;
38
43
  export declare const keyTerm: (e: E, key: string) => boolean;
@@ -104,6 +109,7 @@ export declare function checkStepLaws(m: Machine, laws: Law[], stepLaws: StepLaw
104
109
  perStep?: Record<string, Instances>;
105
110
  trees: Record<string, Tree>;
106
111
  }): boolean;
112
+ export declare const stepZone: (m: Machine, k: number) => string[];
107
113
  export type Fold = {
108
114
  name: string;
109
115
  params: string[];