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,575 @@
|
|
|
1
|
+
// A port of tlapm's anonymization (Expr.Anon, Elab.desugar and Proof.Anon): names become de Bruijn
|
|
2
|
+
// indices into the context, builtin definitions are inlined, and anonymous steps get names.
|
|
3
|
+
|
|
4
|
+
import { TlapmBugError, TlaProofError } from "./errors";
|
|
5
|
+
import {
|
|
6
|
+
adjoin,
|
|
7
|
+
adjoinAll,
|
|
8
|
+
applyBuiltin,
|
|
9
|
+
at,
|
|
10
|
+
consSubstitution,
|
|
11
|
+
exprifySequent,
|
|
12
|
+
findNamed,
|
|
13
|
+
getErrorNote,
|
|
14
|
+
getFresh,
|
|
15
|
+
getLocusPrefix,
|
|
16
|
+
getScopeAt,
|
|
17
|
+
isEqualExpr,
|
|
18
|
+
mapAccumulate,
|
|
19
|
+
mapBoundDomains,
|
|
20
|
+
mapScoped,
|
|
21
|
+
mapSelectorArgs,
|
|
22
|
+
normalizeApply,
|
|
23
|
+
shift,
|
|
24
|
+
shiftExpr,
|
|
25
|
+
substituteExpr,
|
|
26
|
+
type Bound,
|
|
27
|
+
type Defn,
|
|
28
|
+
type ExceptPoint,
|
|
29
|
+
type Expr,
|
|
30
|
+
type ExprCore,
|
|
31
|
+
type Hyp,
|
|
32
|
+
type ObligationWarning,
|
|
33
|
+
type ParenForm,
|
|
34
|
+
type Scope,
|
|
35
|
+
type Selector,
|
|
36
|
+
type Sequent,
|
|
37
|
+
type WhereDefined,
|
|
38
|
+
} from "./obligation-expression";
|
|
39
|
+
import { resolveSubexpression } from "./obligation-deref";
|
|
40
|
+
import {
|
|
41
|
+
getStepName,
|
|
42
|
+
getUnique,
|
|
43
|
+
type Counter,
|
|
44
|
+
type Proof,
|
|
45
|
+
type Step,
|
|
46
|
+
type StepNumber,
|
|
47
|
+
type UseDef,
|
|
48
|
+
type Usable,
|
|
49
|
+
} from "./obligation-proof";
|
|
50
|
+
|
|
51
|
+
const DUMMY_FACT: Hyp = {
|
|
52
|
+
kind: "Fact",
|
|
53
|
+
expression: { kind: "At", isExcept: false },
|
|
54
|
+
visibility: "Visible",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ANON_SELECTOR_FAILURES = {
|
|
58
|
+
Number: "1NUM",
|
|
59
|
+
Down: "1DOWN",
|
|
60
|
+
At: "1AT",
|
|
61
|
+
Instance: "1INST",
|
|
62
|
+
Left: "1",
|
|
63
|
+
Right: "1",
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
const BAD_EXPRESSION: Expr = { kind: "Opaque", name: "$badexp" };
|
|
67
|
+
|
|
68
|
+
interface AnonContext {
|
|
69
|
+
warnedSteps: string[];
|
|
70
|
+
scope: Scope | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const createAnonymizer = (
|
|
74
|
+
counter: Counter,
|
|
75
|
+
expandInstance: (
|
|
76
|
+
scope: Scope | undefined,
|
|
77
|
+
definition: Extract<Defn, { kind: "Instance" }>,
|
|
78
|
+
) => Defn[],
|
|
79
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
80
|
+
) => {
|
|
81
|
+
// Elab.desugar's current_at: the value @ stands for inside an EXCEPT, and the context size there.
|
|
82
|
+
let currentAt: { expression: Expr; size: number } | undefined;
|
|
83
|
+
|
|
84
|
+
const getSize = (context: AnonContext) => context.scope?.size ?? 0;
|
|
85
|
+
|
|
86
|
+
const expr = (context: AnonContext, expression: Expr): Expr => {
|
|
87
|
+
if (expression.kind === "At" && expression.isExcept) {
|
|
88
|
+
if (!currentAt) return expression;
|
|
89
|
+
return shiftExpr(getSize(context) - currentAt.size, currentAt.expression);
|
|
90
|
+
}
|
|
91
|
+
if (expression.kind !== "Except") return anonCore(context, expression);
|
|
92
|
+
const base = expr(context, expression.function);
|
|
93
|
+
return expression.specs.reduce((result, spec) => {
|
|
94
|
+
const [path, trail] = mapAccumulate(
|
|
95
|
+
result,
|
|
96
|
+
spec.trail,
|
|
97
|
+
(current, point): [Expr, ExceptPoint] => {
|
|
98
|
+
if (point.kind === "Dot") {
|
|
99
|
+
return [at({ kind: "Dot", expression: current, field: point.field }, current), point];
|
|
100
|
+
}
|
|
101
|
+
const key = expr(context, point.expression);
|
|
102
|
+
return [
|
|
103
|
+
at({ kind: "FcnApp", function: current, args: [key] }, current),
|
|
104
|
+
{ kind: "Apply", expression: key },
|
|
105
|
+
];
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
const saved = currentAt;
|
|
109
|
+
currentAt = { expression: path, size: getSize(context) };
|
|
110
|
+
const value = expr(context, spec.value);
|
|
111
|
+
currentAt = saved;
|
|
112
|
+
return at({ kind: "Except", function: result, specs: [{ trail, value }] }, expression);
|
|
113
|
+
}, base);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const anonApply = (index: number, args: Expr[], from: Expr): Expr => {
|
|
117
|
+
const indexed = at({ kind: "Ix", index }, from);
|
|
118
|
+
return args.length === 0 ? indexed : at({ kind: "Apply", operator: indexed, args }, from);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const standardForm = (
|
|
122
|
+
index: number,
|
|
123
|
+
whereDefined: WhereDefined,
|
|
124
|
+
body: Expr,
|
|
125
|
+
args: Expr[],
|
|
126
|
+
from: Expr,
|
|
127
|
+
): Expr => {
|
|
128
|
+
if (whereDefined !== "Builtin") return anonApply(index, args, from);
|
|
129
|
+
const shifted = shiftExpr(index, body);
|
|
130
|
+
return at(args.length === 0 ? shifted : normalizeApply(shifted, args), from);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const resolveName = (context: AnonContext, name: string, args: Expr[], from: Expr): Expr => {
|
|
134
|
+
const found = findNamed(context.scope, name);
|
|
135
|
+
if (!found) {
|
|
136
|
+
const message = `Operator "${name}" not found`;
|
|
137
|
+
onWarning(getErrorNote(from.location, message));
|
|
138
|
+
throw new TlaProofError(message, { failure: "Expr.Anon: 4" });
|
|
139
|
+
}
|
|
140
|
+
const index = found.depth + 1;
|
|
141
|
+
const hyp = found.hyp;
|
|
142
|
+
switch (hyp.kind) {
|
|
143
|
+
case "Defn": {
|
|
144
|
+
const definition = hyp.definition;
|
|
145
|
+
if (definition.kind === "Operator") {
|
|
146
|
+
return standardForm(index, hyp.whereDefined, definition.body, args, from);
|
|
147
|
+
}
|
|
148
|
+
if (definition.kind === "Recursive")
|
|
149
|
+
throw new TlaProofError("Recursive operators not supported by TLAPS", {
|
|
150
|
+
failure: "Expr.Anon: Recursive",
|
|
151
|
+
});
|
|
152
|
+
return anonApply(index, args, from);
|
|
153
|
+
}
|
|
154
|
+
case "Fresh":
|
|
155
|
+
return anonApply(index, args, from);
|
|
156
|
+
case "Flex":
|
|
157
|
+
return at({ kind: "Ix", index }, from);
|
|
158
|
+
case "Fact":
|
|
159
|
+
throw new TlapmBugError(
|
|
160
|
+
'File "src/expr/e_anon.ml", line 230, characters 24-36: Assertion failed',
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Expr.Anon's check: a prefix that names nothing joins the next label, as in an instance path.
|
|
166
|
+
const resolveBang = (
|
|
167
|
+
context: AnonContext,
|
|
168
|
+
prefix: string,
|
|
169
|
+
args: Expr[],
|
|
170
|
+
selectors: Selector[],
|
|
171
|
+
isInstance: boolean,
|
|
172
|
+
from: Expr,
|
|
173
|
+
): Expr => {
|
|
174
|
+
const found = findNamed(context.scope, prefix);
|
|
175
|
+
if (!found) {
|
|
176
|
+
const [selector, ...rest] = selectors;
|
|
177
|
+
if (selector?.kind === "Label") {
|
|
178
|
+
const labelArgs = selector.args.map((arg) => expr(context, arg));
|
|
179
|
+
return resolveBang(
|
|
180
|
+
context,
|
|
181
|
+
`${prefix}!${selector.label}`,
|
|
182
|
+
[...args, ...labelArgs],
|
|
183
|
+
rest,
|
|
184
|
+
true,
|
|
185
|
+
from,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const message = `Operator "${prefix}" not found`;
|
|
189
|
+
if (selector) onWarning(getErrorNote(from.location, message));
|
|
190
|
+
throw new TlaProofError(message, {
|
|
191
|
+
failure: `Expr.Anon: ${selector ? ANON_SELECTOR_FAILURES[selector.kind] : "1EMPTY"}`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const hyp = found.hyp;
|
|
195
|
+
if (
|
|
196
|
+
hyp.kind !== "Defn" ||
|
|
197
|
+
(hyp.definition.kind !== "Operator" && hyp.definition.kind !== "Bpragma")
|
|
198
|
+
) {
|
|
199
|
+
throw new TlaProofError("invalid subexpression reference");
|
|
200
|
+
}
|
|
201
|
+
const index = found.depth + 1;
|
|
202
|
+
if (selectors.length === 0 && isInstance) {
|
|
203
|
+
return args.length === 0
|
|
204
|
+
? at({ kind: "Ix", index }, from)
|
|
205
|
+
: standardForm(index, hyp.whereDefined, hyp.definition.body, args, from);
|
|
206
|
+
}
|
|
207
|
+
const body = withLocus(shiftExpr(index, hyp.definition.body), from);
|
|
208
|
+
return withLocus(resolveSubexpression(onWarning, body, args, selectors), from);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const anonCore = (context: AnonContext, expression: Expr): Expr => {
|
|
212
|
+
switch (expression.kind) {
|
|
213
|
+
case "Bang": {
|
|
214
|
+
const inner = expression.expression;
|
|
215
|
+
if (inner.kind === "Apply" && inner.operator.kind === "Opaque") {
|
|
216
|
+
const selectors = expression.selectors.map((selector) =>
|
|
217
|
+
mapSelectorArgs(selector, (arg) => expr(context, arg)),
|
|
218
|
+
);
|
|
219
|
+
const args = inner.args.map((arg) => expr(context, arg));
|
|
220
|
+
const bangContext = { warnedSteps: [], scope: context.scope };
|
|
221
|
+
return resolveBang(bangContext, inner.operator.name, args, selectors, false, expression);
|
|
222
|
+
}
|
|
223
|
+
return expr(context, {
|
|
224
|
+
...expression,
|
|
225
|
+
expression: at({ kind: "Apply", operator: inner, args: [] }, inner),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
case "Apply": {
|
|
229
|
+
if (expression.operator.kind !== "Opaque") return visit(context, expression);
|
|
230
|
+
const name = expression.operator.name;
|
|
231
|
+
if (context.warnedSteps.includes(name)) {
|
|
232
|
+
onWarning({
|
|
233
|
+
text: `${getLocusPrefix(expression.location)}Warning: ${name} does not introduce any assumptions. It does not make sense to use it here.\n`,
|
|
234
|
+
channel: "toolbox",
|
|
235
|
+
});
|
|
236
|
+
return at({ kind: "Internal", builtin: "TRUE" }, expression);
|
|
237
|
+
}
|
|
238
|
+
const args = expression.args.map((arg) => expr(context, arg));
|
|
239
|
+
return resolveName(context, name, args, expression);
|
|
240
|
+
}
|
|
241
|
+
case "Opaque":
|
|
242
|
+
return expr(context, at({ kind: "Apply", operator: expression, args: [] }, expression));
|
|
243
|
+
case "Parens":
|
|
244
|
+
if (expression.form.kind === "Syntax") return expr(context, expression.expression);
|
|
245
|
+
return {
|
|
246
|
+
...expression,
|
|
247
|
+
expression: expr(context, expression.expression),
|
|
248
|
+
form: anonParenForm(context, expression.form),
|
|
249
|
+
};
|
|
250
|
+
default:
|
|
251
|
+
return visit(context, expression);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const anonParenForm = (context: AnonContext, form: ParenForm): ParenForm => {
|
|
256
|
+
if (form.kind !== "Nlabel") return form;
|
|
257
|
+
return {
|
|
258
|
+
kind: "Xlabel",
|
|
259
|
+
label: form.label,
|
|
260
|
+
indices: form.names.map((name) => {
|
|
261
|
+
const found = findNamed(context.scope, name);
|
|
262
|
+
if (!found) throw new TlaProofError(`Identifier "${name}" in label parameters not found`);
|
|
263
|
+
return { name, index: found.depth + 1 };
|
|
264
|
+
}),
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const withScope = (context: AnonContext, scope: Scope | undefined): AnonContext => ({
|
|
269
|
+
warnedSteps: context.warnedSteps,
|
|
270
|
+
scope,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const adjoinContext = (context: AnonContext, hyp: Hyp) =>
|
|
274
|
+
withScope(context, adjoin(context.scope, hyp));
|
|
275
|
+
|
|
276
|
+
const bounds = (context: AnonContext, items: Bound[]): [AnonContext, Bound[]] => {
|
|
277
|
+
const mapped = mapBoundDomains(items, (domain) => expr(context, domain));
|
|
278
|
+
const inner = mapped.reduce(
|
|
279
|
+
(current, bound) => adjoinContext(current, getFresh(bound.name, bound.level)),
|
|
280
|
+
context,
|
|
281
|
+
);
|
|
282
|
+
return [inner, mapped];
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Visit.map, apart from the definitions an ASSUME or a LET binds.
|
|
286
|
+
const visit = (context: AnonContext, expression: Expr): Expr => {
|
|
287
|
+
if (expression.kind === "Sequent") {
|
|
288
|
+
return { ...expression, sequent: sequent(context, expression.sequent)[1] };
|
|
289
|
+
}
|
|
290
|
+
if (expression.kind === "Let") {
|
|
291
|
+
const [inner, definitions] = defns(context, expression.definitions);
|
|
292
|
+
return { ...expression, definitions, body: expr(inner, expression.body) };
|
|
293
|
+
}
|
|
294
|
+
return mapScoped(context, expression, expr, adjoinContext);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Expr.Anon's defn turns a recursive function definition into a CHOOSE over its own equation.
|
|
298
|
+
const defn = (context: AnonContext, definition: Defn): Defn => {
|
|
299
|
+
if (definition.kind !== "Operator") return definition;
|
|
300
|
+
const body = definition.body;
|
|
301
|
+
if (body.kind !== "Fcn") return { ...definition, body: expr(context, body) };
|
|
302
|
+
const inner = expr(adjoinContext(context, getFresh(definition.name, "Constant")), body);
|
|
303
|
+
const marker: Expr = { kind: "Opaque", name: "%%%" };
|
|
304
|
+
const isRecursive = !isEqualExpr(
|
|
305
|
+
substituteExpr(consSubstitution(marker, shift(1)), inner),
|
|
306
|
+
inner,
|
|
307
|
+
);
|
|
308
|
+
if (!isRecursive) return { ...definition, body: shiftExpr(-1, inner) };
|
|
309
|
+
const equation = applyBuiltin("Eq", [at({ kind: "Ix", index: 1 }, inner), inner], inner);
|
|
310
|
+
return {
|
|
311
|
+
...definition,
|
|
312
|
+
body: at({ kind: "Choose", name: definition.name, domain: undefined, body: equation }, inner),
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Module.Elab.anon#defns: an INSTANCE stands for the definitions it instantiates.
|
|
317
|
+
const defns = (context: AnonContext, definitions: Defn[]): [AnonContext, Defn[]] => {
|
|
318
|
+
let current = context;
|
|
319
|
+
const queue = [...definitions];
|
|
320
|
+
const mapped: Defn[] = [];
|
|
321
|
+
for (let index = 0; index < queue.length; index++) {
|
|
322
|
+
const definition = queue[index];
|
|
323
|
+
if (definition.kind === "Instance") {
|
|
324
|
+
queue.splice(index + 1, 0, ...expandInstance(current.scope, definition));
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const result = defn(current, definition);
|
|
328
|
+
current = adjoinContext(current, {
|
|
329
|
+
kind: "Defn",
|
|
330
|
+
definition: result,
|
|
331
|
+
whereDefined: "User",
|
|
332
|
+
visibility: "Visible",
|
|
333
|
+
isLocal: true,
|
|
334
|
+
});
|
|
335
|
+
mapped.push(result);
|
|
336
|
+
}
|
|
337
|
+
return [current, mapped];
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const hyp = (context: AnonContext, item: Hyp): [AnonContext, Hyp] => {
|
|
341
|
+
const mapped = ((): Hyp => {
|
|
342
|
+
switch (item.kind) {
|
|
343
|
+
case "Fresh":
|
|
344
|
+
return item.domain ? { ...item, domain: expr(context, item.domain) } : item;
|
|
345
|
+
case "Flex":
|
|
346
|
+
return item;
|
|
347
|
+
case "Defn":
|
|
348
|
+
return { ...item, definition: defn(context, item.definition) };
|
|
349
|
+
case "Fact":
|
|
350
|
+
return { ...item, expression: expr(context, item.expression) };
|
|
351
|
+
}
|
|
352
|
+
})();
|
|
353
|
+
return [adjoinContext(context, mapped), mapped];
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const sequent = (context: AnonContext, item: Sequent): [AnonContext, Sequent] => {
|
|
357
|
+
const [inner, hyps] = mapAccumulate(context, item.context, hyp);
|
|
358
|
+
return [inner, { context: hyps, active: expr(inner, item.active) }];
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const pushWarnedStep = (context: AnonContext, step: StepNumber): AnonContext => ({
|
|
362
|
+
warnedSteps: [getStepName(step), ...context.warnedSteps],
|
|
363
|
+
scope: context.scope,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const adjStep = (context: AnonContext, step: StepNumber, body: Expr, from: Step) =>
|
|
367
|
+
adjoinContext(context, {
|
|
368
|
+
kind: "Defn",
|
|
369
|
+
definition: { kind: "Operator", name: getStepName(step), body, location: from.location },
|
|
370
|
+
whereDefined: "Proof",
|
|
371
|
+
visibility: "Visible",
|
|
372
|
+
isLocal: true,
|
|
373
|
+
location: from.location,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const bumpContext = (context: AnonContext, count: number) =>
|
|
377
|
+
withScope(
|
|
378
|
+
context,
|
|
379
|
+
adjoinAll(
|
|
380
|
+
context.scope,
|
|
381
|
+
Array.from({ length: count }, () => DUMMY_FACT),
|
|
382
|
+
),
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const tupleForm = (item: Sequent): ExprCore => {
|
|
386
|
+
const facts = item.context.map((entry, index) => {
|
|
387
|
+
const amount = item.context.length - index + 1;
|
|
388
|
+
if (entry.kind === "Fact") return shiftExpr(amount, entry.expression);
|
|
389
|
+
if (entry.kind !== "Fresh" || !entry.domain) return BAD_EXPRESSION;
|
|
390
|
+
const membership = applyBuiltin(
|
|
391
|
+
"Mem",
|
|
392
|
+
[at({ kind: "Ix", index: 0 }, entry.domain), entry.domain],
|
|
393
|
+
entry.domain,
|
|
394
|
+
);
|
|
395
|
+
return shiftExpr(amount, membership);
|
|
396
|
+
});
|
|
397
|
+
const elements = [...facts, shiftExpr(1, item.active)];
|
|
398
|
+
return elements.length === 1 ? elements[0] : { kind: "Tuple", elements };
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const isCitable = (step: Step) =>
|
|
402
|
+
step.kind !== "Use" && step.kind !== "Hide" && step.kind !== "Define";
|
|
403
|
+
|
|
404
|
+
const usable = (context: AnonContext, item: Usable): Usable => ({
|
|
405
|
+
facts: item.facts.map((fact) => expr(context, fact)),
|
|
406
|
+
defs: item.defs.flatMap((def): UseDef[] => {
|
|
407
|
+
if (def.kind === "Dx") return [def];
|
|
408
|
+
const resolved = expr(context, at({ kind: "Opaque", name: def.name }, def));
|
|
409
|
+
if (resolved.kind !== "Ix") {
|
|
410
|
+
onWarning({
|
|
411
|
+
text: `${getLocusPrefix(def.location)}Ignored unexpandable identifier ${JSON.stringify(def.name)} in BY DEF.`,
|
|
412
|
+
channel: "stderr",
|
|
413
|
+
});
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
if (getScopeAt(context.scope, resolved.index - 1)?.hyp.kind !== "Defn") {
|
|
417
|
+
onWarning({
|
|
418
|
+
text: `${getLocusPrefix(def.location)}${JSON.stringify(def.name)} is not a definition and cannot be used in a DEF clause.`,
|
|
419
|
+
channel: "toolbox",
|
|
420
|
+
});
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
return [{ kind: "Dx", index: resolved.index, location: def.location }];
|
|
424
|
+
}),
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const proof = (context: AnonContext, item: Proof): Proof => {
|
|
428
|
+
switch (item.kind) {
|
|
429
|
+
case "Steps": {
|
|
430
|
+
const [inner, steps] = mapAccumulate(context, item.steps, step);
|
|
431
|
+
const qedProof = proof(
|
|
432
|
+
item.step ? pushWarnedStep(inner, item.step) : inner,
|
|
433
|
+
item.qed.proof,
|
|
434
|
+
);
|
|
435
|
+
return { ...item, steps, qed: { ...item.qed, proof: qedProof } };
|
|
436
|
+
}
|
|
437
|
+
case "By":
|
|
438
|
+
return { ...item, usable: usable(context, item.usable) };
|
|
439
|
+
default:
|
|
440
|
+
return item;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const step = (context: AnonContext, original: Step): [AnonContext, Step] => {
|
|
445
|
+
const number: StepNumber | undefined =
|
|
446
|
+
original.step?.kind === "Unnamed" && isCitable(original)
|
|
447
|
+
? {
|
|
448
|
+
kind: "Named",
|
|
449
|
+
level: original.step.level,
|
|
450
|
+
label: `_a${getUnique(counter)}`,
|
|
451
|
+
isAnonymous: true,
|
|
452
|
+
}
|
|
453
|
+
: original.step;
|
|
454
|
+
if (!number) throw new TlapmBugError();
|
|
455
|
+
const item: Step = { ...original, step: number };
|
|
456
|
+
switch (item.kind) {
|
|
457
|
+
case "Assert": {
|
|
458
|
+
const [, mapped] = sequent(context, item.sequent);
|
|
459
|
+
const assumptions = mapped.context.reduce(adjoinContext, context);
|
|
460
|
+
const withTuple = bumpContext(
|
|
461
|
+
adjStep(bumpContext(assumptions, 1), number, at(tupleForm(mapped), item), item),
|
|
462
|
+
1,
|
|
463
|
+
);
|
|
464
|
+
const inner = mapped.context.length === 0 ? pushWarnedStep(withTuple, number) : withTuple;
|
|
465
|
+
const result: Step = { ...item, sequent: mapped, proof: proof(inner, item.proof) };
|
|
466
|
+
return [
|
|
467
|
+
bumpContext(adjStep(context, number, at(exprifySequent(mapped), item), item), 1),
|
|
468
|
+
result,
|
|
469
|
+
];
|
|
470
|
+
}
|
|
471
|
+
case "Pcase": {
|
|
472
|
+
const expression = expr(context, item.expression);
|
|
473
|
+
const tuple = at({ kind: "Tuple", elements: [shiftExpr(2, expression)] }, item);
|
|
474
|
+
const inner = bumpContext(adjStep(bumpContext(context, 2), number, tuple, item), 1);
|
|
475
|
+
const result: Step = { ...item, expression, proof: proof(inner, item.proof) };
|
|
476
|
+
const after = adjStep(
|
|
477
|
+
context,
|
|
478
|
+
number,
|
|
479
|
+
at({ kind: "Tuple", elements: [expression] }, item),
|
|
480
|
+
item,
|
|
481
|
+
);
|
|
482
|
+
return [bumpContext(after, 1), result];
|
|
483
|
+
}
|
|
484
|
+
case "Suffices": {
|
|
485
|
+
const [, mapped] = sequent(context, item.sequent);
|
|
486
|
+
const fact = at({ kind: "Tuple", elements: [at(exprifySequent(mapped), item)] }, item);
|
|
487
|
+
const inner = bumpContext(adjStep(context, number, fact, item), 1);
|
|
488
|
+
const result: Step = { ...item, sequent: mapped, proof: proof(inner, item.proof) };
|
|
489
|
+
const assumptions = mapped.context.reduce(adjoinContext, context);
|
|
490
|
+
const tuple = at({ kind: "Tuple", elements: [at(tupleForm(mapped), item)] }, item);
|
|
491
|
+
return [bumpContext(adjStep(bumpContext(assumptions, 1), number, tuple, item), 1), result];
|
|
492
|
+
}
|
|
493
|
+
case "Have": {
|
|
494
|
+
const expression = expr(context, item.expression);
|
|
495
|
+
const tuple = at({ kind: "Tuple", elements: [shiftExpr(2, expression)] }, item);
|
|
496
|
+
return [
|
|
497
|
+
bumpContext(adjStep(bumpContext(context, 2), number, tuple, item), 1),
|
|
498
|
+
{ ...item, expression },
|
|
499
|
+
];
|
|
500
|
+
}
|
|
501
|
+
case "Take": {
|
|
502
|
+
const [inner, mapped] = bounds(context, item.bounds);
|
|
503
|
+
const universal = at(
|
|
504
|
+
{ kind: "Quant", quantifier: "Forall", bounds: mapped, body: BAD_EXPRESSION },
|
|
505
|
+
item,
|
|
506
|
+
);
|
|
507
|
+
return [
|
|
508
|
+
bumpContext(adjStep(bumpContext(inner, 1), number, universal, item), 1),
|
|
509
|
+
{ ...item, bounds: mapped },
|
|
510
|
+
];
|
|
511
|
+
}
|
|
512
|
+
case "Witness": {
|
|
513
|
+
const expressions = item.expressions.map((expression) => expr(context, expression));
|
|
514
|
+
const tuple = at({ kind: "Tuple", elements: expressions }, item);
|
|
515
|
+
return [
|
|
516
|
+
bumpContext(adjStep(bumpContext(context, 1), number, tuple, item), 1),
|
|
517
|
+
{ ...item, expressions },
|
|
518
|
+
];
|
|
519
|
+
}
|
|
520
|
+
case "Pick": {
|
|
521
|
+
const [boundContext, mapped] = bounds(context, item.bounds);
|
|
522
|
+
const expression = expr(boundContext, item.expression);
|
|
523
|
+
const existential = at(
|
|
524
|
+
{ kind: "Quant", quantifier: "Exists", bounds: mapped, body: expression },
|
|
525
|
+
item,
|
|
526
|
+
);
|
|
527
|
+
const inner = pushWarnedStep(
|
|
528
|
+
bumpContext(adjStep(bumpContext(context, 1), number, existential, item), 1),
|
|
529
|
+
number,
|
|
530
|
+
);
|
|
531
|
+
const result: Step = {
|
|
532
|
+
...item,
|
|
533
|
+
bounds: mapped,
|
|
534
|
+
expression,
|
|
535
|
+
proof: proof(inner, item.proof),
|
|
536
|
+
};
|
|
537
|
+
const declared = mapped.reduce(
|
|
538
|
+
(current, bound) => adjoinContext(current, getFresh(bound.name, bound.level)),
|
|
539
|
+
bumpContext(context, 2),
|
|
540
|
+
);
|
|
541
|
+
const shifted = shiftExpr(4 + mapped.length, existential);
|
|
542
|
+
return [bumpContext(adjStep(bumpContext(declared, 2), number, shifted, item), 1), result];
|
|
543
|
+
}
|
|
544
|
+
case "Use": {
|
|
545
|
+
const mapped = usable(context, item.usable);
|
|
546
|
+
return [bumpContext(context, mapped.facts.length), { ...item, usable: mapped }];
|
|
547
|
+
}
|
|
548
|
+
case "Hide":
|
|
549
|
+
return [context, { ...item, usable: usable(context, item.usable) }];
|
|
550
|
+
case "Define": {
|
|
551
|
+
const [inner, definitions] = defns(context, item.definitions);
|
|
552
|
+
return [inner, { ...item, definitions }];
|
|
553
|
+
}
|
|
554
|
+
case "Forget":
|
|
555
|
+
return [context, item];
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
expr: (scope: Scope | undefined, expression: Expr) =>
|
|
561
|
+
expr({ warnedSteps: [], scope }, expression),
|
|
562
|
+
defn: (scope: Scope | undefined, definition: Defn) =>
|
|
563
|
+
defn({ warnedSteps: [], scope }, definition),
|
|
564
|
+
sequent: (scope: Scope | undefined, item: Sequent) =>
|
|
565
|
+
sequent({ warnedSteps: [], scope }, item)[1],
|
|
566
|
+
usable: (scope: Scope | undefined, item: Usable) => usable({ warnedSteps: [], scope }, item),
|
|
567
|
+
proof: (scope: Scope | undefined, item: Proof) => proof({ warnedSteps: [], scope }, item),
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
// Util.set_locus: the node keeps its properties and takes the location of from.
|
|
572
|
+
const withLocus = (expression: Expr, from: Expr) =>
|
|
573
|
+
from.location ? { ...expression, location: from.location } : expression;
|
|
574
|
+
|
|
575
|
+
export type Anonymizer = ReturnType<typeof createAnonymizer>;
|