gdp-wired 0.1.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.
@@ -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) };
@@ -274,6 +280,83 @@ const preOf = (o) => {
274
280
  throw new Error('a precondition reads only the params');
275
281
  return o.pre ? [o.pre] : [];
276
282
  };
283
+ const identity = { vars: {}, maps: {}, flags: {} };
284
+ const from = (at = identity) => ({ hyps: [], at });
285
+ // Conditions that hold where the run is.
286
+ const holds = (r, cs) => ({ hyps: [...r.hyps, ...cs.map(c => substC(c, r.at))], at: r.at });
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 })])) } } });
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
292
+ // the written key, and what the map held before elsewhere (or, written at every key, its value at the key read).
293
+ const step = (r, u) => {
294
+ const at = r.at, before = (map, key) => (at.maps[map] ? at.maps[map](key) : { op: 'get', map, key });
295
+ return { hyps: r.hyps, at: {
296
+ vars: { ...at.vars, ...Object.fromEntries(Object.entries(u.vars).map(([n, e]) => [n, substE(e, at)])) },
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) })])) },
300
+ flags: at.flags,
301
+ } };
302
+ };
303
+ // The conclusion read at the run's end: plain names at the last frame.
304
+ const atEnd = (r, c) => substC(c, r.at);
305
+ // The conclusion read across the run: plain names at the first frame, primed names (s', and M'[k] as a read of the map
306
+ // named M') at the last; the first step's inputs as it read them, and its flags, decided where it was taken.
307
+ const across = (r, c, state, maps, first) => substC(c, {
308
+ vars: { ...first.inputs, ...Object.fromEntries(state.map(n => [`${n}'`, r.at.vars[n] ?? { op: 'var', name: n }])) },
309
+ maps: Object.fromEntries(maps.map(map => [`${map}'`, (key) => (r.at.maps[map] ? r.at.maps[map](key) : { op: 'get', map, key })])),
310
+ flags: first.flags,
311
+ });
312
+ const goal = (name, r, conclusion) => ({ name, hyps: [...r.hyps, { op: 'not', x: conclusion }] });
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.
324
+ const updateOf = (m) => {
325
+ const flags = { vars: {}, maps: {}, flags: flagsOf(m) };
326
+ return {
327
+ vars: Object.fromEntries(Object.entries(m.step.state ?? {}).map(([n, e]) => [n, substE(e, 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) }])),
329
+ };
330
+ };
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
+ });
346
+ // The laws assumed where the run is: each plain law, and each every-key law at the keys the proof names.
347
+ 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 });
358
+ // The start: the state's start values over the params, every map cell its constant.
359
+ const startOf = (m) => ({ vars: { ...m.state }, maps: Object.fromEntries(Object.entries(m.maps ?? {}).map(([map, c]) => [map, () => ({ op: 'const', value: c })])), flags: {} });
277
360
  function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() }) {
278
361
  switch (t.op) {
279
362
  case 'var':
@@ -287,6 +370,14 @@ function reads(t, out = { vars: new Set(), maps: new Set(), flags: new Set() })
287
370
  case 'flag':
288
371
  out.flags.add(t.name);
289
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
+ }
290
381
  case 'not':
291
382
  reads(t.x, out);
292
383
  break;
@@ -345,10 +436,10 @@ const distinct = (what, names) => { const seen = new Set(); for (const name of n
345
436
  // step reads params, inputs and the state before it; a flag reads the flags declared before it; a start value reads the
346
437
  // params; a law reads the params, the state, the maps and its own key.
347
438
  export function scope(m, laws = []) {
348
- 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 ?? {});
349
440
  const names = [...m.params, ...m.inputs, ...state];
350
- distinct(m.name, [...names, ...maps]);
351
- 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])
352
443
  plainName(m.name, name);
353
444
  for (const name of Object.keys(m.step.state ?? {}))
354
445
  if (!state.includes(name))
@@ -356,19 +447,168 @@ export function scope(m, laws = []) {
356
447
  for (const map of Object.keys(m.step.maps ?? {}))
357
448
  if (!maps.includes(map))
358
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
+ };
359
460
  inScope(`${m.name}'s start`, Object.values(m.state), m.params);
360
- Object.values(m.step.flags ?? {}).forEach((c, i) => inScope(`${m.name}'s flag ${flags[i]}`, [c], names, maps, flags.slice(0, i)));
361
- inScope(`${m.name}'s step`, [...Object.values(m.step.state ?? {}), ...Object.values(m.step.maps ?? {}).flatMap(w => [w.key, w.value])], names, maps, flags);
362
- if (m.assume)
363
- inScope(`${m.name}'s assumption`, [m.assume], names, maps, flags);
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
+ }
364
494
  if (m.pre)
365
495
  inScope(`${m.name}'s precondition`, [m.pre], m.params);
366
- distinct(`${m.name}'s laws`, laws.map(l => l.name));
367
- for (const law of laws) {
368
- if (law.forall && [...names, ...maps].includes(law.forall))
369
- throw new Error(`law ${law.name}'s key ${law.forall} is already a name of ${m.name}`);
370
- inScope(`law ${law.name}`, [law.holds], [...m.params, ...state, ...(law.forall ? [law.forall] : [])], maps);
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);
371
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(...)})`);
372
612
  }
373
613
  // --- histories ---------------------------------------------------------------------------------------------------------
374
614
  // A term or condition that reads only these names, and no map.
@@ -383,6 +623,8 @@ export function historyMachine(m) {
383
623
  const h = m.history;
384
624
  if (!h)
385
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`);
386
628
  const queries = Object.keys(h.queries), sums = Object.keys(h.sums), plainInputs = m.inputs.filter(i => !queries.includes(i));
387
629
  const fields = h.record.map(f => `r.${f}`);
388
630
  const taken = [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.step.state ?? {})];
@@ -425,32 +667,22 @@ export function obligations(m, laws, instances = {}) {
425
667
  return [...obligations(machine, laws, instances), ...goals];
426
668
  }
427
669
  scope(m, laws);
428
- 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)
429
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);
430
- 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)))
431
673
  throw new Error('a negative constant or factor, or a law about a request, is outside the kernel');
432
- const fresh = (law) => `${law.forall}#`;
433
- const atKey = (law, key) => substC(law.holds, { vars: { [law.forall]: key }, maps: {}, flags: {} });
434
- const goal = (law) => (law.forall ? atKey(law, { op: 'var', name: fresh(law) }) : law.holds);
435
- const pre = preOf(m);
436
- // At the start: the precondition, and no request.
437
- const start = { vars: Object.fromEntries(Object.entries(m.state)), maps: Object.fromEntries(Object.entries(m.maps ?? {}).map(([map, c]) => [map, () => ({ op: 'const', value: c })])), flags: {} };
438
- // One step: the state after, as terms over the state before; a map read after the step is the written value at the
439
- // written key, and the old cell elsewhere.
440
- const flags = { vars: {}, maps: {}, flags: m.step.flags ?? {} };
441
- const inline = (c) => substC(c, flags), inlineE = (e) => substE(e, flags);
442
- const step = {
443
- vars: Object.fromEntries(Object.entries(m.step.state ?? {}).map(([name, e]) => [name, inlineE(e)])),
444
- flags: {},
445
- maps: Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([map, w]) => [map, (key) => ({ op: 'if', c: { op: '=', a: key, b: inlineE(w.key) }, then: inlineE(w.value), else: { op: 'get', map, key } })])),
446
- };
447
- const out = [];
448
- for (const law of laws) {
449
- out.push({ name: `${law.name} @start`, hyps: [...pre, { op: 'not', x: substC(goal(law), start) }] });
450
- const before = laws.flatMap(l => (l.forall ? (instances[law.name]?.[l.name] ?? []).map(key => atKey(l, key)) : [l.holds]));
451
- out.push({ name: `${law.name} @step`, hyps: [...pre, ...(m.assume ? [inline(m.assume)] : []), ...before, { op: 'not', x: substC(goal(law), step) }] });
452
- }
453
- return out;
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).
677
+ const proven = (law) => (law.forall ? substC(law.holds, { vars: { [law.forall]: { op: 'var', name: `${law.forall}#` } }, maps: {}, flags: {} }) : law.holds);
678
+ const pre = preOf(m), u = updateOf(m), assume = m.assume ? [inlineFlags(m, m.assume)] : [];
679
+ return laws.flatMap(law => {
680
+ // At the start: the precondition, and no request. One step: from a state where the laws hold, a request allowed.
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);
684
+ return [goal(`${law.name} @start`, start, atEnd(start, proven(law))), goal(`${law.name} @step`, turn, atEnd(turn, proven(law)))];
685
+ });
454
686
  }
455
687
  // The whole set at once: every goal has a tree, and every tree checks.
456
688
  export function checkLaws(m, laws, proof) {
@@ -474,17 +706,14 @@ export function loopObligations(l, laws, rank) {
474
706
  inScope(`${l.name}'s rank and laws`, [...ranks, ...laws.map(law => law.holds)], [...l.params, ...Object.keys(l.state)]);
475
707
  if (ranks.some(r => mentions({ op: '=', a: r, b: r }, l.inputs)))
476
708
  throw new Error('a rank reads the state and params, not the turn\'s input');
477
- 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)))
478
710
  throw new Error('outside the kernel');
479
- const start = { vars: l.state, maps: {}, flags: {} }, turn = { vars: l.body, maps: {}, flags: {} };
480
711
  const pre = preOf(l);
481
- const during = [...pre, ...(l.assume ? [l.assume] : []), l.while, ...laws.map(law => law.holds)];
712
+ const start = holds(from({ vars: { ...l.state }, maps: {}, flags: {} }), pre);
713
+ const turn = step(holds(from(), [...pre, ...(l.assume ? [l.assume] : []), l.while, ...laws.map(law => law.holds)]), { vars: l.body, maps: {} });
482
714
  return [
483
- ...laws.flatMap(law => [
484
- { name: `${law.name} @start`, hyps: [...pre, { op: 'not', x: substC(law.holds, start) }] },
485
- { name: `${law.name} @turn`, hyps: [...during, { op: 'not', x: substC(law.holds, turn) }] },
486
- ]),
487
- { name: 'rank drops', hyps: [...during, { op: 'not', x: drops(ranks, turn) }] },
715
+ ...laws.flatMap(law => [goal(`${law.name} @start`, start, atEnd(start, law.holds)), goal(`${law.name} @turn`, turn, atEnd(turn, law.holds))]),
716
+ goal('rank drops', turn, drops(ranks, turn.at)),
488
717
  ];
489
718
  }
490
719
  const drops = (ranks, turn) => {
@@ -504,57 +733,105 @@ export function progressObligations(m, laws, instances, p) {
504
733
  if (mentions({ op: '=', a: e, b: e }, m.inputs))
505
734
  throw new Error('a request is named from the state and params');
506
735
  const req = { vars: p.request, maps: {}, flags: {} };
507
- const flags = Object.fromEntries(Object.entries(m.step.flags ?? {}).map(([f, c]) => [f, substC(c, req)]));
508
- const now = (e) => substE(substE(e, { vars: {}, maps: {}, flags: m.step.flags ?? {} }), req);
509
- const after = {
510
- vars: { ...p.request, ...Object.fromEntries(Object.keys(m.state).map(n => [`${n}'`, { op: 'var', name: n }])), ...Object.fromEntries(Object.entries(m.step.state ?? {}).map(([n, e]) => [`${n}'`, now(e)])) },
511
- maps: {
512
- ...Object.fromEntries(Object.keys(m.maps ?? {}).map(map => [`${map}'`, (key) => ({ op: 'get', map, key })])),
513
- ...Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([map, w]) => [`${map}'`, (key) => ({ op: 'if', c: { op: '=', a: key, b: now(w.key) }, then: now(w.value), else: { op: 'get', map, key } })])),
514
- },
515
- flags,
516
- };
517
- const before = [...preOf(m), ...laws.flatMap(l => (l.forall ? (instances[l.name] ?? []).map(key => substC(l.holds, { vars: { [l.forall]: key }, maps: {}, flags: {} })) : [l.holds])), p.when];
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));
518
742
  return [
519
- ...(m.assume ? [{ name: `${p.name}: the request is allowed`, hyps: [...before, { op: 'not', x: substC(m.assume, req) }] }] : []),
520
- { name: `${p.name}: the step it causes`, hyps: [...before, { op: 'not', x: substC(p.then, after) }] },
743
+ ...(m.assume ? [goal(`${p.name}: the request is allowed`, asked, substC(inlineFlags(m, m.assume), req))] : []),
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 })),
521
745
  ];
522
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
+ }
523
789
  export function stepLawObligations(machine, laws, instances, sl) {
524
790
  const m = machine.history ? historyMachine(machine).machine : machine;
525
791
  scope(m, laws);
526
- const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), flagNames = Object.keys(m.step.flags ?? {});
792
+ const k = sl.steps ?? 1;
793
+ if (!Number.isInteger(k) || k < 1 || k > 16)
794
+ throw new Error(`step law ${sl.name}: steps is a whole number from 1 to 16`);
795
+ const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
796
+ if (whens.length > k)
797
+ throw new Error(`step law ${sl.name}: one when per step at most`);
798
+ const state = Object.keys(m.state), maps = Object.keys(m.maps ?? {}), carried = m.inputMaps ?? [], flagNames = Object.keys(m.step.flags ?? {});
527
799
  const primes = [...state, ...maps].map(n => `${n}'`);
528
- const taken = [...m.params, ...m.inputs, ...state, ...maps];
529
- for (const p of primes)
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])
530
804
  if (taken.includes(p))
531
- throw new Error(`${m.name}: ${p} is a name of the machine and a primed name of a step law`);
532
- if (sl.forall && [...taken, ...primes].includes(sl.forall))
533
- throw new Error(`step law ${sl.name}'s key ${sl.forall} is already a name of ${m.name}`);
534
- inScope(`step law ${sl.name}`, [sl.then], [...taken, ...state.map(n => `${n}'`), ...(sl.forall ? [sl.forall] : [])], [...maps, ...maps.map(n => `${n}'`)], flagNames);
535
- if (sl.when)
536
- inScope(`step law ${sl.name}'s when`, [sl.when], taken, maps, flagNames);
537
- if (!naturalC(sl.then) || (sl.when && !naturalC(sl.when)))
805
+ throw new Error(`${m.name}: ${p} is a name of the machine and a name a step law reads`);
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
+ }
817
+ if (!naturalC(sl.then) || !whens.every(naturalC))
538
818
  throw new Error(`step law ${sl.name}: a negative constant or factor is outside the kernel`);
539
- const flags = { vars: {}, maps: {}, flags: m.step.flags ?? {} };
540
- const now = (e) => substE(e, flags), nowC = (c) => substC(c, flags);
541
- const after = {
542
- vars: Object.fromEntries(state.map(n => [`${n}'`, now(m.step.state?.[n] ?? { op: 'var', name: n })])),
543
- maps: Object.fromEntries(maps.map(map => {
544
- const w = m.step.maps?.[map];
545
- return [`${map}'`, (key) => (w ? { op: 'if', c: { op: '=', a: key, b: now(w.key) }, then: now(w.value), else: { op: 'get', map, key } } : { op: 'get', map, key })];
546
- })),
547
- flags: m.step.flags ?? {},
548
- };
549
- const before = laws.flatMap(l => (l.forall ? (instances[l.name] ?? []).map(key => substC(l.holds, { vars: { [l.forall]: key }, maps: {}, flags: {} })) : [l.holds]));
550
- return [{ name: `${sl.name} @step law`, hyps: [...preOf(m), ...before, ...(m.assume ? [nowC(m.assume)] : []), ...(sl.when ? [nowC(sl.when)] : []), { op: 'not', x: substC(sl.then, after) }] }];
819
+ const u = updateOf(m), assume = m.assume ? [inlineFlags(m, m.assume)] : [];
820
+ let run = given(m, holds(from(), preOf(m)), instances);
821
+ for (let j = 1; j <= k; j++) {
822
+ if (j > 1)
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);
825
+ }
826
+ return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: flagsOf(m) }))];
551
827
  }
552
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`).
553
830
  export function checkStepLaws(m, laws, stepLaws, proof) {
554
- 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])));
555
832
  }
556
833
  export function foldObligations(f, laws) {
557
- 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)))
558
835
  throw new Error('outside the kernel');
559
836
  const names = Object.keys(f.leaf);
560
837
  distinct(f.name, [...f.params, ...f.inputs, ...names]);
@@ -566,9 +843,9 @@ export function foldObligations(f, laws) {
566
843
  inScope(`${f.name}'s laws`, laws.map(law => law.holds), [...f.params, ...names]);
567
844
  const side = (p) => ({ vars: Object.fromEntries(names.map(n => [n, { op: 'var', name: `${p}${n}` }])), maps: {}, flags: {} });
568
845
  // A fold's inputs are each leaf's and node's own, so its assumption holds at every leaf and node it reads them at.
846
+ // A leaf is a start from its inputs; a node, a step from the frame of its two children, the laws holding at each.
569
847
  const assume = [...preOf(f), ...(f.assume ? [f.assume] : [])];
570
- return laws.flatMap(law => [
571
- { name: `${law.name} @leaf`, hyps: [...assume, { op: 'not', x: substC(law.holds, { vars: f.leaf, maps: {}, flags: {} }) }] },
572
- { name: `${law.name} @node`, hyps: [...assume, ...laws.flatMap(h => [substC(h.holds, side('l_')), substC(h.holds, side('r_'))]), { op: 'not', x: substC(law.holds, { vars: f.node, maps: {}, flags: {} }) }] },
573
- ]);
848
+ const leaf = holds(from({ vars: { ...f.leaf }, maps: {}, flags: {} }), assume);
849
+ const node = step(holds(from(), [...assume, ...laws.flatMap(h => [substC(h.holds, side('l_')), substC(h.holds, side('r_'))])]), { vars: f.node, maps: {} });
850
+ return laws.flatMap(law => [goal(`${law.name} @leaf`, leaf, atEnd(leaf, law.holds)), goal(`${law.name} @node`, node, atEnd(node, law.holds))]);
574
851
  }
@@ -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;
@@ -24,20 +24,44 @@ export type Report = {
24
24
  checked: boolean;
25
25
  };
26
26
  export declare function proveLaws(m: Machine, laws: Law[]): Report & {
27
+ laws?: Law[];
27
28
  proof?: {
28
29
  instances: Record<string, Instances>;
29
30
  trees: Record<string, Tree>;
30
31
  };
31
32
  };
32
- export declare function proveStepLaws(m: Machine, laws: Law[], stepLaws: StepLaw[]): Report & {
33
- proof?: {
34
- laws: {
35
- instances: Record<string, Instances>;
36
- trees: Record<string, Tree>;
37
- };
38
- instances: Instances;
33
+ type StepProof = {
34
+ laws: {
35
+ instances: Record<string, Instances>;
39
36
  trees: Record<string, Tree>;
40
37
  };
38
+ instances: Instances;
39
+ perStep?: Record<string, Instances>;
40
+ trees: Record<string, Tree>;
41
+ };
42
+ export declare function proveStepLaws(m: Machine, given: Law[], stepLaws: StepLaw[]): Report & {
43
+ laws?: Law[];
44
+ stepLaws?: StepLaw[];
45
+ proof?: StepProof;
46
+ };
47
+ export declare function proveProgress(m: Machine, laws: Law[], p: {
48
+ name: string;
49
+ when: C;
50
+ then: C;
51
+ request: Record<string, E>;
52
+ }): Report & {
53
+ instances?: Instances;
54
+ trees?: Record<string, Tree>;
55
+ };
56
+ export declare function proveLiveness(m: Machine, laws: Law[], l: Liveness): Report & {
57
+ steps?: ReturnType<typeof proveStepLaws>;
41
58
  };
42
59
  export declare const countLeaves: (t: Tree) => number;
43
60
  export declare function concretize(goal: Goal, bound?: number): Record<string, number> | undefined;
61
+ export declare function proveBuilt(m: Machine, laws: Law[], build: (inst: Instances) => Goal): {
62
+ proven: boolean;
63
+ instances?: Instances;
64
+ goal?: Goal;
65
+ tree?: Tree;
66
+ };
67
+ export {};