gdp-wired 0.2.0 → 0.4.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 +128 -92
- package/dist/core/compose.d.ts +15 -1
- package/dist/core/compose.js +132 -8
- package/dist/core/dsl.d.ts +272 -17
- package/dist/core/dsl.js +1098 -60
- package/dist/core/machine.d.ts +34 -6
- package/dist/core/machine.js +194 -22
- package/dist/core/watch.d.ts +14 -0
- package/dist/core/watch.js +78 -0
- package/dist/kernel/kernel.d.ts +30 -2
- package/dist/kernel/kernel.js +306 -48
- package/dist/kernel/prove.d.ts +36 -9
- package/dist/kernel/prove.js +317 -28
- package/dist/lean-kernel/emit.d.ts +124 -0
- package/dist/lean-kernel/emit.js +875 -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/kernel/kernel.js
CHANGED
|
@@ -106,6 +106,7 @@ function comparison(c, path) {
|
|
|
106
106
|
export function truth(c, path) {
|
|
107
107
|
switch (c.op) {
|
|
108
108
|
case 'flag': throw new Error('flags are inlined before the kernel sees them');
|
|
109
|
+
case 'all': return undefined; // closed before a goal is checked; opaque here, so it gives a leaf nothing
|
|
109
110
|
case 'not': {
|
|
110
111
|
const t = truth(c.x, path);
|
|
111
112
|
return t === undefined ? undefined : !t;
|
|
@@ -131,6 +132,7 @@ export function truth(c, path) {
|
|
|
131
132
|
export function facts(c, path, positive = true) {
|
|
132
133
|
switch (c.op) {
|
|
133
134
|
case 'flag': throw new Error('flags are inlined before the kernel sees them');
|
|
135
|
+
case 'all': return [];
|
|
134
136
|
case 'not': return facts(c.x, path, !positive);
|
|
135
137
|
case 'and':
|
|
136
138
|
case 'or': {
|
|
@@ -234,8 +236,8 @@ function natural(e) {
|
|
|
234
236
|
default: return unreachable(e);
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
|
-
const naturalC = (c) => c.op === 'flag' ? true : c.op === 'not' ? naturalC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.every(naturalC) : natural(c.a) && natural(c.b);
|
|
238
|
-
function substE(e, s) {
|
|
239
|
+
const naturalC = (c) => c.op === 'flag' ? true : c.op === 'all' ? naturalC(c.c) : c.op === 'not' ? naturalC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.every(naturalC) : natural(c.a) && natural(c.b);
|
|
240
|
+
export function substE(e, s) {
|
|
239
241
|
switch (e.op) {
|
|
240
242
|
case 'var': return s.vars[e.name] ?? e;
|
|
241
243
|
case 'const': return e;
|
|
@@ -262,6 +264,10 @@ export function substC(c, s) {
|
|
|
262
264
|
}
|
|
263
265
|
if (c.op === 'not')
|
|
264
266
|
return { op: 'not', x: substC(c.x, s) };
|
|
267
|
+
if (c.op === 'all') {
|
|
268
|
+
const { [c.key]: _bound, ...vars } = s.vars;
|
|
269
|
+
return { op: 'all', key: c.key, c: substC(c.c, { ...s, vars }) };
|
|
270
|
+
}
|
|
265
271
|
if (c.op === 'and' || c.op === 'or')
|
|
266
272
|
return { op: c.op, xs: c.xs.map(y => substC(y, s)) };
|
|
267
273
|
return { ...c, a: substE(c.a, s), b: substE(c.b, s) };
|
|
@@ -278,15 +284,19 @@ const identity = { vars: {}, maps: {}, flags: {} };
|
|
|
278
284
|
const from = (at = identity) => ({ hyps: [], at });
|
|
279
285
|
// Conditions that hold where the run is.
|
|
280
286
|
const holds = (r, cs) => ({ hyps: [...r.hyps, ...cs.map(c => substC(c, r.at))], at: r.at });
|
|
281
|
-
// Names read where the run is: inputs named for this step (a request's terms, or fresh names for a later step)
|
|
282
|
-
|
|
287
|
+
// Names read where the run is: inputs named for this step (a request's terms, or fresh names for a later step), and the
|
|
288
|
+
// maps a request carries, renamed for a later step.
|
|
289
|
+
const reading = (r, vars, maps = {}) => ({ hyps: r.hyps, at: { ...r.at, vars: { ...r.at.vars, ...vars },
|
|
290
|
+
maps: { ...r.at.maps, ...Object.fromEntries(Object.entries(maps).map(([map, name]) => [map, (key) => ({ op: 'get', map: name, key })])) } } });
|
|
283
291
|
// One step: each name the update sets is its term read where the run is; a map read after it is the written value at
|
|
284
|
-
// the written key, and what the map held before elsewhere.
|
|
292
|
+
// the written key, and what the map held before elsewhere (or, written at every key, its value at the key read).
|
|
285
293
|
const step = (r, u) => {
|
|
286
294
|
const at = r.at, before = (map, key) => (at.maps[map] ? at.maps[map](key) : { op: 'get', map, key });
|
|
287
295
|
return { hyps: r.hyps, at: {
|
|
288
296
|
vars: { ...at.vars, ...Object.fromEntries(Object.entries(u.vars).map(([n, e]) => [n, substE(e, at)])) },
|
|
289
|
-
maps: { ...at.maps, ...Object.fromEntries(Object.entries(u.maps).map(([map, w]) => [map,
|
|
297
|
+
maps: { ...at.maps, ...Object.fromEntries(Object.entries(u.maps).map(([map, w]) => [map, 'each' in w
|
|
298
|
+
? (key) => substE(w.value, { ...at, vars: { ...at.vars, [w.each]: key } })
|
|
299
|
+
: (key) => ({ op: 'if', c: { op: '=', a: key, b: substE(w.key, at) }, then: substE(w.value, at), else: before(map, key) })])) },
|
|
290
300
|
flags: at.flags,
|
|
291
301
|
} };
|
|
292
302
|
};
|
|
@@ -300,17 +310,51 @@ const across = (r, c, state, maps, first) => substC(c, {
|
|
|
300
310
|
flags: first.flags,
|
|
301
311
|
});
|
|
302
312
|
const goal = (name, r, conclusion) => ({ name, hyps: [...r.hyps, { op: 'not', x: conclusion }] });
|
|
303
|
-
// A machine's
|
|
313
|
+
// A machine's flags as its terms read them (decided before the step, over the frame it starts from): a plain flag its
|
|
314
|
+
// condition (through the flags before it), a quantified flag (one whose condition holds at every key) its 0/1 variable
|
|
315
|
+
// ?f, which `closing` ties to the condition.
|
|
316
|
+
const flagsOf = (m) => {
|
|
317
|
+
const out = {};
|
|
318
|
+
for (const [f, c] of Object.entries(m.step.flags ?? {}))
|
|
319
|
+
out[f] = c.op === 'all' ? { op: '=', a: { op: 'var', name: `?${f}` }, b: { op: 'const', value: 1 } } : substC(c, { vars: {}, maps: {}, flags: out });
|
|
320
|
+
return out;
|
|
321
|
+
};
|
|
322
|
+
const quantified = (m) => Object.entries(m.step.flags ?? {}).filter((e) => e[1].op === 'all');
|
|
323
|
+
// A machine's step as an update, its flags inlined.
|
|
304
324
|
const updateOf = (m) => {
|
|
305
|
-
const flags = { vars: {}, maps: {}, flags: m
|
|
325
|
+
const flags = { vars: {}, maps: {}, flags: flagsOf(m) };
|
|
306
326
|
return {
|
|
307
327
|
vars: Object.fromEntries(Object.entries(m.step.state ?? {}).map(([n, e]) => [n, substE(e, flags)])),
|
|
308
|
-
maps: Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([map, w]) => [map, { key: substE(w.key, flags), value: substE(w.value, flags) }])),
|
|
328
|
+
maps: Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([map, w]) => [map, 'each' in w ? { each: w.each, value: substE(w.value, flags) } : { key: substE(w.key, flags), value: substE(w.value, flags) }])),
|
|
309
329
|
};
|
|
310
330
|
};
|
|
311
|
-
const inlineFlags = (m, c) => substC(c, { vars: {}, maps: {}, flags: m
|
|
331
|
+
const inlineFlags = (m, c) => substC(c, { vars: {}, maps: {}, flags: flagsOf(m) });
|
|
332
|
+
// A quantified flag ?f, closed: it is 0 or 1; where it is 1, its condition holds at each key the proof names (instances
|
|
333
|
+
// under ?f); where it is 0, the condition fails at a fresh witness w?f. A real step decides ?f by the condition, and if
|
|
334
|
+
// that fails some key fails it, so these hold of every real step: a goal that refutes them refutes the machine.
|
|
335
|
+
const closing = (m, instances) => quantified(m).flatMap(([f, q]) => {
|
|
336
|
+
const v = { op: 'var', name: `?${f}` }, zero = { op: 'const', value: 0 }, one = { op: 'const', value: 1 };
|
|
337
|
+
const body = (key) => substC(q.c, { vars: { [q.key]: key }, maps: {}, flags: flagsOf(m) });
|
|
338
|
+
return [{ op: '<=', a: v, b: one }, ...(instances[`?${f}`] ?? []).map((key) => ({ op: 'or', xs: [{ op: '=', a: v, b: zero }, body(key)] })),
|
|
339
|
+
{ op: 'or', xs: [{ op: '=', a: v, b: one }, { op: 'not', x: body({ op: 'var', name: `w?${f}` }) }] }];
|
|
340
|
+
});
|
|
341
|
+
// The names a later step (j >= 2) reads its request by: inputs, quantified flags and their witnesses, and carried maps.
|
|
342
|
+
const laterNames = (m, j) => ({
|
|
343
|
+
vars: Object.fromEntries([...m.inputs, ...quantified(m).flatMap(([f]) => [`?${f}`, `w?${f}`])].map(name => [name, { op: 'var', name: `${name}@${j}` }])),
|
|
344
|
+
maps: Object.fromEntries((m.inputMaps ?? []).map(map => [map, `${map}@${j}`])),
|
|
345
|
+
});
|
|
312
346
|
// The laws assumed where the run is: each plain law, and each every-key law at the keys the proof names.
|
|
313
|
-
const assumed = (laws, instances) => laws.flatMap(l =>
|
|
347
|
+
export const assumed = (laws, instances) => laws.flatMap(l => {
|
|
348
|
+
const keys = instances[l.name] ?? [];
|
|
349
|
+
if (l.keys?.length) {
|
|
350
|
+
const names = [l.forall, ...l.keys], n = names.length;
|
|
351
|
+
return Array.from({ length: Math.floor(keys.length / n) }, (_, t) => substC(l.holds, { vars: Object.fromEntries(names.map((name, i) => [name, keys[t * n + i]])), maps: {}, flags: {} }));
|
|
352
|
+
}
|
|
353
|
+
return l.forall ? keys.map(key => substC(l.holds, { vars: { [l.forall]: key }, maps: {}, flags: {} })) : [l.holds];
|
|
354
|
+
});
|
|
355
|
+
// The facts about the functions, at the keys the proof names: as they are (they read the params and functions alone, so
|
|
356
|
+
// any frame reads them alike).
|
|
357
|
+
const given = (m, r, instances) => ({ hyps: [...r.hyps, ...assumed(m.facts ?? [], instances)], at: r.at });
|
|
314
358
|
// The start: the state's start values over the params, every map cell its constant.
|
|
315
359
|
const startOf = (m) => ({ vars: { ...m.state }, maps: Object.fromEntries(Object.entries(m.maps ?? {}).map(([map, c]) => [map, () => ({ op: 'const', value: c })])), flags: {} });
|
|
316
360
|
function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() }) {
|
|
@@ -326,6 +370,14 @@ function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() })
|
|
|
326
370
|
case 'flag':
|
|
327
371
|
out.flags.add(t.name);
|
|
328
372
|
break;
|
|
373
|
+
case 'all': {
|
|
374
|
+
const inner = reads(t.c);
|
|
375
|
+
inner.vars.delete(t.key);
|
|
376
|
+
for (const k of ['vars', 'maps', 'flags'])
|
|
377
|
+
for (const name of inner[k])
|
|
378
|
+
out[k].add(name);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
329
381
|
case 'not':
|
|
330
382
|
reads(t.x, out);
|
|
331
383
|
break;
|
|
@@ -384,10 +436,10 @@ const distinct = (what, names) => { const seen = new Set(); for (const name of n
|
|
|
384
436
|
// step reads params, inputs and the state before it; a flag reads the flags declared before it; a start value reads the
|
|
385
437
|
// params; a law reads the params, the state, the maps and its own key.
|
|
386
438
|
export function scope(m, laws = []) {
|
|
387
|
-
const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flags = Object.keys(m.step.flags ?? {});
|
|
439
|
+
const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flags = Object.keys(m.step.flags ?? {}), carried = m.inputMaps ?? [], fns = Object.keys(m.fns ?? {});
|
|
388
440
|
const names = [...m.params, ...m.inputs, ...state];
|
|
389
|
-
distinct(m.name, [...names, ...maps]);
|
|
390
|
-
for (const name of [...names, ...maps, ...flags])
|
|
441
|
+
distinct(m.name, [...names, ...maps, ...carried, ...fns]);
|
|
442
|
+
for (const name of [...names, ...maps, ...carried, ...fns, ...flags])
|
|
391
443
|
plainName(m.name, name);
|
|
392
444
|
for (const name of Object.keys(m.step.state ?? {}))
|
|
393
445
|
if (!state.includes(name))
|
|
@@ -395,19 +447,168 @@ export function scope(m, laws = []) {
|
|
|
395
447
|
for (const map of Object.keys(m.step.maps ?? {}))
|
|
396
448
|
if (!maps.includes(map))
|
|
397
449
|
throw new Error(`${m.name}'s step writes the map ${map}, which it doesn't declare`);
|
|
450
|
+
// The step reads the maps a request carries too. A key bound by a quantified flag or a write at every key is new and
|
|
451
|
+
// read only as a map's key (so every key the maps hold nothing at reads alike); a quantifier is a whole flag, nowhere
|
|
452
|
+
// else.
|
|
453
|
+
const readable = [...maps, ...carried, ...fns];
|
|
454
|
+
const bound = (what, key, t) => {
|
|
455
|
+
plainName(what, key);
|
|
456
|
+
if ([...names, ...readable, ...flags].includes(key))
|
|
457
|
+
throw new Error(`${what}: the key ${key} is already a name of ${m.name}`);
|
|
458
|
+
keyOnly(what, t, key, fns);
|
|
459
|
+
};
|
|
398
460
|
inScope(`${m.name}'s start`, Object.values(m.state), m.params);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
461
|
+
flags.forEach((f, i) => {
|
|
462
|
+
const c = m.step.flags[f], what = `${m.name}'s flag ${f}`;
|
|
463
|
+
// A quantified flag's body reads no flag (the twin decides quantified flags before the plain ones).
|
|
464
|
+
if (c.op === 'all') {
|
|
465
|
+
bound(what, c.key, c.c);
|
|
466
|
+
unquantified(what, c.c);
|
|
467
|
+
inScope(what, [c.c], [...names, c.key], readable, []);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
unquantified(what, c);
|
|
471
|
+
inScope(what, [c], names, readable, flags.slice(0, i));
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
for (const [map, w] of Object.entries(m.step.maps ?? {})) {
|
|
475
|
+
const what = `${m.name}'s write to ${map}`;
|
|
476
|
+
if ('each' in w) {
|
|
477
|
+
bound(what, w.each, w.value);
|
|
478
|
+
unquantified(what, w.value);
|
|
479
|
+
inScope(what, [w.value], [...names, w.each], readable, flags);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
unquantified(what, w.key);
|
|
483
|
+
unquantified(what, w.value);
|
|
484
|
+
inScope(what, [w.key, w.value], names, readable, flags);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
for (const e of Object.values(m.step.state ?? {}))
|
|
488
|
+
unquantified(`${m.name}'s step`, e);
|
|
489
|
+
inScope(`${m.name}'s step`, Object.values(m.step.state ?? {}), names, readable, flags);
|
|
490
|
+
if (m.assume) {
|
|
491
|
+
unquantified(`${m.name}'s assumption`, m.assume);
|
|
492
|
+
inScope(`${m.name}'s assumption`, [m.assume], names, readable, flags);
|
|
493
|
+
}
|
|
403
494
|
if (m.pre)
|
|
404
495
|
inScope(`${m.name}'s precondition`, [m.pre], m.params);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
496
|
+
const facts = m.facts ?? [];
|
|
497
|
+
distinct(`${m.name}'s laws and facts`, [...laws, ...facts].map(l => l.name));
|
|
498
|
+
for (const law of [...laws, ...facts]) {
|
|
499
|
+
const what = facts.includes(law) ? `fact ${law.name}` : `law ${law.name}`;
|
|
500
|
+
for (const key of [law.forall, ...(law.keys ?? [])])
|
|
501
|
+
if (key && [...names, ...maps, ...carried, ...fns].includes(key))
|
|
502
|
+
throw new Error(`${what}'s key ${key} is already a name of ${m.name}`);
|
|
503
|
+
if (law.keys?.length && (!law.forall || new Set([law.forall, ...law.keys]).size !== law.keys.length + 1))
|
|
504
|
+
throw new Error(`${what}: further keys come with a first, each a different name`);
|
|
505
|
+
if (law.name.startsWith('?'))
|
|
506
|
+
throw new Error(`${what}: a name starting with ? is a flag's (its instances)`);
|
|
507
|
+
unquantified(what, law.holds);
|
|
410
508
|
}
|
|
509
|
+
const keysOf = (l) => [...(l.forall ? [l.forall] : []), ...(l.keys ?? [])];
|
|
510
|
+
for (const law of laws)
|
|
511
|
+
inScope(`law ${law.name}`, [law.holds], [...m.params, ...state, ...keysOf(law)], [...maps, ...fns]);
|
|
512
|
+
// A fact reads the params and the functions alone: what holds of them holds at every state.
|
|
513
|
+
for (const fact of facts)
|
|
514
|
+
inScope(`fact ${fact.name}`, [fact.holds], [...m.params, ...keysOf(fact)], fns);
|
|
515
|
+
}
|
|
516
|
+
// A key bound in t is read in key terms only: the key, or the key plus or less a term that doesn't read it (k + e, e + k,
|
|
517
|
+
// k ∸ e), each a map's key (no function's: a function's values don't stop where a map's cells do) or a side of a
|
|
518
|
+
// comparison whose other side doesn't read the key. Then t is constant between finitely many keys (the runtimes'
|
|
519
|
+
// breakpoints: each map's cells and each compared value, shifted by the offsets), so a runtime decides it on those.
|
|
520
|
+
const readsKey = (t, key) => JSON.stringify(t).includes(`"op":"var","name":${JSON.stringify(key)}`);
|
|
521
|
+
export const keyTerm = (e, key) => e.op === 'var' ? e.name === key
|
|
522
|
+
: e.op === 'add' ? (e.a.op === 'var' && e.a.name === key && !readsKey(e.b, key)) || (e.b.op === 'var' && e.b.name === key && !readsKey(e.a, key))
|
|
523
|
+
: e.op === 'sub' && e.a.op === 'var' && e.a.name === key && !readsKey(e.b, key);
|
|
524
|
+
function keyOnly(what, t, key, fns = []) {
|
|
525
|
+
const bad = () => { throw new Error(`${what}: the key ${key} is read only in a map's key or a comparison, alone or plus or less a term that doesn't read it`); };
|
|
526
|
+
const walk = (u) => {
|
|
527
|
+
switch (u.op) {
|
|
528
|
+
case 'var':
|
|
529
|
+
if (u.name === key)
|
|
530
|
+
bad();
|
|
531
|
+
break;
|
|
532
|
+
case 'get':
|
|
533
|
+
if (!(keyTerm(u.key, key) && !fns.includes(u.map)))
|
|
534
|
+
walk(u.key);
|
|
535
|
+
break;
|
|
536
|
+
case 'const':
|
|
537
|
+
case 'flag': break;
|
|
538
|
+
case 'not':
|
|
539
|
+
walk(u.x);
|
|
540
|
+
break;
|
|
541
|
+
case 'all':
|
|
542
|
+
walk(u.c);
|
|
543
|
+
break;
|
|
544
|
+
case 'and':
|
|
545
|
+
case 'or':
|
|
546
|
+
u.xs.forEach(walk);
|
|
547
|
+
break;
|
|
548
|
+
case 'mul':
|
|
549
|
+
walk(u.a);
|
|
550
|
+
break;
|
|
551
|
+
case 'if':
|
|
552
|
+
walk(u.c);
|
|
553
|
+
walk(u.then);
|
|
554
|
+
walk(u.else);
|
|
555
|
+
break;
|
|
556
|
+
case '<':
|
|
557
|
+
case '<=':
|
|
558
|
+
case '=':
|
|
559
|
+
if (keyTerm(u.a, key) && !readsKey(u.b, key))
|
|
560
|
+
walk(u.b);
|
|
561
|
+
else if (keyTerm(u.b, key) && !readsKey(u.a, key))
|
|
562
|
+
walk(u.a);
|
|
563
|
+
else {
|
|
564
|
+
walk(u.a);
|
|
565
|
+
walk(u.b);
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
case 'add':
|
|
569
|
+
case 'sub':
|
|
570
|
+
case 'min':
|
|
571
|
+
case 'max':
|
|
572
|
+
case 'prod':
|
|
573
|
+
walk(u.a);
|
|
574
|
+
walk(u.b);
|
|
575
|
+
break;
|
|
576
|
+
default: unreachable(u);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
walk(t);
|
|
580
|
+
}
|
|
581
|
+
// Whether the key is read only as a map's key, plainly (what the GPU searches decide: every cell, and one past them).
|
|
582
|
+
export const plainKey = (t, key) => {
|
|
583
|
+
const walk = (u) => {
|
|
584
|
+
switch (u.op) {
|
|
585
|
+
case 'var': return u.name !== key;
|
|
586
|
+
case 'get': return (u.key.op === 'var' && u.key.name === key) || walk(u.key);
|
|
587
|
+
case 'const':
|
|
588
|
+
case 'flag': return true;
|
|
589
|
+
case 'not': return walk(u.x);
|
|
590
|
+
case 'all': return walk(u.c);
|
|
591
|
+
case 'and':
|
|
592
|
+
case 'or': return u.xs.every(walk);
|
|
593
|
+
case 'mul': return walk(u.a);
|
|
594
|
+
case 'if': return walk(u.c) && walk(u.then) && walk(u.else);
|
|
595
|
+
case 'add':
|
|
596
|
+
case 'sub':
|
|
597
|
+
case 'min':
|
|
598
|
+
case 'max':
|
|
599
|
+
case 'prod':
|
|
600
|
+
case '<':
|
|
601
|
+
case '<=':
|
|
602
|
+
case '=': return walk(u.a) && walk(u.b);
|
|
603
|
+
default: return unreachable(u);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
return walk(t);
|
|
607
|
+
};
|
|
608
|
+
// No quantifier inside t: a quantifier is a whole flag.
|
|
609
|
+
function unquantified(what, t) {
|
|
610
|
+
if (JSON.stringify(t).includes('"op":"all"'))
|
|
611
|
+
throw new Error(`${what}: a condition at every key is a flag of its own (flags: {name: all(...)})`);
|
|
411
612
|
}
|
|
412
613
|
// --- histories ---------------------------------------------------------------------------------------------------------
|
|
413
614
|
// A term or condition that reads only these names, and no map.
|
|
@@ -422,6 +623,8 @@ export function historyMachine(m) {
|
|
|
422
623
|
const h = m.history;
|
|
423
624
|
if (!h)
|
|
424
625
|
return { machine: m, goals: [] };
|
|
626
|
+
if (m.inputMaps?.length || quantified(m).length || Object.values(m.step.maps ?? {}).some(w => 'each' in w))
|
|
627
|
+
throw new Error(`${m.name}: a machine with a history has no maps carried by requests, quantified flags or writes at every key`);
|
|
425
628
|
const queries = Object.keys(h.queries), sums = Object.keys(h.sums), plainInputs = m.inputs.filter(i => !queries.includes(i));
|
|
426
629
|
const fields = h.record.map(f => `r.${f}`);
|
|
427
630
|
const taken = [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.step.state ?? {})];
|
|
@@ -464,17 +667,20 @@ export function obligations(m, laws, instances = {}) {
|
|
|
464
667
|
return [...obligations(machine, laws, instances), ...goals];
|
|
465
668
|
}
|
|
466
669
|
scope(m, laws);
|
|
467
|
-
const ok = [...Object.values(m.state), ...Object.values(m.step.state ?? {}), ...Object.values(m.step.maps ?? {}).flatMap(w => [w.key, w.value])].every(natural)
|
|
670
|
+
const ok = [...Object.values(m.state), ...Object.values(m.step.state ?? {}), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.value] : [w.key, w.value]))].every(natural)
|
|
468
671
|
&& Object.values(m.step.flags ?? {}).every(naturalC) && laws.every(l => naturalC(l.holds)) && Object.values(m.maps ?? {}).every(c => Number.isInteger(c) && c >= 0);
|
|
469
|
-
if (!ok || laws.some(l => mentions(l.holds, m.inputs)))
|
|
672
|
+
if (!ok || !(m.facts ?? []).every(l => naturalC(l.holds)) || laws.some(l => mentions(l.holds, m.inputs)))
|
|
470
673
|
throw new Error('a negative constant or factor, or a law about a request, is outside the kernel');
|
|
471
674
|
// A law that holds at every key is proven at a fresh one.
|
|
675
|
+
// A law about every two keys keeps its second key as it is: a fresh name nothing else reads, so the goal is about every
|
|
676
|
+
// value of it too (the Lean twin reads it so).
|
|
472
677
|
const proven = (law) => (law.forall ? substC(law.holds, { vars: { [law.forall]: { op: 'var', name: `${law.forall}#` } }, maps: {}, flags: {} }) : law.holds);
|
|
473
678
|
const pre = preOf(m), u = updateOf(m), assume = m.assume ? [inlineFlags(m, m.assume)] : [];
|
|
474
679
|
return laws.flatMap(law => {
|
|
475
680
|
// At the start: the precondition, and no request. One step: from a state where the laws hold, a request allowed.
|
|
476
|
-
|
|
477
|
-
const
|
|
681
|
+
// The facts hold throughout, read where the run begins (they read no state).
|
|
682
|
+
const start = given(m, holds(from(startOf(m)), pre), instances[law.name] ?? {});
|
|
683
|
+
const turn = step(holds(given(m, from(), instances[law.name] ?? {}), [...pre, ...assume, ...assumed(laws, instances[law.name] ?? {}), ...closing(m, instances[law.name] ?? {})]), u);
|
|
478
684
|
return [goal(`${law.name} @start`, start, atEnd(start, proven(law))), goal(`${law.name} @step`, turn, atEnd(turn, proven(law)))];
|
|
479
685
|
});
|
|
480
686
|
}
|
|
@@ -500,7 +706,7 @@ export function loopObligations(l, laws, rank) {
|
|
|
500
706
|
inScope(`${l.name}'s rank and laws`, [...ranks, ...laws.map(law => law.holds)], [...l.params, ...Object.keys(l.state)]);
|
|
501
707
|
if (ranks.some(r => mentions({ op: '=', a: r, b: r }, l.inputs)))
|
|
502
708
|
throw new Error('a rank reads the state and params, not the turn\'s input');
|
|
503
|
-
if (!parts.every(natural) || !naturalC(l.while) || !laws.every(law => !law.forall && naturalC(law.holds) && !mentions(law.holds, l.inputs)))
|
|
709
|
+
if (!parts.every(natural) || !naturalC(l.while) || !laws.every(law => !law.forall && !law.keys?.length && naturalC(law.holds) && !mentions(law.holds, l.inputs)))
|
|
504
710
|
throw new Error('outside the kernel');
|
|
505
711
|
const pre = preOf(l);
|
|
506
712
|
const start = holds(from({ vars: { ...l.state }, maps: {}, flags: {} }), pre);
|
|
@@ -527,15 +733,59 @@ export function progressObligations(m, laws, instances, p) {
|
|
|
527
733
|
if (mentions({ op: '=', a: e, b: e }, m.inputs))
|
|
528
734
|
throw new Error('a request is named from the state and params');
|
|
529
735
|
const req = { vars: p.request, maps: {}, flags: {} };
|
|
530
|
-
const flags = Object.fromEntries(Object.entries(m
|
|
531
|
-
// The run: a state where the laws and `when` hold, then the step
|
|
532
|
-
|
|
533
|
-
const
|
|
736
|
+
const flags = Object.fromEntries(Object.entries(flagsOf(m)).map(([f, c]) => [f, substC(c, req)]));
|
|
737
|
+
// The run: a state where the laws and `when` hold, the named request (its quantified flags closed), then the step it
|
|
738
|
+
// causes. Maps a request carries are left as any maps: the step is shown for all of them.
|
|
739
|
+
const before = holds(given(m, from(), instances), [...preOf(m), ...assumed(laws, instances), p.when]);
|
|
740
|
+
const asked = holds(reading(before, p.request), closing(m, instances));
|
|
741
|
+
const caused = step(asked, updateOf(m));
|
|
534
742
|
return [
|
|
535
|
-
...(m.assume ? [goal(`${p.name}: the request is allowed`,
|
|
743
|
+
...(m.assume ? [goal(`${p.name}: the request is allowed`, asked, substC(inlineFlags(m, m.assume), req))] : []),
|
|
536
744
|
goal(`${p.name}: the step it causes`, caused, across(caused, p.then, Object.keys(m.state), Object.keys(m.maps ?? {}), { inputs: p.request, flags })),
|
|
537
745
|
];
|
|
538
746
|
}
|
|
747
|
+
const zero = { op: 'const', value: 0 };
|
|
748
|
+
export const trueC = { op: '<=', a: zero, b: zero }, falseC = { op: '<', a: zero, b: zero };
|
|
749
|
+
const and2 = (a, b) => ({ op: 'and', xs: [a, b] }), or2 = (a, b) => ({ op: 'or', xs: [a, b] });
|
|
750
|
+
const lexLt = (after, before) => (after.length && before.length ? or2({ op: '<', a: after[0], b: before[0] }, and2({ op: '=', a: after[0], b: before[0] }, lexLt(after.slice(1), before.slice(1)))) : falseC);
|
|
751
|
+
const eqs = (after, before) => (after.length && before.length ? and2({ op: '=', a: after[0], b: before[0] }, eqs(after.slice(1), before.slice(1))) : trueC);
|
|
752
|
+
const keep = (ws, prime) => (ws.length ? and2(or2({ op: 'not', x: ws[0] }, prime(ws[0])), keep(ws.slice(1), prime)) : trueC);
|
|
753
|
+
const anyOf = (ws) => (ws.length ? or2(ws[0], anyOf(ws.slice(1))) : falseC);
|
|
754
|
+
const named = (req) => (req.length ? and2({ op: '=', a: { op: 'var', name: req[0][0] }, b: req[0][1] }, named(req.slice(1))) : trueC);
|
|
755
|
+
export const livenessRanks = (l) => (Array.isArray(l.rank) ? l.rank : [l.rank]);
|
|
756
|
+
export const livenessHelpful = (l) => {
|
|
757
|
+
if ((l.request === undefined) === (l.helpful === undefined))
|
|
758
|
+
throw new Error(`liveness ${l.name}: a helpful request, or helpful requests with their regions`);
|
|
759
|
+
return (l.helpful ?? [{ request: l.request }]).map(h => ({ when: h.when ?? trueC, request: h.request }));
|
|
760
|
+
};
|
|
761
|
+
export function livenessParts(m, l) {
|
|
762
|
+
// A machine with a history as its plain machine (its sums are state, its queries inputs no request names).
|
|
763
|
+
const plain = m.history ? historyMachine(m).machine : m;
|
|
764
|
+
const queries = Object.keys(m.history?.queries ?? {}), client = plain.inputs.filter(i => !queries.includes(i));
|
|
765
|
+
const ranks = livenessRanks(l), helpful = livenessHelpful(l);
|
|
766
|
+
if (!ranks.length || !helpful.length)
|
|
767
|
+
throw new Error(`liveness ${l.name}: a rank of one natural or more, and a helpful request or more`);
|
|
768
|
+
for (const [what, t] of [['from', l.from], ['to', l.to], ...ranks.map((r, i) => [`rank ${i + 1}`, { op: '=', a: r, b: r }]), ...helpful.map((h, i) => [`region ${i + 1}`, h.when])]) {
|
|
769
|
+
if (mentions(t, plain.inputs))
|
|
770
|
+
throw new Error(`liveness ${l.name}: ${what} reads the state and params, not a request`);
|
|
771
|
+
}
|
|
772
|
+
for (const h of helpful)
|
|
773
|
+
for (const [input, t] of Object.entries(h.request)) {
|
|
774
|
+
if (!client.includes(input))
|
|
775
|
+
throw new Error(`liveness ${l.name}: a helpful request names inputs a client sends (${input} is ${queries.includes(input) ? 'a query, computed from the log' : 'none'})`);
|
|
776
|
+
if (mentions({ op: '=', a: t, b: t }, plain.inputs))
|
|
777
|
+
throw new Error(`liveness ${l.name}: the request's ${input} is a term over the state and params`);
|
|
778
|
+
}
|
|
779
|
+
const after = { vars: Object.fromEntries(Object.keys(plain.state).map(n => [n, { op: 'var', name: `${n}'` }])), maps: Object.fromEntries(Object.keys(plain.maps ?? {}).map(map => [map, (key) => ({ op: 'get', map: `${map}'`, key })])), flags: {} };
|
|
780
|
+
const prime = (c) => substC(c, after), ranks2 = ranks.map(r => substE(r, after)), to2 = prime(l.to);
|
|
781
|
+
const pending = and2(l.from, { op: 'not', x: l.to }), lower = lexLt(ranks2, ranks), regions = helpful.map(h => h.when);
|
|
782
|
+
return {
|
|
783
|
+
stay: { name: `${l.name}: each step stays or gets there, the rank kept only with its region`, when: pending,
|
|
784
|
+
then: and2(or2(prime(l.from), to2), or2(to2, or2(lower, and2(eqs(ranks2, ranks), keep(regions, prime))))) },
|
|
785
|
+
cover: { name: `${l.name}: some helpful request applies`, when: pending, then: anyOf(regions) },
|
|
786
|
+
helps: helpful.map((h, i) => ({ name: `${l.name}: helpful request ${i + 1}`, when: and2(pending, and2(h.when, named(Object.entries(h.request)))), then: or2(to2, lower) })),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
539
789
|
export function stepLawObligations(machine, laws, instances, sl) {
|
|
540
790
|
const m = machine.history ? historyMachine(machine).machine : machine;
|
|
541
791
|
scope(m, laws);
|
|
@@ -545,35 +795,43 @@ export function stepLawObligations(machine, laws, instances, sl) {
|
|
|
545
795
|
const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
|
|
546
796
|
if (whens.length > k)
|
|
547
797
|
throw new Error(`step law ${sl.name}: one when per step at most`);
|
|
548
|
-
const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flagNames = Object.keys(m.step.flags ?? {});
|
|
798
|
+
const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), carried = m.inputMaps ?? [], flagNames = Object.keys(m.step.flags ?? {});
|
|
549
799
|
const primes = [...state, ...maps].map(n => `${n}'`);
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
800
|
+
const laters = Array.from({ length: k - 1 }, (_, i) => laterNames(m, i + 2));
|
|
801
|
+
const later = laters.flatMap(l => Object.values(l.vars).map(e => e.name)), laterMaps = laters.flatMap(l => Object.values(l.maps));
|
|
802
|
+
const taken = [...m.params, ...m.inputs, ...state, ...maps, ...carried];
|
|
803
|
+
for (const p of [...primes, ...later, ...laterMaps])
|
|
553
804
|
if (taken.includes(p))
|
|
554
805
|
throw new Error(`${m.name}: ${p} is a name of the machine and a name a step law reads`);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
806
|
+
for (const key of [sl.forall, ...(sl.keys ?? [])])
|
|
807
|
+
if (key && [...taken, ...primes, ...later].includes(key))
|
|
808
|
+
throw new Error(`step law ${sl.name}'s key ${key} is already a name of ${m.name}`);
|
|
809
|
+
if (sl.keys?.length && (!sl.forall || new Set([sl.forall, ...sl.keys]).size !== sl.keys.length + 1))
|
|
810
|
+
throw new Error(`step law ${sl.name}: further keys come with a first, each a different name`);
|
|
811
|
+
unquantified(`step law ${sl.name}`, sl.then);
|
|
812
|
+
inScope(`step law ${sl.name}`, [sl.then], [...taken, ...state.map(n => `${n}'`), ...later, ...(sl.forall ? [sl.forall] : []), ...(sl.keys ?? [])], [...maps, ...carried, ...maps.map(n => `${n}'`), ...laterMaps], flagNames);
|
|
813
|
+
for (const w of whens) {
|
|
814
|
+
unquantified(`step law ${sl.name}'s when`, w);
|
|
815
|
+
inScope(`step law ${sl.name}'s when`, [w], taken, [...maps, ...carried], flagNames);
|
|
816
|
+
}
|
|
560
817
|
if (!naturalC(sl.then) || !whens.every(naturalC))
|
|
561
818
|
throw new Error(`step law ${sl.name}: a negative constant or factor is outside the kernel`);
|
|
562
819
|
const u = updateOf(m), assume = m.assume ? [inlineFlags(m, m.assume)] : [];
|
|
563
|
-
let run = holds(from(), preOf(m));
|
|
820
|
+
let run = given(m, holds(from(), preOf(m)), instances);
|
|
564
821
|
for (let j = 1; j <= k; j++) {
|
|
565
822
|
if (j > 1)
|
|
566
|
-
run = reading(run,
|
|
567
|
-
run = step(holds(run, [...assumed(laws, instances), ...assume, ...(whens[j - 1] ? [inlineFlags(m, whens[j - 1])] : [])]), u);
|
|
823
|
+
run = reading(run, laters[j - 2].vars, laters[j - 2].maps);
|
|
824
|
+
run = step(holds(run, [...assumed(laws, instances), ...assume, ...(whens[j - 1] ? [inlineFlags(m, whens[j - 1])] : []), ...closing(m, instances)]), u);
|
|
568
825
|
}
|
|
569
|
-
return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: m
|
|
826
|
+
return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: flagsOf(m) }))];
|
|
570
827
|
}
|
|
571
828
|
// The laws' proof and every step law's tree: all must check.
|
|
829
|
+
// `perStep`: a step law's own instances, where it has them (else `instances`).
|
|
572
830
|
export function checkStepLaws(m, laws, stepLaws, proof) {
|
|
573
|
-
return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => stepLawObligations(m, laws, proof.instances, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name])));
|
|
831
|
+
return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => stepLawObligations(m, laws, proof.perStep?.[sl.name] ?? proof.instances, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name])));
|
|
574
832
|
}
|
|
575
833
|
export function foldObligations(f, laws) {
|
|
576
|
-
if (![...Object.values(f.leaf), ...Object.values(f.node)].every(natural) || !laws.every(law => !law.forall && naturalC(law.holds) && !mentions(law.holds, f.inputs)))
|
|
834
|
+
if (![...Object.values(f.leaf), ...Object.values(f.node)].every(natural) || !laws.every(law => !law.forall && !law.keys?.length && naturalC(law.holds) && !mentions(law.holds, f.inputs)))
|
|
577
835
|
throw new Error('outside the kernel');
|
|
578
836
|
const names = Object.keys(f.leaf);
|
|
579
837
|
distinct(f.name, [...f.params, ...f.inputs, ...names]);
|
package/dist/kernel/prove.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Law, Machine } from '../core/machine.js';
|
|
2
|
-
import { type Goal, type Instances, type Path, type StepLaw, type Tree } from './kernel.js';
|
|
1
|
+
import type { C, E, Law, Machine } from '../core/machine.js';
|
|
2
|
+
import { type Goal, type Instances, type Liveness, type Path, type StepLaw, type Tree } from './kernel.js';
|
|
3
3
|
export type Outcome = {
|
|
4
4
|
tree: Tree;
|
|
5
5
|
nodes: number;
|
|
@@ -7,6 +7,7 @@ export type Outcome = {
|
|
|
7
7
|
stuck: 'sat' | 'unknown';
|
|
8
8
|
witness?: Record<string, number>;
|
|
9
9
|
path: Path;
|
|
10
|
+
why?: string;
|
|
10
11
|
};
|
|
11
12
|
export declare function grow(goal: Goal, path?: Path, budget?: {
|
|
12
13
|
nodes: number;
|
|
@@ -20,24 +21,50 @@ export type Report = {
|
|
|
20
21
|
goal: string;
|
|
21
22
|
stuck: string;
|
|
22
23
|
witness?: Record<string, number>;
|
|
24
|
+
why?: string;
|
|
23
25
|
};
|
|
24
26
|
checked: boolean;
|
|
25
27
|
};
|
|
26
28
|
export declare function proveLaws(m: Machine, laws: Law[]): Report & {
|
|
29
|
+
laws?: Law[];
|
|
27
30
|
proof?: {
|
|
28
31
|
instances: Record<string, Instances>;
|
|
29
32
|
trees: Record<string, Tree>;
|
|
30
33
|
};
|
|
31
34
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
instances: Record<string, Instances>;
|
|
36
|
-
trees: Record<string, Tree>;
|
|
37
|
-
};
|
|
38
|
-
instances: Instances;
|
|
35
|
+
type StepProof = {
|
|
36
|
+
laws: {
|
|
37
|
+
instances: Record<string, Instances>;
|
|
39
38
|
trees: Record<string, Tree>;
|
|
40
39
|
};
|
|
40
|
+
instances: Instances;
|
|
41
|
+
perStep?: Record<string, Instances>;
|
|
42
|
+
trees: Record<string, Tree>;
|
|
43
|
+
};
|
|
44
|
+
export declare function proveStepLaws(m: Machine, given: Law[], stepLaws: StepLaw[]): Report & {
|
|
45
|
+
laws?: Law[];
|
|
46
|
+
stepLaws?: StepLaw[];
|
|
47
|
+
proof?: StepProof;
|
|
48
|
+
};
|
|
49
|
+
export declare function proveProgress(m: Machine, laws: Law[], p: {
|
|
50
|
+
name: string;
|
|
51
|
+
when: C;
|
|
52
|
+
then: C;
|
|
53
|
+
request: Record<string, E>;
|
|
54
|
+
}): Report & {
|
|
55
|
+
instances?: Instances;
|
|
56
|
+
trees?: Record<string, Tree>;
|
|
57
|
+
};
|
|
58
|
+
export declare function proveLiveness(m: Machine, laws: Law[], l: Liveness): Report & {
|
|
59
|
+
steps?: ReturnType<typeof proveStepLaws>;
|
|
41
60
|
};
|
|
42
61
|
export declare const countLeaves: (t: Tree) => number;
|
|
43
62
|
export declare function concretize(goal: Goal, bound?: number): Record<string, number> | undefined;
|
|
63
|
+
export declare function factsFor(m: Machine, goal: Goal): C[];
|
|
64
|
+
export declare function proveBuilt(m: Machine, laws: Law[], build: (inst: Instances) => Goal): {
|
|
65
|
+
proven: boolean;
|
|
66
|
+
instances?: Instances;
|
|
67
|
+
goal?: Goal;
|
|
68
|
+
tree?: Tree;
|
|
69
|
+
};
|
|
70
|
+
export {};
|