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