gdp-wired 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/core/dsl.d.ts +106 -8
- package/dist/core/dsl.js +310 -31
- package/dist/core/gdp.js +2 -2
- package/dist/core/machine.d.ts +3 -0
- package/dist/core/machine.js +41 -3
- package/dist/kernel/kernel.js +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,8 +69,9 @@ All of these are proven automatically, and each proof is also a Lean theorem:
|
|
|
69
69
|
(`examples/datatypes.ts`).
|
|
70
70
|
- **Lists a request carries, and trees**: an invoice's lines, with folds over them (the total, the taxable part) and
|
|
71
71
|
lemmas about the folds proven by induction over the lines (`examples/invoices.ts`); a fold over a tree as a value (a
|
|
72
|
-
spend roll-up
|
|
73
|
-
(`examples/folds.ts`)
|
|
72
|
+
spend roll-up, sign-offs over a document's sections, an org chart where a manager has any number of reports), its laws
|
|
73
|
+
proven by induction over the tree (`examples/folds.ts`); a search tree, and a sheet of rows of cells, kept in a rule's
|
|
74
|
+
state as tables of nodes (`examples/trees.ts`).
|
|
74
75
|
- **Pure functions and two-run laws**: laws about what a function returns, and laws comparing two calls, like "the
|
|
75
76
|
secret never changes the answer". `examples/jev-fabric/` ports a Bend project's policy core and proves all 59 laws its
|
|
76
77
|
Bend proofs state.
|
|
@@ -228,13 +229,24 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
|
|
|
228
229
|
carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
|
|
229
230
|
array, and a request that doesn't meet them isn't allowed), read as `Lines.fold.total`; `Lines.every`, `some`, `has`,
|
|
230
231
|
`increasing(by)`, `at(i)`. A lemma about the folds (`({taxed, total}) => taxed.atMost(total)`) is proven by induction
|
|
231
|
-
over the items when the rule is built, then assumed at every prefix
|
|
232
|
+
over the items when the rule is built, then assumed at every prefix. A fold's start and step, and a lemma, may read the
|
|
233
|
+
params (`{from: ({fee}) => fee, step: (acc, line, {ceil}) => acc.plus(min(line.amount, ceil))}`); a request is then
|
|
234
|
+
encoded with them, `rule.encodeInput(values, params)` (`examples/invoices.ts`).
|
|
232
235
|
- **Folds over trees** (`fold(name).params(...).inputs(...).leaf(env => ({...})).node((env, left, right) => ({...}))`,
|
|
233
236
|
`proveFold`): a tree as a value (`{leaf: inputs}` or `{node: inputs, left, right}`), each leaf's values from its inputs
|
|
234
237
|
and each node's from its own and its children's; a law about the values is proven by induction over the tree (the
|
|
235
238
|
kernel's leaf and node goals, Lean's `fold_sound`), `f.run(tree, params)` evaluates the terms proven and `f.crossCheck`
|
|
236
|
-
holds the builders to them
|
|
237
|
-
|
|
239
|
+
holds the builders to them. Nodes with any number of children: `.children(from, (acc, kid) => ..., lemmas)` folds a
|
|
240
|
+
node's children into `kids`, then `.node((env, kids) => ...)`; it is the kernel's binary fold underneath (each child
|
|
241
|
+
list a chain of glue nodes), so Lean's `fold_sound` checks it unchanged, and `f.run` takes `{node, children: [...]}`
|
|
242
|
+
(`examples/folds.ts`).
|
|
243
|
+
- **Trees in a rule's state, and lists of lists** (`.nodes('Tree', record({key: nat}), ['left', 'right'], folds)`): a
|
|
244
|
+
table of nodes that only grows, node 0 the empty tree, `Tree.make(value, {left, right}, when)` making node size + 1 from
|
|
245
|
+
nodes already there; each fold (`{lo: {empty: 0, node: (x, kids) => when(kids.left.empty, x.key, kids.left.fold.lo)}}`)
|
|
246
|
+
kept with its node when it is made, and may read a table declared before (a row's total over its cells). A law about
|
|
247
|
+
a subtree is then a law about one node (`Tree.node(n).fold.lo`), proven like any law about every key; the table's own
|
|
248
|
+
laws (children earlier, folds coherent, nothing past the end) are proven beside it (`examples/trees.ts`: a search tree,
|
|
249
|
+
a sheet of rows of cells). Claims that need a lemma about the tree's shape nobody stated stay Lean's.
|
|
238
250
|
- **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
|
|
239
251
|
a step writes each kind's own values, `when(c, a, b)` chooses between any two values of a kind, and `rule.decode(state)`
|
|
240
252
|
reads them back as runtime values.
|
|
@@ -329,7 +341,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
|
|
|
329
341
|
| `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 |
|
|
330
342
|
| `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
|
|
331
343
|
| `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
|
|
332
|
-
| `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `datatypes.ts` (records and variants), `lemmas.ts` (recurrences and their lemmas), `invoices.ts` (lists a request carries), `folds.ts` (folds over trees), `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) |
|
|
344
|
+
| `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `datatypes.ts` (records and variants), `lemmas.ts` (recurrences and their lemmas), `invoices.ts` (lists a request carries), `folds.ts` (folds over trees), `trees.ts` (trees and lists of lists in the state), `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) |
|
|
333
345
|
| `docs/` | The comparison with Bend and Lean, and the full scope |
|
|
334
346
|
| `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
|
|
335
347
|
| `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/`) |
|
package/dist/core/dsl.d.ts
CHANGED
|
@@ -310,11 +310,11 @@ export type ItemsOp = {
|
|
|
310
310
|
readonly parts: Record<string, ListOp>;
|
|
311
311
|
readonly len: TermLike;
|
|
312
312
|
};
|
|
313
|
-
export type ListFold<V> = {
|
|
314
|
-
from: number;
|
|
315
|
-
step: (acc: Term, item: V) => TermLike;
|
|
313
|
+
export type ListFold<V, P = any> = {
|
|
314
|
+
from: number | ((p: P) => TermLike);
|
|
315
|
+
step: (acc: Term, item: V, p: P) => TermLike;
|
|
316
316
|
};
|
|
317
|
-
export type ListLemma = (acc: Record<string, Term>, i: Term) => Cond;
|
|
317
|
+
export type ListLemma<P = any> = (acc: Record<string, Term>, i: Term, p: P) => Cond;
|
|
318
318
|
export declare class CarriedList<V, Fd extends string = never> {
|
|
319
319
|
readonly name: string;
|
|
320
320
|
readonly kind: Kind<V>;
|
|
@@ -322,8 +322,10 @@ export declare class CarriedList<V, Fd extends string = never> {
|
|
|
322
322
|
readonly folds: Record<Fd, ListFold<V>>;
|
|
323
323
|
private readonly map;
|
|
324
324
|
readonly lemmas: Record<string, ListLemma>;
|
|
325
|
+
private readonly params?;
|
|
326
|
+
private readonly paramKinds;
|
|
325
327
|
readonly fold: Record<Fd, Term>;
|
|
326
|
-
constructor(name: string, kind: Kind<V>, len: Term, folds: Record<Fd, ListFold<V>>, map: (part: string) => MapRef, lemmas?: Record<string, ListLemma>);
|
|
328
|
+
constructor(name: string, kind: Kind<V>, len: Term, folds: Record<Fd, ListFold<V>>, map: (part: string) => MapRef, lemmas?: Record<string, ListLemma>, params?: Record<string, unknown> | undefined, paramKinds?: [string, Kind<unknown>][]);
|
|
327
329
|
at(i: TermLike): V;
|
|
328
330
|
after(fold: Fd, i: TermLike): Term;
|
|
329
331
|
every(f: (item: V, i: Term) => Cond): Cond;
|
|
@@ -331,6 +333,8 @@ export declare class CarriedList<V, Fd extends string = never> {
|
|
|
331
333
|
has(x: V): Cond;
|
|
332
334
|
increasing(by: (item: V) => Term): Cond;
|
|
333
335
|
equations(): Cond[];
|
|
336
|
+
private get readsParams();
|
|
337
|
+
private p;
|
|
334
338
|
private running;
|
|
335
339
|
prove(): Verdict;
|
|
336
340
|
get maps(): string[];
|
|
@@ -358,6 +362,35 @@ export declare class ItemsRef<V> {
|
|
|
358
362
|
within(): Forall;
|
|
359
363
|
increasing(by: (x: V) => Term): Forall;
|
|
360
364
|
}
|
|
365
|
+
export type NodeRef<V, C extends string, Fd extends string> = {
|
|
366
|
+
readonly index: Term;
|
|
367
|
+
readonly empty: Cond;
|
|
368
|
+
readonly value: V;
|
|
369
|
+
readonly fold: Record<Fd, Term>;
|
|
370
|
+
readonly kids: Record<C, Term>;
|
|
371
|
+
};
|
|
372
|
+
export type NodeFold<V, C extends string, Fd extends string = string, T = Record<string, NodeTable<any, any, any>>> = {
|
|
373
|
+
empty: number;
|
|
374
|
+
node: (value: V, kids: Record<C, NodeRef<V, C, Fd>>, tables: T) => TermLike;
|
|
375
|
+
};
|
|
376
|
+
type TablesOf<M> = {
|
|
377
|
+
[k in keyof M as M[k] extends NodeTable<any, any, any> ? k : never]: M[k];
|
|
378
|
+
};
|
|
379
|
+
export declare class NodeTable<V, C extends string = string, Fd extends string = string> {
|
|
380
|
+
readonly name: string;
|
|
381
|
+
readonly kind: Kind<V>;
|
|
382
|
+
readonly children: C[];
|
|
383
|
+
readonly folds: Record<Fd, NodeFold<V, C, Fd>>;
|
|
384
|
+
readonly size: Term;
|
|
385
|
+
private readonly map;
|
|
386
|
+
private readonly tables;
|
|
387
|
+
constructor(name: string, kind: Kind<V>, children: C[], folds: Record<Fd, NodeFold<V, C, Fd>>, size: Term, map: (part: string) => MapRef, tables?: Record<string, NodeTable<any, any, any>>);
|
|
388
|
+
node(n: TermLike): NodeRef<V, C, Fd>;
|
|
389
|
+
at(n: TermLike): V;
|
|
390
|
+
fits(kids: Record<C, TermLike>): Cond;
|
|
391
|
+
make(value: Writes<V>, kids: Record<C, TermLike>, when?: Cond): Record<string, TermLike | PartsWrite>;
|
|
392
|
+
laws(): [string, Forall][];
|
|
393
|
+
}
|
|
361
394
|
export type Forall = {
|
|
362
395
|
readonly forall: (key: Term, ...more: Term[]) => Cond;
|
|
363
396
|
};
|
|
@@ -419,6 +452,7 @@ type Spec = {
|
|
|
419
452
|
lists?: string[];
|
|
420
453
|
cells?: [string, Kind<unknown>][];
|
|
421
454
|
items?: [string, Kind<unknown>][];
|
|
455
|
+
nodes?: [string, Kind<unknown>, string[], Record<string, NodeFold<unknown, string>>][];
|
|
422
456
|
kinded?: [string, Kind<unknown>][];
|
|
423
457
|
carried?: string[];
|
|
424
458
|
lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
|
|
@@ -467,6 +501,13 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
467
501
|
}, M & {
|
|
468
502
|
[k in keyof T]: ItemsRef<T[k] extends Kind<infer V> ? V : never>;
|
|
469
503
|
}, F, R, Su, Q>;
|
|
504
|
+
nodes<const N extends string, V, const C extends string, Fd extends string = never>(name: N, of: Kind<V>, children: readonly C[], folds?: {
|
|
505
|
+
[f in Fd]: NodeFold<V, C, Fd, TablesOf<M>>;
|
|
506
|
+
}): Rule<P, I, S & {
|
|
507
|
+
[k in N as `${k}_size`]: Term;
|
|
508
|
+
}, M & {
|
|
509
|
+
[k in N]: NodeTable<V, C, Fd>;
|
|
510
|
+
}, F, R, Su, Q>;
|
|
470
511
|
fns<T extends Record<string, FnDef>>(defs: T): Rule<P & {
|
|
471
512
|
[k in keyof T]: (...args: TermLike[]) => Term;
|
|
472
513
|
}, I, S, M, F, R, Su, Q>;
|
|
@@ -474,7 +515,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
474
515
|
carries<const N extends string>(...names: N[]): Rule<P, I & {
|
|
475
516
|
[k in N]: MapRef;
|
|
476
517
|
}, S, M, F, R, Su, Q>;
|
|
477
|
-
carriesList<const N extends string, V, Fd extends string = never>(name: N, of: Kind<V>, folds?: Record<Fd, ListFold<V>>, lemmas?: Record<string, (acc: Record<Fd, Term>, i: Term) => Cond>): Rule<P, I & {
|
|
518
|
+
carriesList<const N extends string, V, Fd extends string = never>(name: N, of: Kind<V>, folds?: Record<Fd, ListFold<V, P>>, lemmas?: Record<string, (acc: Record<Fd, Term>, i: Term, p: P) => Cond>): Rule<P, I & {
|
|
478
519
|
[k in N]: CarriedList<V, Fd>;
|
|
479
520
|
}, S, M, F, R, Su, Q>;
|
|
480
521
|
flags<T extends Record<string, Cond>>(f: (env: P & I & S & M & F & Su & Q) => T): Rule<P, I, S, M, F & {
|
|
@@ -550,9 +591,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
550
591
|
}): Record<string, number>;
|
|
551
592
|
encodeInput(values: {
|
|
552
593
|
[k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
|
|
594
|
+
}, params?: {
|
|
595
|
+
[k in keyof P]: Raw;
|
|
553
596
|
}): Request;
|
|
554
597
|
randomParams(rand: Rand, bound: number): Record<string, number>;
|
|
555
|
-
|
|
598
|
+
private paramValues;
|
|
599
|
+
randomInput(rand: Rand, bound: number, ps?: Record<string, number>): Request;
|
|
556
600
|
decode(st: State): Record<string, Raw>;
|
|
557
601
|
get orderedBy(): string | undefined;
|
|
558
602
|
interpreter(): {
|
|
@@ -754,6 +798,15 @@ export declare class FoldRule<P extends Obj = {}, I extends Obj = {}, S extends
|
|
|
754
798
|
node(f: (env: P & I, left: S, right: S) => {
|
|
755
799
|
[k in keyof S]: TermLike;
|
|
756
800
|
}): FoldRule<P, I, S>;
|
|
801
|
+
children<A extends Record<string, number | ((p: P) => TermLike)>>(from: A, step: (acc: {
|
|
802
|
+
[k in keyof A]: Term;
|
|
803
|
+
}, kid: S, p: P) => {
|
|
804
|
+
[k in keyof A]: TermLike;
|
|
805
|
+
}, lemmas?: Record<string, (acc: {
|
|
806
|
+
[k in keyof A]: Term;
|
|
807
|
+
}, p: P) => Cond>): RoseFold<P, I, S, {
|
|
808
|
+
[k in keyof A]: Term;
|
|
809
|
+
}>;
|
|
757
810
|
private env;
|
|
758
811
|
private names;
|
|
759
812
|
private side;
|
|
@@ -774,4 +827,49 @@ export declare class FoldRule<P extends Obj = {}, I extends Obj = {}, S extends
|
|
|
774
827
|
}): Agreement;
|
|
775
828
|
}
|
|
776
829
|
export declare const fold: (name: string) => FoldRule;
|
|
777
|
-
export declare const proveFold: (f:
|
|
830
|
+
export declare const proveFold: (f: {
|
|
831
|
+
fold: Fold;
|
|
832
|
+
lemmas?: Law[];
|
|
833
|
+
}, laws: Law[]) => Verdict;
|
|
834
|
+
export type RoseValue<I> = {
|
|
835
|
+
leaf: I;
|
|
836
|
+
} | {
|
|
837
|
+
node: I;
|
|
838
|
+
children: RoseValue<I>[];
|
|
839
|
+
};
|
|
840
|
+
type RoseSpec = {
|
|
841
|
+
from: Record<string, number | ((p: never) => TermLike)>;
|
|
842
|
+
step: (acc: never, kid: never, p: never) => Record<string, TermLike>;
|
|
843
|
+
lemmas: Record<string, (acc: never, p: never) => Cond>;
|
|
844
|
+
};
|
|
845
|
+
export declare class RoseFold<P extends Obj = {}, I extends Obj = {}, S extends Obj = {}, A extends Obj = {}> {
|
|
846
|
+
private readonly spec;
|
|
847
|
+
private readonly kids;
|
|
848
|
+
private readonly nodeOf?;
|
|
849
|
+
constructor(spec: FoldSpec, kids: RoseSpec, nodeOf?: ((env: never, kids: never) => Record<string, TermLike>) | undefined);
|
|
850
|
+
get name(): string;
|
|
851
|
+
node(f: (env: P & I, kids: A) => {
|
|
852
|
+
[k in keyof S]: TermLike;
|
|
853
|
+
}): RoseFold<P, I, S, A>;
|
|
854
|
+
private env;
|
|
855
|
+
private names;
|
|
856
|
+
private side;
|
|
857
|
+
private accs;
|
|
858
|
+
private start;
|
|
859
|
+
law(name: string, f: (env: P & S) => Cond): Law;
|
|
860
|
+
get lemmas(): Law[];
|
|
861
|
+
get fold(): Fold;
|
|
862
|
+
run(tree: RoseValue<{
|
|
863
|
+
[k in keyof I]: Raw;
|
|
864
|
+
}>, params?: {
|
|
865
|
+
[k in keyof P]?: Raw;
|
|
866
|
+
}): {
|
|
867
|
+
[k in keyof S]: number;
|
|
868
|
+
};
|
|
869
|
+
crossCheck(laws?: Law[], opts?: {
|
|
870
|
+
trials?: number;
|
|
871
|
+
depth?: number;
|
|
872
|
+
bound?: number;
|
|
873
|
+
rand?: Rand;
|
|
874
|
+
}): Agreement;
|
|
875
|
+
}
|
package/dist/core/dsl.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//
|
|
16
16
|
// A step returns its updates as one object: every right-hand side reads the state before the step, as the kernel and
|
|
17
17
|
// both runtimes do.
|
|
18
|
-
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf } from "./machine.js";
|
|
18
|
+
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
|
|
19
19
|
import { allowed, keysOf, requestEnv, watch } from "./watch.js";
|
|
20
20
|
import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
|
|
21
21
|
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
|
|
@@ -867,6 +867,7 @@ export class CellsRef {
|
|
|
867
867
|
return { parts: Object.fromEntries(this.kind.vars(this.name).map(part => [part, this.map(part).each(k => partsOf(this.kind, this.name, f(k))[part])])) };
|
|
868
868
|
}
|
|
869
869
|
}
|
|
870
|
+
const fromOf = (d, p) => (typeof d.from === 'number' ? d.from : d.from(p));
|
|
870
871
|
export class CarriedList {
|
|
871
872
|
name;
|
|
872
873
|
kind;
|
|
@@ -874,14 +875,20 @@ export class CarriedList {
|
|
|
874
875
|
folds;
|
|
875
876
|
map;
|
|
876
877
|
lemmas;
|
|
878
|
+
params;
|
|
879
|
+
paramKinds;
|
|
877
880
|
fold;
|
|
878
|
-
|
|
881
|
+
// `params`: the params' values the folds read (by their kinds: terms for the kernel, constants to encode a request),
|
|
882
|
+
// undefined where none are given; `paramKinds`, the params themselves, for the lemmas' induction.
|
|
883
|
+
constructor(name, kind, len, folds, map, lemmas = {}, params, paramKinds = []) {
|
|
879
884
|
this.name = name;
|
|
880
885
|
this.kind = kind;
|
|
881
886
|
this.len = len;
|
|
882
887
|
this.folds = folds;
|
|
883
888
|
this.map = map;
|
|
884
889
|
this.lemmas = lemmas;
|
|
890
|
+
this.params = params;
|
|
891
|
+
this.paramKinds = paramKinds;
|
|
885
892
|
this.fold = Object.fromEntries(Object.keys(folds).map(f => [f, map(`${name}$${f}`).at(len)]));
|
|
886
893
|
}
|
|
887
894
|
at(i) { return this.kind.build(this.name, part => this.map(part).at(i)); }
|
|
@@ -897,12 +904,21 @@ export class CarriedList {
|
|
|
897
904
|
equations() {
|
|
898
905
|
const folds = Object.entries(this.folds), lemmas = Object.values(this.lemmas);
|
|
899
906
|
const at = (k) => {
|
|
900
|
-
const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x)));
|
|
907
|
+
const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x, this.p())));
|
|
901
908
|
const below = [...this.kind.invariant(x), ...steps];
|
|
902
|
-
return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k))))] : []));
|
|
909
|
+
return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k, this.p()))))] : []));
|
|
903
910
|
};
|
|
904
911
|
const any = this.kind.invariant(this.kind.value(this.name)).length + folds.length + lemmas.length > 0;
|
|
905
|
-
return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(d.
|
|
912
|
+
return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(fromOf(d, this.p()))), ...(any ? [everyKey(at)] : [])];
|
|
913
|
+
}
|
|
914
|
+
// Whether a fold reads the params (its start a function, or its step taking them).
|
|
915
|
+
get readsParams() { return Object.values(this.folds).some(d => typeof d.from === 'function' || d.step.length > 2); }
|
|
916
|
+
p() {
|
|
917
|
+
if (this.params)
|
|
918
|
+
return this.params;
|
|
919
|
+
if (this.readsParams)
|
|
920
|
+
throw new RangeError(`${this.name}'s folds read the params: give them (encodeInput(values, params))`);
|
|
921
|
+
return {};
|
|
906
922
|
}
|
|
907
923
|
running(k) { return Object.fromEntries(Object.keys(this.folds).map(f => [f, this.after(f, k)])); }
|
|
908
924
|
// The lemmas, proven: the folds as a machine counting i up from 0 over items that are functions of i known only by
|
|
@@ -916,10 +932,11 @@ export class CarriedList {
|
|
|
916
932
|
const kindFact = this.kind.invariant(this.kind.value(this.name)).length ? { 'every item is a value of its kind': forall(k => all(...this.kind.invariant(item(k)))) } : {};
|
|
917
933
|
const fns = Object.fromEntries(parts.map((p, j) => [fname(p), fn(1, 'x => 0', j === 0 ? { facts: () => kindFact } : {})]));
|
|
918
934
|
const folds = Object.entries(this.folds);
|
|
919
|
-
const r = rule(`${this.name}'s folds, by induction`).
|
|
920
|
-
.
|
|
935
|
+
const r = rule(`${this.name}'s folds, by induction`).params(Object.fromEntries(this.paramKinds)).fns(fns).inputs({})
|
|
936
|
+
.state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
|
|
937
|
+
.step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i), env)])) }));
|
|
921
938
|
const accs = (env) => Object.fromEntries(folds.map(([f]) => [f, env[`acc$${f}`]]));
|
|
922
|
-
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i))));
|
|
939
|
+
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))));
|
|
923
940
|
}
|
|
924
941
|
// Every carried map it is kept in: the items' parts, then each fold's.
|
|
925
942
|
get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
|
|
@@ -933,13 +950,13 @@ export class CarriedList {
|
|
|
933
950
|
if (x !== 0)
|
|
934
951
|
maps[part].set(i, x); });
|
|
935
952
|
for (const [f, d] of Object.entries(this.folds)) {
|
|
936
|
-
const acc = maps[`${this.name}$${f}`];
|
|
937
|
-
let value = d
|
|
953
|
+
const acc = maps[`${this.name}$${f}`], p = this.p();
|
|
954
|
+
let value = valOf(fromOf(d, p));
|
|
938
955
|
if (value !== 0)
|
|
939
956
|
acc.set(0, value);
|
|
940
957
|
items.forEach((enc, i) => {
|
|
941
958
|
const item = this.kind.build(this.name, part => new Term({ op: 'const', value: enc[part] }, enc[part]));
|
|
942
|
-
const next = valOf(d.step(new Term({ op: 'const', value }, value), item));
|
|
959
|
+
const next = valOf(d.step(new Term({ op: 'const', value }, value), item, p));
|
|
943
960
|
if (next === undefined || !Number.isSafeInteger(next) || next < 0)
|
|
944
961
|
throw new RangeError(`${this.name}'s fold ${f} reads more than its items`);
|
|
945
962
|
value = next;
|
|
@@ -981,6 +998,66 @@ export class ItemsRef {
|
|
|
981
998
|
within() { return forall(k => this.len.atMost(k).implies(all(...this.names.map(part => this.list(part).at(k).eq(0))))); }
|
|
982
999
|
increasing(by) { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(by(this.at(i)).lt(by(this.at(j))))); }
|
|
983
1000
|
}
|
|
1001
|
+
export class NodeTable {
|
|
1002
|
+
name;
|
|
1003
|
+
kind;
|
|
1004
|
+
children;
|
|
1005
|
+
folds;
|
|
1006
|
+
size;
|
|
1007
|
+
map;
|
|
1008
|
+
tables;
|
|
1009
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1010
|
+
constructor(name, kind, children, folds, size, map, tables = {}) {
|
|
1011
|
+
this.name = name;
|
|
1012
|
+
this.kind = kind;
|
|
1013
|
+
this.children = children;
|
|
1014
|
+
this.folds = folds;
|
|
1015
|
+
this.size = size;
|
|
1016
|
+
this.map = map;
|
|
1017
|
+
this.tables = tables;
|
|
1018
|
+
}
|
|
1019
|
+
// The node at n (0, or past the end: the empty tree).
|
|
1020
|
+
node(n) {
|
|
1021
|
+
const index = termAt(n);
|
|
1022
|
+
return { index, empty: index.eq(0), value: this.kind.build(this.name, part => this.map(part).at(index)),
|
|
1023
|
+
fold: Object.fromEntries(Object.keys(this.folds).map(f => [f, this.map(`${this.name}$${f}`).at(index)])),
|
|
1024
|
+
kids: Object.fromEntries(this.children.map(c => [c, this.map(`${this.name}^${c}`).at(index)])) };
|
|
1025
|
+
}
|
|
1026
|
+
at(n) { return this.node(n).value; }
|
|
1027
|
+
// Whether nodes can be made from these children (each 0 or a node already there).
|
|
1028
|
+
fits(kids) { return all(...this.children.map(c => termAt(kids[c]).atMost(this.size))); }
|
|
1029
|
+
// Node size + 1, from a value and children already there, when `when` holds (and they fit): its value, children and
|
|
1030
|
+
// folds written, the size grown.
|
|
1031
|
+
make(value, kids, when = yes) {
|
|
1032
|
+
const n = this.size.plus(1), go = when.and(this.fits(kids)), parts = partsOf(this.kind, this.name, value);
|
|
1033
|
+
const val = this.kind.build(this.name, part => termAt(parts[part]));
|
|
1034
|
+
const kidRefs = Object.fromEntries(this.children.map(c => [c, this.node(termAt(kids[c]))]));
|
|
1035
|
+
const writes = {};
|
|
1036
|
+
const put = (part, x) => { writes[part] = this.map(part).set(n, when_(go, x, this.map(part).at(n))); };
|
|
1037
|
+
for (const part of this.kind.vars(this.name))
|
|
1038
|
+
put(part, parts[part]);
|
|
1039
|
+
for (const c of this.children)
|
|
1040
|
+
put(`${this.name}^${c}`, kids[c]);
|
|
1041
|
+
for (const [f, d] of Object.entries(this.folds))
|
|
1042
|
+
put(`${this.name}$${f}`, d.node(val, kidRefs, this.tables));
|
|
1043
|
+
return { [this.name]: { parts: writes }, [`${this.name}_size`]: when_(go, n, this.size) };
|
|
1044
|
+
}
|
|
1045
|
+
// The table's own laws: every node's children are earlier nodes and its folds are its children's, and node 0 and every
|
|
1046
|
+
// key past the end hold nothing (each fold's empty).
|
|
1047
|
+
laws() {
|
|
1048
|
+
const inside = (k) => k.atLeast(1).and(k.atMost(this.size));
|
|
1049
|
+
const folds = Object.entries(this.folds);
|
|
1050
|
+
return [
|
|
1051
|
+
...(this.children.length ? [[`every ${this.name} node's children are earlier nodes`, forall(k => inside(k).implies(all(...this.children.map(c => this.node(k).kids[c].lt(k)))))]] : []),
|
|
1052
|
+
...(folds.length ? [[`every ${this.name} node's folds are its children's`, forall(k => inside(k).implies(all(...folds.map(([f, d]) => {
|
|
1053
|
+
const x = this.node(k);
|
|
1054
|
+
return x.fold[f].eq(d.node(x.value, Object.fromEntries(this.children.map(c => [c, this.node(x.kids[c])])), this.tables));
|
|
1055
|
+
}))))]] : []),
|
|
1056
|
+
[`nothing in ${this.name} past its end`, forall(k => k.eq(0).or(this.size.lt(k)).implies(all(...this.kind.vars(this.name).map(part => this.map(part).at(k).eq(0)), ...this.children.map(c => this.node(k).kids[c].eq(0)), ...folds.map(([f, d]) => this.node(k).fold[f].eq(d.empty)))))],
|
|
1057
|
+
];
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
const termAt = (t) => (t instanceof Term ? t : lit(t));
|
|
984
1061
|
// A law about every key of a map: proven at a fresh key, assumed at the keys a step reads.
|
|
985
1062
|
// With two parameters or more, about every two keys or more: forall((i, j) => i.lt(j).implies(...)).
|
|
986
1063
|
export function forall(f) { return { forall: f }; }
|
|
@@ -1194,6 +1271,8 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1194
1271
|
if (!plain.test(n) || reservedInRecur.includes(n) || n === name)
|
|
1195
1272
|
throw new Error(`recur ${name}: ${n} can't name an argument or a function here (${reservedInRecur.join(', ')} and ${name} are taken)`);
|
|
1196
1273
|
distinctNames(`recur ${name}`, [...args, ...uses.map(([u]) => u)]);
|
|
1274
|
+
// A rule reads the function at its arguments paired, which the kernel multiplies out: with five further arguments that
|
|
1275
|
+
// is a polynomial of degree 32, past what it can hold.
|
|
1197
1276
|
if (args.length > 4)
|
|
1198
1277
|
throw new Error(`recur ${name}: at most four further arguments`);
|
|
1199
1278
|
const named = (n) => new Term({ op: 'var', name: n });
|
|
@@ -1244,8 +1323,9 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1244
1323
|
const keysOf = (n) => (f) => {
|
|
1245
1324
|
if (n === 0)
|
|
1246
1325
|
return f();
|
|
1247
|
-
|
|
1248
|
-
|
|
1326
|
+
// A law's keys are counted by its callback's parameters: this one takes n.
|
|
1327
|
+
const by = (...ks) => f(...ks);
|
|
1328
|
+
Object.defineProperty(by, 'length', { value: n });
|
|
1249
1329
|
return forall(by);
|
|
1250
1330
|
};
|
|
1251
1331
|
const facts = (withBetween) => (f, at = {}) => {
|
|
@@ -1279,7 +1359,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1279
1359
|
};
|
|
1280
1360
|
const distinctNames = (what, names) => { if (new Set(names).size !== names.length)
|
|
1281
1361
|
throw new Error(`${what}: a name given twice`); };
|
|
1282
|
-
// A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each
|
|
1362
|
+
// A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each called on its arguments,
|
|
1283
1363
|
// as the runtimes call it).
|
|
1284
1364
|
const recurJs = (name, args, zero, next, uses) => {
|
|
1285
1365
|
const fns = new Map(uses.map(([u], i) => [u, `$f${i}`]));
|
|
@@ -1294,7 +1374,10 @@ const recurJs = (name, args, zero, next, uses) => {
|
|
|
1294
1374
|
case 'min': return `Math.min(${js(e.a)}, ${js(e.b)})`;
|
|
1295
1375
|
case 'max': return `Math.max(${js(e.a)}, ${js(e.b)})`;
|
|
1296
1376
|
case 'if': return `(${jc(e.c)} ? ${js(e.then)} : ${js(e.else)})`;
|
|
1297
|
-
case 'get':
|
|
1377
|
+
case 'get': {
|
|
1378
|
+
const i = uses.findIndex(([u]) => u === e.map), args = uses[i][1].arity > 1 ? pairedArgs(e.key, uses[i][1].arity) : undefined;
|
|
1379
|
+
return args ? `$g${i}(${args.map(js).join(', ')})` : `${fns.get(e.map)}(${js(e.key)})`;
|
|
1380
|
+
}
|
|
1298
1381
|
}
|
|
1299
1382
|
};
|
|
1300
1383
|
const jc = (c) => {
|
|
@@ -1309,7 +1392,7 @@ const recurJs = (name, args, zero, next, uses) => {
|
|
|
1309
1392
|
case 'all': throw new Error('a recurrence takes no flag or quantifier');
|
|
1310
1393
|
}
|
|
1311
1394
|
};
|
|
1312
|
-
const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); `).join('');
|
|
1395
|
+
const inlined = uses.map(([u, d], i) => `const $f${i} = (${fnSource(u, d)}); ${d.arity > 1 ? `const $g${i} = (${jsOf(d)}); ` : ''}`).join('');
|
|
1313
1396
|
// The last value computed at each tuple of further arguments is kept, so reading f at x after f at a smaller x takes
|
|
1314
1397
|
// only the steps between (a rule that reads f at a count that grows costs one step a request).
|
|
1315
1398
|
return `(() => { ${inlined}const $memo = new Map(); return (x, ${args.map(a => `${a}, `).join('')}) => { const $k = ${args.length ? `[${args.join(', ')}].join()` : "''"}; let $at = $memo.get($k); if (!$at || $at[0] > x) $at = [0, ${js(zero)}]; let x0 = $at[0], prev = $at[1]; for (; x0 < x; x0++) { prev = ((x, prev) => ${js(next)})(x0, prev); if (!Number.isSafeInteger(prev)) throw new RangeError(${JSON.stringify(`${name} passes the safe integers`)}); } if ($memo.size > 4096) $memo.clear(); $memo.set($k, [x, prev]); return prev; }; })()`;
|
|
@@ -1322,8 +1405,9 @@ const flatFns = (defs) => defs.flatMap(([n, d]) => {
|
|
|
1322
1405
|
const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
|
|
1323
1406
|
const argsKey = (args) => args.slice(1).reduce(pairE, args[0]);
|
|
1324
1407
|
// A function's JavaScript as one function of its key: its arguments unpaired (left-nested, as argsKey pairs them).
|
|
1408
|
+
const jsOf = (d) => (typeof d.js === 'string' ? d.js : String(d.js));
|
|
1325
1409
|
const fnSource = (name, d) => {
|
|
1326
|
-
const src =
|
|
1410
|
+
const src = jsOf(d);
|
|
1327
1411
|
let f;
|
|
1328
1412
|
try {
|
|
1329
1413
|
f = compileFunction([], `return (${src})`)();
|
|
@@ -1446,6 +1530,19 @@ export class Rule {
|
|
|
1446
1530
|
return this.with({ maps: [...this.spec.maps, ...entries.flatMap(([n, k]) => k.vars(n).map(part => [part, 0]))],
|
|
1447
1531
|
state: [...this.spec.state, ...entries.map(([n]) => [`${n}_len`, 0])], items: [...(this.spec.items ?? []), ...entries] });
|
|
1448
1532
|
}
|
|
1533
|
+
// A table of nodes, empty at the start (NodeTable): a tree in the state, made one node a step.
|
|
1534
|
+
nodes(name, of, children, folds = {}) {
|
|
1535
|
+
for (const c of children)
|
|
1536
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c) || c === 'value')
|
|
1537
|
+
throw new Error(`${name}: ${c} can't name a child`);
|
|
1538
|
+
for (const f of Object.keys(folds))
|
|
1539
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))
|
|
1540
|
+
throw new Error(`${name}: ${f} is not a plain fold name`);
|
|
1541
|
+
distinctNames(name, [...children]);
|
|
1542
|
+
const maps = [...of.vars(name).map(part => [part, 0]), ...children.map(c => [`${name}^${c}`, 0]),
|
|
1543
|
+
...Object.entries(folds).map(([f, d]) => [`${name}$${f}`, d.empty])];
|
|
1544
|
+
return this.with({ maps: [...this.spec.maps, ...maps], state: [...this.spec.state, [`${name}_size`, 0]], nodes: [...(this.spec.nodes ?? []), [name, of, [...children], folds]] });
|
|
1545
|
+
}
|
|
1449
1546
|
// Functions defined outside the rule (fn, divBy, modBy), read like params that take arguments: f(x).
|
|
1450
1547
|
fns(defs) {
|
|
1451
1548
|
return this.with({ fns: [...(this.spec.fns ?? []), ...Object.entries(defs)] });
|
|
@@ -1456,6 +1553,7 @@ export class Rule {
|
|
|
1456
1553
|
carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
|
|
1457
1554
|
// A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
|
|
1458
1555
|
// .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
|
|
1556
|
+
// Each fold's start and step, and each lemma, may read the params declared before it.
|
|
1459
1557
|
carriesList(name, of, folds = {}, lemmas = {}) {
|
|
1460
1558
|
const entries = [[name, of, folds, lemmas]];
|
|
1461
1559
|
for (const [n, , fs] of entries)
|
|
@@ -1464,7 +1562,7 @@ export class Rule {
|
|
|
1464
1562
|
throw new Error(`${n}: ${f} is not a plain fold name`);
|
|
1465
1563
|
// The folds' lemmas, proven now (a lemma that isn't throws, with the verdict).
|
|
1466
1564
|
for (const [n, k, fs, ls] of entries) {
|
|
1467
|
-
const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls).prove();
|
|
1565
|
+
const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, undefined, this.spec.params).prove();
|
|
1468
1566
|
if (!(v2.proven && v2.checked))
|
|
1469
1567
|
throw new Unproven(`${n}'s fold lemmas`, v2);
|
|
1470
1568
|
}
|
|
@@ -1530,6 +1628,12 @@ export class Rule {
|
|
|
1530
1628
|
if (k.invariant(k.value(n)).length) {
|
|
1531
1629
|
out.push(this.law(`every ${n} cell is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
|
|
1532
1630
|
}
|
|
1631
|
+
for (const [n, k] of this.spec.nodes ?? []) {
|
|
1632
|
+
if (k.invariant(k.value(n)).length)
|
|
1633
|
+
out.push(this.law(`every ${n} node is a value of its kind`, env => forall(key => all(...k.invariant(of(env, n).at(key))))));
|
|
1634
|
+
for (const [name] of this.env({ flags: false, inputs: false, queries: false })[n].laws())
|
|
1635
|
+
out.push(this.law(name, env => of(env, n).laws().find(([m]) => m === name)[1]));
|
|
1636
|
+
}
|
|
1533
1637
|
return out;
|
|
1534
1638
|
}
|
|
1535
1639
|
// A law about one step, for every request the rule allows: `then(before, after)` relates what the step reads (params,
|
|
@@ -1547,7 +1651,7 @@ export class Rule {
|
|
|
1547
1651
|
const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
|
|
1548
1652
|
return withSource(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }, src);
|
|
1549
1653
|
}
|
|
1550
|
-
// A law about k steps in a row (k from 2 to
|
|
1654
|
+
// A law about k steps in a row (k from 2 to 64): `then(first, last, requests)` relates what the first step reads to the
|
|
1551
1655
|
// state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
|
|
1552
1656
|
// j)` narrows the j-th step's request (j from 0), read at the state that step starts from.
|
|
1553
1657
|
// meter.stepsLaw('two requests spend at most 2m', 2, ({spent, m}, after) => after.spent.atMost(spent.plus(m).plus(m)))
|
|
@@ -1649,8 +1753,9 @@ export class Rule {
|
|
|
1649
1753
|
out[n] = k.value(n);
|
|
1650
1754
|
for (const n of this.spec.carried ?? [])
|
|
1651
1755
|
out[n] = new MapRef(n, 0);
|
|
1756
|
+
const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
|
|
1652
1757
|
for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
|
|
1653
|
-
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls);
|
|
1758
|
+
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
|
|
1654
1759
|
for (const part of l.maps)
|
|
1655
1760
|
delete out[part];
|
|
1656
1761
|
out[n] = l;
|
|
@@ -1697,6 +1802,15 @@ export class Rule {
|
|
|
1697
1802
|
delete out[part];
|
|
1698
1803
|
out[n] = new ItemsRef(n, k, len(n), part => new ListRef(at(part), len(n)));
|
|
1699
1804
|
}
|
|
1805
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1806
|
+
const tables = {};
|
|
1807
|
+
for (const [n, k, cs, fs] of this.spec.nodes ?? []) {
|
|
1808
|
+
const t = new NodeTable(n, k, cs, fs, v(at(`${n}_size`)), part => new MapRef(at(part), init.get(part)), { ...tables });
|
|
1809
|
+
tables[n] = t;
|
|
1810
|
+
for (const part of [...k.vars(n), ...cs.map(c => `${n}^${c}`), ...Object.keys(fs).map(f => `${n}$${f}`)])
|
|
1811
|
+
delete out[part];
|
|
1812
|
+
out[n] = t;
|
|
1813
|
+
}
|
|
1700
1814
|
}
|
|
1701
1815
|
// Every name the rule declares is declared once (a flag named again replaces the earlier one, so flags are checked as
|
|
1702
1816
|
// they are defined): an input and a state variable sharing a name would hand the callbacks only one of them.
|
|
@@ -1705,6 +1819,7 @@ export class Rule {
|
|
|
1705
1819
|
return [...s.params.map(([n]) => [n, 'a param']), ...(s.fns ?? []).map(([n]) => [n, 'a function']), ...s.inputs.map(([n]) => [n, 'an input']), ...(s.carried ?? []).map(n => [n, 'a carried map']),
|
|
1706
1820
|
...s.state.map(([n]) => [n, 'a state variable']), ...s.maps.map(([n]) => [n, 'a map']), ...s.sums.map(([n]) => [n, 'a sum']), ...s.queries.map(([n]) => [n, 'a query']),
|
|
1707
1821
|
...[...(s.cells ?? []), ...(s.items ?? [])].filter(([n, k]) => !k.vars(n).includes(n)).map(([n]) => [n, 'a map of a kind']),
|
|
1822
|
+
...(s.nodes ?? []).map(([n]) => [n, 'a table of nodes']),
|
|
1708
1823
|
];
|
|
1709
1824
|
}
|
|
1710
1825
|
whatIs(n) { return this.names().find(([m]) => m === n)?.[1] ?? 'something else'; }
|
|
@@ -1770,7 +1885,7 @@ export class Rule {
|
|
|
1770
1885
|
// Functions, and their facts: each function's own (named after it), then the rule's.
|
|
1771
1886
|
// The functions a function reads come first, each named <function>.<use>.
|
|
1772
1887
|
const flat = flatFns(s.fns ?? []);
|
|
1773
|
-
const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
|
|
1888
|
+
const fns = Object.fromEntries(flat.map(([n, d]) => [n, { js: fnSource(n, d), ...(d.arity > 1 ? { arity: d.arity, call: jsOf(d) } : {}), ...(d.lean ? { lean: d.lean } : {}), ...(d.recur ? { recur: d.recur } : {}) }]));
|
|
1774
1889
|
const facts = [
|
|
1775
1890
|
...flat.flatMap(([n, d, uses]) => Object.entries(d.facts?.(fnTerm(n, d), uses) ?? {}).map(([name, f]) => this.lawOf(`${n}: ${name}`, f))),
|
|
1776
1891
|
...(s.facts ?? []).flatMap(f => Object.entries(f(pEnv)).map(([name, out]) => this.lawOf(name, out))),
|
|
@@ -1837,7 +1952,7 @@ export class Rule {
|
|
|
1837
1952
|
}
|
|
1838
1953
|
if (d === (opts.depth ?? 8))
|
|
1839
1954
|
break;
|
|
1840
|
-
const input = this.randomInput(rand, bound);
|
|
1955
|
+
const input = this.randomInput(rand, bound, params);
|
|
1841
1956
|
if (s.prune || !allowed(m, run, params, st, input))
|
|
1842
1957
|
continue;
|
|
1843
1958
|
d++;
|
|
@@ -1961,7 +2076,7 @@ export class Rule {
|
|
|
1961
2076
|
const edges = trial === 0 ? opts.edges ?? [] : [];
|
|
1962
2077
|
for (let d = 0; d < (opts.depth ?? 10) + edges.length; d++) {
|
|
1963
2078
|
const request = edges[d] ?? opts.draw?.(rand) ?? sample(s.inputs);
|
|
1964
|
-
ours = gen.step(ps, ours, this.encodeInput(request));
|
|
2079
|
+
ours = gen.step(ps, ours, this.encodeInput(request, params));
|
|
1965
2080
|
theirs = reference.step(params, theirs, request);
|
|
1966
2081
|
const got = this.decode(ours);
|
|
1967
2082
|
for (const [field, want] of Object.entries(theirs)) {
|
|
@@ -1977,10 +2092,12 @@ export class Rule {
|
|
|
1977
2092
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
1978
2093
|
encodeParams(values) { return encode(this.spec.params, values); }
|
|
1979
2094
|
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural.
|
|
1980
|
-
// A carried list is an array of its items' runtime values (its length and folds computed from them)
|
|
2095
|
+
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
2096
|
+
// folds that read the params are computed with.
|
|
1981
2097
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1982
|
-
encodeInput(values) {
|
|
1983
|
-
const
|
|
2098
|
+
encodeInput(values, params) {
|
|
2099
|
+
const at = params && this.paramValues(this.encodeParams(params));
|
|
2100
|
+
const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, at).encode(values[n])]));
|
|
1984
2101
|
const out = encode(this.spec.inputs, { ...values, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
|
|
1985
2102
|
for (const l of Object.values(lists))
|
|
1986
2103
|
Object.assign(out, l.maps);
|
|
@@ -1994,13 +2111,18 @@ export class Rule {
|
|
|
1994
2111
|
}
|
|
1995
2112
|
// Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
|
|
1996
2113
|
randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
|
|
1997
|
-
|
|
2114
|
+
// The params' values by their kinds, as constants (what a fold that reads them is computed with).
|
|
2115
|
+
paramValues(ps) {
|
|
2116
|
+
return Object.fromEntries(this.spec.params.map(([n, k]) => [n, k.build(n, part => new Term({ op: 'const', value: ps[part] }, ps[part]))]));
|
|
2117
|
+
}
|
|
2118
|
+
// `ps`: the params (encoded), for folds that read them.
|
|
2119
|
+
randomInput(rand, bound, ps) {
|
|
1998
2120
|
const out = Object.assign({}, ...this.spec.inputs.map(([n, k]) => k.random(n, rand, bound)));
|
|
1999
2121
|
for (const n of this.spec.carried ?? [])
|
|
2000
2122
|
out[n] = new Map(Array.from({ length: bound + 1 }, (_, key) => [key, rand(2)]).filter(([, cell]) => cell === 1));
|
|
2001
2123
|
// A carried list: items drawn by their kind, its length and folds as the encoding computes them.
|
|
2002
2124
|
for (const [n, k, fs] of this.spec.lists2 ?? []) {
|
|
2003
|
-
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
|
|
2125
|
+
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, ps && this.paramValues(ps)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
|
|
2004
2126
|
Object.assign(out, l.maps, { [`${n}_len`]: l.len });
|
|
2005
2127
|
}
|
|
2006
2128
|
return out;
|
|
@@ -2022,6 +2144,11 @@ export class Rule {
|
|
|
2022
2144
|
}
|
|
2023
2145
|
for (const [n, k] of this.spec.items ?? [])
|
|
2024
2146
|
out[n] = Array.from({ length: st.scalars[`${n}_len`] }, (_, i) => cell(n, k, i));
|
|
2147
|
+
// A table of nodes: node 1 first, each its value and its children.
|
|
2148
|
+
for (const [n, k, cs] of this.spec.nodes ?? []) {
|
|
2149
|
+
const at = (part, key) => st.maps[part]?.get(key) ?? 0;
|
|
2150
|
+
out[n] = Array.from({ length: st.scalars[`${n}_size`] }, (_, i) => ({ value: cell(n, k, i + 1), ...Object.fromEntries(cs.map(c => [c, at(`${n}^${c}`, i + 1)])) }));
|
|
2151
|
+
}
|
|
2025
2152
|
return out;
|
|
2026
2153
|
}
|
|
2027
2154
|
// The prune order's variable, if the history is pruned (a runtime must send it nondecreasing).
|
|
@@ -2105,7 +2232,7 @@ export class Fun {
|
|
|
2105
2232
|
this.compiledRule ??= (rule => ({ rule, gen: rule.compiled() }))(this.rule);
|
|
2106
2233
|
const { rule, gen } = this.compiledRule, ps = rule.encodeParams(params);
|
|
2107
2234
|
const kinds = Object.entries(this.result(this.r['env']({ flags: false, state: false, queries: false })));
|
|
2108
|
-
const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values)));
|
|
2235
|
+
const st = rule.decode(gen.step(ps, gen.init(ps), rule.encodeInput(values, params)));
|
|
2109
2236
|
return Object.fromEntries(kinds.map(([k]) => [k, st[`out.${k}`]]));
|
|
2110
2237
|
}
|
|
2111
2238
|
}
|
|
@@ -2212,7 +2339,7 @@ export function crossCheckDecision(d, opts = {}) {
|
|
|
2212
2339
|
const r = d.rule, m = r.machine, bound = opts.bound ?? 4, rand = opts.rand ?? seeded(11), own = d.given.slice(d.given.length - src.own);
|
|
2213
2340
|
let checked = 0;
|
|
2214
2341
|
for (let i = 0; i < (opts.trials ?? 500); i++) {
|
|
2215
|
-
const
|
|
2342
|
+
const ps = r.randomParams(rand, bound), input = r.randomInput(rand, bound, ps), values = { ...ps, ...numbersOf(input) };
|
|
2216
2343
|
const st = { scalars: {}, maps: Object.fromEntries(Object.entries(input).filter((e) => typeof e[1] !== 'number').map(([n, cells]) => [n, new Map(cells)])) };
|
|
2217
2344
|
const keys = exactKeys(keysOf(bound, st), values, st);
|
|
2218
2345
|
const sc = { vars: values, maps: Object.fromEntries(Object.entries(st.maps).map(([n, cells]) => [n, { cells, dflt: 0 }])), keys, flags: {} };
|
|
@@ -2243,6 +2370,11 @@ export class FoldRule {
|
|
|
2243
2370
|
assume(f) { return this.with({ assume: [...this.spec.assume, f] }); }
|
|
2244
2371
|
leaf(f) { return this.with({ leaf: f }); }
|
|
2245
2372
|
node(f) { return this.with({ node: f }); }
|
|
2373
|
+
// Nodes with any number of children (RoseFold): the children's values folded into `kids`, from `from` by `step` (which
|
|
2374
|
+
// reads the params), each lemma a law of every prefix of every node's children that meet the fold's laws.
|
|
2375
|
+
children(from, step, lemmas = {}) {
|
|
2376
|
+
return new RoseFold(this.spec, { from: from, step: step, lemmas: lemmas });
|
|
2377
|
+
}
|
|
2246
2378
|
env(inputs = true) {
|
|
2247
2379
|
const out = {};
|
|
2248
2380
|
for (const [n, k] of this.spec.params)
|
|
@@ -2337,4 +2469,151 @@ export class FoldRule {
|
|
|
2337
2469
|
}
|
|
2338
2470
|
}
|
|
2339
2471
|
export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
|
|
2340
|
-
|
|
2472
|
+
// A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
|
|
2473
|
+
export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, [...(f.lemmas ?? []), ...laws]));
|
|
2474
|
+
const asTerm = (t) => (t instanceof Term ? t : lit(t));
|
|
2475
|
+
// A fold over trees whose nodes have any number of children, as the kernel's binary fold (so the kernel and Lean's
|
|
2476
|
+
// fold_sound check it as they check any fold): a node's children become a chain of glue nodes, each folding one child
|
|
2477
|
+
// into the values of the rest (`kids`, from `from` at the chain's end), and the node reads the chain's values. Every
|
|
2478
|
+
// tree node carries `kind$` (0 a node or leaf of the tree, 1 a glue node or the chain's end) and its role as an input; a
|
|
2479
|
+
// law of the fold is claimed where kind$ is 0, each lemma where it is 1. A binary tree no rose tree maps to (a child
|
|
2480
|
+
// where a chain belongs, or a chain where a child does) is read as one that does: a node reads a chain only, a glue
|
|
2481
|
+
// node a child only, so the laws are proven of every binary tree, and of each rose tree's image in particular.
|
|
2482
|
+
// fold('org chart').params({cap: nat}).inputs({pay: nat})
|
|
2483
|
+
// .leaf(({pay}) => ({cost: pay, heads: 1}))
|
|
2484
|
+
// .children({cost: 0, heads: 0}, (acc, kid) => ({cost: acc.cost.plus(kid.cost), heads: acc.heads.plus(kid.heads)}))
|
|
2485
|
+
// .node(({pay}, kids) => ({cost: kids.cost.plus(pay), heads: kids.heads.plus(1)}))
|
|
2486
|
+
export class RoseFold {
|
|
2487
|
+
spec;
|
|
2488
|
+
kids;
|
|
2489
|
+
nodeOf;
|
|
2490
|
+
constructor(spec, kids, nodeOf) {
|
|
2491
|
+
this.spec = spec;
|
|
2492
|
+
this.kids = kids;
|
|
2493
|
+
this.nodeOf = nodeOf;
|
|
2494
|
+
}
|
|
2495
|
+
get name() { return this.spec.name; }
|
|
2496
|
+
node(f) { return new RoseFold(this.spec, this.kids, f); }
|
|
2497
|
+
env(inputs = true) {
|
|
2498
|
+
const out = {};
|
|
2499
|
+
for (const [n, k] of this.spec.params)
|
|
2500
|
+
out[n] = k.value(n);
|
|
2501
|
+
if (inputs)
|
|
2502
|
+
for (const [n, k] of this.spec.inputs)
|
|
2503
|
+
out[n] = k.value(n);
|
|
2504
|
+
return out;
|
|
2505
|
+
}
|
|
2506
|
+
names() {
|
|
2507
|
+
if (!this.spec.leaf)
|
|
2508
|
+
throw new Error(`${this.spec.name}: a fold needs a leaf`);
|
|
2509
|
+
return Object.keys(this.spec.leaf(this.env()));
|
|
2510
|
+
}
|
|
2511
|
+
side(prefix) { return Object.fromEntries(this.names().map(n => [n, v(`${prefix}${n}`)])); }
|
|
2512
|
+
accs(prefix) { return Object.fromEntries(Object.keys(this.kids.from).map(a => [a, v(`${prefix}kids$${a}`)])); }
|
|
2513
|
+
start(p) { return Object.fromEntries(Object.entries(this.kids.from).map(([a, x]) => [a, typeof x === 'number' ? lit(x) : asTerm(x(p))])); }
|
|
2514
|
+
// A law about the values and the params, claimed of every node and leaf of the tree.
|
|
2515
|
+
law(name, f) { return { name, holds: v('kind$').atLeast(1).or(f({ ...this.env(false), ...this.side('') })).c }; }
|
|
2516
|
+
// The children's lemmas (claimed of every glue node), and that kind$ is 0 or 1: proveFold adds them.
|
|
2517
|
+
get lemmas() {
|
|
2518
|
+
const p = this.env(false);
|
|
2519
|
+
return [{ name: `${this.spec.name}: kind$ is 0 or 1`, holds: v('kind$').atMost(1).c },
|
|
2520
|
+
...Object.entries(this.kids.lemmas).map(([n, l]) => ({ name: n, holds: v('kind$').eq(0).or(l(this.accs(''), p)).c }))];
|
|
2521
|
+
}
|
|
2522
|
+
get fold() {
|
|
2523
|
+
const s = this.spec, node = this.nodeOf;
|
|
2524
|
+
if (!s.leaf || !node)
|
|
2525
|
+
throw new Error(`${s.name}: a fold needs a leaf and a node`);
|
|
2526
|
+
const env = this.env(), pEnv = this.env(false), names = this.names(), accNames = Object.keys(this.kids.from);
|
|
2527
|
+
const role = v('role$'), real = role.eq(0);
|
|
2528
|
+
const pre = [...s.params.flatMap(([n, k]) => k.assume(n)).map(c => c.c), ...s.pre.map(f => f(pEnv).c)];
|
|
2529
|
+
const own = all(...s.inputs.flatMap(([n, k]) => k.assume(n)), ...s.assume.map(f => f(env)));
|
|
2530
|
+
const assume = role.atMost(1).and(role.eq(1).or(own)).c;
|
|
2531
|
+
const start = this.start(pEnv);
|
|
2532
|
+
// A leaf of the tree, or the end of a chain (no children yet).
|
|
2533
|
+
const leafOut = s.leaf(env);
|
|
2534
|
+
const leaf = { ...Object.fromEntries(names.map(n => [n, when(real, leafOut[n], 0).e])),
|
|
2535
|
+
...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, start[a]).e])), 'kind$': when(real, 0, 1).e };
|
|
2536
|
+
// A node reads the chain on its left (its children's values); a glue node folds its left child into the rest.
|
|
2537
|
+
const lAcc = this.accs('l_'), rAcc = this.accs('r_'), lk = v('l_kind$'), rk = v('r_kind$');
|
|
2538
|
+
const kidsIn = Object.fromEntries(accNames.map(a => [a, when(lk.eq(1), lAcc[a], start[a])]));
|
|
2539
|
+
const rest = Object.fromEntries(accNames.map(a => [a, when(rk.eq(1), rAcc[a], start[a])]));
|
|
2540
|
+
const nodeOut = node(env, kidsIn), stepped = this.kids.step(rest, this.side('l_'), pEnv);
|
|
2541
|
+
const nodeE = { ...Object.fromEntries(names.map(n => [n, when(real, nodeOut[n], 0).e])),
|
|
2542
|
+
...Object.fromEntries(accNames.map(a => [`kids$${a}`, when(real, 0, when(lk.eq(0), stepped[a], rest[a])).e])), 'kind$': when(real, 0, 1).e };
|
|
2543
|
+
return { name: s.name, params: s.params.flatMap(([n, k]) => k.vars(n)), inputs: [...s.inputs.flatMap(([n, k]) => k.vars(n)), 'role$'], leaf, node: nodeE,
|
|
2544
|
+
...(pre.length ? { pre: pre.length === 1 ? pre[0] : { op: 'and', xs: pre } } : {}), assume };
|
|
2545
|
+
}
|
|
2546
|
+
// The values of a rose tree, by the terms proven (the tree as the binary tree the fold reads it as).
|
|
2547
|
+
run(tree, params = {}) {
|
|
2548
|
+
const f = this.fold, ps = encode(this.spec.params, params), none = { name: f.name, params: [], inputs: [], state: {}, step: {} };
|
|
2549
|
+
const st = { scalars: {}, maps: {} };
|
|
2550
|
+
if (f.pre && !evalC(f.pre, ps, st, none))
|
|
2551
|
+
throw new RangeError(`${f.name}: the params don't meet its precondition`);
|
|
2552
|
+
const zero = Object.fromEntries(f.inputs.map(n => [n, 0]));
|
|
2553
|
+
const at = (exprs, env) => Object.fromEntries(Object.entries(exprs).map(([n, e]) => [n, evalE(e, env, st, none)]));
|
|
2554
|
+
const kidsOf = (vals, prefix) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [`${prefix}${n}`, x]));
|
|
2555
|
+
const chain = (cs) => {
|
|
2556
|
+
const env = { ...ps, ...zero, 'role$': 1 };
|
|
2557
|
+
if (!cs.length)
|
|
2558
|
+
return at(f.leaf, env);
|
|
2559
|
+
return at(f.node, { ...env, ...kidsOf(go(cs[0]), 'l_'), ...kidsOf(chain(cs.slice(1)), 'r_') });
|
|
2560
|
+
};
|
|
2561
|
+
const go = (t) => {
|
|
2562
|
+
const own = 'leaf' in t ? t.leaf : t.node, env = { ...ps, ...encode(this.spec.inputs, own), 'role$': 0 };
|
|
2563
|
+
if (f.assume && !evalC(f.assume, env, st, none))
|
|
2564
|
+
throw new RangeError(`${f.name}: ${JSON.stringify(own)} doesn't meet its assumption`);
|
|
2565
|
+
if ('leaf' in t)
|
|
2566
|
+
return at(f.leaf, env);
|
|
2567
|
+
return at(f.node, { ...env, ...kidsOf(chain(t.children), 'l_'), ...kidsOf(chain([]), 'r_') });
|
|
2568
|
+
};
|
|
2569
|
+
const out = go(tree);
|
|
2570
|
+
return Object.fromEntries(this.names().map(n => [n, out[n]]));
|
|
2571
|
+
}
|
|
2572
|
+
// The builders against what they built, on random rose trees: each value by the callbacks' own arithmetic (the
|
|
2573
|
+
// children folded by step, then the node) against the terms proven, and each law and lemma at the root.
|
|
2574
|
+
crossCheck(laws = [], opts = {}) {
|
|
2575
|
+
const s = this.spec, rand = opts.rand ?? seeded(17), bound = opts.bound ?? 6, f = this.fold;
|
|
2576
|
+
let checked = 0;
|
|
2577
|
+
const sample = (kinds) => Object.fromEntries(kinds.map(([n, k]) => [n, k.sample(rand, bound)]));
|
|
2578
|
+
const tree = (d) => (d === 0 || rand(3) === 0 ? { leaf: sample(s.inputs) } : { node: sample(s.inputs), children: Array.from({ length: rand(4) }, () => tree(d - 1)) });
|
|
2579
|
+
const known = (vals) => Object.fromEntries(Object.entries(vals).map(([n, x]) => [n, new Term({ op: 'const', value: x }, x)]));
|
|
2580
|
+
const nums = (out) => { const vals = Object.entries(out).map(([n, x]) => [n, valOf(x)]); return vals.every(([, x]) => x !== undefined) ? Object.fromEntries(vals) : undefined; };
|
|
2581
|
+
for (let t = 0; t < (opts.trials ?? 200); t++) {
|
|
2582
|
+
const params = sample(s.params), ps = encode(s.params, params);
|
|
2583
|
+
const pv = known(ps), pEnv = Object.fromEntries(s.params.map(([n, k]) => [n, k.build(n, part => pv[part])]));
|
|
2584
|
+
const builder = (tr) => {
|
|
2585
|
+
const own = 'leaf' in tr ? tr.leaf : tr.node, iv = known(encode(s.inputs, own)), env = { ...pEnv, ...Object.fromEntries(s.inputs.map(([n, k]) => [n, k.build(n, part => iv[part])])) };
|
|
2586
|
+
if ('leaf' in tr)
|
|
2587
|
+
return nums(s.leaf(env));
|
|
2588
|
+
let acc = nums(this.start(pEnv));
|
|
2589
|
+
for (const c of [...tr.children].reverse()) {
|
|
2590
|
+
const kid = builder(c);
|
|
2591
|
+
acc = acc && kid && nums(this.kids.step(known(acc), known(kid), pEnv));
|
|
2592
|
+
}
|
|
2593
|
+
return acc && nums(this.nodeOf(env, known(acc)));
|
|
2594
|
+
};
|
|
2595
|
+
const tr = tree(opts.depth ?? 3);
|
|
2596
|
+
let ours;
|
|
2597
|
+
try {
|
|
2598
|
+
ours = this.run(tr, params);
|
|
2599
|
+
}
|
|
2600
|
+
catch {
|
|
2601
|
+
continue;
|
|
2602
|
+
}
|
|
2603
|
+
const theirs = builder(tr);
|
|
2604
|
+
if (!theirs)
|
|
2605
|
+
continue;
|
|
2606
|
+
for (const [n, x] of Object.entries(theirs)) {
|
|
2607
|
+
checked++;
|
|
2608
|
+
if (ours[n] !== x)
|
|
2609
|
+
return { checked, failure: `${f.name}: ${n} is ${x} by its callbacks, ${ours[n]} by the terms proven, for ${JSON.stringify(tr)}` };
|
|
2610
|
+
}
|
|
2611
|
+
for (const law of laws) {
|
|
2612
|
+
checked++;
|
|
2613
|
+
if (!evalC(law.holds, { ...ps, ...ours, 'kind$': 0 }, { scalars: {}, maps: {} }, { name: f.name, params: [], inputs: [], state: {}, step: {} }))
|
|
2614
|
+
return { checked, failure: `${f.name}: law ${law.name} fails for ${JSON.stringify(tr)} with params ${JSON.stringify(params)}` };
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return { checked };
|
|
2618
|
+
}
|
|
2619
|
+
}
|
package/dist/core/gdp.js
CHANGED
|
@@ -21,7 +21,7 @@ export function guard(prover, fn, field, opts) {
|
|
|
21
21
|
if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
|
|
22
22
|
throw new Error(`${fn.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
|
|
23
23
|
return (args, ...about) => {
|
|
24
|
-
const input = r.encodeInput(args);
|
|
24
|
+
const input = r.encodeInput(args, params);
|
|
25
25
|
if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
|
|
26
26
|
return null;
|
|
27
27
|
return atPath(fn.run(args, params), field) === true ? prover.prove(...about) : null;
|
|
@@ -46,7 +46,7 @@ export function gate(prover, r, flag, opts) {
|
|
|
46
46
|
let st = opts.state ?? compiled.init(ps);
|
|
47
47
|
return {
|
|
48
48
|
request: (args, ...about) => {
|
|
49
|
-
const input = r.encodeInput(args);
|
|
49
|
+
const input = r.encodeInput(args, params);
|
|
50
50
|
if (!allowed(m, run, ps, st, input))
|
|
51
51
|
return null;
|
|
52
52
|
const granted = requestEnv(m, run, ps, st, input)[`?${flag}`] === 1;
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export type Fn = {
|
|
|
71
71
|
js: string;
|
|
72
72
|
lean?: string;
|
|
73
73
|
recur?: Recurrence;
|
|
74
|
+
arity?: number;
|
|
75
|
+
call?: string;
|
|
74
76
|
};
|
|
75
77
|
export type Recurrence = {
|
|
76
78
|
args: string[];
|
|
@@ -162,6 +164,7 @@ export type Request = Record<string, number | ReadonlyMap<number, number>>;
|
|
|
162
164
|
export declare const naturalIn: (v: unknown, what: string) => number;
|
|
163
165
|
export declare function evalE(e: E, env: Env, st: State, m: Machine): number;
|
|
164
166
|
export declare const fnValue: (fn: Fn, key: number) => number;
|
|
167
|
+
export declare function pairedArgs(key: E, n: number): E[] | undefined;
|
|
165
168
|
export declare function evalC(c: C, env: Env, st: State, m: Machine): boolean;
|
|
166
169
|
export declare function carriedView(m: Machine, st: State, input: Request, rename?: (map: string) => string): State;
|
|
167
170
|
export declare const numbersOf: (input: Request) => Record<string, number>;
|
package/dist/core/machine.js
CHANGED
|
@@ -130,7 +130,12 @@ export function evalE(e, env, st, m) {
|
|
|
130
130
|
case 'max': return Math.max(evalE(e.a, env, st, m), evalE(e.b, env, st, m));
|
|
131
131
|
case 'if': return evalC(e.c, env, st, m) ? evalE(e.then, env, st, m) : evalE(e.else, env, st, m);
|
|
132
132
|
case 'get': {
|
|
133
|
-
const
|
|
133
|
+
const fn = m.fns?.[e.map], args = fn?.call ? pairedArgs(e.key, fn.arity) : undefined;
|
|
134
|
+
if (args) {
|
|
135
|
+
const xs = args.map(a => evalE(a, env, st, m));
|
|
136
|
+
return naturalIn(callOf(fn)(...xs), `${e.map}(${xs.join(', ')})`);
|
|
137
|
+
}
|
|
138
|
+
const key = evalE(e.key, env, st, m);
|
|
134
139
|
return fn ? naturalIn(fnOf(fn)(key), `${e.map}(${key})`) : st.maps[e.map].get(key) ?? st.defaults?.[e.map] ?? m.maps[e.map];
|
|
135
140
|
}
|
|
136
141
|
}
|
|
@@ -147,6 +152,34 @@ const fnOf = (fn) => {
|
|
|
147
152
|
}
|
|
148
153
|
return f;
|
|
149
154
|
};
|
|
155
|
+
const compiledCalls = new WeakMap();
|
|
156
|
+
const callOf = (fn) => {
|
|
157
|
+
let f = compiledCalls.get(fn);
|
|
158
|
+
if (!f) {
|
|
159
|
+
f = compileFunction([], `return (${fn.call})`)();
|
|
160
|
+
compiledCalls.set(fn, f);
|
|
161
|
+
}
|
|
162
|
+
return f;
|
|
163
|
+
};
|
|
164
|
+
// The n arguments a key pairs (left-nested, ((a, b), c)..., each pair (s)² + b with s = a + b), if it is their pairing.
|
|
165
|
+
const paired = new WeakMap();
|
|
166
|
+
export function pairedArgs(key, n) {
|
|
167
|
+
let byN = paired.get(key);
|
|
168
|
+
if (!byN)
|
|
169
|
+
paired.set(key, (byN = new Map()));
|
|
170
|
+
if (byN.has(n))
|
|
171
|
+
return byN.get(n);
|
|
172
|
+
let out;
|
|
173
|
+
if (n === 1)
|
|
174
|
+
out = [key];
|
|
175
|
+
else if (key.op === 'add' && key.a.op === 'prod' && key.a.a.op === 'add' && alike(key.a.a, key.a.b) && alike(key.a.a.b, key.b)) {
|
|
176
|
+
const first = pairedArgs(key.a.a.a, n - 1);
|
|
177
|
+
out = first && [...first, key.b];
|
|
178
|
+
}
|
|
179
|
+
byN.set(n, out);
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
const alike = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
150
183
|
export function evalC(c, env, st, m) {
|
|
151
184
|
switch (c.op) {
|
|
152
185
|
case '<': return evalE(c.a, env, st, m) < evalE(c.b, env, st, m);
|
|
@@ -323,7 +356,12 @@ export function compileJs(m) {
|
|
|
323
356
|
case 'min': return `Math.min(${ex(e.a)}, ${ex(e.b)})`;
|
|
324
357
|
case 'max': return `Math.max(${ex(e.a)}, ${ex(e.b)})`;
|
|
325
358
|
case 'if': return `(${cx(e.c)} ? ${ex(e.then)} : ${ex(e.else)})`;
|
|
326
|
-
case 'get':
|
|
359
|
+
case 'get': {
|
|
360
|
+
const fn = m.fns?.[e.map], args = fn?.call ? pairedArgs(e.key, fn.arity) : undefined;
|
|
361
|
+
if (args)
|
|
362
|
+
return `$n(fnc${fns.indexOf(e.map)}(${args.map(ex).join(', ')}), ${id(`${e.map}()`)})`;
|
|
363
|
+
return fns.includes(e.map) ? `$n(fn${fns.indexOf(e.map)}(${ex(e.key)}), ${id(`${e.map}()`)})` : `(maps[${id(e.map)}].get(${ex(e.key)}) ?? ${carriedMaps.includes(e.map) ? '0' : `dft[${id(e.map)}] ?? ${plain.maps[e.map]}`})`;
|
|
364
|
+
}
|
|
327
365
|
}
|
|
328
366
|
};
|
|
329
367
|
const cx = (c) => {
|
|
@@ -379,7 +417,7 @@ function $n(v, what) { if (typeof v !== 'number' || !Number.isSafeInteger(v) ||
|
|
|
379
417
|
function $x(v) { if (v > 9007199254740991) throw new RangeError('a value passed 2^53 - 1, past which the proofs over the naturals no longer describe the run'); return v; }
|
|
380
418
|
function $m(v, what) { if (!(v instanceof Map)) throw new RangeError(what + ' must be a Map of naturals'); for (const [key, x] of v) { $n(key, 'a key of ' + what); $n(x, 'a cell of ' + what); } return v; }
|
|
381
419
|
function $keys(maps, names) { const keys = new Set(); let past = 0; for (const c of Object.values(maps)) for (const key of c.keys()) past = Math.max(past, key + 1); for (const n of names) for (const key of maps[n].keys()) keys.add(key); return [...keys, past]; }
|
|
382
|
-
${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
|
|
420
|
+
${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n${m.fns[name].call ? `const fnc${i} = (${m.fns[name].call});\n` : ''}`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
|
|
383
421
|
function $dense(keys, f) { const top = keys[keys.length - 1]; if (top > 1000000) throw new RangeError('a write at every key reads its key in arithmetic past a million keys (' + top + ')'); const d = f(top), cells = new Map(); for (let k = 0; k < top; k++) { const v = f(k); if (v !== d) cells.set(k, v); } return {cells, d}; }
|
|
384
422
|
function $every(keys, f) { const past = keys[keys.length - 1], d = f(past), cells = new Map(); for (let i = 0; i < keys.length - 1; i++) { const v = f(keys[i]); if (v !== d) cells.set(keys[i], v); } return {cells, d}; }
|
|
385
423
|
export function init(params) {
|
package/dist/kernel/kernel.js
CHANGED
|
@@ -1042,8 +1042,9 @@ export function stepLawObligations(machine, laws, instances, sl) {
|
|
|
1042
1042
|
const m = machine.history ? historyMachine(machine).machine : machine;
|
|
1043
1043
|
scope(m, laws);
|
|
1044
1044
|
const k = sl.steps ?? 1;
|
|
1045
|
-
|
|
1046
|
-
|
|
1045
|
+
// Past 64 the cost is the bound (proving grows about as k³: 64 steps in about 90 s; Lean checks 32 in about 13 minutes).
|
|
1046
|
+
if (!Number.isInteger(k) || k < 1 || k > 64)
|
|
1047
|
+
throw new Error(`step law ${sl.name}: steps is a whole number from 1 to 64`);
|
|
1047
1048
|
const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
|
|
1048
1049
|
if (whens.length > k)
|
|
1049
1050
|
throw new Error(`step law ${sl.name}: one when per step at most`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"verification",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"build": "bun scripts/build.ts",
|
|
90
90
|
"prepublishOnly": "bun scripts/build.ts",
|
|
91
91
|
"smoke": "sh scripts/smoke.sh",
|
|
92
|
-
"test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts invoices.test.ts folds.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
|
|
92
|
+
"test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts invoices.test.ts folds.test.ts trees.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@gdp-ts/core": "0.1.0",
|