tla-ts 0.0.2 → 0.0.3-dev.a50fc89
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 +56 -10
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +1 -1
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -2
- package/dist/run-cli.js +20 -2
- package/dist/transfer.cjs +12 -12
- package/dist/transfer.js +12 -12
- package/package.json +3 -1
- package/src/ast.ts +43 -3
- package/src/check.ts +43 -10
- package/src/cli-options.ts +58 -0
- package/src/errors.ts +37 -1
- package/src/evaluate.ts +3 -1
- package/src/lexer.ts +6 -1
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parser.ts +285 -52
- package/src/run-cli.ts +295 -27
- package/src/standard-levels.ts +3 -3
- package/src/transfer.ts +22 -1
- package/src/values.ts +53 -20
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
// A port of tlapm's module pipeline: flattening EXTENDS (Module.Flatten), normalization
|
|
2
|
+
// (Module.Elab.normalize) and obligation generation (Module.Gen).
|
|
3
|
+
|
|
4
|
+
import { TlaProofError, TlapmBugError } from "./errors";
|
|
5
|
+
import { lambdify } from "./obligation-action";
|
|
6
|
+
import type { Anonymizer } from "./obligation-anon";
|
|
7
|
+
import {
|
|
8
|
+
adjoinAll,
|
|
9
|
+
at,
|
|
10
|
+
bumpBy,
|
|
11
|
+
consAll,
|
|
12
|
+
consSubstitution,
|
|
13
|
+
exprifySequent,
|
|
14
|
+
findNamed,
|
|
15
|
+
getAtPrefix,
|
|
16
|
+
getLocusText,
|
|
17
|
+
getLocusPrefix,
|
|
18
|
+
getScopeHyps,
|
|
19
|
+
mapAccumulate,
|
|
20
|
+
getFresh,
|
|
21
|
+
isEqualHyp,
|
|
22
|
+
shiftExpr,
|
|
23
|
+
substituteDefn,
|
|
24
|
+
substituteExpr,
|
|
25
|
+
substituteExprs,
|
|
26
|
+
substituteHyp,
|
|
27
|
+
substituteSequent,
|
|
28
|
+
shift,
|
|
29
|
+
type Builtin,
|
|
30
|
+
type Defn,
|
|
31
|
+
type Expr,
|
|
32
|
+
type Hyp,
|
|
33
|
+
type InstanceDefinition,
|
|
34
|
+
type Locus,
|
|
35
|
+
type ObligationWarning,
|
|
36
|
+
type Scope,
|
|
37
|
+
type Sequent,
|
|
38
|
+
type Substitution,
|
|
39
|
+
type Visibility,
|
|
40
|
+
type WhereDefined,
|
|
41
|
+
} from "./obligation-expression";
|
|
42
|
+
import {
|
|
43
|
+
EMPTY_SUMMARY,
|
|
44
|
+
getProofSummary,
|
|
45
|
+
joinSummaries,
|
|
46
|
+
type Generator,
|
|
47
|
+
type ProofSummary,
|
|
48
|
+
} from "./obligation-generate";
|
|
49
|
+
import {
|
|
50
|
+
getUnique,
|
|
51
|
+
type Counter,
|
|
52
|
+
type Obligation,
|
|
53
|
+
type Proof,
|
|
54
|
+
type Usable,
|
|
55
|
+
} from "./obligation-proof";
|
|
56
|
+
|
|
57
|
+
export type ModuleUnit = ModuleUnitCore & { location?: Locus };
|
|
58
|
+
|
|
59
|
+
type ModuleUnitCore =
|
|
60
|
+
| { kind: "Constants" | "Recursives"; declarations: { name: string; shape: number }[] }
|
|
61
|
+
| { kind: "Variables"; names: string[] }
|
|
62
|
+
| {
|
|
63
|
+
kind: "Definition";
|
|
64
|
+
definition: Defn;
|
|
65
|
+
whereDefined: WhereDefined;
|
|
66
|
+
visibility: Visibility;
|
|
67
|
+
isLocal: boolean;
|
|
68
|
+
}
|
|
69
|
+
| { kind: "Axiom"; name: string | undefined; expression: Expr }
|
|
70
|
+
| {
|
|
71
|
+
kind: "Theorem";
|
|
72
|
+
name: string | undefined;
|
|
73
|
+
sequent: Sequent;
|
|
74
|
+
axiomCount: number;
|
|
75
|
+
proof: Proof;
|
|
76
|
+
}
|
|
77
|
+
| { kind: "Submod"; module: Mule }
|
|
78
|
+
| { kind: "Mutate"; mode: "Use" | "Hide"; isSuppressed: boolean; usable: Usable }
|
|
79
|
+
| { kind: "Anoninst"; instance: InstanceDefinition; isLocal: boolean };
|
|
80
|
+
|
|
81
|
+
// depth is the size of the context the module was normalized in, which instantiation shifts
|
|
82
|
+
// its body by.
|
|
83
|
+
export interface Mule {
|
|
84
|
+
name: string;
|
|
85
|
+
extendees: string[];
|
|
86
|
+
extendeeLocations?: Locus[];
|
|
87
|
+
body: ModuleUnit[];
|
|
88
|
+
isImportant: boolean;
|
|
89
|
+
isStandard?: boolean;
|
|
90
|
+
depth?: number;
|
|
91
|
+
location?: Locus;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const getBuiltinConstant = (name: string, builtin: Builtin): ModuleUnit => ({
|
|
95
|
+
kind: "Definition",
|
|
96
|
+
definition: { kind: "Operator", name, body: { kind: "Internal", builtin } },
|
|
97
|
+
whereDefined: "Builtin",
|
|
98
|
+
visibility: "Visible",
|
|
99
|
+
isLocal: false,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const getBuiltinOperator = (arity: number, name: string, builtin: Builtin): ModuleUnit => ({
|
|
103
|
+
kind: "Definition",
|
|
104
|
+
definition: {
|
|
105
|
+
kind: "Operator",
|
|
106
|
+
name,
|
|
107
|
+
body: {
|
|
108
|
+
kind: "Lambda",
|
|
109
|
+
params: Array.from({ length: arity }, (_item, index) => ({
|
|
110
|
+
name: `x${index + 1}`,
|
|
111
|
+
shape: 0,
|
|
112
|
+
})),
|
|
113
|
+
body: {
|
|
114
|
+
kind: "Apply",
|
|
115
|
+
operator: { kind: "Internal", builtin },
|
|
116
|
+
args: Array.from({ length: arity }, (_item, index) => ({
|
|
117
|
+
kind: "Ix",
|
|
118
|
+
index: arity - index,
|
|
119
|
+
})),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
whereDefined: "Builtin",
|
|
124
|
+
visibility: "Visible",
|
|
125
|
+
isLocal: false,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const NATURALS: ModuleUnit[] = [
|
|
129
|
+
getBuiltinConstant("Nat", "Nat"),
|
|
130
|
+
getBuiltinOperator(2, "+", "Plus"),
|
|
131
|
+
getBuiltinOperator(2, "-", "Minus"),
|
|
132
|
+
getBuiltinOperator(2, "*", "Times"),
|
|
133
|
+
getBuiltinOperator(2, "^", "Exp"),
|
|
134
|
+
getBuiltinOperator(2, "<", "Lt"),
|
|
135
|
+
getBuiltinOperator(2, ">", "Gt"),
|
|
136
|
+
getBuiltinOperator(2, "=<", "Lteq"),
|
|
137
|
+
getBuiltinOperator(2, ">=", "Gteq"),
|
|
138
|
+
getBuiltinOperator(2, "%", "Remainder"),
|
|
139
|
+
getBuiltinOperator(2, "\\div", "Quotient"),
|
|
140
|
+
getBuiltinOperator(2, "..", "Range"),
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const INTEGERS = [
|
|
144
|
+
...NATURALS,
|
|
145
|
+
getBuiltinConstant("Int", "Int"),
|
|
146
|
+
getBuiltinOperator(1, "-.", "Uminus"),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
const REALS = [
|
|
150
|
+
...INTEGERS,
|
|
151
|
+
getBuiltinConstant("Real", "Real"),
|
|
152
|
+
getBuiltinOperator(2, "/", "Ratio"),
|
|
153
|
+
getBuiltinConstant("Infinity", "Infinity"),
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const SEQUENCES = [
|
|
157
|
+
getBuiltinOperator(1, "Seq", "Seq"),
|
|
158
|
+
getBuiltinOperator(1, "Len", "Len"),
|
|
159
|
+
getBuiltinOperator(2, "BSeq", "BSeq"),
|
|
160
|
+
getBuiltinOperator(2, "\\o", "Cat"),
|
|
161
|
+
getBuiltinOperator(2, "Append", "Append"),
|
|
162
|
+
getBuiltinOperator(1, "Head", "Head"),
|
|
163
|
+
getBuiltinOperator(1, "Tail", "Tail"),
|
|
164
|
+
getBuiltinOperator(3, "SubSeq", "SubSeq"),
|
|
165
|
+
getBuiltinOperator(2, "SelectSeq", "SelectSeq"),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const TLC = [
|
|
169
|
+
getBuiltinOperator(2, "Print", "Print"),
|
|
170
|
+
getBuiltinOperator(1, "PrintT", "PrintT"),
|
|
171
|
+
getBuiltinOperator(2, "Assert", "Assert"),
|
|
172
|
+
getBuiltinConstant("JavaTime", "JavaTime"),
|
|
173
|
+
getBuiltinOperator(1, "TLCGet", "TLCGet"),
|
|
174
|
+
getBuiltinOperator(2, "TLCSet", "TLCSet"),
|
|
175
|
+
getBuiltinOperator(2, ":>", "OneArg"),
|
|
176
|
+
getBuiltinOperator(2, "@@", "Extend"),
|
|
177
|
+
getBuiltinOperator(1, "Permutations", "Permutations"),
|
|
178
|
+
getBuiltinOperator(2, "SortSeq", "SortSeq"),
|
|
179
|
+
getBuiltinOperator(1, "RandomElement", "RandomElement"),
|
|
180
|
+
getBuiltinConstant("Any", "Any"),
|
|
181
|
+
getBuiltinOperator(1, "ToString", "ToString"),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const getStandard = (name: string, body: ModuleUnit[]): Mule => ({
|
|
185
|
+
name,
|
|
186
|
+
extendees: [],
|
|
187
|
+
body,
|
|
188
|
+
isImportant: false,
|
|
189
|
+
isStandard: true,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Module.Standard.initctx, apart from $TLAPM, whose Builtins! names no module of the corpora uses.
|
|
193
|
+
export const STANDARD_MODULES = new Map(
|
|
194
|
+
[
|
|
195
|
+
getStandard("Naturals", NATURALS),
|
|
196
|
+
getStandard("Integers", INTEGERS),
|
|
197
|
+
getStandard("Reals", REALS),
|
|
198
|
+
getStandard("Sequences", SEQUENCES),
|
|
199
|
+
getStandard("TLC", TLC),
|
|
200
|
+
].map((module) => [module.name, module]),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const getUnitHyps = (unit: ModuleUnit): Hyp[] => {
|
|
204
|
+
const location = unit.location;
|
|
205
|
+
switch (unit.kind) {
|
|
206
|
+
case "Constants":
|
|
207
|
+
return unit.declarations.map((declaration) => ({
|
|
208
|
+
kind: "Fresh",
|
|
209
|
+
name: declaration.name,
|
|
210
|
+
shape: declaration.shape,
|
|
211
|
+
level: "Constant",
|
|
212
|
+
domain: undefined,
|
|
213
|
+
domainVisibility: "Visible",
|
|
214
|
+
location,
|
|
215
|
+
}));
|
|
216
|
+
case "Recursives":
|
|
217
|
+
return unit.declarations.map((declaration) => ({
|
|
218
|
+
kind: "Defn",
|
|
219
|
+
definition: {
|
|
220
|
+
kind: "Recursive",
|
|
221
|
+
name: declaration.name,
|
|
222
|
+
shape: declaration.shape,
|
|
223
|
+
location,
|
|
224
|
+
},
|
|
225
|
+
whereDefined: "User",
|
|
226
|
+
visibility: "Hidden",
|
|
227
|
+
isLocal: false,
|
|
228
|
+
location,
|
|
229
|
+
}));
|
|
230
|
+
case "Variables":
|
|
231
|
+
return unit.names.map((name) => ({ kind: "Flex", name, location }));
|
|
232
|
+
case "Definition":
|
|
233
|
+
return [
|
|
234
|
+
{
|
|
235
|
+
kind: "Defn",
|
|
236
|
+
definition: unit.definition,
|
|
237
|
+
whereDefined: unit.whereDefined,
|
|
238
|
+
visibility: unit.visibility,
|
|
239
|
+
isLocal: unit.isLocal,
|
|
240
|
+
location,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
case "Axiom":
|
|
244
|
+
return unit.name === undefined
|
|
245
|
+
? [{ kind: "Fact", expression: unit.expression, visibility: "Hidden", location }]
|
|
246
|
+
: getNamedFact(unit.name, unit.expression, location);
|
|
247
|
+
case "Theorem": {
|
|
248
|
+
const sequent = substituteSequent(shift(-unit.axiomCount), {
|
|
249
|
+
...unit.sequent,
|
|
250
|
+
context: unit.sequent.context.slice(unit.axiomCount),
|
|
251
|
+
});
|
|
252
|
+
const body = at(exprifySequent(sequent), unit);
|
|
253
|
+
return unit.name === undefined
|
|
254
|
+
? [{ kind: "Fact", expression: body, visibility: "Hidden", location }]
|
|
255
|
+
: getNamedFact(unit.name, body, location);
|
|
256
|
+
}
|
|
257
|
+
case "Mutate":
|
|
258
|
+
return unit.mode === "Use"
|
|
259
|
+
? unit.usable.facts.map((fact, index) => ({
|
|
260
|
+
kind: "Fact",
|
|
261
|
+
expression: shiftExpr(index, fact),
|
|
262
|
+
visibility: "Visible",
|
|
263
|
+
location,
|
|
264
|
+
}))
|
|
265
|
+
: [];
|
|
266
|
+
case "Submod":
|
|
267
|
+
case "Anoninst":
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const getHypSize = (unit: ModuleUnit) =>
|
|
273
|
+
unit.kind === "Axiom" || unit.kind === "Theorem"
|
|
274
|
+
? unit.name === undefined
|
|
275
|
+
? 1
|
|
276
|
+
: 2
|
|
277
|
+
: getUnitHyps(unit).length;
|
|
278
|
+
|
|
279
|
+
const getNamedFact = (name: string, expression: Expr, location: Locus | undefined): Hyp[] => [
|
|
280
|
+
{
|
|
281
|
+
kind: "Defn",
|
|
282
|
+
definition: { kind: "Operator", name, body: expression, location },
|
|
283
|
+
whereDefined: "User",
|
|
284
|
+
visibility: "Visible",
|
|
285
|
+
isLocal: false,
|
|
286
|
+
location,
|
|
287
|
+
},
|
|
288
|
+
{ kind: "Fact", expression: { kind: "Ix", index: 1, location }, visibility: "Hidden", location },
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
// Module.Flatten.exports: what an extending module sees, with local definitions inlined like
|
|
292
|
+
// builtins and proofs omitted.
|
|
293
|
+
const getExports = (module: Mule): ModuleUnit[] =>
|
|
294
|
+
module.body.flatMap((unit): ModuleUnit[] => {
|
|
295
|
+
switch (unit.kind) {
|
|
296
|
+
case "Definition":
|
|
297
|
+
return [{ ...unit, whereDefined: unit.isLocal ? "Builtin" : unit.whereDefined }];
|
|
298
|
+
case "Mutate":
|
|
299
|
+
return [];
|
|
300
|
+
case "Theorem":
|
|
301
|
+
return [
|
|
302
|
+
{ ...unit, proof: { kind: "Omitted", omission: "Elsewhere", location: unit.location } },
|
|
303
|
+
];
|
|
304
|
+
case "Submod":
|
|
305
|
+
return [{ ...unit, module: { ...unit.module, body: getExports(unit.module) } }];
|
|
306
|
+
default:
|
|
307
|
+
return [unit];
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// tlapm recurses on a module that extends itself until the stack overflows.
|
|
312
|
+
export const flatten = (
|
|
313
|
+
modules: Map<string, Mule>,
|
|
314
|
+
module: Mule,
|
|
315
|
+
seen: Set<string>,
|
|
316
|
+
extending = new Set([module.name]),
|
|
317
|
+
): Mule => {
|
|
318
|
+
const extension: ModuleUnit[] = [];
|
|
319
|
+
for (const name of module.extendees) {
|
|
320
|
+
if (seen.has(name)) continue;
|
|
321
|
+
if (extending.has(name)) throw new TlapmBugError("Stack overflow");
|
|
322
|
+
const extended = modules.get(name);
|
|
323
|
+
if (!extended) throw new TlaProofError(`Module "${name}" unknown`);
|
|
324
|
+
extension.push(...getExports(flatten(modules, extended, seen, new Set([...extending, name]))));
|
|
325
|
+
seen.add(name);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
...module,
|
|
329
|
+
body: [...extension, ...module.body].map((unit) =>
|
|
330
|
+
unit.kind === "Submod" ? { ...unit, module: flatten(modules, unit.module, new Set()) } : unit,
|
|
331
|
+
),
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Module.Dep.external_deps: the modules a module extends or instantiates, including those of its
|
|
336
|
+
// submodules.
|
|
337
|
+
export const getDependencies = (module: Mule) => {
|
|
338
|
+
const dependencies = new Map<string, Locus | undefined>();
|
|
339
|
+
const addDependency = (name: string, location: Locus | undefined) => {
|
|
340
|
+
if (!dependencies.has(name)) dependencies.set(name, location);
|
|
341
|
+
};
|
|
342
|
+
const addExtendees = (extending: Mule) =>
|
|
343
|
+
extending.extendees.forEach((name, index) =>
|
|
344
|
+
addDependency(name, extending.extendeeLocations?.[index]),
|
|
345
|
+
);
|
|
346
|
+
const locals = new Set<string>();
|
|
347
|
+
const submodules = new Map<string, Mule>();
|
|
348
|
+
// Every INSTANCE definition inside a value, such as one in a LET.
|
|
349
|
+
const addInstances = (value: unknown) => {
|
|
350
|
+
if (typeof value !== "object" || value === null) return;
|
|
351
|
+
if ("instance" in value && isInstanceDefinition(value.instance)) {
|
|
352
|
+
addDependency(value.instance.module, value.instance.location);
|
|
353
|
+
}
|
|
354
|
+
Object.values(value).forEach(addInstances);
|
|
355
|
+
};
|
|
356
|
+
// The hypotheses of each unit, leaving out the proofs of theorems.
|
|
357
|
+
const visit = (unit: ModuleUnit) => {
|
|
358
|
+
if (unit.kind === "Theorem") addInstances(unit.sequent);
|
|
359
|
+
else if (unit.kind !== "Submod") addInstances(unit);
|
|
360
|
+
if (unit.kind !== "Submod") return;
|
|
361
|
+
locals.add(unit.module.name);
|
|
362
|
+
submodules.set(unit.module.name, unit.module);
|
|
363
|
+
addExtendees(unit.module);
|
|
364
|
+
unit.module.body.forEach(visit);
|
|
365
|
+
};
|
|
366
|
+
module.body.forEach(visit);
|
|
367
|
+
locals.forEach((name) => dependencies.delete(name));
|
|
368
|
+
addExtendees(module);
|
|
369
|
+
return {
|
|
370
|
+
dependencies: [...dependencies.keys()].sort(compareNames),
|
|
371
|
+
locations: dependencies,
|
|
372
|
+
submodules,
|
|
373
|
+
};
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const isInstanceDefinition = (value: unknown): value is InstanceDefinition =>
|
|
377
|
+
typeof value === "object" &&
|
|
378
|
+
value !== null &&
|
|
379
|
+
"module" in value &&
|
|
380
|
+
typeof value.module === "string" &&
|
|
381
|
+
"substitutions" in value;
|
|
382
|
+
|
|
383
|
+
export const compareNames = (left: string, right: string) =>
|
|
384
|
+
left < right ? -1 : left > right ? 1 : 0;
|
|
385
|
+
|
|
386
|
+
// latestModules is the module context of the latest anonymization of an axiom, theorem or USE,
|
|
387
|
+
// which tlapm's anonymizer keeps using for the instances inside later definitions.
|
|
388
|
+
export interface Pipeline {
|
|
389
|
+
anonymizer: Anonymizer;
|
|
390
|
+
generator: Generator;
|
|
391
|
+
salts: Counter;
|
|
392
|
+
latestModules: Map<string, Mule>;
|
|
393
|
+
onWarning: (warning: ObligationWarning) => void;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const substituteUsable = (substitution: Substitution, usable: Usable): Usable => ({
|
|
397
|
+
facts: substituteExprs(substitution, usable.facts),
|
|
398
|
+
defs: usable.defs.map((def) => {
|
|
399
|
+
if (def.kind === "Dvar") return def;
|
|
400
|
+
const substituted = substituteExpr(substitution, { kind: "Ix", index: def.index });
|
|
401
|
+
if (substituted.kind !== "Ix") throw new TlaProofError("substitution produced bad definition");
|
|
402
|
+
return { ...def, index: substituted.index };
|
|
403
|
+
}),
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Module.Subst.app_modunit. Instantiated theorems carry omitted proofs, which a substitution leaves
|
|
407
|
+
// alone.
|
|
408
|
+
const substituteUnit = (
|
|
409
|
+
substitution: Substitution,
|
|
410
|
+
unit: ModuleUnit,
|
|
411
|
+
): [Substitution, ModuleUnit] => {
|
|
412
|
+
switch (unit.kind) {
|
|
413
|
+
case "Constants":
|
|
414
|
+
case "Recursives":
|
|
415
|
+
return [bumpBy(unit.declarations.length, substitution), unit];
|
|
416
|
+
case "Variables":
|
|
417
|
+
return [bumpBy(unit.names.length, substitution), unit];
|
|
418
|
+
case "Definition":
|
|
419
|
+
return [
|
|
420
|
+
bumpBy(1, substitution),
|
|
421
|
+
{ ...unit, definition: substituteDefn(substitution, unit.definition) },
|
|
422
|
+
];
|
|
423
|
+
case "Axiom":
|
|
424
|
+
return [
|
|
425
|
+
bumpBy(getHypSize(unit), substitution),
|
|
426
|
+
{ ...unit, expression: substituteExpr(substitution, unit.expression) },
|
|
427
|
+
];
|
|
428
|
+
case "Theorem":
|
|
429
|
+
return [
|
|
430
|
+
bumpBy(getHypSize(unit), substitution),
|
|
431
|
+
{ ...unit, sequent: substituteSequent(substitution, unit.sequent) },
|
|
432
|
+
];
|
|
433
|
+
case "Submod":
|
|
434
|
+
return [
|
|
435
|
+
substitution,
|
|
436
|
+
{
|
|
437
|
+
...unit,
|
|
438
|
+
module: { ...unit.module, body: substituteUnits(substitution, unit.module.body) },
|
|
439
|
+
},
|
|
440
|
+
];
|
|
441
|
+
case "Mutate":
|
|
442
|
+
return [
|
|
443
|
+
unit.mode === "Hide" ? substitution : bumpBy(unit.usable.facts.length, substitution),
|
|
444
|
+
{ ...unit, usable: substituteUsable(substitution, unit.usable) },
|
|
445
|
+
];
|
|
446
|
+
case "Anoninst":
|
|
447
|
+
return [
|
|
448
|
+
substitution,
|
|
449
|
+
{
|
|
450
|
+
...unit,
|
|
451
|
+
instance: {
|
|
452
|
+
...unit.instance,
|
|
453
|
+
substitutions: unit.instance.substitutions.map((entry) => ({
|
|
454
|
+
name: entry.name,
|
|
455
|
+
expression: substituteExpr(substitution, entry.expression),
|
|
456
|
+
})),
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
];
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const substituteUnits = (substitution: Substitution, units: ModuleUnit[]) =>
|
|
464
|
+
mapAccumulate(substitution, units, substituteUnit)[1];
|
|
465
|
+
|
|
466
|
+
const lambdifyTheorem = (
|
|
467
|
+
pipeline: Pipeline,
|
|
468
|
+
scope: Scope | undefined,
|
|
469
|
+
name: string | undefined,
|
|
470
|
+
sequent: Sequent,
|
|
471
|
+
) => {
|
|
472
|
+
try {
|
|
473
|
+
const lambdified = lambdify(pipeline.anonymizer, pipeline.onWarning, scope, {
|
|
474
|
+
kind: "Sequent",
|
|
475
|
+
sequent,
|
|
476
|
+
});
|
|
477
|
+
if (lambdified.kind !== "Sequent") throw new TlapmBugError();
|
|
478
|
+
return lambdified.sequent;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
if (!(error instanceof TlaProofError) || error.failure === undefined) throw error;
|
|
481
|
+
const location = sequent.active.location;
|
|
482
|
+
const message = `${error.failure}\nCould not instantiate the theorem ${name === undefined ? "" : `named: ${name}, `}at: ${location ? getLocusText(location) : "<unknown location>"}`;
|
|
483
|
+
pipeline.onWarning({ text: `${getAtPrefix(location)}[ERROR]${message}`, channel: "stdout" });
|
|
484
|
+
pipeline.onWarning({ text: message, channel: "toolboxOnly" });
|
|
485
|
+
return { context: [], active: at({ kind: "Internal", builtin: "TRUE" }, sequent.active) };
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const lambdifyUnit = (pipeline: Pipeline, scope: Scope | undefined, unit: ModuleUnit) => {
|
|
490
|
+
if (unit.kind === "Theorem") {
|
|
491
|
+
return { ...unit, sequent: lambdifyTheorem(pipeline, scope, unit.name, unit.sequent) };
|
|
492
|
+
}
|
|
493
|
+
if (unit.kind !== "Definition") return unit;
|
|
494
|
+
const definition = unit.definition;
|
|
495
|
+
if (definition.kind !== "Operator" && definition.kind !== "Bpragma") return unit;
|
|
496
|
+
const body = lambdify(pipeline.anonymizer, pipeline.onWarning, scope, definition.body);
|
|
497
|
+
return { ...unit, definition: { ...definition, body } };
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// lambdify_enabled_cdot: the ENABLED and \cdot of each definition and theorem statement bind
|
|
501
|
+
// their variables before instantiation substitutes for them. A theorem that cannot be lambdified
|
|
502
|
+
// states TRUE.
|
|
503
|
+
const lambdifyUnits = (
|
|
504
|
+
pipeline: Pipeline,
|
|
505
|
+
scope: Scope | undefined,
|
|
506
|
+
units: ModuleUnit[],
|
|
507
|
+
): [Scope | undefined, ModuleUnit[]] =>
|
|
508
|
+
mapAccumulate(scope, units, (current, unit): [Scope | undefined, ModuleUnit] => {
|
|
509
|
+
if (unit.kind === "Submod") {
|
|
510
|
+
const [inner, body] = lambdifyUnits(pipeline, current, unit.module.body);
|
|
511
|
+
return [inner, { ...unit, module: { ...unit.module, body } }];
|
|
512
|
+
}
|
|
513
|
+
const lambdified = lambdifyUnit(pipeline, current, unit);
|
|
514
|
+
return [adjoinAll(current, getUnitHyps(lambdified)), lambdified];
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// apply_subst: each parameter declaration of the instancee gives way to its substitute.
|
|
518
|
+
const applyParameters = (units: ModuleUnit[], substitutes: Map<string, Expr>) => {
|
|
519
|
+
const body: ModuleUnit[] = [];
|
|
520
|
+
let size = 0;
|
|
521
|
+
let rest = units;
|
|
522
|
+
while (rest.length > 0) {
|
|
523
|
+
const [unit, ...tail] = rest;
|
|
524
|
+
const split =
|
|
525
|
+
unit.kind === "Constants"
|
|
526
|
+
? {
|
|
527
|
+
name: unit.declarations[0].name,
|
|
528
|
+
remaining: { ...unit, declarations: unit.declarations.slice(1) },
|
|
529
|
+
}
|
|
530
|
+
: unit.kind === "Variables"
|
|
531
|
+
? { name: unit.names[0], remaining: { ...unit, names: unit.names.slice(1) } }
|
|
532
|
+
: undefined;
|
|
533
|
+
if (split) {
|
|
534
|
+
const substitute = substitutes.get(split.name);
|
|
535
|
+
if (!substitute) throw new TlaProofError(`Parameter ${split.name} has no substitute`);
|
|
536
|
+
const isEmpty = getHypSize(split.remaining) === 0;
|
|
537
|
+
rest = substituteUnits(
|
|
538
|
+
consSubstitution(shiftExpr(size, substitute), shift(0)),
|
|
539
|
+
isEmpty ? tail : [split.remaining, ...tail],
|
|
540
|
+
);
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
body.push(unit);
|
|
544
|
+
size += getHypSize(unit);
|
|
545
|
+
rest = tail;
|
|
546
|
+
}
|
|
547
|
+
return body;
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// resub_for: references to the instancee's own units become applications to the instance
|
|
551
|
+
// arguments, which the definitions now bind themselves.
|
|
552
|
+
const getLocalSubstitution = (
|
|
553
|
+
count: number,
|
|
554
|
+
argumentCount: number,
|
|
555
|
+
location: Locus | undefined,
|
|
556
|
+
): Substitution => {
|
|
557
|
+
const args = Array.from({ length: argumentCount }, (_item, index) =>
|
|
558
|
+
at({ kind: "Ix", index: argumentCount - index }, { location }),
|
|
559
|
+
);
|
|
560
|
+
const parameters = Array.from({ length: argumentCount }, (_item, index) =>
|
|
561
|
+
at({ kind: "Ix", index: index + 1 }, { location }),
|
|
562
|
+
);
|
|
563
|
+
const locals = Array.from({ length: count }, (_item, index): Expr => {
|
|
564
|
+
const operator = at({ kind: "Ix", index: index + argumentCount + 1 }, { location });
|
|
565
|
+
return argumentCount > 0 ? at({ kind: "Apply", operator, args }, { location }) : operator;
|
|
566
|
+
});
|
|
567
|
+
return consAll([...locals, ...parameters].reverse(), shift(count + argumentCount));
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const addParameters = (expression: Expr, args: string[]): Expr => {
|
|
571
|
+
const params = args.map((name) => ({ name, shape: 0 }));
|
|
572
|
+
if (expression.kind === "Lambda")
|
|
573
|
+
return { ...expression, params: [...params, ...expression.params] };
|
|
574
|
+
return args.length === 0
|
|
575
|
+
? expression
|
|
576
|
+
: at({ kind: "Lambda", params, body: expression }, expression);
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// localize: renames the instancee's units under the instance name, salts its local definitions
|
|
580
|
+
const localize = (
|
|
581
|
+
pipeline: Pipeline,
|
|
582
|
+
units: ModuleUnit[],
|
|
583
|
+
prefix: string,
|
|
584
|
+
instance: InstanceDefinition,
|
|
585
|
+
isLocal: boolean,
|
|
586
|
+
location: Locus | undefined,
|
|
587
|
+
) => {
|
|
588
|
+
const getName = (name: string) => (prefix === "" ? name : `${prefix}!${name}`);
|
|
589
|
+
const argumentCount = instance.args.length;
|
|
590
|
+
const body: ModuleUnit[] = [];
|
|
591
|
+
let size = 0;
|
|
592
|
+
let rest = units;
|
|
593
|
+
let isComplained = argumentCount === 0;
|
|
594
|
+
const complain = (text: string) => {
|
|
595
|
+
if (isComplained) return;
|
|
596
|
+
isComplained = true;
|
|
597
|
+
pipeline.onWarning({ text, channel: "stderr" });
|
|
598
|
+
};
|
|
599
|
+
while (rest.length > 0) {
|
|
600
|
+
const [unit, ...tail] = rest;
|
|
601
|
+
rest = tail;
|
|
602
|
+
const localized = (expression: Expr) =>
|
|
603
|
+
addParameters(
|
|
604
|
+
substituteExpr(getLocalSubstitution(size, argumentCount, location), expression),
|
|
605
|
+
instance.args,
|
|
606
|
+
);
|
|
607
|
+
switch (unit.kind) {
|
|
608
|
+
case "Definition": {
|
|
609
|
+
const definition = unit.definition;
|
|
610
|
+
if (definition.kind !== "Operator" && definition.kind !== "Bpragma") {
|
|
611
|
+
throw new TlaProofError("Module.Elab.localize");
|
|
612
|
+
}
|
|
613
|
+
const name = unit.isLocal
|
|
614
|
+
? `${definition.name}$${getUnique(pipeline.salts)}`
|
|
615
|
+
: definition.name;
|
|
616
|
+
body.push({
|
|
617
|
+
...unit,
|
|
618
|
+
definition: { ...definition, name: getName(name), body: localized(definition.body) },
|
|
619
|
+
whereDefined: unit.isLocal ? "Builtin" : unit.whereDefined,
|
|
620
|
+
isLocal: unit.isLocal || isLocal,
|
|
621
|
+
});
|
|
622
|
+
size += 1;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
case "Axiom": {
|
|
626
|
+
complain(
|
|
627
|
+
`${getLocusPrefix(location)}Instancing produces parametric axioms.\nSuch axioms will be forcibly hidden and cannot be cited.\n(This warning appears at-most once per INSTANCE declaration.)`,
|
|
628
|
+
);
|
|
629
|
+
const axiom: ModuleUnit = {
|
|
630
|
+
...unit,
|
|
631
|
+
name: unit.name === undefined ? undefined : getName(unit.name),
|
|
632
|
+
expression: localized(unit.expression),
|
|
633
|
+
};
|
|
634
|
+
body.push(axiom);
|
|
635
|
+
size += getHypSize(axiom);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
case "Theorem": {
|
|
639
|
+
complain(
|
|
640
|
+
`${getAtPrefix(location)}Warning: Instancing produces parametric theorems.\nSuch theorems will be forcibly hidden and cannot be cited.\n(This warning appears at-most once per INSTANCE declaration.)`,
|
|
641
|
+
);
|
|
642
|
+
const expression = localized(at(exprifySequent(unit.sequent), unit));
|
|
643
|
+
body.push({
|
|
644
|
+
...unit,
|
|
645
|
+
name: unit.name === undefined ? undefined : getName(unit.name),
|
|
646
|
+
sequent:
|
|
647
|
+
expression.kind === "Sequent"
|
|
648
|
+
? expression.sequent
|
|
649
|
+
: { context: [], active: expression },
|
|
650
|
+
proof: { kind: "Omitted", omission: "Elsewhere", location: unit.location },
|
|
651
|
+
});
|
|
652
|
+
size += 1;
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
case "Mutate":
|
|
656
|
+
if (unit.mode === "Use") rest = substituteUnits(shift(-unit.usable.facts.length), rest);
|
|
657
|
+
continue;
|
|
658
|
+
case "Submod":
|
|
659
|
+
continue;
|
|
660
|
+
default:
|
|
661
|
+
throw new TlaProofError("Module.Elab.localize");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return body;
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// localize_axioms: an axiom becomes a hypothesis of each theorem after it, and a named one also a
|
|
668
|
+
// definition.
|
|
669
|
+
const localizeAxioms = (units: ModuleUnit[]) => {
|
|
670
|
+
const body: ModuleUnit[] = [];
|
|
671
|
+
let rest = units;
|
|
672
|
+
while (rest.length > 0) {
|
|
673
|
+
const [unit, ...tail] = rest;
|
|
674
|
+
if (unit.kind !== "Axiom") {
|
|
675
|
+
body.push(unit);
|
|
676
|
+
rest = tail;
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
const location = unit.location;
|
|
680
|
+
if (unit.name !== undefined) {
|
|
681
|
+
body.push({
|
|
682
|
+
kind: "Definition",
|
|
683
|
+
definition: { kind: "Operator", name: unit.name, body: unit.expression, location },
|
|
684
|
+
whereDefined: "Proof",
|
|
685
|
+
visibility: "Visible",
|
|
686
|
+
isLocal: false,
|
|
687
|
+
location,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
const hyp: Hyp = {
|
|
691
|
+
kind: "Fact",
|
|
692
|
+
expression:
|
|
693
|
+
unit.name === undefined
|
|
694
|
+
? shiftExpr(1, unit.expression)
|
|
695
|
+
: at({ kind: "Ix", index: 2 }, unit),
|
|
696
|
+
visibility: "Visible",
|
|
697
|
+
location,
|
|
698
|
+
};
|
|
699
|
+
let distance = 0;
|
|
700
|
+
const inserted = tail.map((next): ModuleUnit => {
|
|
701
|
+
const size = distance;
|
|
702
|
+
distance += getHypSize(next);
|
|
703
|
+
if (next.kind !== "Theorem") return next;
|
|
704
|
+
const sequent = substituteSequent(shift(1), next.sequent);
|
|
705
|
+
return {
|
|
706
|
+
...next,
|
|
707
|
+
sequent: { ...sequent, context: [substituteHyp(shift(size), hyp), ...sequent.context] },
|
|
708
|
+
axiomCount: next.axiomCount + 1,
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
rest = substituteUnits(shift(-1), inserted);
|
|
712
|
+
}
|
|
713
|
+
return body;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const getParameters = (module: Mule) =>
|
|
717
|
+
new Set(
|
|
718
|
+
module.body.flatMap((unit) =>
|
|
719
|
+
unit.kind === "Constants"
|
|
720
|
+
? unit.declarations.map((declaration) => declaration.name)
|
|
721
|
+
: unit.kind === "Variables"
|
|
722
|
+
? unit.names
|
|
723
|
+
: [],
|
|
724
|
+
),
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
// Module.Elab.instantiate: the units of an instantiated module, in the context of the instance.
|
|
728
|
+
const instantiate = (
|
|
729
|
+
pipeline: Pipeline,
|
|
730
|
+
modules: Map<string, Mule>,
|
|
731
|
+
scope: Scope | undefined,
|
|
732
|
+
instance: InstanceDefinition,
|
|
733
|
+
isLocal: boolean,
|
|
734
|
+
prefix: string,
|
|
735
|
+
location: Locus | undefined,
|
|
736
|
+
) => {
|
|
737
|
+
const module = modules.get(instance.module);
|
|
738
|
+
if (!module) throw new TlaProofError(`Module "${instance.module}" unknown`);
|
|
739
|
+
const parameters = getParameters(module);
|
|
740
|
+
for (const substitution of instance.substitutions) {
|
|
741
|
+
if (!parameters.has(substitution.name)) {
|
|
742
|
+
throw new TlaProofError(
|
|
743
|
+
`Parameter ${substitution.name} occurs in the statement \`INSTANCE ${instance.module} WITH\`, and is not declared in the module ${instance.module}`,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
const instanceScope = adjoinAll(
|
|
748
|
+
scope,
|
|
749
|
+
instance.args.map((name) => getFresh(name, "Unknown")),
|
|
750
|
+
);
|
|
751
|
+
const substitutes = new Map(
|
|
752
|
+
instance.substitutions.map((entry) => [entry.name, entry.expression]),
|
|
753
|
+
);
|
|
754
|
+
for (const name of [...parameters].sort(compareNames)) {
|
|
755
|
+
if (substitutes.has(name)) continue;
|
|
756
|
+
if (!findNamed(instanceScope, name)) {
|
|
757
|
+
throw new TlaProofError(
|
|
758
|
+
`Required parameter ${name} for module ${instance.module} could not be inferred.`,
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
substitutes.set(name, at({ kind: "Opaque", name }, { location }));
|
|
762
|
+
}
|
|
763
|
+
const anonymized = new Map(
|
|
764
|
+
[...substitutes].map(([name, expression]) => [
|
|
765
|
+
name,
|
|
766
|
+
pipeline.anonymizer.expr(instanceScope, expression),
|
|
767
|
+
]),
|
|
768
|
+
);
|
|
769
|
+
const body = substituteUnits(
|
|
770
|
+
shift((instanceScope?.size ?? 0) - (module.depth ?? 0)),
|
|
771
|
+
module.body.map((unit): ModuleUnit =>
|
|
772
|
+
unit.kind === "Theorem"
|
|
773
|
+
? {
|
|
774
|
+
...unit,
|
|
775
|
+
proof: { kind: "Omitted", omission: "Implicit", location: unit.proof.location },
|
|
776
|
+
}
|
|
777
|
+
: unit,
|
|
778
|
+
),
|
|
779
|
+
);
|
|
780
|
+
const [, lambdified] = lambdifyUnits(pipeline, instanceScope, body);
|
|
781
|
+
return localizeAxioms(
|
|
782
|
+
localize(
|
|
783
|
+
pipeline,
|
|
784
|
+
applyParameters(lambdified, anonymized),
|
|
785
|
+
prefix,
|
|
786
|
+
instance,
|
|
787
|
+
isLocal,
|
|
788
|
+
location,
|
|
789
|
+
),
|
|
790
|
+
);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// A named theorem binds its name as a proof definition, which its statement shifts past.
|
|
794
|
+
const bindTheoremName = (
|
|
795
|
+
name: string | undefined,
|
|
796
|
+
sequent: Sequent,
|
|
797
|
+
location: Locus | undefined,
|
|
798
|
+
): { hyps: Hyp[]; sequent: Sequent } =>
|
|
799
|
+
name === undefined
|
|
800
|
+
? { hyps: [], sequent }
|
|
801
|
+
: {
|
|
802
|
+
hyps: [
|
|
803
|
+
{
|
|
804
|
+
kind: "Defn",
|
|
805
|
+
definition: {
|
|
806
|
+
kind: "Operator",
|
|
807
|
+
name,
|
|
808
|
+
body: at(exprifySequent(sequent), { location }),
|
|
809
|
+
location,
|
|
810
|
+
},
|
|
811
|
+
whereDefined: "Proof",
|
|
812
|
+
visibility: "Visible",
|
|
813
|
+
isLocal: false,
|
|
814
|
+
location,
|
|
815
|
+
},
|
|
816
|
+
],
|
|
817
|
+
sequent: substituteSequent(shift(1), sequent),
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// make_defs: the definitions among the units an INSTANCE inside a LET or DEFINE stands for.
|
|
821
|
+
export const expandInstance = (
|
|
822
|
+
pipeline: Pipeline,
|
|
823
|
+
scope: Scope | undefined,
|
|
824
|
+
definition: Extract<Defn, { kind: "Instance" }>,
|
|
825
|
+
): Defn[] => {
|
|
826
|
+
const name = definition.instance.module;
|
|
827
|
+
if (!pipeline.latestModules.has(name)) {
|
|
828
|
+
pipeline.onWarning({
|
|
829
|
+
text: `${getLocusPrefix(definition.instance.location)}Module ${JSON.stringify(name)} unknown\n`,
|
|
830
|
+
channel: "toolbox",
|
|
831
|
+
});
|
|
832
|
+
throw new TlaProofError(`Module ${JSON.stringify(name)} unknown`, {
|
|
833
|
+
failure: "Module.Elab.anon#defns",
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
const units = instantiate(
|
|
837
|
+
pipeline,
|
|
838
|
+
pipeline.latestModules,
|
|
839
|
+
scope,
|
|
840
|
+
definition.instance,
|
|
841
|
+
true,
|
|
842
|
+
definition.name,
|
|
843
|
+
definition.location,
|
|
844
|
+
);
|
|
845
|
+
const definitions: Defn[] = [];
|
|
846
|
+
let rest = units;
|
|
847
|
+
while (rest.length > 0) {
|
|
848
|
+
const [unit, ...tail] = rest;
|
|
849
|
+
if (unit.kind !== "Definition") {
|
|
850
|
+
rest = substituteUnits(shift(-getHypSize(unit)), tail);
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
definitions.push(unit.definition);
|
|
854
|
+
rest = tail;
|
|
855
|
+
}
|
|
856
|
+
return definitions;
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// Module.Elab.normalize: anonymizes each unit in the context of the ones before it, expands
|
|
860
|
+
// instances, merges repeated definitions, and simplifies the proofs of important modules. The
|
|
861
|
+
// result also joins the modules that later instances can name.
|
|
862
|
+
export const normalize = (
|
|
863
|
+
pipeline: Pipeline,
|
|
864
|
+
modules: Map<string, Mule>,
|
|
865
|
+
scope: Scope | undefined,
|
|
866
|
+
module: Mule,
|
|
867
|
+
): Mule => {
|
|
868
|
+
const body: ModuleUnit[] = [];
|
|
869
|
+
let current = scope;
|
|
870
|
+
const emit = (unit: ModuleUnit) => {
|
|
871
|
+
body.push(unit);
|
|
872
|
+
current = adjoinAll(current, getUnitHyps(unit));
|
|
873
|
+
};
|
|
874
|
+
const isRedundant = (name: string | undefined) =>
|
|
875
|
+
name !== undefined && findNamed(current, name) !== undefined;
|
|
876
|
+
// Instantiated units arrive anonymized.
|
|
877
|
+
const queue = module.body.map((unit) => ({ unit, isAnonymized: false }));
|
|
878
|
+
for (let index = 0; index < queue.length; index++) {
|
|
879
|
+
const unit = queue[index].unit;
|
|
880
|
+
const expand = (instance: InstanceDefinition, isLocal: boolean, prefix: string) => {
|
|
881
|
+
const units = instantiate(
|
|
882
|
+
pipeline,
|
|
883
|
+
modules,
|
|
884
|
+
current,
|
|
885
|
+
instance,
|
|
886
|
+
isLocal,
|
|
887
|
+
prefix,
|
|
888
|
+
unit.location,
|
|
889
|
+
);
|
|
890
|
+
queue.splice(index + 1, 0, ...units.map((inner) => ({ unit: inner, isAnonymized: true })));
|
|
891
|
+
};
|
|
892
|
+
switch (unit.kind) {
|
|
893
|
+
case "Anoninst":
|
|
894
|
+
expand(unit.instance, unit.isLocal, "");
|
|
895
|
+
continue;
|
|
896
|
+
case "Definition": {
|
|
897
|
+
const definition = unit.definition;
|
|
898
|
+
if (definition.kind === "Instance") {
|
|
899
|
+
expand(definition.instance, unit.isLocal, definition.name);
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const anonymized =
|
|
903
|
+
definition.kind === "Recursive" || queue[index].isAnonymized
|
|
904
|
+
? definition
|
|
905
|
+
: pipeline.anonymizer.defn(current, definition);
|
|
906
|
+
const normalized: ModuleUnit = { ...unit, definition: anonymized };
|
|
907
|
+
if (definition.kind === "Recursive") {
|
|
908
|
+
emit(normalized);
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
const hyp = getUnitHyps(normalized)[0];
|
|
912
|
+
const found = findNamed(current, definition.name);
|
|
913
|
+
if (found?.hyp.kind === "Defn") {
|
|
914
|
+
const isMerged =
|
|
915
|
+
found.hyp.definition.kind === "Recursive" ||
|
|
916
|
+
isEqualHyp(substituteHyp(shift(found.depth + 1), found.hyp), hyp);
|
|
917
|
+
if (isMerged) {
|
|
918
|
+
const rest = queue.slice(index + 1);
|
|
919
|
+
const substituted = substituteUnits(
|
|
920
|
+
consSubstitution(at({ kind: "Ix", index: found.depth + 1 }, unit), shift(0)),
|
|
921
|
+
rest.map((entry) => entry.unit),
|
|
922
|
+
);
|
|
923
|
+
queue.splice(
|
|
924
|
+
index + 1,
|
|
925
|
+
rest.length,
|
|
926
|
+
...rest.map((entry, offset) => ({ ...entry, unit: substituted[offset] })),
|
|
927
|
+
);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
emit(normalized);
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
case "Recursives":
|
|
935
|
+
case "Constants":
|
|
936
|
+
case "Variables":
|
|
937
|
+
emit(unit);
|
|
938
|
+
continue;
|
|
939
|
+
case "Axiom":
|
|
940
|
+
if (isRedundant(unit.name)) continue;
|
|
941
|
+
pipeline.latestModules = new Map(modules);
|
|
942
|
+
emit({ ...unit, expression: pipeline.anonymizer.expr(current, unit.expression) });
|
|
943
|
+
continue;
|
|
944
|
+
case "Theorem": {
|
|
945
|
+
const isDuplicate = isRedundant(unit.name);
|
|
946
|
+
const name = isDuplicate ? `(redundant)${unit.name}` : unit.name;
|
|
947
|
+
const proof: Proof = isDuplicate
|
|
948
|
+
? { kind: "Obvious", location: unit.proof.location }
|
|
949
|
+
: unit.proof;
|
|
950
|
+
pipeline.latestModules = new Map(modules);
|
|
951
|
+
const sequent = pipeline.anonymizer.sequent(current, unit.sequent);
|
|
952
|
+
const binding = bindTheoremName(name, sequent, unit.location);
|
|
953
|
+
const proofScope = adjoinAll(adjoinAll(current, binding.hyps), binding.sequent.context);
|
|
954
|
+
const anonymized = pipeline.anonymizer.proof(proofScope, proof);
|
|
955
|
+
const simplified = module.isImportant
|
|
956
|
+
? pipeline.generator.simplify(
|
|
957
|
+
getScopeHyps(proofScope),
|
|
958
|
+
binding.sequent.active,
|
|
959
|
+
anonymized,
|
|
960
|
+
)
|
|
961
|
+
: anonymized;
|
|
962
|
+
emit({ ...unit, name, sequent, proof: simplified });
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
case "Mutate":
|
|
966
|
+
pipeline.latestModules = new Map(modules);
|
|
967
|
+
emit({ ...unit, usable: pipeline.anonymizer.usable(current, unit.usable) });
|
|
968
|
+
continue;
|
|
969
|
+
case "Submod":
|
|
970
|
+
emit({ ...unit, module: normalize(pipeline, modules, current, unit.module) });
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const normalized = { ...module, body, depth: scope?.size ?? 0 };
|
|
975
|
+
modules.set(module.name, normalized);
|
|
976
|
+
return normalized;
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
export interface TheoremSummary extends ProofSummary {
|
|
980
|
+
location: Locus | undefined;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export interface ModuleSummary extends ProofSummary {
|
|
984
|
+
theorems: TheoremSummary[];
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Module.Gen.generate: a named theorem binds its name as a proof definition for what follows.
|
|
988
|
+
export const generate = (
|
|
989
|
+
pipeline: Pipeline,
|
|
990
|
+
context: Hyp[],
|
|
991
|
+
module: Mule,
|
|
992
|
+
): { obligations: Obligation[]; summary: ModuleSummary } => {
|
|
993
|
+
const obligations: Obligation[] = [];
|
|
994
|
+
const theorems: TheoremSummary[] = [];
|
|
995
|
+
let summary = EMPTY_SUMMARY;
|
|
996
|
+
let current = context;
|
|
997
|
+
for (const unit of module.body) {
|
|
998
|
+
switch (unit.kind) {
|
|
999
|
+
case "Theorem": {
|
|
1000
|
+
const binding = bindTheoremName(unit.name, unit.sequent, unit.location);
|
|
1001
|
+
const named = [...current, ...binding.hyps];
|
|
1002
|
+
const proof = pipeline.generator.generate(
|
|
1003
|
+
{ context: [...named, ...binding.sequent.context], active: binding.sequent.active },
|
|
1004
|
+
unit.proof,
|
|
1005
|
+
);
|
|
1006
|
+
obligations.push(...pipeline.generator.collect(proof));
|
|
1007
|
+
const theorem = getProofSummary(proof);
|
|
1008
|
+
theorems.push({ ...theorem, location: unit.location });
|
|
1009
|
+
summary = joinSummaries(summary, theorem);
|
|
1010
|
+
const fact: Expr =
|
|
1011
|
+
unit.name === undefined
|
|
1012
|
+
? at(exprifySequent(unit.sequent), unit)
|
|
1013
|
+
: { kind: "Ix", index: 1, location: unit.location };
|
|
1014
|
+
current = [
|
|
1015
|
+
...named,
|
|
1016
|
+
{ kind: "Fact", expression: fact, visibility: "Hidden", location: unit.location },
|
|
1017
|
+
];
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
case "Submod": {
|
|
1021
|
+
const generated = generate(pipeline, current, unit.module);
|
|
1022
|
+
obligations.push(...generated.obligations);
|
|
1023
|
+
summary = joinSummaries(summary, generated.summary);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
case "Mutate": {
|
|
1027
|
+
const [next, generated] = pipeline.generator.mutate(
|
|
1028
|
+
current,
|
|
1029
|
+
unit.mode,
|
|
1030
|
+
unit.usable,
|
|
1031
|
+
unit.location,
|
|
1032
|
+
);
|
|
1033
|
+
obligations.push(...generated);
|
|
1034
|
+
current = next;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
default:
|
|
1038
|
+
current = [...current, ...getUnitHyps(unit)];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return { obligations, summary: { ...summary, theorems } };
|
|
1042
|
+
};
|