gdp-wired 0.20.0 → 0.21.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 +37 -6
- package/dist/core/cycles.d.ts +12 -0
- package/dist/core/cycles.js +7 -0
- package/dist/core/data.d.ts +138 -13
- package/dist/core/data.js +842 -145
- package/dist/core/dsl.d.ts +27 -1
- package/dist/core/dsl.js +137 -26
- package/dist/core/exists.d.ts +26 -0
- package/dist/core/exists.js +625 -0
- package/dist/core/infer.d.ts +13 -0
- package/dist/core/infer.js +258 -41
- package/dist/core/machine.d.ts +27 -0
- package/dist/core/machine.js +45 -0
- package/dist/core/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +5 -1
- package/dist/kernel/prove.js +11 -5
- package/dist/lean-kernel/emit.d.ts +2 -0
- package/dist/lean-kernel/emit.js +196 -15
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
package/dist/core/data.js
CHANGED
|
@@ -2,13 +2,21 @@ import { substC, substE } from "../kernel/kernel.js";
|
|
|
2
2
|
import { proveCyclic } from "./cyclic.js";
|
|
3
3
|
import { clock, counted, pairedKeys, withDeadline } from "../kernel/prove.js";
|
|
4
4
|
import { work } from "./solver.js";
|
|
5
|
+
import { ranksOfRuns } from "./cycles.js";
|
|
6
|
+
import { proveThrough, transferPlan } from "./transfer.js";
|
|
5
7
|
// --- sorts and the AST ------------------------------------------------------------------------------------------------
|
|
6
8
|
export const self = { self: true };
|
|
7
9
|
export const natural = 'nat';
|
|
8
10
|
export const boolean = 'bool';
|
|
11
|
+
// A signed integer (Lean's Int): opt-in, beside the naturals, whose truncated subtraction keeps its meaning. Encoded as
|
|
12
|
+
// its zigzag code (z ↦ 2z for z ≥ 0, −2z − 1 below), a bijection with the naturals like a datatype's, so a value is one
|
|
13
|
+
// natural and two values are equal exactly when their codes are; its arithmetic reads a code as two naturals, p − n
|
|
14
|
+
// (from the code's quotient and remainder by 2), and writes the result's code back (`encI`, `codeOfPair`).
|
|
15
|
+
export const integer = 'int';
|
|
16
|
+
export const isDataField = (s) => typeof s === 'object';
|
|
9
17
|
// --- the surface ------------------------------------------------------------------------------------------------------
|
|
10
18
|
let fresh = 0;
|
|
11
|
-
const bindName = (field) => `${field}#${++fresh}`;
|
|
19
|
+
export const bindName = (field) => `${field}#${++fresh}`;
|
|
12
20
|
export class Nat {
|
|
13
21
|
x;
|
|
14
22
|
constructor(x) {
|
|
@@ -17,6 +25,11 @@ export class Nat {
|
|
|
17
25
|
plus(b) { return new Nat({ op: 'add', a: this.x, b: natX(b) }); }
|
|
18
26
|
minus(b) { return new Nat({ op: 'sub', a: this.x, b: natX(b) }); }
|
|
19
27
|
times(k) { return new Nat({ op: 'mul', k, a: this.x }); }
|
|
28
|
+
// A product of two naturals (the kernel multiplies it out; nonlinear: what proves it is products of facts and squares).
|
|
29
|
+
mul(b) { return new Nat({ op: 'prod', a: this.x, b: natX(b) }); }
|
|
30
|
+
modBy(k) { return new Nat({ op: 'mod', k: divisorOf(k), a: this.x }); }
|
|
31
|
+
divBy(k) { return new Nat({ op: 'div', k: divisorOf(k), a: this.x }); }
|
|
32
|
+
toInt() { return new Integer({ op: 'ofnat', a: this.x }); }
|
|
20
33
|
lt(b) { return new Bool({ op: '<', a: this.x, b: natX(b) }); }
|
|
21
34
|
atMost(b) { return new Bool({ op: '<=', a: this.x, b: natX(b) }); }
|
|
22
35
|
gt(b) { return new Bool({ op: '<', a: natX(b), b: this.x }); }
|
|
@@ -27,12 +40,41 @@ export class Nat {
|
|
|
27
40
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
28
41
|
match(cases) {
|
|
29
42
|
const pred = bindName('pred'), zero = cases.zero(), succ = cases.succ({ pred: new Nat({ op: 'var', name: pred }) });
|
|
30
|
-
const
|
|
43
|
+
const int = zero instanceof Integer || succ instanceof Integer;
|
|
44
|
+
const body = (o) => (typeof o === 'number' ? (int ? intX(o) : natX(o)) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
|
|
31
45
|
const x = { op: 'natmatch', of: this.x, zero: body(zero), pred, succ: body(succ) };
|
|
32
|
-
return (zero instanceof Bool ? new Bool({ op: 'holds', x }) : zero instanceof Val ? new Val(zero.data, x) : new Nat(x));
|
|
46
|
+
return (zero instanceof Bool ? new Bool({ op: 'holds', x }) : zero instanceof Val ? new Val(zero.data, x) : int ? new Integer(x) : new Nat(x));
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
49
|
const natX = (t) => (typeof t === 'number' ? { op: 'nat', value: t } : t.x);
|
|
50
|
+
const divisorOf = (k) => { if (!Number.isSafeInteger(k) || k < 1)
|
|
51
|
+
throw new RangeError(`a divisor is a whole number at least 1, not ${k}`); return k; };
|
|
52
|
+
// An integer's term; a number is a literal (any sign).
|
|
53
|
+
export class Integer {
|
|
54
|
+
x;
|
|
55
|
+
constructor(x) {
|
|
56
|
+
this.x = x;
|
|
57
|
+
}
|
|
58
|
+
plus(b) { return new Integer({ op: 'iadd', a: this.x, b: intX(b) }); }
|
|
59
|
+
minus(b) { return new Integer({ op: 'isub', a: this.x, b: intX(b) }); }
|
|
60
|
+
negate() { return new Integer({ op: 'ineg', a: this.x }); }
|
|
61
|
+
times(k) { if (!Number.isSafeInteger(k))
|
|
62
|
+
throw new RangeError(`${k} is not an integer`); return new Integer({ op: 'imul', k, a: this.x }); }
|
|
63
|
+
modBy(k) { return new Integer({ op: 'imod', k: divisorOf(k), a: this.x }); }
|
|
64
|
+
divBy(k) { return new Integer({ op: 'idiv', k: divisorOf(k), a: this.x }); }
|
|
65
|
+
lt(b) { return new Bool({ op: 'i<', a: this.x, b: intX(b) }); }
|
|
66
|
+
atMost(b) { return new Bool({ op: 'i<=', a: this.x, b: intX(b) }); }
|
|
67
|
+
gt(b) { return new Bool({ op: 'i<', a: intX(b), b: this.x }); }
|
|
68
|
+
atLeast(b) { return new Bool({ op: 'i<=', a: intX(b), b: this.x }); }
|
|
69
|
+
eq(b) { return new Bool({ op: '=', a: this.x, b: intX(b) }); }
|
|
70
|
+
ne(b) { return this.eq(b).not(); }
|
|
71
|
+
}
|
|
72
|
+
// An integer function's result: a natural literal in a branch (a match's case, an if's arm) is the integer's.
|
|
73
|
+
const intResult = (x) => (x.op === 'nat' ? { op: 'int', value: x.value } : x.op === 'if' ? { ...x, then: intResult(x.then), else: intResult(x.else) }
|
|
74
|
+
: x.op === 'match' ? { ...x, cases: x.cases.map(c => ({ ...c, body: intResult(c.body) })) } : x.op === 'natmatch' ? { ...x, zero: intResult(x.zero), succ: intResult(x.succ) } : x);
|
|
75
|
+
const intX = (t) => { if (typeof t !== 'number')
|
|
76
|
+
return t.x; if (!Number.isSafeInteger(t))
|
|
77
|
+
throw new RangeError(`${t} is not an integer`); return { op: 'int', value: t }; };
|
|
36
78
|
export class Bool {
|
|
37
79
|
y;
|
|
38
80
|
constructor(y) {
|
|
@@ -66,23 +108,28 @@ export class Val {
|
|
|
66
108
|
return { binds, out: f(fields) };
|
|
67
109
|
});
|
|
68
110
|
const first = built[0].out;
|
|
69
|
-
|
|
111
|
+
// A case that is an integer makes every case's number an integer's literal.
|
|
112
|
+
const int = built.some(b => b.out instanceof Integer);
|
|
113
|
+
const body = (o) => (typeof o === 'number' ? (int ? intX(o) : natX(o)) : o instanceof Bool ? { op: 'bool', c: o.y } : o.x);
|
|
70
114
|
const x = { op: 'match', data: d.name, of: this.x, cases: built.map(b => ({ binds: b.binds, body: body(b.out) })) };
|
|
71
|
-
return (first instanceof Bool ? new Bool({ op: 'holds', x }) : first instanceof Val ? new Val(first.data, x) : new Nat(x));
|
|
115
|
+
return (first instanceof Bool ? new Bool({ op: 'holds', x }) : first instanceof Val ? new Val(first.data, x) : int ? new Integer(x) : new Nat(x));
|
|
72
116
|
}
|
|
73
117
|
is(ctor) { return new Bool({ op: 'is', data: this.data.name, of: this.x, ctor: this.data.index(ctor) }); }
|
|
118
|
+
// Its size: 1 plus the sizes of its fields of its datatype's group (a list's length plus 1, a tree's nodes and leaves).
|
|
119
|
+
size() { return new Nat({ op: 'size', data: this.data.name, of: this.x }); }
|
|
74
120
|
eq(b) { return new Bool({ op: '=', a: this.x, b: b.x }); }
|
|
75
121
|
ne(b) { return this.eq(b).not(); }
|
|
76
122
|
}
|
|
77
123
|
// A value of a sort from its term.
|
|
78
|
-
export const wrap = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool({ op: 'holds', x }) : new Val(s, x));
|
|
79
|
-
const xOf = (s, v) => (s === 'nat' ? natX(v) : s === 'bool' ? { op: 'bool', c: v.y } : v.x);
|
|
124
|
+
export const wrap = (s, x) => (s === 'nat' ? new Nat(x) : s === 'bool' ? new Bool({ op: 'holds', x }) : s === 'int' ? new Integer(x) : new Val(s, x));
|
|
125
|
+
const xOf = (s, v) => (s === 'nat' ? natX(v) : s === 'bool' ? { op: 'bool', c: v.y } : s === 'int' ? intX(v) : v.x);
|
|
80
126
|
export function when(c, a, b) {
|
|
81
127
|
if (a instanceof Bool)
|
|
82
128
|
return new Bool({ op: 'or', xs: [{ op: 'and', xs: [c.y, a.y] }, { op: 'and', xs: [{ op: 'not', x: c.y }, b.y] }] });
|
|
83
|
-
const
|
|
129
|
+
const int = a instanceof Integer || b instanceof Integer, lit = (t) => (int ? intX(t) : natX(t));
|
|
130
|
+
const ax = typeof a === 'number' ? lit(a) : a.x, bx = typeof b === 'number' ? lit(b) : b.x;
|
|
84
131
|
const x = { op: 'if', c: c.y, then: ax, else: bx };
|
|
85
|
-
return (a instanceof Val ? new Val(a.data, x) : new Nat(x));
|
|
132
|
+
return (a instanceof Val ? new Val(a.data, x) : int ? new Integer(x) : new Nat(x));
|
|
86
133
|
}
|
|
87
134
|
// A datatype: its constructors, each with fields that are naturals or values of a datatype (`self`, another of its group,
|
|
88
135
|
// or one declared before it). Its constructors are ordered as Lean's bijection numbers them: those of no field, then
|
|
@@ -92,8 +139,8 @@ let declaredSeq = 0;
|
|
|
92
139
|
export const dataNamed = (name) => { const d = declared.get(name); if (!d)
|
|
93
140
|
throw new Error(`no datatype ${name}`); return d; };
|
|
94
141
|
// A field's sort; whether it is of the datatype's group (the fields its size counts, and an induction descends into).
|
|
95
|
-
export const fieldSort = (f) => (f.sort === 'nat'
|
|
96
|
-
export const inGroup = (d, f) => f.sort
|
|
142
|
+
export const fieldSort = (f) => (f.sort === 'nat' || f.sort === 'int' ? f.sort : dataNamed(f.sort.data));
|
|
143
|
+
export const inGroup = (d, f) => isDataField(f.sort) && d.group.includes(f.sort.data);
|
|
97
144
|
export class Data {
|
|
98
145
|
name;
|
|
99
146
|
group;
|
|
@@ -107,9 +154,9 @@ export class Data {
|
|
|
107
154
|
const all = Object.entries(decl).map(([c, fs]) => ({ name: c, fields: Object.entries(fs).map(([f, s]) => ({ name: f, sort: s })) }));
|
|
108
155
|
for (const c of all)
|
|
109
156
|
for (const f of c.fields)
|
|
110
|
-
if (f.sort
|
|
157
|
+
if (isDataField(f.sort) && !group.includes(f.sort.data) && !declared.has(f.sort.data))
|
|
111
158
|
throw new Error(`${name}.${c.name}.${f.name}: no datatype ${f.sort.data}`);
|
|
112
|
-
const rank = (c) => (!c.fields.length ? 0 : c.fields.every(f => !(f.sort
|
|
159
|
+
const rank = (c) => (!c.fields.length ? 0 : c.fields.every(f => !(isDataField(f.sort) && group.includes(f.sort.data))) ? 1 : 2);
|
|
113
160
|
this.ctors = [...all].sort((a, b) => rank(a) - rank(b));
|
|
114
161
|
declared.set(name, this);
|
|
115
162
|
}
|
|
@@ -133,7 +180,7 @@ function groupHeights(group) {
|
|
|
133
180
|
changed = false;
|
|
134
181
|
for (const n of group)
|
|
135
182
|
for (const c of dataNamed(n).ctors) {
|
|
136
|
-
const v = c.fields.reduce((m, f) => (f.sort
|
|
183
|
+
const v = c.fields.reduce((m, f) => (isDataField(f.sort) && group.includes(f.sort.data) ? Math.max(m, 1 + h.get(f.sort.data)) : m), 0);
|
|
137
184
|
if (v < h.get(n)) {
|
|
138
185
|
h.set(n, v);
|
|
139
186
|
changed = true;
|
|
@@ -170,7 +217,7 @@ export function weakOrder(ds) {
|
|
|
170
217
|
busy.add(d.name);
|
|
171
218
|
const o = 1 + Math.max(0, ...d.ctors.flatMap(c => c.fields.flatMap(f => (inGroup(d, f) && f.sort.data !== d.name ? [visit(dataNamed(f.sort.data))] : []))));
|
|
172
219
|
busy.delete(d.name);
|
|
173
|
-
if (d.ctors.some(c => c.fields.some(f => f.sort
|
|
220
|
+
if (d.ctors.some(c => c.fields.some(f => isDataField(f.sort) && f.sort.data === d.name)) && weak(d))
|
|
174
221
|
throw new Error(`${d.name}: no constructor of no field and its first constructor has a field of ${d.name}, which the bijection with the naturals doesn't take`);
|
|
175
222
|
out.set(d.name, o);
|
|
176
223
|
return o;
|
|
@@ -183,11 +230,13 @@ const normField = (own, group) => (s) => {
|
|
|
183
230
|
return { data: own };
|
|
184
231
|
if (s === 'nat' || (s && typeof s === 'object' && 'nat' in s))
|
|
185
232
|
return 'nat';
|
|
233
|
+
if (s === 'int')
|
|
234
|
+
return 'int';
|
|
186
235
|
if (s instanceof Data)
|
|
187
236
|
return { data: s.name };
|
|
188
237
|
if (typeof s === 'string' && group.includes(s))
|
|
189
238
|
return { data: s };
|
|
190
|
-
throw new Error(`${own}: a field is natural, self, a datatype declared before, or a name of its group`);
|
|
239
|
+
throw new Error(`${own}: a field is natural, integer, self, a datatype declared before, or a name of its group`);
|
|
191
240
|
};
|
|
192
241
|
const withCtors = (d) => { for (const c of d.ctors)
|
|
193
242
|
Object.defineProperty(d, c.name, c.fields.length ? { value: (...args) => d.make(c.name, ...args) } : { get: () => d.make(c.name) }); };
|
|
@@ -210,21 +259,35 @@ export function mutual(decls) {
|
|
|
210
259
|
return Object.fromEntries(ds.map(d => [d.name, d]));
|
|
211
260
|
}
|
|
212
261
|
const fns = new Map();
|
|
213
|
-
export function define(name, params, ret, body) {
|
|
262
|
+
export function define(name, params, ret, body, opts = {}) {
|
|
214
263
|
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
|
|
215
264
|
throw new Error(`${name}: a function's name is letters and digits`);
|
|
216
265
|
const ps = Object.entries(params).map(([n, s]) => ({ name: n, sort: s }));
|
|
217
|
-
|
|
218
|
-
|
|
266
|
+
const stated = opts.measure && measureOf(name, ps, opts.measure);
|
|
267
|
+
let built, rec, failed;
|
|
268
|
+
// The recursion, checked once (a refusal kept and given again).
|
|
269
|
+
const got = () => {
|
|
270
|
+
if (failed)
|
|
271
|
+
throw failed;
|
|
272
|
+
try {
|
|
273
|
+
return (rec ??= recursion(fn));
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
failed = e;
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const fn = { name, params: ps, ret, paramFns: [], stated, body: () => {
|
|
219
281
|
if (!built) {
|
|
220
282
|
const args = Object.fromEntries(ps.map(p => [p.name, wrap(p.sort, { op: 'var', name: `${name}.${p.name}` })]));
|
|
221
|
-
built = xOf(ret, body(args));
|
|
283
|
+
built = ret === 'int' ? intResult(xOf(ret, body(args))) : xOf(ret, body(args));
|
|
222
284
|
fn.paramFns = [...new Set(paramsIn(built))];
|
|
223
285
|
walk(built, o => { if (o.op === 'call' && !fns.has(o.fn))
|
|
224
286
|
throw new Error(`${name} calls ${String(o.fn)}, which isn't defined`); });
|
|
225
287
|
}
|
|
226
288
|
return built;
|
|
227
|
-
}, get decreases() { return (
|
|
289
|
+
}, get decreases() { return got().decreases; }, get ring() { return got().ring; }, get measure() { return got().measure; }, get termination() { return got().termination; },
|
|
290
|
+
set rec(r) { rec = r; } };
|
|
228
291
|
fns.set(name, fn);
|
|
229
292
|
const call = (...args) => {
|
|
230
293
|
if (args.length !== ps.length)
|
|
@@ -318,13 +381,318 @@ function recursion(fn) {
|
|
|
318
381
|
}
|
|
319
382
|
return undefined;
|
|
320
383
|
};
|
|
321
|
-
const got = choose(0, []);
|
|
322
|
-
if (!got)
|
|
323
|
-
|
|
384
|
+
const got = fn.stated ? undefined : choose(0, []);
|
|
385
|
+
if (!got) {
|
|
386
|
+
const why = `${fn.name}: not structural recursion (no parameter whose field every recursive call passes${members.length > 1 ? `, in its ring ${members.map(f => f.name).join(', ')}` : ''}${fn.stated ? '; a measure is given' : ''})`;
|
|
387
|
+
if (members.length > 1)
|
|
388
|
+
throw new Error(`${why}, and a ring of functions is taken by structural recursion only`);
|
|
389
|
+
const t = termination(fn, why);
|
|
390
|
+
return { ring: [fn.name], measure: t.measure, termination: t };
|
|
391
|
+
}
|
|
324
392
|
const names = members.map(f => f.name);
|
|
325
393
|
members.forEach((f, a) => { f.rec = { decreases: got[a], ring: names }; });
|
|
326
394
|
return { decreases: got[0], ring: names };
|
|
327
395
|
}
|
|
396
|
+
export const terminationMs = { ms: 10_000 };
|
|
397
|
+
const measurable = (s) => s === 'nat' || s instanceof Data;
|
|
398
|
+
function measureOf(name, ps, m) {
|
|
399
|
+
if (!m.length)
|
|
400
|
+
throw new Error(`${name}: an empty measure`);
|
|
401
|
+
return { comps: m.map(c => {
|
|
402
|
+
const names = typeof c === 'string' ? [c] : c;
|
|
403
|
+
return { coef: ps.map(p => names.filter(n => n === p.name).length), c: names.every(n => ps.some(p => p.name === n && measurable(p.sort))) ? 0 : (() => { throw new Error(`${name}: a measure names its parameters of a datatype or naturals (${names.join(', ')})`); })() };
|
|
404
|
+
}) };
|
|
405
|
+
}
|
|
406
|
+
export const showMeasure = (f, m) => {
|
|
407
|
+
const comp = (c) => {
|
|
408
|
+
const parts = c.coef.flatMap((k, i) => (k ? [`${k < 0 ? '−' : ''}${Math.abs(k) === 1 ? '' : `${Math.abs(k)}·`}${f.params[i].sort === 'nat' ? f.params[i].name : `size(${f.params[i].name})`}`] : []));
|
|
409
|
+
const t = [...parts, ...(c.c ? [String(c.c)] : [])].join(' + ').replace(/\+ −/g, '− ');
|
|
410
|
+
return t || '0';
|
|
411
|
+
};
|
|
412
|
+
return m.comps.length === 1 ? comp(m.comps[0]) : `(${m.comps.map(comp).join(', ')})`;
|
|
413
|
+
};
|
|
414
|
+
// A component at arguments, as a term (its negative part subtracted, truncated as the encoding reads it).
|
|
415
|
+
const sizeX = (s, a) => (s === 'nat' ? a : { op: 'size', data: s.name, of: a });
|
|
416
|
+
export function measureX(f, c, args) {
|
|
417
|
+
const pos = [], neg = [];
|
|
418
|
+
c.coef.forEach((k, i) => { if (!k)
|
|
419
|
+
return; const t = sizeX(f.params[i].sort, args[i]); (k > 0 ? pos : neg).push(Math.abs(k) === 1 ? t : { op: 'mul', k: Math.abs(k), a: t }); });
|
|
420
|
+
if (c.c > 0)
|
|
421
|
+
pos.push({ op: 'nat', value: c.c });
|
|
422
|
+
if (c.c < 0)
|
|
423
|
+
neg.push({ op: 'nat', value: -c.c });
|
|
424
|
+
const sum = (ts) => (ts.length ? ts.reduce((a, b) => ({ op: 'add', a, b })) : { op: 'nat', value: 0 });
|
|
425
|
+
return neg.length ? { op: 'sub', a: sum(pos), b: sum(neg) } : sum(pos);
|
|
426
|
+
}
|
|
427
|
+
const lexY = (as, bs) => (as.length === 1 ? { op: '<', a: as[0], b: bs[0] } : { op: 'or', xs: [{ op: '<', a: as[0], b: bs[0] }, { op: 'and', xs: [{ op: '=', a: as[0], b: bs[0] }, lexY(as.slice(1), bs.slice(1))] }] });
|
|
428
|
+
function callSites(fn) {
|
|
429
|
+
const out = [], yesX = { op: 'bool', c: yes.y };
|
|
430
|
+
const at = (sh, name) => { let t = sh.get(name); while (t && t.op === 'var' && sh.has(t.name))
|
|
431
|
+
t = sh.get(t.name); return t; };
|
|
432
|
+
const goY = (y, w) => {
|
|
433
|
+
switch (y.op) {
|
|
434
|
+
case '<':
|
|
435
|
+
case '<=':
|
|
436
|
+
case '=':
|
|
437
|
+
case 'i<':
|
|
438
|
+
case 'i<=':
|
|
439
|
+
goX(y.a, w);
|
|
440
|
+
goX(y.b, w);
|
|
441
|
+
return;
|
|
442
|
+
case 'and':
|
|
443
|
+
case 'or':
|
|
444
|
+
y.xs.forEach(z => goY(z, w));
|
|
445
|
+
return;
|
|
446
|
+
case 'not':
|
|
447
|
+
goY(y.x, w);
|
|
448
|
+
return;
|
|
449
|
+
case 'is':
|
|
450
|
+
goX(y.of, w);
|
|
451
|
+
return;
|
|
452
|
+
case 'holds':
|
|
453
|
+
goX(y.x, w);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const goX = (x, w) => {
|
|
458
|
+
switch (x.op) {
|
|
459
|
+
case 'var':
|
|
460
|
+
case 'nat': return;
|
|
461
|
+
case 'add':
|
|
462
|
+
case 'sub':
|
|
463
|
+
case 'min':
|
|
464
|
+
case 'max':
|
|
465
|
+
case 'prod':
|
|
466
|
+
case 'iadd':
|
|
467
|
+
case 'isub':
|
|
468
|
+
goX(x.a, w);
|
|
469
|
+
goX(x.b, w);
|
|
470
|
+
return;
|
|
471
|
+
case 'mul':
|
|
472
|
+
case 'mod':
|
|
473
|
+
case 'div':
|
|
474
|
+
case 'imul':
|
|
475
|
+
case 'imod':
|
|
476
|
+
case 'idiv':
|
|
477
|
+
case 'ineg':
|
|
478
|
+
case 'ofnat':
|
|
479
|
+
goX(x.a, w);
|
|
480
|
+
return;
|
|
481
|
+
case 'int': return;
|
|
482
|
+
case 'apply':
|
|
483
|
+
goX(x.arg, w);
|
|
484
|
+
return;
|
|
485
|
+
case 'size':
|
|
486
|
+
goX(x.of, w);
|
|
487
|
+
return;
|
|
488
|
+
case 'bool':
|
|
489
|
+
goY(x.c, w);
|
|
490
|
+
return;
|
|
491
|
+
case 'ctor':
|
|
492
|
+
x.args.forEach(a => goX(a, w));
|
|
493
|
+
return;
|
|
494
|
+
case 'call':
|
|
495
|
+
x.args.forEach(a => goX(a, w));
|
|
496
|
+
if (x.fn === fn.name)
|
|
497
|
+
out.push({ args: x.args, ...w });
|
|
498
|
+
return;
|
|
499
|
+
case 'if':
|
|
500
|
+
goY(x.c, w);
|
|
501
|
+
goX(x.then, { ...w, wrap: y => w.wrap({ op: 'or', xs: [{ op: 'not', x: x.c }, y] }) });
|
|
502
|
+
goX(x.else, { ...w, wrap: y => w.wrap({ op: 'or', xs: [x.c, y] }) });
|
|
503
|
+
return;
|
|
504
|
+
case 'match': {
|
|
505
|
+
goX(x.of, w);
|
|
506
|
+
const d = dataNamed(x.data), v = x.of.op === 'var' ? x.of.name : undefined, had = v ? at(w.shape, v) : undefined;
|
|
507
|
+
x.cases.forEach((c, j) => {
|
|
508
|
+
if (v && had?.op === 'ctor') {
|
|
509
|
+
// matched before on this path: the same case, its fields the ones bound then; another, never reached
|
|
510
|
+
if (had.ctor === j)
|
|
511
|
+
goX(c.body, { ...w, shape: new Map([...w.shape, ...c.binds.map((b, i) => [b, had.args[i]])]) });
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (v && !had) {
|
|
515
|
+
const fields = c.binds.map((b, i) => ({ name: b, sort: fieldSort(d.ctors[j].fields[i]) }));
|
|
516
|
+
goX(c.body, { wrap: w.wrap, shape: new Map([...w.shape, [v, { op: 'ctor', data: x.data, ctor: j, args: c.binds.map(b => ({ op: 'var', name: b })) }]]), vars: [...w.vars, ...fields] });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
goX(c.body, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'match', data: x.data, of: x.of, cases: x.cases.map((c2, i) => ({ binds: c2.binds, body: i === j ? { op: 'bool', c: y } : yesX })) } }) });
|
|
520
|
+
});
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
case 'natmatch': {
|
|
524
|
+
goX(x.of, w);
|
|
525
|
+
const v = x.of.op === 'var' ? x.of.name : undefined;
|
|
526
|
+
if (v && !at(w.shape, v)) {
|
|
527
|
+
goX(x.zero, { ...w, shape: new Map([...w.shape, [v, { op: 'nat', value: 0 }]]) });
|
|
528
|
+
goX(x.succ, { wrap: w.wrap, shape: new Map([...w.shape, [v, { op: 'add', a: { op: 'var', name: x.pred }, b: { op: 'nat', value: 1 } }]]), vars: [...w.vars, { name: x.pred, sort: 'nat' }] });
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
goX(x.zero, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'natmatch', of: x.of, zero: { op: 'bool', c: y }, pred: x.pred, succ: yesX } }) });
|
|
532
|
+
goX(x.succ, { ...w, wrap: y => w.wrap({ op: 'holds', x: { op: 'natmatch', of: x.of, zero: yesX, pred: x.pred, succ: { op: 'bool', c: y } } }) });
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
goX(fn.body(), { wrap: y => y, shape: new Map(), vars: fn.params.map(q => ({ name: `${fn.name}.${q.name}`, sort: q.sort })) });
|
|
538
|
+
return out;
|
|
539
|
+
}
|
|
540
|
+
// The obligation of a call: under its path, the measure at its arguments below the measure at the parameters, a law over
|
|
541
|
+
// the path's variables (the parameters not matched, the fields of the cases taken).
|
|
542
|
+
function obligation(fn, m, s, i) {
|
|
543
|
+
const own = fn.params.map(q => ({ op: 'var', name: `${fn.name}.${q.name}` }));
|
|
544
|
+
const shaped = (t) => (Array.isArray(t) ? t.map(shaped) : t && typeof t === 'object' ? (t.op === 'var' && s.shape.has(t.name) ? shaped(s.shape.get(t.name)) : Object.fromEntries(Object.entries(t).map(([key, v]) => [key, shaped(v)]))) : t);
|
|
545
|
+
const holds = shaped(s.wrap(lexY(m.comps.map(c => measureX(fn, c, s.args)), m.comps.map(c => measureX(fn, c, own)))));
|
|
546
|
+
return { name: `${fn.name}'s call ${i + 1} lowers ${showMeasure(fn, m)}`, vars: s.vars.filter(v => !s.shape.has(v.name)), holds, params: [...new Set(paramsIn(holds))] };
|
|
547
|
+
}
|
|
548
|
+
// The calls the function's runs make, each from its parameters' sizes to its arguments' (random arguments, up to 2,000
|
|
549
|
+
// calls a run: a run past that is cut, its calls kept).
|
|
550
|
+
let tracing;
|
|
551
|
+
function runEdges(fn) {
|
|
552
|
+
const rand = rng(17), edges = new Map();
|
|
553
|
+
const feat = (vs) => fn.params.map((p, i) => (p.sort === 'nat' ? vs[i] : p.sort instanceof Data ? sizeOf(p.sort, vs[i]) : 0));
|
|
554
|
+
const tables = new Map();
|
|
555
|
+
const params = new Proxy({}, { get: (_, key) => tables.get(key) ?? (tables.set(key, randomFn(rand)), tables.get(key)) });
|
|
556
|
+
for (let i = 0; i < 120 && edges.size < 400; i++) {
|
|
557
|
+
const vals = fn.params.map(p => randomValue(p.sort, rand, 1 + (i % 7)));
|
|
558
|
+
tracing = { fn: fn.name, stack: [], fuel: 2000, edge: (a, b) => { const e = { s: feat(a), t: feat(b) }; edges.set(JSON.stringify(e), e); } };
|
|
559
|
+
try {
|
|
560
|
+
evalX({ op: 'call', fn: fn.name, args: vals.map((_, j) => ({ op: 'var', name: `$a${j}` })) }, new Map(vals.map((v, j) => [`$a${j}`, v])), params);
|
|
561
|
+
}
|
|
562
|
+
catch { /* cut: its calls kept */ }
|
|
563
|
+
finally {
|
|
564
|
+
tracing = undefined;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return [...edges.values()];
|
|
568
|
+
}
|
|
569
|
+
const valueOf = (c, xs) => Math.max(0, c.coef.reduce((a, k, i) => a + k * xs[i], c.c));
|
|
570
|
+
const lowers = (m, e) => {
|
|
571
|
+
for (const c of m.comps) {
|
|
572
|
+
const a = valueOf(c, e.t), b = valueOf(c, e.s);
|
|
573
|
+
if (a < b)
|
|
574
|
+
return true;
|
|
575
|
+
if (a > b)
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
return false;
|
|
579
|
+
};
|
|
580
|
+
function candidates(fn, edges) {
|
|
581
|
+
if (fn.stated)
|
|
582
|
+
return [fn.stated];
|
|
583
|
+
const ix = fn.params.flatMap((p, i) => (measurable(p.sort) ? [i] : [])), n = fn.params.length;
|
|
584
|
+
const one = (i) => ({ coef: fn.params.map((_, j) => (j === i ? 1 : 0)), c: 0 }), sum = (is) => ({ coef: fn.params.map((_, j) => (is.includes(j) ? 1 : 0)), c: 0 });
|
|
585
|
+
const out = ix.map(i => ({ comps: [one(i)] }));
|
|
586
|
+
for (const i of ix)
|
|
587
|
+
for (const j of ix)
|
|
588
|
+
if (i !== j)
|
|
589
|
+
out.push({ comps: [one(i), one(j)] });
|
|
590
|
+
for (const [a, i] of ix.entries())
|
|
591
|
+
for (const j of ix.slice(a + 1))
|
|
592
|
+
out.push({ comps: [sum([i, j])] });
|
|
593
|
+
if (ix.length <= 4)
|
|
594
|
+
for (const i of ix)
|
|
595
|
+
for (const j of ix)
|
|
596
|
+
for (const k of ix)
|
|
597
|
+
if (i !== j && j !== k && i !== k)
|
|
598
|
+
out.push({ comps: [one(i), one(j), one(k)] });
|
|
599
|
+
if (ix.length > 2)
|
|
600
|
+
out.push({ comps: [sum(ix)] });
|
|
601
|
+
// The LP's rank over the runs (coefficients and constants), where the runs give edges.
|
|
602
|
+
if (edges.length) {
|
|
603
|
+
const vars = ix.map(i => `p${i}`), env = (xs) => Object.fromEntries(ix.map(i => [`p${i}`, xs[i]]));
|
|
604
|
+
const r = ranksOfRuns(vars, edges.flatMap(e => [env(e.s), env(e.t)]), edges.map(e => ({ s: env(e.s), t: env(e.t) })));
|
|
605
|
+
if (!r.left && r.comps.length)
|
|
606
|
+
out.push({ comps: r.comps.map(c => ({ coef: Array.from({ length: n }, (_, j) => c.coef[`p${j}`] ?? 0), c: c.c })) });
|
|
607
|
+
}
|
|
608
|
+
return out;
|
|
609
|
+
}
|
|
610
|
+
// An obligation as one node of a cyclic proof (no use, no models): its conclusion, each definition it reads unfolded
|
|
611
|
+
// once, the constructors' facts at the values built, the shape at each size it reads and at its variables of a
|
|
612
|
+
// datatype, the congruences 16 random models' explanations use. The kernel checks it as any cyclic proof; undefined where it isn't proven.
|
|
613
|
+
function directly(law, ms) {
|
|
614
|
+
const p = programOf([law]), fs = factsOf(p), concl = encY(p, law.holds, new Map());
|
|
615
|
+
varSorts = new Map(law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : [])));
|
|
616
|
+
const until = clock() + ms;
|
|
617
|
+
const insts = instancesOf(p, fs, { roots: [concl], others: [], depth: 1, shapesAt: [...varSorts.keys()].map(ev), etaAt: [], models: modelsFor(p, law, 16, 3), until });
|
|
618
|
+
if (clock() > until)
|
|
619
|
+
return undefined;
|
|
620
|
+
const was = pairedKeys.skip;
|
|
621
|
+
pairedKeys.skip = true;
|
|
622
|
+
try {
|
|
623
|
+
const v = withDeadline(until, () => proveCyclic(fs.facts, [{ name: law.name, hyps: [], concl, rank: [k(0)], insts, uses: [] }]));
|
|
624
|
+
return v.proven && v.checked ? v : undefined;
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
pairedKeys.skip = was;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function termination(fn, why) {
|
|
631
|
+
return counted(dataRate, () => {
|
|
632
|
+
if (dataTrace)
|
|
633
|
+
console.error(`[data] termination of ${fn.name}`);
|
|
634
|
+
const t0 = performance.now(), sites = callSites(fn);
|
|
635
|
+
if (!fn.params.some(p => measurable(p.sort)))
|
|
636
|
+
throw new Error(`${why}, and it has no parameter a measure reads (a natural or a datatype value)`);
|
|
637
|
+
if (dataTrace)
|
|
638
|
+
console.error(`[data] ${sites.length} calls`);
|
|
639
|
+
const edges = runEdges(fn), until = clock() + terminationMs.ms, tried = [], seen = new Set();
|
|
640
|
+
if (dataTrace)
|
|
641
|
+
console.error(`[data] its runs: ${edges.length} calls at ${Math.round(performance.now() - t0)} ms`);
|
|
642
|
+
for (const m of candidates(fn, edges)) {
|
|
643
|
+
if (dataTrace)
|
|
644
|
+
console.error(`[data] ${showMeasure(fn, m)} at ${Math.round(performance.now() - t0)} ms`);
|
|
645
|
+
if (clock() > until) {
|
|
646
|
+
tried.push('out of time');
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
const text = showMeasure(fn, m), bad = edges.find(e => !lowers(m, e));
|
|
650
|
+
if (seen.has(text))
|
|
651
|
+
continue;
|
|
652
|
+
seen.add(text);
|
|
653
|
+
if (bad) {
|
|
654
|
+
if (fn.stated || !tried.length)
|
|
655
|
+
tried.push(`${text}: a call of its runs doesn't lower it (from ${JSON.stringify(bad.s)} to ${JSON.stringify(bad.t)}, the parameters' sizes)`);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const obligations = [];
|
|
659
|
+
let failed;
|
|
660
|
+
for (const [i, site] of sites.entries()) {
|
|
661
|
+
const law = obligation(fn, m, site, i);
|
|
662
|
+
// A call nested in its own argument or condition: the obligation would read the function being taken.
|
|
663
|
+
if (programOf([law]).fns.some(g => g.name === fn.name))
|
|
664
|
+
throw new Error(`${why}, and a call to itself is nested in the argument or the condition of another (nested recursion isn't taken)`);
|
|
665
|
+
// First one node, the definitions unfolded once (most calls lower the measure by their cases alone); else
|
|
666
|
+
// proveData: by induction where the obligation has a scheme (a call on a filter of the tail: the filter's own
|
|
667
|
+
// recursion), else, or then, the definitions unfolded alone.
|
|
668
|
+
const left = () => Math.max(1, until - clock()), cex = refute(law, 200);
|
|
669
|
+
if (cex) {
|
|
670
|
+
failed = `${text}: its call ${i + 1} doesn't lower it at ${JSON.stringify(cex)}`;
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
const direct = directly(law, Math.min(2000, left()));
|
|
674
|
+
if (direct) {
|
|
675
|
+
obligations.push({ law, proof: direct, lemmas: [] });
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const inductive = schemes(programOf([law]), law).length > 0;
|
|
679
|
+
// (no conjectured lemmas: their making isn't counted in the budget, and a call's decrease mostly needs none)
|
|
680
|
+
let v = proveData(law, inductive ? { ms: Math.min(12_000, left()), lemmas: false } : { induction: false, ms: Math.min(4000, left()) });
|
|
681
|
+
if (inductive && !(v.proven && v.checked) && !v.counterexample && clock() < until)
|
|
682
|
+
v = proveData(law, { induction: false, ms: Math.min(4000, left()) });
|
|
683
|
+
if (!(v.proven && v.checked)) {
|
|
684
|
+
failed = `${text}: its call ${i + 1} ${v.counterexample ? `doesn't lower it at ${JSON.stringify(v.counterexample)}` : 'not proven to lower it'}`;
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
obligations.push({ law, proof: v.proof, scheme: v.scheme, lemmas: v.lemmas });
|
|
688
|
+
}
|
|
689
|
+
if (!failed)
|
|
690
|
+
return { measure: m, text, obligations, ms: Math.round(performance.now() - t0) };
|
|
691
|
+
tried.push(failed);
|
|
692
|
+
}
|
|
693
|
+
throw new Error(`${why}, and no measure is proven to fall at every call${tried.length ? ` (${tried.slice(0, 3).join('; ')})` : ''}`);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
328
696
|
export function law(name, vars, holds) {
|
|
329
697
|
const vs = Object.entries(vars).map(([n, s]) => ({ name: n, sort: s }));
|
|
330
698
|
const y = holds(Object.fromEntries(vs.map(v => [v.name, wrap(v.sort, { op: 'var', name: v.name })]))).y;
|
|
@@ -332,8 +700,20 @@ export function law(name, vars, holds) {
|
|
|
332
700
|
}
|
|
333
701
|
const fnOf = (name) => { const f = fns.get(name); if (!f)
|
|
334
702
|
throw new Error(`no function ${name}`); return f; };
|
|
703
|
+
// A function by its name, if one is defined (core/transfer.ts names its copies apart from every other).
|
|
704
|
+
export const fnNamed = (name) => fns.get(name);
|
|
335
705
|
const asNat = (v) => v;
|
|
336
|
-
|
|
706
|
+
// Evaluation within bounds: a natural past 2^53 (no longer exact) always throws EvalLimit, and within the search
|
|
707
|
+
// (`evalBounds.on`: proveData, refute) so do calls nested past `depth` or past `calls` of one call's evaluation, which
|
|
708
|
+
// recursive powers and factorials reach on small values. The sample that needs one is skipped (refute), the model's
|
|
709
|
+
// read is unknown (the search).
|
|
710
|
+
export class EvalLimit extends RangeError {
|
|
711
|
+
}
|
|
712
|
+
export const evalBounds = { on: false, depth: 1000, calls: 20_000 };
|
|
713
|
+
let evalCalls = 0;
|
|
714
|
+
const exact = (n) => { if (n > Number.MAX_SAFE_INTEGER)
|
|
715
|
+
throw new EvalLimit('past 2^53'); return n; };
|
|
716
|
+
export function evalX(x, env, params, d = 0) {
|
|
337
717
|
switch (x.op) {
|
|
338
718
|
case 'var': {
|
|
339
719
|
const v = env.get(x.name);
|
|
@@ -342,53 +722,92 @@ export function evalX(x, env, params) {
|
|
|
342
722
|
return v;
|
|
343
723
|
}
|
|
344
724
|
case 'nat': return x.value;
|
|
345
|
-
case 'add': return asNat(evalX(x.a, env, params)) + asNat(evalX(x.b, env, params));
|
|
346
|
-
case 'sub': return Math.max(0, asNat(evalX(x.a, env, params)) - asNat(evalX(x.b, env, params)));
|
|
347
|
-
case 'min': return Math.min(asNat(evalX(x.a, env, params)), asNat(evalX(x.b, env, params)));
|
|
348
|
-
case 'max': return Math.max(asNat(evalX(x.a, env, params)), asNat(evalX(x.b, env, params)));
|
|
349
|
-
case 'mul': return x.k * asNat(evalX(x.a, env, params));
|
|
350
|
-
case '
|
|
351
|
-
case '
|
|
352
|
-
case '
|
|
353
|
-
case '
|
|
725
|
+
case 'add': return exact(asNat(evalX(x.a, env, params, d)) + asNat(evalX(x.b, env, params, d)));
|
|
726
|
+
case 'sub': return Math.max(0, asNat(evalX(x.a, env, params, d)) - asNat(evalX(x.b, env, params, d)));
|
|
727
|
+
case 'min': return Math.min(asNat(evalX(x.a, env, params, d)), asNat(evalX(x.b, env, params, d)));
|
|
728
|
+
case 'max': return Math.max(asNat(evalX(x.a, env, params, d)), asNat(evalX(x.b, env, params, d)));
|
|
729
|
+
case 'mul': return exact(x.k * asNat(evalX(x.a, env, params, d)));
|
|
730
|
+
case 'prod': return exact(asNat(evalX(x.a, env, params, d)) * asNat(evalX(x.b, env, params, d)));
|
|
731
|
+
case 'mod': return asNat(evalX(x.a, env, params, d)) % x.k;
|
|
732
|
+
case 'div': return Math.floor(asNat(evalX(x.a, env, params, d)) / x.k);
|
|
733
|
+
case 'int': return x.value;
|
|
734
|
+
case 'iadd': return exact(asNat(evalX(x.a, env, params, d)) + asNat(evalX(x.b, env, params, d)));
|
|
735
|
+
case 'isub': return exact(asNat(evalX(x.a, env, params, d)) - asNat(evalX(x.b, env, params, d)));
|
|
736
|
+
case 'ineg': return -asNat(evalX(x.a, env, params, d));
|
|
737
|
+
case 'imul': return exact(x.k * asNat(evalX(x.a, env, params, d)));
|
|
738
|
+
case 'ofnat': return asNat(evalX(x.a, env, params, d));
|
|
739
|
+
case 'imod': return euclidMod(asNat(evalX(x.a, env, params, d)), x.k);
|
|
740
|
+
case 'idiv': {
|
|
741
|
+
const v = asNat(evalX(x.a, env, params, d));
|
|
742
|
+
return (v - euclidMod(v, x.k)) / x.k;
|
|
743
|
+
}
|
|
744
|
+
case 'if': return evalY(x.c, env, params, d) ? evalX(x.then, env, params, d) : evalX(x.else, env, params, d);
|
|
745
|
+
case 'bool': return evalY(x.c, env, params, d) ? 1 : 0;
|
|
746
|
+
case 'apply': return params[x.param](asNat(evalX(x.arg, env, params, d)));
|
|
747
|
+
case 'size': return sizeOf(dataNamed(x.data), evalX(x.of, env, params, d));
|
|
748
|
+
case 'ctor': return { c: x.ctor, f: x.args.map(a => evalX(a, env, params, d)) };
|
|
354
749
|
case 'natmatch': {
|
|
355
|
-
const n = asNat(evalX(x.of, env, params));
|
|
750
|
+
const n = asNat(evalX(x.of, env, params, d));
|
|
356
751
|
if (n === 0)
|
|
357
|
-
return evalX(x.zero, env, params);
|
|
752
|
+
return evalX(x.zero, env, params, d);
|
|
358
753
|
const e2 = new Map(env);
|
|
359
754
|
e2.set(x.pred, n - 1);
|
|
360
|
-
return evalX(x.succ, e2, params);
|
|
755
|
+
return evalX(x.succ, e2, params, d);
|
|
361
756
|
}
|
|
362
757
|
case 'match': {
|
|
363
|
-
const v = evalX(x.of, env, params), cs = x.cases[v.c];
|
|
758
|
+
const v = evalX(x.of, env, params, d), cs = x.cases[v.c];
|
|
364
759
|
const e2 = new Map(env);
|
|
365
760
|
cs.binds.forEach((b, i) => e2.set(b, v.f[i]));
|
|
366
|
-
return evalX(cs.body, e2, params);
|
|
761
|
+
return evalX(cs.body, e2, params, d);
|
|
367
762
|
}
|
|
368
763
|
case 'call': {
|
|
369
|
-
const f = fnOf(x.fn), body = f.body();
|
|
370
|
-
const e2 = new Map(f.params.map((p, i) => [`${f.name}.${p.name}`,
|
|
371
|
-
|
|
764
|
+
const f = fnOf(x.fn), body = f.body(), vals = x.args.map(a => evalX(a, env, params, d));
|
|
765
|
+
const e2 = new Map(f.params.map((p, i) => [`${f.name}.${p.name}`, vals[i]]));
|
|
766
|
+
if (evalBounds.on) {
|
|
767
|
+
if (d === 0)
|
|
768
|
+
evalCalls = 0;
|
|
769
|
+
if (d >= evalBounds.depth || ++evalCalls > evalBounds.calls)
|
|
770
|
+
throw new EvalLimit(`${f.name}: past the evaluation's bounds`);
|
|
771
|
+
}
|
|
772
|
+
if (tracing?.fn !== f.name)
|
|
773
|
+
return evalX(body, e2, params, d + 1);
|
|
774
|
+
// A run of a function whose termination is being proven: each call recorded, the run cut past its fuel.
|
|
775
|
+
const t = tracing;
|
|
776
|
+
if (--t.fuel < 0)
|
|
777
|
+
throw new RangeError('out of fuel');
|
|
778
|
+
const top = t.stack.at(-1);
|
|
779
|
+
if (top)
|
|
780
|
+
t.edge(top, vals);
|
|
781
|
+
t.stack.push(vals);
|
|
782
|
+
try {
|
|
783
|
+
return evalX(body, e2, params, d + 1);
|
|
784
|
+
}
|
|
785
|
+
finally {
|
|
786
|
+
t.stack.pop();
|
|
787
|
+
}
|
|
372
788
|
}
|
|
373
789
|
}
|
|
374
790
|
}
|
|
375
|
-
export function evalY(y, env, params) {
|
|
791
|
+
export function evalY(y, env, params, d = 0) {
|
|
376
792
|
switch (y.op) {
|
|
377
|
-
case '<': return asNat(evalX(y.a, env, params)) < asNat(evalX(y.b, env, params));
|
|
378
|
-
case '<=': return asNat(evalX(y.a, env, params)) <= asNat(evalX(y.b, env, params));
|
|
379
|
-
case '
|
|
380
|
-
case '
|
|
381
|
-
case '
|
|
382
|
-
case '
|
|
383
|
-
case '
|
|
384
|
-
case '
|
|
793
|
+
case '<': return asNat(evalX(y.a, env, params, d)) < asNat(evalX(y.b, env, params, d));
|
|
794
|
+
case '<=': return asNat(evalX(y.a, env, params, d)) <= asNat(evalX(y.b, env, params, d));
|
|
795
|
+
case 'i<': return asNat(evalX(y.a, env, params, d)) < asNat(evalX(y.b, env, params, d));
|
|
796
|
+
case 'i<=': return asNat(evalX(y.a, env, params, d)) <= asNat(evalX(y.b, env, params, d));
|
|
797
|
+
case '=': return same(evalX(y.a, env, params, d), evalX(y.b, env, params, d));
|
|
798
|
+
case 'and': return y.xs.every(z => evalY(z, env, params, d));
|
|
799
|
+
case 'or': return y.xs.some(z => evalY(z, env, params, d));
|
|
800
|
+
case 'not': return !evalY(y.x, env, params, d);
|
|
801
|
+
case 'is': return evalX(y.of, env, params, d).c === y.ctor;
|
|
802
|
+
case 'holds': return evalX(y.x, env, params, d) === 1;
|
|
385
803
|
}
|
|
386
804
|
}
|
|
805
|
+
const euclidMod = (v, k) => ((v % k) + k) % k;
|
|
387
806
|
const same = (a, b) => (typeof a === 'number' || typeof b === 'number' ? a === b : a.c === b.c && a.f.every((x, i) => same(x, b.f[i])));
|
|
388
807
|
export const showValue = (d, v) => {
|
|
389
808
|
if (typeof v === 'number')
|
|
390
809
|
return String(v);
|
|
391
|
-
if (d === 'nat' || d === 'bool')
|
|
810
|
+
if (d === 'nat' || d === 'bool' || d === 'int')
|
|
392
811
|
return JSON.stringify(v);
|
|
393
812
|
const c = d.ctors[v.c];
|
|
394
813
|
return c.fields.length ? `${c.name}(${c.fields.map((f, i) => showValue(fieldSort(f), v.f[i])).join(', ')})` : c.name;
|
|
@@ -399,6 +818,8 @@ export function randomValue(s, rand, size, natMax = 5) {
|
|
|
399
818
|
return Math.floor(rand() * (natMax + 1));
|
|
400
819
|
if (s === 'bool')
|
|
401
820
|
return rand() < 0.5 ? 0 : 1;
|
|
821
|
+
if (s === 'int')
|
|
822
|
+
return Math.floor(rand() * (2 * natMax + 1)) - natMax;
|
|
402
823
|
// The leaves: constructors of no field of the group, or (a datatype with none, a rose tree) those of least height.
|
|
403
824
|
const base = s.ctors.filter(c => c.fields.every(f => !inGroup(s, f)));
|
|
404
825
|
const leaves = base.length ? base : s.ctors.filter(c => s.ctorHeight(c) === s.height);
|
|
@@ -428,7 +849,7 @@ const yesC = leC(k(0), k(0));
|
|
|
428
849
|
const pairE = (a, b) => { const s = { op: 'add', a, b }; return { op: 'add', a: { op: 'prod', a: s, b: s }, b }; };
|
|
429
850
|
export const keyOf = (args) => args.slice(1).reduce(pairE, args[0]);
|
|
430
851
|
// The arguments of a key built by keyOf (n of them), read back from its shape.
|
|
431
|
-
function argsOf(key, n) {
|
|
852
|
+
export function argsOf(key, n) {
|
|
432
853
|
if (n === 1)
|
|
433
854
|
return [key];
|
|
434
855
|
if (key.op !== 'add' || key.a.op !== 'prod' || key.a.a.op !== 'add')
|
|
@@ -459,6 +880,11 @@ export function programOf(laws, extra = []) {
|
|
|
459
880
|
}
|
|
460
881
|
if (o.op === 'apply')
|
|
461
882
|
ps.add(o.param);
|
|
883
|
+
if (o.op === 'size')
|
|
884
|
+
addSort(dataNamed(o.data));
|
|
885
|
+
// (a datatype read only through a constructor, a match or a test of one: a law with a definition unfolded)
|
|
886
|
+
if ((o.op === 'ctor' || o.op === 'match' || o.op === 'is') && typeof o.data === 'string')
|
|
887
|
+
addSort(dataNamed(o.data));
|
|
462
888
|
});
|
|
463
889
|
for (const l of laws) {
|
|
464
890
|
l.vars.forEach(v => addSort(v.sort));
|
|
@@ -478,11 +904,92 @@ export function programOf(laws, extra = []) {
|
|
|
478
904
|
// (the recursion of each checked)
|
|
479
905
|
for (const f of order)
|
|
480
906
|
void f.decreases;
|
|
481
|
-
|
|
907
|
+
const divs = new Set();
|
|
908
|
+
let ints = false;
|
|
909
|
+
const scan = (t) => walk(t, o => { if (o.op === 'mod' || o.op === 'div' || o.op === 'imod' || o.op === 'idiv')
|
|
910
|
+
divs.add(o.k); if (typeof o.op === 'string' && (intOps.has(o.op) || o.op === 'i<' || o.op === 'i<='))
|
|
911
|
+
ints = true; });
|
|
912
|
+
for (const l of laws) {
|
|
913
|
+
scan(l.holds);
|
|
914
|
+
if (l.vars.some(v => v.sort === 'int'))
|
|
915
|
+
ints = true;
|
|
916
|
+
}
|
|
917
|
+
for (const f of order) {
|
|
918
|
+
scan(f.body());
|
|
919
|
+
if (f.ret === 'int' || f.params.some(q => q.sort === 'int'))
|
|
920
|
+
ints = true;
|
|
921
|
+
}
|
|
922
|
+
for (const d of datas)
|
|
923
|
+
if (d.ctors.some(c => c.fields.some(f => f.sort === 'int')))
|
|
924
|
+
ints = true;
|
|
925
|
+
if (ints)
|
|
926
|
+
divs.add(2);
|
|
927
|
+
return { datas, fns: order, params: [...ps], divisors: [...divs].sort((a, b) => a - b), ints };
|
|
482
928
|
}
|
|
483
929
|
const dataOf = (p, name) => p.datas.find(d => d.name === name);
|
|
930
|
+
// Division and remainder by a constant: the maps div$k and mod$k (core/machine.ts names them so), known by Euclid's fact
|
|
931
|
+
// at each key read (`arith k`).
|
|
932
|
+
export const divMap = (k) => `div$${k}`, modMap = (k) => `mod$${k}`;
|
|
933
|
+
const arithMap = /^(?:div|mod)\$(\d+)$/;
|
|
934
|
+
// Integers by their zigzag code: a code c is the integer p − n, p and n its value's two parts as maps of the code
|
|
935
|
+
// (int$pos, int$neg), known by one fact (`zcode`): c + c mod 2 = 2p + 2n, p = 0 where c is odd, n = 0 where it is even;
|
|
936
|
+
// a pair (p, n) has the code 2(p ∸ n) + (2(n ∸ p) ∸ 1). Neither direction needs an if, so the kernel splits only where a
|
|
937
|
+
// proof needs the sign or the parity.
|
|
938
|
+
export const zig = (z) => (z >= 0 ? 2 * z : -2 * z - 1);
|
|
939
|
+
export const unzig = (c) => (c % 2 === 0 ? c / 2 : -(c + 1) / 2);
|
|
940
|
+
const sumE = (a, b) => (a.op === 'const' && a.value === 0 ? b : b.op === 'const' && b.value === 0 ? a : { op: 'add', a, b });
|
|
941
|
+
export const posMap = 'int$pos', negMap = 'int$neg';
|
|
942
|
+
const pairOfCode = (c) => {
|
|
943
|
+
if (c.op === 'const') {
|
|
944
|
+
const z = unzig(c.value);
|
|
945
|
+
return [k(Math.max(z, 0)), k(Math.max(-z, 0))];
|
|
946
|
+
}
|
|
947
|
+
return [get(posMap, c), get(negMap, c)];
|
|
948
|
+
};
|
|
949
|
+
const codeOfPair = ([pos, neg]) => {
|
|
950
|
+
if (neg.op === 'const' && neg.value === 0)
|
|
951
|
+
return { op: 'mul', k: 2, a: pos };
|
|
952
|
+
return { op: 'add', a: { op: 'mul', k: 2, a: { op: 'sub', a: pos, b: neg } }, b: { op: 'sub', a: { op: 'mul', k: 2, a: { op: 'sub', a: neg, b: pos } }, b: k(1) } };
|
|
953
|
+
};
|
|
954
|
+
const intOps = new Set(['int', 'iadd', 'isub', 'ineg', 'imul', 'ofnat', 'imod', 'idiv']);
|
|
955
|
+
// An integer term as the two naturals p − n of its value.
|
|
956
|
+
function encI(p, x, env) {
|
|
957
|
+
if (x.op === 'int')
|
|
958
|
+
return [k(Math.max(x.value, 0)), k(Math.max(-x.value, 0))];
|
|
959
|
+
if (x.op === 'iadd' || x.op === 'isub') {
|
|
960
|
+
const [a, b] = [encI(p, x.a, env), encI(p, x.b, env)];
|
|
961
|
+
return x.op === 'iadd' ? [sumE(a[0], b[0]), sumE(a[1], b[1])] : [sumE(a[0], b[1]), sumE(a[1], b[0])];
|
|
962
|
+
}
|
|
963
|
+
if (x.op === 'ineg') {
|
|
964
|
+
const [pos, neg] = encI(p, x.a, env);
|
|
965
|
+
return [neg, pos];
|
|
966
|
+
}
|
|
967
|
+
if (x.op === 'imul') {
|
|
968
|
+
const [pos, neg] = encI(p, x.a, env), m = Math.abs(x.k), sc = (e) => (m === 1 ? e : { op: 'mul', k: m, a: e });
|
|
969
|
+
return x.k >= 0 ? [sc(pos), sc(neg)] : [sc(neg), sc(pos)];
|
|
970
|
+
}
|
|
971
|
+
if (x.op === 'ofnat')
|
|
972
|
+
return [encX(p, x.a, env), k(0)];
|
|
973
|
+
// pos + (k − 1)·neg is the value plus k·neg: the same remainder, and the quotient plus neg.
|
|
974
|
+
if (x.op === 'imod' || x.op === 'idiv') {
|
|
975
|
+
const [pos, neg] = encI(p, x.a, env), t = x.k === 1 ? pos : sumE(pos, { op: 'mul', k: x.k - 1, a: neg });
|
|
976
|
+
return x.op === 'imod' ? [get(modMap(x.k), t), k(0)] : [get(divMap(x.k), t), neg];
|
|
977
|
+
}
|
|
978
|
+
return pairOfCode(encX(p, x, env));
|
|
979
|
+
}
|
|
484
980
|
export function encX(p, x, env) {
|
|
485
981
|
switch (x.op) {
|
|
982
|
+
case 'prod': return { op: 'prod', a: encX(p, x.a, env), b: encX(p, x.b, env) };
|
|
983
|
+
case 'mod': return get(modMap(x.k), encX(p, x.a, env));
|
|
984
|
+
case 'div': return get(divMap(x.k), encX(p, x.a, env));
|
|
985
|
+
case 'int': return k(zig(x.value));
|
|
986
|
+
case 'iadd':
|
|
987
|
+
case 'isub':
|
|
988
|
+
case 'ineg':
|
|
989
|
+
case 'imul':
|
|
990
|
+
case 'ofnat':
|
|
991
|
+
case 'imod':
|
|
992
|
+
case 'idiv': return codeOfPair(encI(p, x, env));
|
|
486
993
|
case 'var': return env.get(x.name) ?? ev(x.name);
|
|
487
994
|
case 'nat': return k(x.value);
|
|
488
995
|
case 'add':
|
|
@@ -493,6 +1000,7 @@ export function encX(p, x, env) {
|
|
|
493
1000
|
case 'if': return { op: 'if', c: encY(p, x.c, env), then: encX(p, x.then, env), else: encX(p, x.else, env) };
|
|
494
1001
|
case 'bool': return { op: 'if', c: encY(p, x.c, env), then: k(1), else: k(0) };
|
|
495
1002
|
case 'apply': return get(x.param, encX(p, x.arg, env));
|
|
1003
|
+
case 'size': return get(sizeMap(x.data), encX(p, x.of, env));
|
|
496
1004
|
case 'call': return get(x.fn, keyOf(x.args.map(a => encX(p, a, env))));
|
|
497
1005
|
case 'ctor': {
|
|
498
1006
|
const d = dataOf(p, x.data);
|
|
@@ -512,9 +1020,20 @@ export function encX(p, x, env) {
|
|
|
512
1020
|
}
|
|
513
1021
|
export function encY(p, y, env) {
|
|
514
1022
|
switch (y.op) {
|
|
1023
|
+
case 'i<':
|
|
1024
|
+
case 'i<=': {
|
|
1025
|
+
const [a, b] = [encI(p, y.a, env), encI(p, y.b, env)];
|
|
1026
|
+
return { op: y.op === 'i<' ? '<' : '<=', a: sumE(a[0], b[1]), b: sumE(b[0], a[1]) };
|
|
1027
|
+
}
|
|
1028
|
+
// Two integers where one is arithmetic: equal values (the same as equal codes, without writing the sum's code).
|
|
1029
|
+
case '=':
|
|
1030
|
+
if (intOps.has(y.a.op) || intOps.has(y.b.op)) {
|
|
1031
|
+
const [a, b] = [encI(p, y.a, env), encI(p, y.b, env)];
|
|
1032
|
+
return { op: '=', a: sumE(a[0], b[1]), b: sumE(b[0], a[1]) };
|
|
1033
|
+
}
|
|
1034
|
+
return { op: '=', a: encX(p, y.a, env), b: encX(p, y.b, env) };
|
|
515
1035
|
case '<':
|
|
516
|
-
case '<=':
|
|
517
|
-
case '=': return { op: y.op, a: encX(p, y.a, env), b: encX(p, y.b, env) };
|
|
1036
|
+
case '<=': return { op: y.op, a: encX(p, y.a, env), b: encX(p, y.b, env) };
|
|
518
1037
|
case 'and':
|
|
519
1038
|
case 'or': return { op: y.op, xs: y.xs.map(z => encY(p, z, env)) };
|
|
520
1039
|
case 'not': return { op: 'not', x: encY(p, y.x, env) };
|
|
@@ -552,6 +1071,18 @@ export function factsOf(p) {
|
|
|
552
1071
|
const body = encX(p, f.body(), new Map());
|
|
553
1072
|
put(`fn ${f.name}`, { name: '', keys, holds: andC(eqC(get(f.name, keyOf(keys.map(ev))), body), ...(f.ret === 'bool' ? [leC(get(f.name, keyOf(keys.map(ev))), k(1))] : [])) });
|
|
554
1073
|
}
|
|
1074
|
+
// Euclid's division by each divisor: x = k·(x div k) + x mod k, x mod k < k.
|
|
1075
|
+
for (const k2 of p.divisors) {
|
|
1076
|
+
put(`arith ${k2}`, { name: '', keys: ['x$'], holds: andC(eqC(x, { op: 'add', a: { op: 'mul', k: k2, a: get(divMap(k2), x) }, b: get(modMap(k2), x) }), { op: '<', a: get(modMap(k2), x), b: k(k2) }) });
|
|
1077
|
+
arity.set(divMap(k2), 1);
|
|
1078
|
+
arity.set(modMap(k2), 1);
|
|
1079
|
+
}
|
|
1080
|
+
if (p.ints) {
|
|
1081
|
+
const pos = get(posMap, x), neg = get(negMap, x), par = get(modMap(2), x);
|
|
1082
|
+
put('zcode', { name: '', keys: ['x$'], holds: andC(eqC({ op: 'add', a: x, b: par }, { op: 'add', a: { op: 'mul', k: 2, a: pos }, b: { op: 'mul', k: 2, a: neg } }), { op: 'or', xs: [eqC(par, k(0)), eqC(pos, k(0))] }, { op: 'or', xs: [eqC(par, k(1)), eqC(neg, k(0))] }) });
|
|
1083
|
+
arity.set(posMap, 1);
|
|
1084
|
+
arity.set(negMap, 1);
|
|
1085
|
+
}
|
|
555
1086
|
// Every map the encoding reads: the datatypes' tag, size and fields too (each of one argument).
|
|
556
1087
|
for (const d of p.datas) {
|
|
557
1088
|
arity.set(tagMap(d.name), 1);
|
|
@@ -584,14 +1115,14 @@ export function decode(d, n) {
|
|
|
584
1115
|
if (n < z)
|
|
585
1116
|
return { c: n, f: [] };
|
|
586
1117
|
const q = Math.floor((n - z) / m), j = z + ((n - z) % m), c = d.ctors[j];
|
|
587
|
-
return { c: j, f: c.fields.map((f, i) => { const x = tup(q, c.fields.length, i); return f.sort === 'nat' ? x : decode(dataNamed(f.sort.data), x); }) };
|
|
1118
|
+
return { c: j, f: c.fields.map((f, i) => { const x = tup(q, c.fields.length, i); return f.sort === 'nat' ? x : f.sort === 'int' ? unzig(x) : decode(dataNamed(f.sort.data), x); }) };
|
|
588
1119
|
}
|
|
589
1120
|
export function encode(d, v) {
|
|
590
1121
|
const o = v, z = d.nullary, m = d.ctors.length - z;
|
|
591
1122
|
if (o.c < z)
|
|
592
1123
|
return o.c;
|
|
593
1124
|
const c = d.ctors[o.c];
|
|
594
|
-
return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : encode(dataNamed(f.sort.data), o.f[i])))) + (o.c - z));
|
|
1125
|
+
return safe(z + m * tupk(c.fields.map((f, i) => (f.sort === 'nat' ? o.f[i] : f.sort === 'int' ? zig(o.f[i]) : encode(dataNamed(f.sort.data), o.f[i])))) + (o.c - z));
|
|
595
1126
|
}
|
|
596
1127
|
// Each map of the encoding as a function of its arguments, on the codes (for core/cycles.ts's runs).
|
|
597
1128
|
export function codeValues(p) {
|
|
@@ -600,20 +1131,28 @@ export function codeValues(p) {
|
|
|
600
1131
|
out[tagMap(d.name)] = n => decode(d, n).c;
|
|
601
1132
|
out[sizeMap(d.name)] = n => sizeOf(d, decode(d, n));
|
|
602
1133
|
d.ctors.forEach((c, j) => {
|
|
603
|
-
c.fields.forEach((f, i) => { out[fieldMap(d, j, i)] = n => { const v = decode(d, n); return v.c === j ? (f.sort === 'nat' ? v.f[i] : encode(dataNamed(f.sort.data), v.f[i])) : 0; }; });
|
|
1134
|
+
c.fields.forEach((f, i) => { out[fieldMap(d, j, i)] = n => { const v = decode(d, n); return v.c === j ? (f.sort === 'nat' ? v.f[i] : f.sort === 'int' ? zig(v.f[i]) : encode(dataNamed(f.sort.data), v.f[i])) : 0; }; });
|
|
604
1135
|
if (c.fields.length)
|
|
605
|
-
out[ctorMap(d, j)] = (...xs) => encode(d, { c: j, f: c.fields.map((f, i) => (f.sort === 'nat' ? xs[i] : decode(dataNamed(f.sort.data), xs[i]))) });
|
|
1136
|
+
out[ctorMap(d, j)] = (...xs) => encode(d, { c: j, f: c.fields.map((f, i) => (f.sort === 'nat' ? xs[i] : f.sort === 'int' ? unzig(xs[i]) : decode(dataNamed(f.sort.data), xs[i]))) });
|
|
606
1137
|
});
|
|
607
1138
|
}
|
|
608
1139
|
for (const f of p.fns)
|
|
609
1140
|
out[f.name] = (...xs) => {
|
|
610
|
-
const v = evalX(f.body(), new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, q.sort instanceof Data ? decode(q.sort, xs[i]) : xs[i]])), {});
|
|
611
|
-
return f.ret instanceof Data ? encode(f.ret, v) : v;
|
|
1141
|
+
const v = evalX(f.body(), new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, q.sort instanceof Data ? decode(q.sort, xs[i]) : q.sort === 'int' ? unzig(xs[i]) : xs[i]])), {});
|
|
1142
|
+
return f.ret instanceof Data ? encode(f.ret, v) : f.ret === 'int' ? zig(v) : v;
|
|
612
1143
|
};
|
|
1144
|
+
for (const k2 of p.divisors) {
|
|
1145
|
+
out[divMap(k2)] = n => Math.floor(n / k2);
|
|
1146
|
+
out[modMap(k2)] = n => n % k2;
|
|
1147
|
+
}
|
|
1148
|
+
if (p.ints) {
|
|
1149
|
+
out[posMap] = n => Math.max(unzig(n), 0);
|
|
1150
|
+
out[negMap] = n => Math.max(-unzig(n), 0);
|
|
1151
|
+
}
|
|
613
1152
|
return out;
|
|
614
1153
|
}
|
|
615
1154
|
// That a value's fields of its datatype have smaller codes (true of the codes above: each field at most q).
|
|
616
|
-
export const orderFacts = (p) => p.datas.map(d => ({ name: `order ${d.name}`, keys: ['x$'], holds: andC(yesC, ...d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (f.sort
|
|
1155
|
+
export const orderFacts = (p) => p.datas.map(d => ({ name: `order ${d.name}`, keys: ['x$'], holds: andC(yesC, ...d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (isDataField(f.sort) && f.sort.data === d.name ? [implies(eqC(get(tagMap(d.name), ev('x$')), k(j)), { op: '<', a: get(fieldMap(d, j, i), ev('x$')), b: ev('x$') })] : [])))) }));
|
|
617
1156
|
// --- concrete models: a value of the encoding at random values (which terms are equal, for congruences and matches) ----
|
|
618
1157
|
// Values interned per datatype: a constructor of no field is its tag; the rest numbered from there. A key is a
|
|
619
1158
|
// pairing's natural, read back through its square root.
|
|
@@ -654,8 +1193,8 @@ export class Model {
|
|
|
654
1193
|
return v;
|
|
655
1194
|
}
|
|
656
1195
|
// A value of a sort as the encoding's natural, and back.
|
|
657
|
-
code(s, v) { return s instanceof Data ? this.id(s, v) : v; }
|
|
658
|
-
decode(s, n) { return s instanceof Data ? this.value(s, n) : n; }
|
|
1196
|
+
code(s, v) { return s instanceof Data ? this.id(s, v) : s === 'int' ? zig(v) : v; }
|
|
1197
|
+
decode(s, n) { return s instanceof Data ? this.value(s, n) : s === 'int' ? unzig(n) : n; }
|
|
659
1198
|
memo = new Map();
|
|
660
1199
|
read(map, key) {
|
|
661
1200
|
const at = `${map}|${key}`, had = this.memo.get(at);
|
|
@@ -690,8 +1229,17 @@ export class Model {
|
|
|
690
1229
|
const as = unkey(key, f.params.length);
|
|
691
1230
|
const env = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, this.decode(q.sort, as[i])]));
|
|
692
1231
|
const out = evalX(f.body(), env, this.params);
|
|
693
|
-
return f.ret === 'nat' || f.ret === 'bool' ? out : this.id(f.ret, out);
|
|
1232
|
+
return f.ret === 'nat' || f.ret === 'bool' ? out : f.ret === 'int' ? zig(out) : this.id(f.ret, out);
|
|
694
1233
|
}
|
|
1234
|
+
const ar = arithMap.exec(map);
|
|
1235
|
+
if (ar) {
|
|
1236
|
+
const k2 = Number(ar[1]);
|
|
1237
|
+
return map.startsWith('mod') ? key % k2 : Math.floor(key / k2);
|
|
1238
|
+
}
|
|
1239
|
+
if (map === posMap)
|
|
1240
|
+
return Math.max(unzig(key), 0);
|
|
1241
|
+
if (map === negMap)
|
|
1242
|
+
return Math.max(-unzig(key), 0);
|
|
695
1243
|
const prm = this.params[map];
|
|
696
1244
|
if (prm)
|
|
697
1245
|
return prm(key);
|
|
@@ -751,20 +1299,31 @@ export function unkey(key, n) {
|
|
|
751
1299
|
return [...unkey(a, n - 1), b];
|
|
752
1300
|
}
|
|
753
1301
|
// Random values of the law's variables: a law false at one is refuted with it.
|
|
1302
|
+
// A sample whose evaluation passes the bounds (EvalLimit, or the stack) is skipped.
|
|
754
1303
|
export function refute(l, n = 400, seed = 7) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1304
|
+
return bounded(() => {
|
|
1305
|
+
for (const s of samples(l.vars, programOf([l]).params, n, seed)) {
|
|
1306
|
+
let ok;
|
|
1307
|
+
try {
|
|
1308
|
+
ok = evalY(l.holds, s.env, s.params);
|
|
1309
|
+
}
|
|
1310
|
+
catch (e) {
|
|
1311
|
+
if (e instanceof RangeError)
|
|
1312
|
+
continue;
|
|
1313
|
+
throw new Error(`${l.name}: ${e.message}`);
|
|
1314
|
+
}
|
|
1315
|
+
if (!ok)
|
|
1316
|
+
return Object.fromEntries(l.vars.map(v => [v.name, showValue(v.sort, s.env.get(v.name))]));
|
|
762
1317
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
return undefined;
|
|
1318
|
+
return undefined;
|
|
1319
|
+
});
|
|
767
1320
|
}
|
|
1321
|
+
const bounded = (f) => { const was = evalBounds.on; evalBounds.on = true; try {
|
|
1322
|
+
return f();
|
|
1323
|
+
}
|
|
1324
|
+
finally {
|
|
1325
|
+
evalBounds.on = was;
|
|
1326
|
+
} };
|
|
768
1327
|
// The function a function f calls at its decreasing argument's field of datatype `to` (a rose tree's sum, at its forest:
|
|
769
1328
|
// the forest's sum): the call, the same for every such field, its other arguments f's own parameters or closed terms.
|
|
770
1329
|
function partner(f, to) {
|
|
@@ -783,7 +1342,7 @@ function partner(f, to) {
|
|
|
783
1342
|
const t = x;
|
|
784
1343
|
if (t.op === 'match' && t.of.op === 'var' && t.of.name === own) {
|
|
785
1344
|
const d = dataNamed(t.data);
|
|
786
|
-
t.cases.forEach((c, j) => go(c.body, new Set([...fields, ...c.binds.filter((_, i) => { const fl = d.ctors[j].fields[i]; return fl.sort
|
|
1345
|
+
t.cases.forEach((c, j) => go(c.body, new Set([...fields, ...c.binds.filter((_, i) => { const fl = d.ctors[j].fields[i]; return isDataField(fl.sort) && fl.sort.data === to.name; })])));
|
|
787
1346
|
return;
|
|
788
1347
|
}
|
|
789
1348
|
if (t.op === 'call') {
|
|
@@ -883,6 +1442,31 @@ function mutualScheme(l, v, d) {
|
|
|
883
1442
|
? [{ subst: { [at.get(f.sort.data).v]: get(fieldMap(x, j, i), ev(xv)) }, guard: eqC(get(tagMap(x.name), ev(xv)), k(j)), member: order.indexOf(f.sort.data) }] : [])));
|
|
884
1443
|
return { name: `mutual structure of ${v}`, on: { v, d }, uses: usesOf(d, v), companions: order.slice(1).map(n => { const x = dataNamed(n), a = at.get(n); return { law: a.law, on: { v: a.v, d: x }, uses: usesOf(x, a.v) }; }) };
|
|
885
1444
|
}
|
|
1445
|
+
// The width of a program's ranks' middle: its widest measure (1 when every recursion is structural).
|
|
1446
|
+
const rankWidth = (p) => Math.max(1, ...p.fns.map(f => (f.decreases === undefined ? f.measure?.comps.length ?? 1 : 1)));
|
|
1447
|
+
// Where a well-founded scheme's node wants a datatype's shape: at the values its measure reads, and at those its measure
|
|
1448
|
+
// reads at each use (a constructor's value a call passes: its size from its fields).
|
|
1449
|
+
function rankShapes(shapes, rank, uses) {
|
|
1450
|
+
const out = new Map(shapes.map(e => [tid(e), e]));
|
|
1451
|
+
walk(uses.map(u => rank.map(e => substE(e, { vars: u.subst, maps: {}, flags: {} }))), o => { if (o.op === 'get' && o.map.endsWith('.size'))
|
|
1452
|
+
out.set(tid(o.key), o.key); });
|
|
1453
|
+
return [...out.values()];
|
|
1454
|
+
}
|
|
1455
|
+
// The parameters a function's definition is decided at when they are a constructor's value: a structural one's
|
|
1456
|
+
// decreasing parameter (a datatype's), a well-founded one's parameters of a datatype its body matches on.
|
|
1457
|
+
function unfoldAt(f) {
|
|
1458
|
+
if (f.decreases !== undefined)
|
|
1459
|
+
return f.params[f.decreases].sort instanceof Data ? [f.decreases] : [];
|
|
1460
|
+
if (!f.measure)
|
|
1461
|
+
return [];
|
|
1462
|
+
const out = new Set();
|
|
1463
|
+
walk(f.body(), o => { if (o.op === 'match' && o.of.op === 'var') {
|
|
1464
|
+
const i = f.params.findIndex(q => `${f.name}.${q.name}` === o.of.name);
|
|
1465
|
+
if (i >= 0 && f.params[i].sort instanceof Data)
|
|
1466
|
+
out.add(i);
|
|
1467
|
+
} });
|
|
1468
|
+
return [...out];
|
|
1469
|
+
}
|
|
886
1470
|
// The inductions the search tries for a law (its schemes), for tests.
|
|
887
1471
|
export const schemesOf = (p, l) => schemes(p, l);
|
|
888
1472
|
function schemes(p, l) {
|
|
@@ -898,12 +1482,13 @@ function schemes(p, l) {
|
|
|
898
1482
|
return;
|
|
899
1483
|
const f = fnOf(o.fn), args = o.args;
|
|
900
1484
|
f.body();
|
|
901
|
-
|
|
1485
|
+
const m = f.decreases === undefined ? f.measure : undefined;
|
|
1486
|
+
if (f.decreases === undefined && !m)
|
|
902
1487
|
return;
|
|
903
|
-
const at = args[f.decreases];
|
|
904
|
-
if (at.op !== 'var' || !l.vars.some(v => v.name === at.name))
|
|
1488
|
+
const at = args[f.decreases ?? 0];
|
|
1489
|
+
if (!m && (at.op !== 'var' || !l.vars.some(v => v.name === at.name)))
|
|
905
1490
|
return;
|
|
906
|
-
const d = f.params[f.decreases].sort;
|
|
1491
|
+
const d = f.params[f.decreases ?? 0].sort;
|
|
907
1492
|
const formal = new Map(f.params.map((q, i) => [`${f.name}.${q.name}`, args[i].op === 'var' && l.vars.some(v => v.name === args[i].name) ? ev(args[i].name) : undefined]));
|
|
908
1493
|
const uses = [];
|
|
909
1494
|
const go = (x, env, guard) => {
|
|
@@ -928,6 +1513,14 @@ function schemes(p, l) {
|
|
|
928
1513
|
go(t.succ, e2, [...guard, { op: 'not', x: eqC(of, k(0)) }]);
|
|
929
1514
|
return;
|
|
930
1515
|
}
|
|
1516
|
+
// (a well-founded call's descent may need its if's condition: x − y below x where y ≤ x)
|
|
1517
|
+
if (m && t.op === 'if') {
|
|
1518
|
+
const c = encY(p, t.c, env);
|
|
1519
|
+
go(t.c, env, guard);
|
|
1520
|
+
go(t.then, env, [...guard, c]);
|
|
1521
|
+
go(t.else, env, [...guard, { op: 'not', x: c }]);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
931
1524
|
if (t.op === 'call' && t.fn === f.name) {
|
|
932
1525
|
const subst = {};
|
|
933
1526
|
let ok = true;
|
|
@@ -949,8 +1542,16 @@ function schemes(p, l) {
|
|
|
949
1542
|
};
|
|
950
1543
|
const env = new Map([...formal].flatMap(([key, e]) => (e ? [[key, e]] : [])));
|
|
951
1544
|
go(f.body(), env, []);
|
|
952
|
-
if (uses.length)
|
|
1545
|
+
if (!uses.length)
|
|
1546
|
+
return;
|
|
1547
|
+
if (!m) {
|
|
953
1548
|
add({ name: `recursion of ${f.name} on ${at.name}`, on: { v: at.name, d }, uses });
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
// Well-founded: ranked by the measure at the law's arguments; the law's variables it reads named.
|
|
1552
|
+
const read = f.params.flatMap((q, i) => (m.comps.some(c => c.coef[i]) && args[i].op === 'var' && l.vars.some(v => v.name === args[i].name) ? [{ i, v: args[i].name }] : []));
|
|
1553
|
+
add({ name: `recursion of ${f.name} on ${read.map(r => r.v).join(', ') || 'its arguments'}`, uses, rank: m.comps.map(c => encX(p, measureX(f, c, args), new Map())),
|
|
1554
|
+
shapes: read.filter(r => f.params[r.i].sort instanceof Data).map(r => ev(r.v)) });
|
|
954
1555
|
});
|
|
955
1556
|
// Structural induction on each variable of a datatype, the others fixed; mutual where its fields reach another of its
|
|
956
1557
|
// group (the law and its companions), plain where they reach none or no companion is found.
|
|
@@ -962,7 +1563,7 @@ function schemes(p, l) {
|
|
|
962
1563
|
out.push(mut);
|
|
963
1564
|
continue;
|
|
964
1565
|
}
|
|
965
|
-
add({ name: `structure of ${v.name}`, on: { v: v.name, d }, uses: d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (f.sort
|
|
1566
|
+
add({ name: `structure of ${v.name}`, on: { v: v.name, d }, uses: d.ctors.flatMap((c, j) => c.fields.flatMap((f, i) => (isDataField(f.sort) && f.sort.data === d.name ? [{ subst: { [v.name]: get(fieldMap(d, j, i), x) }, guard: eqC(get(tagMap(d.name), x), k(j)) }] : []))) });
|
|
966
1567
|
}
|
|
967
1568
|
return out;
|
|
968
1569
|
}
|
|
@@ -1024,10 +1625,27 @@ function instancesOf(p, fs, w) {
|
|
|
1024
1625
|
inst(`fn ${f.name}`, as);
|
|
1025
1626
|
const body = reads(holdsAt(`fn ${f.name}`, as));
|
|
1026
1627
|
ctors(note(body));
|
|
1027
|
-
|
|
1628
|
+
for (const b of body)
|
|
1629
|
+
next.push(b);
|
|
1028
1630
|
}
|
|
1029
1631
|
frontier = next;
|
|
1030
1632
|
}
|
|
1633
|
+
// Euclid's fact at every key a remainder or quotient is read at (a read the facts bring is read too).
|
|
1634
|
+
const ariths = () => {
|
|
1635
|
+
for (let pass = 0; pass < 2; pass++)
|
|
1636
|
+
for (const r of [...all]) {
|
|
1637
|
+
if (r.map === posMap || r.map === negMap) {
|
|
1638
|
+
inst('zcode', [r.key]);
|
|
1639
|
+
note(reads(holdsAt('zcode', [r.key])));
|
|
1640
|
+
}
|
|
1641
|
+
const m = arithMap.exec(r.map);
|
|
1642
|
+
if (m) {
|
|
1643
|
+
inst(`arith ${m[1]}`, [r.key]);
|
|
1644
|
+
note(reads(holdsAt(`arith ${m[1]}`, [r.key])));
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
ariths();
|
|
1031
1649
|
lap(`unfolded, ${insts.length} instances, ${all.length} reads`);
|
|
1032
1650
|
const closure = (model) => {
|
|
1033
1651
|
const parent = new Map(), terms = new Map(), adj = new Map();
|
|
@@ -1133,32 +1751,31 @@ function instancesOf(p, fs, w) {
|
|
|
1133
1751
|
}
|
|
1134
1752
|
for (const r of [...all]) {
|
|
1135
1753
|
const f = fnOfMap(r.map);
|
|
1136
|
-
if (!f
|
|
1754
|
+
if (!f)
|
|
1137
1755
|
continue;
|
|
1138
1756
|
const as = argsOf(r.key, f.params.length);
|
|
1139
1757
|
if (!as)
|
|
1140
1758
|
continue;
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
ctors(note(reads(holdsAt(`fn ${f.name}`, as2))));
|
|
1759
|
+
for (const di of unfoldAt(f)) {
|
|
1760
|
+
const own = as[di], d = f.params[di].sort, root = cl.find(tid(own));
|
|
1761
|
+
const built = (t) => (t.op === 'get' && d.ctors.some((c, j) => c.fields.length && t.map === ctorMap(d, j))) || (t.op === 'const' && t.value < d.nullary);
|
|
1762
|
+
const cands = built(own) ? [own] : [...cl.terms.values()].filter(t => built(t) && cl.find(tid(t)) === root);
|
|
1763
|
+
for (const t of cands) {
|
|
1764
|
+
const as2 = as.map((a, i) => (i === di ? t : a)), rid = JSON.stringify({ map: f.name, key: keyOf(as2) }), key2 = JSON.stringify([fs.at.get(`fn ${f.name}`), as2]);
|
|
1765
|
+
const anchor = { read: tid(get(f.name, r.key)), arg: own, value: t }, had = anchoredAt.get(key2) ?? [];
|
|
1766
|
+
if (!had.some(x => x.read === anchor.read))
|
|
1767
|
+
anchoredAt.set(key2, [...had, anchor]);
|
|
1768
|
+
if (unfolded.has(rid) || extra >= 12 || done.has(sigOf(f.name, as2)))
|
|
1769
|
+
continue;
|
|
1770
|
+
unfolded.add(rid);
|
|
1771
|
+
extra++;
|
|
1772
|
+
done.add(sigOf(f.name, as2));
|
|
1773
|
+
if (dataTrace)
|
|
1774
|
+
console.error(`[data] unfold ${f.name} at ${JSON.stringify(as2).slice(0, 160)} for ${JSON.stringify(r.key).slice(0, 80)}`);
|
|
1775
|
+
inst(`fn ${f.name}`, as2);
|
|
1776
|
+
note([{ map: f.name, key: keyOf(as2) }]);
|
|
1777
|
+
ctors(note(reads(holdsAt(`fn ${f.name}`, as2))));
|
|
1778
|
+
}
|
|
1162
1779
|
}
|
|
1163
1780
|
}
|
|
1164
1781
|
}
|
|
@@ -1177,7 +1794,7 @@ function instancesOf(p, fs, w) {
|
|
|
1177
1794
|
if (c.fields.length && t.map === ctorMap(d, j))
|
|
1178
1795
|
return d;
|
|
1179
1796
|
for (const [i, fl] of c.fields.entries())
|
|
1180
|
-
if (fl.sort
|
|
1797
|
+
if (isDataField(fl.sort) && t.map === fieldMap(d, j, i))
|
|
1181
1798
|
return dataNamed(fl.sort.data);
|
|
1182
1799
|
}
|
|
1183
1800
|
}
|
|
@@ -1189,6 +1806,12 @@ function instancesOf(p, fs, w) {
|
|
|
1189
1806
|
} };
|
|
1190
1807
|
for (const t of w.shapesAt)
|
|
1191
1808
|
shape(t, typeOfVar(p, t));
|
|
1809
|
+
// A size the goal reads (a measure's, in a well-founded call's obligation): the shape of its value, which gives the
|
|
1810
|
+
// size from the fields.
|
|
1811
|
+
for (const r of reads([w.roots, w.others]))
|
|
1812
|
+
for (const d of p.datas)
|
|
1813
|
+
if (r.map === sizeMap(d.name))
|
|
1814
|
+
shape(r.key, d);
|
|
1192
1815
|
if (w.depth >= 2)
|
|
1193
1816
|
for (const r of [...all])
|
|
1194
1817
|
for (const d of p.datas)
|
|
@@ -1345,6 +1968,9 @@ function instancesOf(p, fs, w) {
|
|
|
1345
1968
|
for (const d of used.values())
|
|
1346
1969
|
if (!w.every || d.n >= w.models.length)
|
|
1347
1970
|
inst(`cong ${d.map}`, d.at);
|
|
1971
|
+
for (const q of [...insts])
|
|
1972
|
+
note(reads(holdsAt(fs.facts[q.fact].name, q.at)));
|
|
1973
|
+
ariths();
|
|
1348
1974
|
lap(`congruences, ${insts.length} instances, ${w.models.length} models`);
|
|
1349
1975
|
return insts;
|
|
1350
1976
|
}
|
|
@@ -1482,7 +2108,7 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1482
2108
|
}
|
|
1483
2109
|
// The scheme's nodes: the law's, then each companion's (a mutual induction), each its own models. A use of member i
|
|
1484
2110
|
// reads node -1 - i until the nodes are placed.
|
|
1485
|
-
const members = [{ law: l, on: scheme?.on, uses: scheme?.uses ?? [], pool }, ...(scheme?.companions ?? []).map(c => ({ ...c, pool: modelsFor(p, c.law, pool.length, 5) }))];
|
|
2111
|
+
const members = [{ law: l, on: scheme?.on, uses: scheme?.uses ?? [], pool, rank: scheme?.rank, shapes: scheme?.shapes }, ...(scheme?.companions ?? []).map(c => ({ ...c, pool: modelsFor(p, c.law, pool.length, 5) }))];
|
|
1486
2112
|
const concls = members.map(mb => encY(p, mb.law.holds, new Map()));
|
|
1487
2113
|
const base = specs.length, title = `${l.name}${scheme ? ` (${scheme.name})` : ''}`;
|
|
1488
2114
|
varSorts = new Map(members.flatMap(mb => mb.law.vars.flatMap(v => (v.sort instanceof Data ? [[v.name, v.sort]] : []))));
|
|
@@ -1492,7 +2118,7 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1492
2118
|
for (const [mi, mb] of members.entries()) {
|
|
1493
2119
|
const models = mb.pool.slice(0, 24), concl = concls[mi], name = mi === 0 ? title : `${title}, at ${mb.on.d.name}`;
|
|
1494
2120
|
const ih = mb.uses.map(u => ({ node: -1 - (u.member ?? mi), subst: u.subst, guard: u.guard }));
|
|
1495
|
-
const shapesAt = mb.on && mb.on.d !== 'nat' ? [ev(mb.on.v)] : [];
|
|
2121
|
+
const shapesAt = mb.rank ? rankShapes(mb.shapes ?? [], mb.rank, mb.uses) : mb.on && mb.on.d !== 'nat' ? [ev(mb.on.v)] : [];
|
|
1496
2122
|
const ihConcls = ih.map(useConcl);
|
|
1497
2123
|
// Uses of the lemmas, where a lemma's largest read matches a read of this goal (unfolded), its key up to the
|
|
1498
2124
|
// equalities the unfolded definitions give (any branch); twice over, the uses' own conclusions read the second time.
|
|
@@ -1538,8 +2164,10 @@ function attempt(p, fs, l, scheme, depth, lemmas, level, pool) {
|
|
|
1538
2164
|
const offsets = plans.map((_, i) => base + plans.slice(0, i).reduce((n, q) => n + q.length, 0));
|
|
1539
2165
|
for (const [mi, plan] of plans.entries()) {
|
|
1540
2166
|
const mb = members[mi], size = !mb.on ? k(0) : mb.on.d === 'nat' ? ev(mb.on.v) : get(sizeMap(mb.on.d.name), ev(mb.on.v));
|
|
2167
|
+
// (level, the measure or the size, 0 or 1), the measure's place padded to the program's widest measure
|
|
2168
|
+
const comps = mb.rank ?? [size], mid = [...comps, ...Array.from({ length: rankWidth(p) - comps.length }, () => k(0))];
|
|
1541
2169
|
for (const { sub, cases, ...n } of plan)
|
|
1542
|
-
specs.push({ ...n, rank: [k(level),
|
|
2170
|
+
specs.push({ ...n, rank: [k(level), ...mid, k(sub)], uses: cases ? cases.map((g, i) => ({ node: offsets[mi] + 1 + i, subst: {}, guard: g })) : n.uses.map(u => (u.node < 0 ? { ...u, node: offsets[-1 - u.node] } : u)) });
|
|
1543
2171
|
}
|
|
1544
2172
|
const name = title;
|
|
1545
2173
|
// Each attempt within its own time (a goal past it is left unknown, and the attempt fails).
|
|
@@ -1613,6 +2241,12 @@ function reached(p, fs, insts, from, ms) {
|
|
|
1613
2241
|
return [get(name.slice(3), keyOf(q.at))];
|
|
1614
2242
|
if (name.startsWith('ctor '))
|
|
1615
2243
|
return [get(name.slice(5), keyOf(q.at))];
|
|
2244
|
+
if (name.startsWith('arith ')) {
|
|
2245
|
+
const k2 = Number(name.slice(6));
|
|
2246
|
+
return [get(divMap(k2), q.at[0]), get(modMap(k2), q.at[0])];
|
|
2247
|
+
}
|
|
2248
|
+
if (name === 'zcode')
|
|
2249
|
+
return [get(posMap, q.at[0]), get(negMap, q.at[0])];
|
|
1616
2250
|
if (name.startsWith('shape ') || name.startsWith('eta ')) {
|
|
1617
2251
|
const d = name.split(' ')[1];
|
|
1618
2252
|
return [get(tagMap(d), q.at[0]), get(sizeMap(d), q.at[0]), ...p.datas.filter(x => x.name === d).flatMap(x => x.ctors.flatMap((c, j) => c.fields.map((_, i) => get(fieldMap(x, j, i), q.at[0]))))];
|
|
@@ -1817,10 +2451,14 @@ function modelsFor(p, l, n, seed) {
|
|
|
1817
2451
|
});
|
|
1818
2452
|
}
|
|
1819
2453
|
// Prove a law with the lemmas given (each proven, with its nodes), trying each scheme at each depth.
|
|
1820
|
-
|
|
2454
|
+
// `without`: 'plain', the law with no induction where no scheme applies; 'cases', no induction, then by the cases of
|
|
2455
|
+
// each variable (a natural's zero and successor, a datatype's constructors), none of them using the law again.
|
|
2456
|
+
function proveWith(p, fs, l, lemmas, level, opts, until, per, depths, without) {
|
|
1821
2457
|
const models = modelsFor(p, l, 240, opts.seed ?? 3);
|
|
1822
2458
|
let tried = 0, last;
|
|
1823
|
-
const
|
|
2459
|
+
const cases = l.vars.flatMap((v) => (v.sort === 'nat' || v.sort instanceof Data ? [{ name: `cases of ${v.name}`, on: { v: v.name, d: v.sort }, uses: [] }] : []));
|
|
2460
|
+
const found = opts.induction === false || without === 'cases' ? [] : schemes(p, l);
|
|
2461
|
+
const ss = opts.induction === false ? [undefined] : without === 'cases' ? [undefined, ...cases] : without === 'plain' && !found.length ? [undefined] : found;
|
|
1824
2462
|
for (const depth of depths.filter(d => d <= (opts.depth ?? 3)))
|
|
1825
2463
|
for (const s of ss) {
|
|
1826
2464
|
if (clock() > until)
|
|
@@ -1829,7 +2467,7 @@ function proveWith(p, fs, l, lemmas, level, opts, until, per, depths) {
|
|
|
1829
2467
|
const { verdict, specs, main } = (attemptMs.ms = Math.min(per * (opts.patience ?? 1), until - clock()), attempt(p, fs, l, s, depth, lemmas, level, models));
|
|
1830
2468
|
last = verdict;
|
|
1831
2469
|
if (verdict.proven && verdict.checked)
|
|
1832
|
-
return { out: { law: l, spec: specs[main], nodes: specs }, verdict, scheme: s?.name, tried };
|
|
2470
|
+
return { out: { law: l, spec: specs[main], nodes: specs }, verdict, scheme: s?.name ?? (without ? 'no induction' : undefined), tried };
|
|
1833
2471
|
}
|
|
1834
2472
|
return { tried, last };
|
|
1835
2473
|
}
|
|
@@ -2005,8 +2643,10 @@ export function conjectures(p, l, max = 24, maxSize = 6) {
|
|
|
2005
2643
|
const kept = [];
|
|
2006
2644
|
// The definitions, unfolded at a constructor: f(.., C(x, y), ..) → its case's body, where that is a plain term.
|
|
2007
2645
|
for (const f of fnsUsed) {
|
|
2008
|
-
|
|
2009
|
-
|
|
2646
|
+
// (a structural function's decreasing parameter, a well-founded one's the body matches on first)
|
|
2647
|
+
const body = f.body(), top = body.op === 'match' && body.of.op === 'var' ? f.params.findIndex(q => `${f.name}.${q.name}` === body.of.name) : -1;
|
|
2648
|
+
const di = f.decreases ?? (f.measure ? top : -1);
|
|
2649
|
+
if (body.op !== 'match' || di < 0 || di !== top)
|
|
2010
2650
|
continue;
|
|
2011
2651
|
const d = f.params[di].sort;
|
|
2012
2652
|
body.cases.forEach((c, j) => {
|
|
@@ -2109,9 +2749,21 @@ export const showX = (x) => {
|
|
|
2109
2749
|
case 'min':
|
|
2110
2750
|
case 'max': return `${x.op}(${showX(x.a)}, ${showX(x.b)})`;
|
|
2111
2751
|
case 'mul': return `${x.k}·${showX(x.a)}`;
|
|
2752
|
+
case 'prod': return `${showX(x.a)}·${showX(x.b)}`;
|
|
2753
|
+
case 'mod':
|
|
2754
|
+
case 'imod': return `${showX(x.a)} mod ${x.k}`;
|
|
2755
|
+
case 'div':
|
|
2756
|
+
case 'idiv': return `${showX(x.a)} div ${x.k}`;
|
|
2757
|
+
case 'int': return String(x.value);
|
|
2758
|
+
case 'iadd': return `${showX(x.a)} + ${showX(x.b)}`;
|
|
2759
|
+
case 'isub': return `${showX(x.a)} − ${showX(x.b)}`;
|
|
2760
|
+
case 'ineg': return `−${showX(x.a)}`;
|
|
2761
|
+
case 'imul': return `${x.k}·${showX(x.a)}`;
|
|
2762
|
+
case 'ofnat': return showX(x.a);
|
|
2112
2763
|
case 'if': return `if ${showY(x.c)} then ${showX(x.then)} else ${showX(x.else)}`;
|
|
2113
2764
|
case 'bool': return showY(x.c);
|
|
2114
2765
|
case 'apply': return `${x.param}(${showX(x.arg)})`;
|
|
2766
|
+
case 'size': return `size(${showX(x.of)})`;
|
|
2115
2767
|
case 'call': return `${x.fn}(${x.args.map(showX).join(', ')})`;
|
|
2116
2768
|
case 'ctor': {
|
|
2117
2769
|
const d = [...fns.values()].length >= 0 ? x.data : x.data;
|
|
@@ -2126,6 +2778,8 @@ export const showY = (y) => {
|
|
|
2126
2778
|
case '<':
|
|
2127
2779
|
case '<=':
|
|
2128
2780
|
case '=': return `${showX(y.a)} ${y.op} ${showX(y.b)}`;
|
|
2781
|
+
case 'i<':
|
|
2782
|
+
case 'i<=': return `${showX(y.a)} ${y.op.slice(1)} ${showX(y.b)}`;
|
|
2129
2783
|
case 'and': return y.xs.map(showY).join(' and ');
|
|
2130
2784
|
case 'or': return y.xs.map(showY).join(' or ');
|
|
2131
2785
|
case 'not': return `not (${showY(y.x)})`;
|
|
@@ -2136,21 +2790,49 @@ export const showY = (y) => {
|
|
|
2136
2790
|
// Prove a law over datatypes: refuted by a random value where one breaks it; else proven by a cycle the search finds,
|
|
2137
2791
|
// with lemmas found from the values when the law alone isn't (`lemmas: false` turns that off; an array gives lemmas to
|
|
2138
2792
|
// prove and use instead, which the corpus never does).
|
|
2139
|
-
export function proveData(l, opts = {}) { return counted(dataRate, () => proveDataCounted(l, opts)); }
|
|
2793
|
+
export function proveData(l, opts = {}) { return counted(dataRate, () => bounded(() => proveDataCounted(l, opts))); }
|
|
2140
2794
|
function proveDataCounted(l, opts) {
|
|
2141
|
-
const t0 = performance.now(),
|
|
2142
|
-
|
|
2795
|
+
const t0 = performance.now(), until = clock() + (opts.ms ?? 120_000);
|
|
2796
|
+
// Where the law calls a non-recursive function, or reaches a recursive one that agrees with a built-in (core/transfer.ts):
|
|
2797
|
+
// the law alone at one unfolding first, then the law said through them, then the rest of the search on the law.
|
|
2798
|
+
const plan = opts.transfer === false || opts.induction === false || Array.isArray(opts.lemmas) ? undefined : transferPlan(l);
|
|
2799
|
+
if (!plan)
|
|
2800
|
+
return searchIn(l, opts, until).verdict;
|
|
2801
|
+
const quick = searchIn(l, opts, until, undefined, 'first').verdict;
|
|
2802
|
+
if (quick.proven || quick.counterexample)
|
|
2803
|
+
return quick;
|
|
2804
|
+
// (a law said through its definitions past what the encoding's terms take is left as stated)
|
|
2805
|
+
let through;
|
|
2806
|
+
try {
|
|
2807
|
+
through = proveThrough(l, plan, opts, until);
|
|
2808
|
+
}
|
|
2809
|
+
catch (e) {
|
|
2810
|
+
if (!(e instanceof RangeError))
|
|
2811
|
+
throw e;
|
|
2812
|
+
}
|
|
2813
|
+
if (through?.proven)
|
|
2814
|
+
return { ...through, ms: Math.round(performance.now() - t0) };
|
|
2815
|
+
const rest = searchIn(l, opts, until, undefined, 'rest').verdict;
|
|
2816
|
+
return { ...rest, tried: rest.tried + quick.tried + (through?.tried ?? 0), ms: Math.round(performance.now() - t0), ...(rest.goal || rest.proven ? {} : { goal: quick.goal, stuck: quick.stuck }) };
|
|
2817
|
+
}
|
|
2818
|
+
export function searchIn(l, opts, until, ctx, phase = 'all') {
|
|
2819
|
+
const t0 = performance.now(), w0 = work.n;
|
|
2820
|
+
const p = ctx?.p ?? programOf([l, ...(Array.isArray(opts.lemmas) ? opts.lemmas : [])]), fs = ctx?.fs ?? factsOf(p);
|
|
2821
|
+
const base = ctx?.base ?? [], lv = ctx?.level ?? 0;
|
|
2822
|
+
let out;
|
|
2143
2823
|
const done = (v) => {
|
|
2144
2824
|
if (dataTrace)
|
|
2145
2825
|
console.error(`[data] ${l.name}: ${work.n - w0} units of work in ${Math.round(performance.now() - t0)} ms`);
|
|
2146
|
-
return { law: l.name, ...v, ms: Math.round(performance.now() - t0) };
|
|
2826
|
+
return { verdict: { law: l.name, ...v, ms: Math.round(performance.now() - t0) }, out };
|
|
2147
2827
|
};
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2828
|
+
if (phase !== 'rest') {
|
|
2829
|
+
const cex = refute(l, opts.samples ?? 400);
|
|
2830
|
+
if (cex)
|
|
2831
|
+
return done({ proven: false, checked: false, counterexample: cex, lemmas: [], tried: 0 });
|
|
2832
|
+
}
|
|
2151
2833
|
// The law alone at one unfolding; then lemmas at one unfolding each (the law tried again after each its goal
|
|
2152
|
-
// matches); then the law at two; then lemmas at two; last the law at three
|
|
2153
|
-
// proof uses it.
|
|
2834
|
+
// matches); then the law at two; then lemmas at two; last the law at three, and without induction (by cases). A lemma
|
|
2835
|
+
// is named in the verdict when the proof uses it.
|
|
2154
2836
|
let tried = 0;
|
|
2155
2837
|
const used = (r, lemmas) => {
|
|
2156
2838
|
const nodes = r.nodes, seen = new Set(), todo = [nodes.indexOf(r.spec)];
|
|
@@ -2164,38 +2846,49 @@ function proveDataCounted(l, opts) {
|
|
|
2164
2846
|
}
|
|
2165
2847
|
return lemmas.filter(q => seen.has(nodes.indexOf(nodes.find(n => n.name === q.spec.name)))).map(q => q.law.name);
|
|
2166
2848
|
};
|
|
2167
|
-
const ok = (r, lemmas, prog = p) => (tried += r.tried, r.out ? done({ proven: true, checked: true, proof: r.verdict, lemmas: used(r.out, lemmas), scheme: r.scheme, tried, program: prog, node: r.out.nodes.indexOf(r.out.spec) }) : undefined);
|
|
2168
|
-
|
|
2169
|
-
|
|
2849
|
+
const ok = (r, lemmas, prog = p) => (tried += r.tried, r.out ? (out = r.out, done({ proven: true, checked: true, proof: r.verdict, lemmas: used(r.out, lemmas), scheme: r.scheme, tried, program: prog, node: r.out.nodes.indexOf(r.out.spec) })) : undefined);
|
|
2850
|
+
// A law that calls no function (arithmetic alone: an integer's, a remainder's) has nothing to induct on: its cases.
|
|
2851
|
+
if (phase !== 'rest' && opts.induction !== false && !JSON.stringify(l.holds).includes('"op":"call"')) {
|
|
2852
|
+
const c = ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base);
|
|
2853
|
+
if (c)
|
|
2854
|
+
return c;
|
|
2855
|
+
}
|
|
2856
|
+
const first = phase === 'rest' ? undefined : proveWith(p, fs, l, base, lv + 1, opts, until, 6000, [1], 'plain');
|
|
2857
|
+
const one = first && ok(first, base);
|
|
2170
2858
|
if (one)
|
|
2171
2859
|
return one;
|
|
2860
|
+
if (phase === 'first')
|
|
2861
|
+
return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first.last) });
|
|
2172
2862
|
const without = opts.lemmas === false || opts.induction === false;
|
|
2173
2863
|
if (without)
|
|
2174
|
-
return ok(proveWith(p, fs, l,
|
|
2864
|
+
return ok(proveWith(p, fs, l, base, lv + 1, opts, until, 20000, [2, 3]), base) ?? (opts.induction === false ? undefined : ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base)) ?? done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first?.last) });
|
|
2175
2865
|
const cands = Array.isArray(opts.lemmas) ? opts.lemmas : conjectures(p, l);
|
|
2176
|
-
const p2 = programOf([l, ...cands]), fs2 = factsOf(p2);
|
|
2866
|
+
const p2 = ctx ? p : programOf([l, ...cands]), fs2 = ctx ? fs : factsOf(p2);
|
|
2177
2867
|
// Each scheme's goals: the law's, and each companion's (a lemma may be what only a companion's goal reads).
|
|
2178
|
-
const goalOf = (law, uses, on, concls) => {
|
|
2868
|
+
const goalOf = (law, uses, on, concls, shapes) => {
|
|
2179
2869
|
const concl = encY(p2, law.holds, new Map()), ihc = uses.map(u => substC(concls[u.member ?? 0] ?? concl, { vars: u.subst, maps: {}, flags: {} }));
|
|
2180
|
-
return { from: [concl, ihc], insts: instancesOf(p2, fs2, { roots: [concl], others: ihc, depth: 2, shapesAt: on && on.d !== 'nat' ? [ev(on.v)] : [], etaAt: [], models: [] }) };
|
|
2870
|
+
return { from: [concl, ihc], insts: instancesOf(p2, fs2, { roots: [concl], others: ihc, depth: 2, shapesAt: shapes ?? (on && on.d !== 'nat' ? [ev(on.v)] : []), etaAt: [], models: [] }) };
|
|
2181
2871
|
};
|
|
2182
2872
|
const goals = schemes(p2, l).flatMap(s => {
|
|
2183
|
-
const all = [{ law: l, uses: s.uses, on: s.on }, ...(s.companions ?? [])], concls = all.map(m => encY(p2, m.law.holds, new Map()));
|
|
2184
|
-
return all.map((m, i) => goalOf(m.law, m.uses.map(u => ({ ...u, member: u.member ?? i })), m.on, concls));
|
|
2873
|
+
const all = [{ law: l, uses: s.uses, on: s.on, shapes: s.shapes }, ...(s.companions ?? [])], concls = all.map(m => encY(p2, m.law.holds, new Map()));
|
|
2874
|
+
return all.map((m, i) => goalOf(m.law, m.uses.map(u => ({ ...u, member: u.member ?? i })), m.on, concls, 'shapes' in m ? m.shapes : undefined));
|
|
2185
2875
|
});
|
|
2186
2876
|
const matchesGoal = (c) => goals.some(g => lemmaUsesFor(p2, fs2, [{ law: c, node: 0, concl: encY(p2, c.holds, new Map()) }], g.from, g.insts).length > 0);
|
|
2187
2877
|
// Those the goal matches first, of those the ones of the law's shape (an implication for an implication) first.
|
|
2188
2878
|
const implication = (y) => y.op === 'or' && y.xs.length === 2 && y.xs[0].op === 'not';
|
|
2189
2879
|
const score = (c, hit) => (hit ? 2 : 0) + (implication(c.holds) === implication(l.holds) ? 1 : 0);
|
|
2880
|
+
if (dataTrace)
|
|
2881
|
+
console.error(`[data] ${l.name}: ${cands.length} conjectures at ${Math.round(clock())} of ${Math.round(until)}`);
|
|
2190
2882
|
const ranked = cands.map(c => ({ c, hit: matchesGoal(c) })).sort((a, b) => score(b.c, b.hit) - score(a.c, a.hit));
|
|
2191
|
-
|
|
2883
|
+
// The lemmas proven: those given (`base`) and those found, each found one a level above the last.
|
|
2884
|
+
const proven = [...base], failed = new Set(), level = () => lv + 1 + proven.length - base.length;
|
|
2192
2885
|
const lemmasAt = (depth) => {
|
|
2193
2886
|
for (const { c, hit } of ranked) {
|
|
2194
2887
|
if (clock() > until)
|
|
2195
2888
|
return undefined;
|
|
2196
2889
|
if (proven.some(q => q.law === c) || (depth === 2 && !failed.has(c)) || refute(c, 200))
|
|
2197
2890
|
continue;
|
|
2198
|
-
const r = proveWith(p2, fs2, c, proven,
|
|
2891
|
+
const r = proveWith(p2, fs2, c, proven, level(), opts, Math.min(until, clock() + (depth === 1 ? 8000 : 12000) * (opts.patience ?? 1)), 5000, [depth]);
|
|
2199
2892
|
tried += r.tried;
|
|
2200
2893
|
if (!r.out) {
|
|
2201
2894
|
failed.add(c);
|
|
@@ -2205,7 +2898,7 @@ function proveDataCounted(l, opts) {
|
|
|
2205
2898
|
proven.push(r.out);
|
|
2206
2899
|
if (!hit && implication(c.holds) !== implication(l.holds))
|
|
2207
2900
|
continue;
|
|
2208
|
-
const again = ok(proveWith(p2, fs2, l, proven,
|
|
2901
|
+
const again = ok(proveWith(p2, fs2, l, proven, level(), opts, until, 6000, depth === 1 ? [1] : [1, 2]), proven, p2);
|
|
2209
2902
|
if (again)
|
|
2210
2903
|
return again;
|
|
2211
2904
|
}
|
|
@@ -2213,19 +2906,23 @@ function proveDataCounted(l, opts) {
|
|
|
2213
2906
|
};
|
|
2214
2907
|
const steps = [
|
|
2215
2908
|
() => lemmasAt(1),
|
|
2216
|
-
() => ok(proveWith(p, fs, l,
|
|
2217
|
-
() => (proven.length ? ok(proveWith(p2, fs2, l, proven,
|
|
2909
|
+
() => ok(proveWith(p, fs, l, base, lv + 1, opts, until, 30000, [2]), base),
|
|
2910
|
+
() => (proven.length > base.length ? ok(proveWith(p2, fs2, l, proven, level(), opts, until, 30000, [2]), proven, p2) : undefined),
|
|
2218
2911
|
() => lemmasAt(2),
|
|
2219
|
-
() => ok(proveWith(p2, fs2, l, proven,
|
|
2912
|
+
() => ok(proveWith(p2, fs2, l, proven, level(), opts, until, 12000, [3]), proven, p2),
|
|
2913
|
+
// No induction: the law by the cases of a variable (or none), its definitions unfolded.
|
|
2914
|
+
() => ok(proveWith(p, fs, l, base, lv + 1, opts, until, 3000, [1, 2], 'cases'), base),
|
|
2220
2915
|
];
|
|
2221
|
-
for (const step of steps) {
|
|
2916
|
+
for (const [i, step] of steps.entries()) {
|
|
2222
2917
|
if (clock() > until)
|
|
2223
2918
|
break;
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2919
|
+
if (dataTrace)
|
|
2920
|
+
console.error(`[data] ${l.name}: step ${i} at ${Math.round(clock())} of ${Math.round(until)}`);
|
|
2921
|
+
const r = step();
|
|
2922
|
+
if (r)
|
|
2923
|
+
return r;
|
|
2227
2924
|
}
|
|
2228
|
-
return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first
|
|
2925
|
+
return done({ proven: false, checked: false, lemmas: [], tried, ...stuckOf(first?.last) });
|
|
2229
2926
|
}
|
|
2230
2927
|
const stuckOf = (v) => (v && !v.proven ? { goal: v.goal, stuck: v.stuck } : v && !v.checked ? { goal: 'check', stuck: v.why } : {});
|
|
2231
2928
|
// The law with no cycle: its definitions unfolded, the kernel's prover alone (what the library proves without the search).
|