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/kernel/prove.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { add, k, scale, solve, sub } from "../core/solver.js";
|
|
2
|
-
import { canon, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, obligations, stepLawObligations, truth, unreachable, withProducts } from "./kernel.js";
|
|
2
|
+
import { canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
|
|
3
3
|
// The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
|
|
4
4
|
function openE(e, path, out) {
|
|
5
5
|
switch (e.op) {
|
|
@@ -32,7 +32,7 @@ function openE(e, path, out) {
|
|
|
32
32
|
function openC(c, path, out) {
|
|
33
33
|
if (truth(c, path) !== undefined)
|
|
34
34
|
return;
|
|
35
|
-
if (c.op === 'flag')
|
|
35
|
+
if (c.op === 'flag' || c.op === 'all')
|
|
36
36
|
return;
|
|
37
37
|
if (c.op === 'not')
|
|
38
38
|
return openC(c.x, path, out);
|
|
@@ -68,7 +68,7 @@ function openH(c, path, out, positive = true) {
|
|
|
68
68
|
openC(c, path, out);
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
if (c.op === 'flag')
|
|
71
|
+
if (c.op === 'flag' || c.op === 'all')
|
|
72
72
|
return;
|
|
73
73
|
const before = out.length;
|
|
74
74
|
openE(c.a, path, out), openE(c.b, path, out);
|
|
@@ -113,8 +113,9 @@ function parse(text) {
|
|
|
113
113
|
}
|
|
114
114
|
return out;
|
|
115
115
|
}
|
|
116
|
-
// The budget: 200,000 nodes and, by default, 20 seconds per goal; past either the goal
|
|
117
|
-
|
|
116
|
+
// The budget: 200,000 nodes and, by default, a second per hypothesis (at least 20 seconds) per goal; past either the goal
|
|
117
|
+
// is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
|
|
118
|
+
export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
|
|
118
119
|
if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
|
|
119
120
|
return { stuck: 'unknown', path };
|
|
120
121
|
const rows = leafConstraints(goal, path);
|
|
@@ -223,11 +224,17 @@ function fractional(point, path) {
|
|
|
223
224
|
return undefined;
|
|
224
225
|
}
|
|
225
226
|
// The keys a proof assumes the every-key laws at: every key the step and the laws read, and the fresh key of the goal.
|
|
227
|
+
// A key bound by a quantified flag or a write at every key isn't one: those reads are at every key already.
|
|
226
228
|
function readKeys(m, laws) {
|
|
227
229
|
const out = new Map();
|
|
230
|
+
const bound = new Set([...Object.values(m.step.flags ?? {}).flatMap(c => (c.op === 'all' ? [c.key] : [])), ...Object.values(m.step.maps ?? {}).flatMap(w => ('each' in w ? [w.each] : []))]);
|
|
231
|
+
const free = (e) => !JSON.stringify(e).match(/"name":"([^"]+)"/g)?.some(n => bound.has(n.slice(8, -1)));
|
|
228
232
|
const inE = (e) => {
|
|
229
|
-
if (e.op === 'get')
|
|
230
|
-
(
|
|
233
|
+
if (e.op === 'get') {
|
|
234
|
+
if (free(e.key))
|
|
235
|
+
out.set(JSON.stringify(e.key), e.key);
|
|
236
|
+
inE(e.key);
|
|
237
|
+
}
|
|
231
238
|
else if (e.op === 'if')
|
|
232
239
|
(inC(e.c), inE(e.then), inE(e.else));
|
|
233
240
|
else if (e.op === 'mul')
|
|
@@ -235,20 +242,208 @@ function readKeys(m, laws) {
|
|
|
235
242
|
else if ('a' in e)
|
|
236
243
|
(inE(e.a), inE(e.b));
|
|
237
244
|
};
|
|
238
|
-
const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
|
|
245
|
+
const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
|
|
239
246
|
Object.values(m.step.state ?? {}).forEach(inE);
|
|
240
247
|
Object.values(m.step.flags ?? {}).forEach(inC);
|
|
241
|
-
for (const w of Object.values(m.step.maps ?? {}))
|
|
242
|
-
(
|
|
248
|
+
for (const w of Object.values(m.step.maps ?? {})) {
|
|
249
|
+
if (!('each' in w)) {
|
|
250
|
+
out.set(JSON.stringify(w.key), w.key);
|
|
251
|
+
inE(w.key);
|
|
252
|
+
}
|
|
253
|
+
inE(w.value);
|
|
254
|
+
}
|
|
243
255
|
for (const l of laws.filter(l => !l.forall))
|
|
244
256
|
inC(l.holds);
|
|
245
257
|
return [...out.values()];
|
|
246
258
|
}
|
|
259
|
+
const shifts = (m) => Object.values(m.step.maps ?? {}).some(w => 'each' in w && !plainKey(w.value, w.each));
|
|
260
|
+
// The keys some goals read a map at.
|
|
261
|
+
function mapKeys(goals) {
|
|
262
|
+
const out = new Map();
|
|
263
|
+
const inE = (e) => {
|
|
264
|
+
if (e.op === 'get') {
|
|
265
|
+
out.set(JSON.stringify(e.key), e.key);
|
|
266
|
+
inE(e.key);
|
|
267
|
+
}
|
|
268
|
+
else if (e.op === 'if')
|
|
269
|
+
(inC(e.c), inE(e.then), inE(e.else));
|
|
270
|
+
else if (e.op === 'mul')
|
|
271
|
+
inE(e.a);
|
|
272
|
+
else if ('a' in e)
|
|
273
|
+
(inE(e.a), inE(e.b));
|
|
274
|
+
};
|
|
275
|
+
const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
|
|
276
|
+
for (const g of goals)
|
|
277
|
+
g.hyps.forEach(inC);
|
|
278
|
+
return [...out.values()];
|
|
279
|
+
}
|
|
280
|
+
// Instances with each every-key law also at these keys. A law about two keys or more goes at tuples that relate the
|
|
281
|
+
// goal's own keys (`focus`: the skolems of the law it proves, or a step law's keys) to each other and to the keys that
|
|
282
|
+
// read none of them: at the i-th place, a key that reads the i-th focus alone (the first, past the focus there is), and
|
|
283
|
+
// at most one place a key that reads no focus.
|
|
284
|
+
const withReads = (laws, inst, keys, focus) => ({ ...inst, ...Object.fromEntries(laws.filter(l => l.forall).map(l => {
|
|
285
|
+
const had = inst[l.name] ?? [];
|
|
286
|
+
if (l.keys?.length) {
|
|
287
|
+
const n = l.keys.length + 1;
|
|
288
|
+
const reads = (e, x) => x !== undefined && JSON.stringify(e).includes(`"name":${JSON.stringify(x)}`);
|
|
289
|
+
const own = (i) => { const f = focus[i] ?? focus[0]; return keys.filter(e => reads(e, f) && focus.every(g => g === f || !reads(e, g))); };
|
|
290
|
+
const R = keys.filter(e => !focus.some(f => reads(e, f)));
|
|
291
|
+
const out = new Map();
|
|
292
|
+
for (let i = 0; i + n <= had.length; i += n)
|
|
293
|
+
out.set(JSON.stringify(had.slice(i, i + n)), had.slice(i, i + n));
|
|
294
|
+
const go = (i, tuple, rest) => {
|
|
295
|
+
if (i === n) {
|
|
296
|
+
out.set(JSON.stringify(tuple), tuple);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
for (const e of own(i))
|
|
300
|
+
go(i + 1, [...tuple, e], rest);
|
|
301
|
+
if (!rest)
|
|
302
|
+
for (const e of R)
|
|
303
|
+
go(i + 1, [...tuple, e], true);
|
|
304
|
+
};
|
|
305
|
+
go(0, [], false);
|
|
306
|
+
return [l.name, [...out.values()].flat()];
|
|
307
|
+
}
|
|
308
|
+
const seen = new Set(had.map(e => JSON.stringify(e)));
|
|
309
|
+
return [l.name, [...had, ...keys.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
|
|
310
|
+
})) });
|
|
311
|
+
// A law about every n keys at every n-tuple of the keys (flattened, as Instances keeps tuples).
|
|
312
|
+
const tuples = (keys, n) => (n === 0 ? [] : n === 1 ? keys : keys.flatMap(a => { const rest = tuples(keys, n - 1); return Array.from({ length: rest.length / (n - 1) }, (_, t) => [a, ...rest.slice(t * (n - 1), (t + 1) * (n - 1))]).flat(); }));
|
|
313
|
+
// The keys some goals read a function at.
|
|
314
|
+
function fnKeys(m, goals) {
|
|
315
|
+
const fns = Object.keys(m.fns ?? {}), out = new Map();
|
|
316
|
+
const inE = (e) => {
|
|
317
|
+
if (e.op === 'get') {
|
|
318
|
+
if (fns.includes(e.map))
|
|
319
|
+
out.set(JSON.stringify(e.key), e.key);
|
|
320
|
+
inE(e.key);
|
|
321
|
+
}
|
|
322
|
+
else if (e.op === 'if')
|
|
323
|
+
(inC(e.c), inE(e.then), inE(e.else));
|
|
324
|
+
else if (e.op === 'mul')
|
|
325
|
+
inE(e.a);
|
|
326
|
+
else if ('a' in e)
|
|
327
|
+
(inE(e.a), inE(e.b));
|
|
328
|
+
};
|
|
329
|
+
const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? inC(c.c) : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
|
|
330
|
+
for (const g of goals)
|
|
331
|
+
g.hyps.forEach(inC);
|
|
332
|
+
return [...out.values()];
|
|
333
|
+
}
|
|
334
|
+
// Instances with each fact about every key assumed where it reads a function at one of these keys: its key at each
|
|
335
|
+
// value that makes one of its reads one of these (by matching: a function of several arguments reads a paired key), and
|
|
336
|
+
// at the keys themselves.
|
|
337
|
+
const withFacts = (m, inst, keys) => ({ ...inst, ...Object.fromEntries((m.facts ?? []).filter(f => f.forall).map(f => {
|
|
338
|
+
if (f.keys?.length)
|
|
339
|
+
return [f.name, tuples(keys, f.keys.length + 1)];
|
|
340
|
+
const had = inst[f.name] ?? [], seen = new Set(had.map(e => JSON.stringify(e)));
|
|
341
|
+
const reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
|
|
342
|
+
const at = [...keys, ...reads.flatMap(pattern => keys.flatMap(key => { const b = match(pattern, key, f.forall); return b ? [b] : []; }))];
|
|
343
|
+
return [f.name, [...had, ...at.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
|
|
344
|
+
})) });
|
|
345
|
+
// The value of x that makes pattern the term, if one does (structurally).
|
|
346
|
+
function match(pattern, term, x) {
|
|
347
|
+
let bound;
|
|
348
|
+
const go = (p, t) => {
|
|
349
|
+
if (p.op === 'var' && p.name === x) {
|
|
350
|
+
if (bound)
|
|
351
|
+
return JSON.stringify(bound) === JSON.stringify(t);
|
|
352
|
+
bound = t;
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
if (p.op !== t.op)
|
|
356
|
+
return false;
|
|
357
|
+
const pe = Object.entries(p), te = t;
|
|
358
|
+
return pe.every(([k, v]) => k === 'op' || (typeof v === 'object' && v !== null ? (Array.isArray(v) ? Array.isArray(te[k]) && te[k].length === v.length && v.every((y, i) => go(y, te[k][i])) : go(v, te[k])) : v === te[k]));
|
|
359
|
+
};
|
|
360
|
+
return go(pattern, term) ? bound : undefined;
|
|
361
|
+
}
|
|
362
|
+
// Each quantified flag's witness w?f: a key where its condition fails, when it does; every-key facts are wanted there too.
|
|
363
|
+
const witnesses = (m) => Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [{ op: 'var', name: `w?${f}` }] : []));
|
|
364
|
+
// The keys each quantified flag is assumed at (instances under ?f).
|
|
365
|
+
const flagInstances = (m, keys) => Object.fromEntries(Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [[`?${f}`, keys]] : [])));
|
|
366
|
+
const someKeys = (c, positive = true) => c.op === 'all' ? [...(positive ? [] : [c]), ...someKeys(c.c, positive)] : c.op === 'not' ? someKeys(c.x, !positive) : c.op === 'and' || c.op === 'or' ? c.xs.flatMap(y => someKeys(y, positive)) : [];
|
|
367
|
+
function atWitness(c, picks, positive = true) {
|
|
368
|
+
switch (c.op) {
|
|
369
|
+
case 'all': return !positive && picks[c.key] ? atWitness(substVar(c.c, c.key, picks[c.key]), picks, positive) : { ...c, c: atWitness(c.c, picks, positive) };
|
|
370
|
+
case 'not': return { op: 'not', x: atWitness(c.x, picks, !positive) };
|
|
371
|
+
case 'and':
|
|
372
|
+
case 'or': return { op: c.op, xs: c.xs.map(y => atWitness(y, picks, positive)) };
|
|
373
|
+
case 'flag':
|
|
374
|
+
case '<':
|
|
375
|
+
case '<=':
|
|
376
|
+
case '=': return c;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function substVar(c, name, by) {
|
|
380
|
+
const e = (t) => {
|
|
381
|
+
switch (t.op) {
|
|
382
|
+
case 'var': return t.name === name ? by : t;
|
|
383
|
+
case 'const': return t;
|
|
384
|
+
case 'mul': return { ...t, a: e(t.a) };
|
|
385
|
+
case 'if': return { op: 'if', c: substVar(t.c, name, by), then: e(t.then), else: e(t.else) };
|
|
386
|
+
case 'get': return { ...t, key: e(t.key) };
|
|
387
|
+
case 'add':
|
|
388
|
+
case 'sub':
|
|
389
|
+
case 'min':
|
|
390
|
+
case 'max':
|
|
391
|
+
case 'prod': return { ...t, a: e(t.a), b: e(t.b) };
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
return c.op === 'flag' ? c : c.op === 'all' ? { ...c, c: substVar(c.c, name, by) } : c.op === 'not' ? { op: 'not', x: substVar(c.x, name, by) } : c.op === 'and' || c.op === 'or' ? { op: c.op, xs: c.xs.map(y => substVar(y, name, by)) } : { ...c, a: e(c.a), b: e(c.b) };
|
|
395
|
+
}
|
|
396
|
+
const candidates = (m, names) => {
|
|
397
|
+
const vars = [...new Set([...names, ...m.params, ...Object.keys(m.state)])];
|
|
398
|
+
return [{ op: 'const', value: 0 }, ...vars.map((name) => ({ op: 'var', name })), ...Object.keys(m.maps ?? {}).flatMap(map => vars.map((name) => ({ op: 'get', map, key: { op: 'var', name } })))];
|
|
399
|
+
};
|
|
400
|
+
// Witnesses for every some-key in `items`, chosen in order: each the first candidate under which `proves` accepts the
|
|
401
|
+
// items whose some-keys are all named so far. Undefined, with the some-key's law, if none does.
|
|
402
|
+
function chooseWitnesses(items, body, names, m, proves, rebuild) {
|
|
403
|
+
const picks = {};
|
|
404
|
+
for (const t of items)
|
|
405
|
+
for (const q of someKeys(body(t)))
|
|
406
|
+
if (q.witness)
|
|
407
|
+
picks[q.key] = q.witness;
|
|
408
|
+
const resolved = (all) => items.filter(t => all || someKeys(body(t)).every(q => q.key in picks)).map(t => (someKeys(body(t)).length ? rebuild(t, atWitness(body(t), picks)) : t));
|
|
409
|
+
for (const t of items)
|
|
410
|
+
for (const q of someKeys(body(t))) {
|
|
411
|
+
if (q.key in picks)
|
|
412
|
+
continue;
|
|
413
|
+
const found = candidates(m, names(t)).find(w => { picks[q.key] = w; return proves(resolved(false)); });
|
|
414
|
+
if (!found) {
|
|
415
|
+
delete picks[q.key];
|
|
416
|
+
return { stuck: `no witness found for some key in ${t.name}: name one (someKey(f, witness))` };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return { items: resolved(true) };
|
|
420
|
+
}
|
|
247
421
|
export function proveLaws(m, laws) {
|
|
248
|
-
|
|
422
|
+
if (!laws.some(l => someKeys(l.holds).length))
|
|
423
|
+
return proveLawsAsGiven(m, laws);
|
|
424
|
+
const out = chooseWitnesses(laws, l => l.holds, l => (l.forall ? [l.forall] : []), m, ls => { const r = proveLawsAsGiven(m, ls); return r.proven && r.checked; }, (l, holds) => ({ ...l, holds, stated: l.stated ?? l.holds }));
|
|
425
|
+
if ('stuck' in out)
|
|
426
|
+
return { proven: false, nodes: 0, leaves: 0, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
|
|
427
|
+
return { ...proveLawsAsGiven(m, out.items), laws: out.items };
|
|
428
|
+
}
|
|
429
|
+
function proveLawsAsGiven(m, laws) {
|
|
430
|
+
const keys = [...readKeys(m, laws), ...witnesses(m)];
|
|
249
431
|
const instances = {};
|
|
250
|
-
for (const law of laws)
|
|
251
|
-
|
|
432
|
+
for (const law of laws) {
|
|
433
|
+
const at = [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : []), ...(law.keys ?? []).map((name) => ({ op: 'var', name }))];
|
|
434
|
+
instances[law.name] = { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : at])), ...flagInstances(m, at) };
|
|
435
|
+
}
|
|
436
|
+
// The facts about functions, at every key each law's goals read a function at (found from the goals, twice round).
|
|
437
|
+
for (let round = 0; round < 2 && m.facts?.length; round++) {
|
|
438
|
+
const goals = obligations(m, laws, instances);
|
|
439
|
+
laws.forEach((law, i) => { instances[law.name] = withFacts(m, instances[law.name], fnKeys(m, goals.slice(2 * i, 2 * i + 2))); });
|
|
440
|
+
}
|
|
441
|
+
// Where positions shift (a write at every key reading its key at k ∸ 1 or k + 1) or a law is about two keys, the
|
|
442
|
+
// every-key laws also at each key the goals read a map at, once round.
|
|
443
|
+
if (shifts(m) || laws.some(l => l.keys?.length)) {
|
|
444
|
+
const goals = obligations(m, laws, instances);
|
|
445
|
+
laws.forEach((law, i) => { instances[law.name] = withReads(laws, instances[law.name], mapKeys(goals.slice(2 * i, 2 * i + 2)), [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])]); });
|
|
446
|
+
}
|
|
252
447
|
const trees = {};
|
|
253
448
|
let nodes = 0, leaves = 0;
|
|
254
449
|
for (const goal of obligations(m, laws, instances)) {
|
|
@@ -262,19 +457,39 @@ export function proveLaws(m, laws) {
|
|
|
262
457
|
const proof = { instances, trees };
|
|
263
458
|
return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
|
|
264
459
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
export function proveStepLaws(m, laws, stepLaws) {
|
|
268
|
-
const base = proveLaws(m, laws);
|
|
460
|
+
export function proveStepLaws(m, given, stepLaws) {
|
|
461
|
+
const base = proveLaws(m, given);
|
|
269
462
|
if (!base.proven || !base.proof)
|
|
270
463
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
464
|
+
const laws = base.laws ?? given;
|
|
465
|
+
if (stepLaws.some(sl => someKeys(sl.then).length)) {
|
|
466
|
+
// Each step law's witnesses on its own (a step law assumes no other), the request and the state after among them.
|
|
467
|
+
const plain = m.history ? historyMachine(m).machine : m, after = Object.keys(plain.state).map(n => `${n}'`);
|
|
468
|
+
const out = chooseWitnesses(stepLaws, sl => sl.then, sl => [...(sl.forall ? [sl.forall] : []), ...plain.inputs, ...after], plain, sls => sls.every(sl => { const r = proveStepLawsAsGiven(m, laws, [sl], base); return r.proven && r.checked; }), (sl, then) => ({ ...sl, then }));
|
|
469
|
+
if ('stuck' in out)
|
|
470
|
+
return { proven: false, nodes: base.nodes, leaves: base.leaves, failed: { goal: 'witnesses', stuck: out.stuck }, checked: false };
|
|
471
|
+
return { ...proveStepLawsAsGiven(m, laws, out.items, base), ...(base.laws ? { laws } : {}), stepLaws: out.items };
|
|
472
|
+
}
|
|
473
|
+
return { ...proveStepLawsAsGiven(m, laws, stepLaws, base), ...(base.laws ? { laws } : {}) };
|
|
474
|
+
}
|
|
475
|
+
function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
476
|
+
if (!base.proof)
|
|
477
|
+
return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
|
|
271
478
|
const plain = m.history ? historyMachine(m).machine : m;
|
|
272
|
-
const keys = [...readKeys(plain, laws), ...stepLaws.flatMap(sl => [sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC))
|
|
273
|
-
|
|
479
|
+
const keys = [...readKeys(plain, laws), ...witnesses(plain), ...stepLaws.flatMap(sl => [sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC)),
|
|
480
|
+
...stepLaws.flatMap(sl => [sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : [])))];
|
|
481
|
+
let instances = { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : keys])), ...flagInstances(plain, keys) };
|
|
482
|
+
for (let round = 0; round < 2 && plain.facts?.length; round++)
|
|
483
|
+
instances = withFacts(plain, instances, fnKeys(plain, stepLaws.flatMap(sl => stepLawObligations(m, laws, instances, sl))));
|
|
484
|
+
// Where positions shift or a law is about two keys, each step law gets its own instances, from its own goal.
|
|
485
|
+
const perStep = {};
|
|
486
|
+
if (shifts(plain) || laws.some(l => l.keys?.length))
|
|
487
|
+
for (const sl of stepLaws)
|
|
488
|
+
perStep[sl.name] = withReads(laws, instances, mapKeys(stepLawObligations(m, laws, instances, sl)), [sl.forall, ...(sl.keys ?? [])].flatMap(k => (k ? [k] : [])));
|
|
274
489
|
const trees = {};
|
|
275
490
|
let nodes = base.nodes, leaves = base.leaves;
|
|
276
491
|
for (const sl of stepLaws)
|
|
277
|
-
for (const goal of stepLawObligations(m, laws, instances, sl)) {
|
|
492
|
+
for (const goal of stepLawObligations(m, laws, perStep[sl.name] ?? instances, sl)) {
|
|
278
493
|
const out = grow(goal);
|
|
279
494
|
if (!('tree' in out))
|
|
280
495
|
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
@@ -282,9 +497,53 @@ export function proveStepLaws(m, laws, stepLaws) {
|
|
|
282
497
|
nodes += out.nodes;
|
|
283
498
|
leaves += countLeaves(out.tree);
|
|
284
499
|
}
|
|
285
|
-
const proof = { laws: base.proof, instances, trees };
|
|
500
|
+
const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
|
|
286
501
|
return { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
|
|
287
502
|
}
|
|
503
|
+
// Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
|
|
504
|
+
// key the goals read a map at.
|
|
505
|
+
export function proveProgress(m, laws, p) {
|
|
506
|
+
const base = proveLaws(m, laws);
|
|
507
|
+
if (!base.proven || !base.checked)
|
|
508
|
+
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
509
|
+
const ls = base.laws ?? laws;
|
|
510
|
+
let instances = { ...withReads(ls, {}, mapKeys(progressObligations(m, ls, {}, p)), []), ...flagInstances(m, mapKeys(progressObligations(m, ls, {}, p))) };
|
|
511
|
+
instances = { ...instances, ...withReads(ls, instances, mapKeys(progressObligations(m, ls, instances, p)), []) };
|
|
512
|
+
if (m.facts?.length)
|
|
513
|
+
instances = withFacts(m, instances, fnKeys(m, progressObligations(m, ls, instances, p)));
|
|
514
|
+
const trees = {};
|
|
515
|
+
let nodes = base.nodes, leaves = base.leaves;
|
|
516
|
+
for (const goal of progressObligations(m, ls, instances, p)) {
|
|
517
|
+
const out = grow(goal);
|
|
518
|
+
if (!('tree' in out))
|
|
519
|
+
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
520
|
+
trees[goal.name] = out.tree;
|
|
521
|
+
nodes += out.nodes;
|
|
522
|
+
leaves += countLeaves(out.tree);
|
|
523
|
+
}
|
|
524
|
+
return { proven: true, nodes, leaves, checked: progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name])), instances, trees };
|
|
525
|
+
}
|
|
526
|
+
// Liveness (kernel.ts's livenessParts): its step laws, proven together. The claim needs no more (a run takes the
|
|
527
|
+
// request, so it is allowed); but a request never allowed would leave the claim about no run, so where a helpful request
|
|
528
|
+
// names every input a client sends and carries no maps, it is proven allowed in its region too.
|
|
529
|
+
export function proveLiveness(m, laws, l) {
|
|
530
|
+
const { stay, cover, helps } = livenessParts(m, l);
|
|
531
|
+
const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
|
|
532
|
+
if (!a.proven || !a.checked)
|
|
533
|
+
return { proven: false, nodes: a.nodes, leaves: a.leaves, ...(a.failed ? { failed: a.failed } : {}), checked: false };
|
|
534
|
+
const plain = m.history ? historyMachine(m).machine : m;
|
|
535
|
+
let nodes = a.nodes, leaves = a.leaves;
|
|
536
|
+
for (const [i, h] of livenessHelpful(l).entries()) {
|
|
537
|
+
if (m.history || plain.inputMaps?.length || !plain.assume || !plain.inputs.every(inp => inp in h.request))
|
|
538
|
+
continue;
|
|
539
|
+
const b = proveProgress(plain, laws, { name: `${l.name}: helpful request ${i + 1} is allowed`, when: { op: 'and', xs: [l.from, { op: 'not', x: l.to }, h.when] }, then: trueC, request: h.request });
|
|
540
|
+
nodes += b.nodes;
|
|
541
|
+
leaves += b.leaves;
|
|
542
|
+
if (!b.proven || !b.checked)
|
|
543
|
+
return { proven: false, nodes, leaves, ...(b.failed ? { failed: b.failed } : {}), checked: false };
|
|
544
|
+
}
|
|
545
|
+
return { proven: true, checked: true, nodes, leaves, steps: a };
|
|
546
|
+
}
|
|
288
547
|
// The key terms a condition reads maps at; a primed map's key is a key of the map before the step too.
|
|
289
548
|
function readKeysC(c) {
|
|
290
549
|
const out = [];
|
|
@@ -296,7 +555,7 @@ function readKeysC(c) {
|
|
|
296
555
|
inE(e.a);
|
|
297
556
|
else if ('a' in e)
|
|
298
557
|
(inE(e.a), inE(e.b)); };
|
|
299
|
-
const inC = (x) => (x.op === 'flag' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
|
|
558
|
+
const inC = (x) => (x.op === 'flag' || x.op === 'all' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
|
|
300
559
|
inC(c);
|
|
301
560
|
return out;
|
|
302
561
|
}
|
|
@@ -315,7 +574,7 @@ export function concretize(goal, bound = 12) {
|
|
|
315
574
|
(walkC(e.c), walkE(e.then), walkE(e.else));
|
|
316
575
|
else if ('a' in e)
|
|
317
576
|
(walkE(e.a), 'b' in e && walkE(e.b)); };
|
|
318
|
-
const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
|
|
577
|
+
const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'all' ? void (reads = true) : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
|
|
319
578
|
goal.hyps.forEach(walkC);
|
|
320
579
|
if (reads || names.size > 4)
|
|
321
580
|
return undefined;
|
|
@@ -334,7 +593,7 @@ export function concretize(goal, bound = 12) {
|
|
|
334
593
|
}
|
|
335
594
|
};
|
|
336
595
|
const holds = (c, env) => c.op === 'not' ? !holds(c.x, env) : c.op === 'and' ? c.xs.every(y => holds(y, env)) : c.op === 'or' ? c.xs.some(y => holds(y, env))
|
|
337
|
-
: c.op === 'flag' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
|
|
596
|
+
: c.op === 'flag' || c.op === 'all' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
|
|
338
597
|
const vars = [...names];
|
|
339
598
|
const total = (bound + 1) ** vars.length;
|
|
340
599
|
for (let i = 0; i < total; i++) {
|
|
@@ -345,3 +604,18 @@ export function concretize(goal, bound = 12) {
|
|
|
345
604
|
}
|
|
346
605
|
return undefined;
|
|
347
606
|
}
|
|
607
|
+
// A goal built from its instances (an exporter's goal, shaped as its Lean twin builds it): the every-key laws, the
|
|
608
|
+
// quantified flags and the facts assumed at every key it reads a map or function at, then a tree, checked.
|
|
609
|
+
export function proveBuilt(m, laws, build) {
|
|
610
|
+
let instances = {};
|
|
611
|
+
for (let round = 0; round < 2; round++) {
|
|
612
|
+
const g = build(instances), keys = mapKeys([g]);
|
|
613
|
+
instances = { ...withReads(laws, instances, keys, []), ...flagInstances(m, keys) };
|
|
614
|
+
if (m.facts?.length)
|
|
615
|
+
instances = withFacts(m, instances, fnKeys(m, [build(instances)]));
|
|
616
|
+
}
|
|
617
|
+
const goal = build(instances), out = grow(goal);
|
|
618
|
+
if (!('tree' in out) || !check(goal, out.tree))
|
|
619
|
+
return { proven: false };
|
|
620
|
+
return { proven: true, instances, goal, tree: out.tree };
|
|
621
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { C, E, Law, Machine } from '../core/machine.js';
|
|
2
|
+
import { type Step } from '../core/solver.js';
|
|
3
|
+
import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
|
|
4
|
+
import { leanLibrary } from './library.js';
|
|
5
|
+
export { leanLibrary };
|
|
6
|
+
export type Exported = {
|
|
7
|
+
name: string;
|
|
8
|
+
goal: Goal;
|
|
9
|
+
tree: Tree;
|
|
10
|
+
};
|
|
11
|
+
export declare const int: (v: number) => string;
|
|
12
|
+
export declare const list: (xs: string[]) => string;
|
|
13
|
+
export declare function leaves(t: Tree, path: Path, visit: (t: Tree & {
|
|
14
|
+
leaf: Step[];
|
|
15
|
+
products?: [number, number][];
|
|
16
|
+
}, path: Path) => void): void;
|
|
17
|
+
export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[]): {
|
|
18
|
+
tab: string;
|
|
19
|
+
hyps: string;
|
|
20
|
+
tree: string;
|
|
21
|
+
cols: string[];
|
|
22
|
+
c: (t: C) => string;
|
|
23
|
+
e: (t: E) => string;
|
|
24
|
+
};
|
|
25
|
+
export declare function goalToLean(ex: Exported, i: number, tamper?: boolean, dropStep?: boolean): string;
|
|
26
|
+
export declare function machineToLean(id: string, m: Machine, laws: Law[], corollary?: string): string;
|
|
27
|
+
export declare const TRUE = ".le (.const 0) (.const 0)";
|
|
28
|
+
export declare const grown: (g: Goal) => Tree;
|
|
29
|
+
export declare function loopToLean(id: string, l: Loop, laws: Law[], rank: E | E[], tamper?: {
|
|
30
|
+
what: string;
|
|
31
|
+
loop: Loop;
|
|
32
|
+
}): string;
|
|
33
|
+
export declare function foldToLean(id: string, f: Fold, laws: Law[], extra?: string): string;
|
|
34
|
+
export declare function readable(id: string, laws: Law[], vars: string[], maps: string[], reach: {
|
|
35
|
+
binders: string;
|
|
36
|
+
hyp: string;
|
|
37
|
+
call: string;
|
|
38
|
+
}): string;
|
|
39
|
+
export type ProgressCase = {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
when: C;
|
|
43
|
+
then: C;
|
|
44
|
+
request: Record<string, E>;
|
|
45
|
+
instances: Instances;
|
|
46
|
+
tamper?: {
|
|
47
|
+
what: string;
|
|
48
|
+
when: C;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts?: {
|
|
52
|
+
corollary?: string;
|
|
53
|
+
tamper?: {
|
|
54
|
+
what: string;
|
|
55
|
+
machine: Machine;
|
|
56
|
+
};
|
|
57
|
+
progress?: ProgressCase[];
|
|
58
|
+
free?: string[];
|
|
59
|
+
stepLaws?: StepLaw[];
|
|
60
|
+
liveness?: Liveness[];
|
|
61
|
+
ghosts?: string[];
|
|
62
|
+
after?: (ix: (name: string) => number) => string;
|
|
63
|
+
refined?: {
|
|
64
|
+
impl: Machine;
|
|
65
|
+
spec: Machine;
|
|
66
|
+
wit: Record<string, E>;
|
|
67
|
+
stutter: C;
|
|
68
|
+
specPre: number;
|
|
69
|
+
};
|
|
70
|
+
}): string;
|
|
71
|
+
export declare const hasSets: (m: Machine) => boolean;
|
|
72
|
+
export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts?: {
|
|
73
|
+
stepLaws?: StepLaw[];
|
|
74
|
+
liveness?: Liveness[];
|
|
75
|
+
tamper?: {
|
|
76
|
+
what: string;
|
|
77
|
+
machine: Machine;
|
|
78
|
+
};
|
|
79
|
+
simp?: string[];
|
|
80
|
+
free?: string[];
|
|
81
|
+
refined?: {
|
|
82
|
+
impl: Machine;
|
|
83
|
+
spec: Machine;
|
|
84
|
+
wit: Record<string, E>;
|
|
85
|
+
stutter: C;
|
|
86
|
+
specPre: number;
|
|
87
|
+
};
|
|
88
|
+
}): string;
|
|
89
|
+
export declare function hmachineToLean(id: string, pruned: Machine, laws: Law[], tampers?: {
|
|
90
|
+
what: string;
|
|
91
|
+
relations: {
|
|
92
|
+
when: C;
|
|
93
|
+
holds: C;
|
|
94
|
+
}[];
|
|
95
|
+
withRecordTrees: boolean;
|
|
96
|
+
}[], corollary?: (ix: (name: string) => number) => string): string;
|
|
97
|
+
export type LeanItem = {
|
|
98
|
+
id: string;
|
|
99
|
+
machine: Machine;
|
|
100
|
+
laws: Law[];
|
|
101
|
+
stepLaws?: StepLaw[];
|
|
102
|
+
liveness?: Liveness[];
|
|
103
|
+
ghosts?: string[];
|
|
104
|
+
lean?: {
|
|
105
|
+
defs?: string;
|
|
106
|
+
simp?: string[];
|
|
107
|
+
after?: (ix: (name: string) => number) => string;
|
|
108
|
+
};
|
|
109
|
+
} | {
|
|
110
|
+
id: string;
|
|
111
|
+
refinement: {
|
|
112
|
+
impl: Machine;
|
|
113
|
+
spec: Machine;
|
|
114
|
+
relation: Law[];
|
|
115
|
+
witness: Record<string, E>;
|
|
116
|
+
stutter?: C;
|
|
117
|
+
};
|
|
118
|
+
} | {
|
|
119
|
+
id: string;
|
|
120
|
+
loop: Loop;
|
|
121
|
+
rank: E | E[];
|
|
122
|
+
laws?: Law[];
|
|
123
|
+
} | {
|
|
124
|
+
id: string;
|
|
125
|
+
goal: Goal;
|
|
126
|
+
};
|
|
127
|
+
export declare function toLean(items: LeanItem[], opts?: {
|
|
128
|
+
title?: string;
|
|
129
|
+
}): string;
|
|
130
|
+
export declare function leanProject(file: string, opts?: {
|
|
131
|
+
name?: string;
|
|
132
|
+
}): Record<string, string>;
|