tla-ts 0.0.2 → 0.0.3-dev.763cc71
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/parallel-worker.ts +26 -7
- 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,637 @@
|
|
|
1
|
+
// A port of tlapm's Expr.Deref: a subexpression reference such as Op!2!<< resolves to the
|
|
2
|
+
// subexpression it names, abstracted over the bound names it uses.
|
|
3
|
+
|
|
4
|
+
import { TlaProofError, TlapmBugError } from "./errors";
|
|
5
|
+
import {
|
|
6
|
+
at,
|
|
7
|
+
consAll,
|
|
8
|
+
consSubstitution,
|
|
9
|
+
getErrorNote,
|
|
10
|
+
getChildren,
|
|
11
|
+
getFresh,
|
|
12
|
+
mapChildren,
|
|
13
|
+
mapSelectorArgs,
|
|
14
|
+
shift,
|
|
15
|
+
substituteExpr,
|
|
16
|
+
type Bound,
|
|
17
|
+
type Defn,
|
|
18
|
+
type Expr,
|
|
19
|
+
type Hyp,
|
|
20
|
+
type ObligationWarning,
|
|
21
|
+
type Param,
|
|
22
|
+
type Selector,
|
|
23
|
+
} from "./obligation-expression";
|
|
24
|
+
|
|
25
|
+
interface Deref {
|
|
26
|
+
params: Param[];
|
|
27
|
+
definitions: Defn[];
|
|
28
|
+
expression: Expr;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Label {
|
|
32
|
+
context: Hyp[];
|
|
33
|
+
expression: Expr;
|
|
34
|
+
indices: { name: string; index: number }[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const BAD = { kind: "Opaque", name: "$Bad" } satisfies Expr;
|
|
38
|
+
|
|
39
|
+
const isBad = (expression: Expr) => expression.kind === "Opaque" && expression.name === BAD.name;
|
|
40
|
+
|
|
41
|
+
const clean = (expression: Expr): Expr => {
|
|
42
|
+
const mapped =
|
|
43
|
+
expression.kind === "Parens" ? clean(expression.expression) : mapChildren(expression, clean);
|
|
44
|
+
if (!mapped.isAtomic) return mapped;
|
|
45
|
+
const { isAtomic: _isAtomic, ...rest } = mapped;
|
|
46
|
+
return rest;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Parentheses go everywhere but in the definitions of a LET.
|
|
50
|
+
const cleanParens = (expression: Expr): Expr => {
|
|
51
|
+
if (expression.kind === "Let") {
|
|
52
|
+
return at(
|
|
53
|
+
{ kind: "Let", definitions: expression.definitions, body: cleanParens(expression.body) },
|
|
54
|
+
expression.body,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (expression.kind === "Parens") return cleanParens(expression.expression);
|
|
58
|
+
return mapChildren(expression, cleanParens);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const hasNegativeIndex = (expression: Expr): boolean =>
|
|
62
|
+
expression.kind === "Ix" ? expression.index <= 0 : getChildren(expression).some(hasNegativeIndex);
|
|
63
|
+
|
|
64
|
+
// Visit.iter's context for each binder, stopping at labels with other names and at declarations
|
|
65
|
+
// in an ASSUME.
|
|
66
|
+
const findLabel = (expression: Expr, label: string, context: Hyp[]): Label | undefined => {
|
|
67
|
+
const search = (inner: Expr, innerContext = context) => findLabel(inner, label, innerContext);
|
|
68
|
+
const searchBounds = (bounds: Bound[], body: Expr) =>
|
|
69
|
+
bounds.reduce<Label | undefined>(
|
|
70
|
+
(found, bound) =>
|
|
71
|
+
found ?? (bound.domain.kind === "Domain" ? search(bound.domain.expression) : undefined),
|
|
72
|
+
undefined,
|
|
73
|
+
) ?? search(body, [...context, ...bounds.map((bound) => getFresh(bound.name, bound.level))]);
|
|
74
|
+
switch (expression.kind) {
|
|
75
|
+
case "Parens":
|
|
76
|
+
if (expression.form.kind !== "Xlabel") return search(expression.expression);
|
|
77
|
+
return expression.form.label === label
|
|
78
|
+
? { context, expression: expression.expression, indices: expression.form.indices }
|
|
79
|
+
: undefined;
|
|
80
|
+
case "Lambda":
|
|
81
|
+
return search(expression.body, [
|
|
82
|
+
...context,
|
|
83
|
+
...expression.params.map((param) => getFresh(param.name, "Unknown", param.shape)),
|
|
84
|
+
]);
|
|
85
|
+
case "Let": {
|
|
86
|
+
const inner = [...context];
|
|
87
|
+
for (const definition of expression.definitions) {
|
|
88
|
+
const found =
|
|
89
|
+
definition.kind === "Operator" || definition.kind === "Bpragma"
|
|
90
|
+
? search(definition.body, [...inner])
|
|
91
|
+
: undefined;
|
|
92
|
+
if (found) return found;
|
|
93
|
+
inner.push({
|
|
94
|
+
kind: "Defn",
|
|
95
|
+
definition,
|
|
96
|
+
whereDefined: "Builtin",
|
|
97
|
+
visibility: "Visible",
|
|
98
|
+
isLocal: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return search(expression.body, inner);
|
|
102
|
+
}
|
|
103
|
+
case "Quant":
|
|
104
|
+
case "Fcn":
|
|
105
|
+
return searchBounds(expression.bounds, expression.body);
|
|
106
|
+
case "SetOf":
|
|
107
|
+
return searchBounds(expression.bounds, expression.element);
|
|
108
|
+
case "Tquant":
|
|
109
|
+
return search(expression.body, [
|
|
110
|
+
...context,
|
|
111
|
+
...expression.names.map((name): Hyp => ({ kind: "Flex", name })),
|
|
112
|
+
]);
|
|
113
|
+
case "Choose":
|
|
114
|
+
case "SetSt": {
|
|
115
|
+
const found = expression.domain && search(expression.domain);
|
|
116
|
+
return found ?? search(expression.body, [...context, getFresh(expression.name, "Constant")]);
|
|
117
|
+
}
|
|
118
|
+
case "Sequent": {
|
|
119
|
+
const inner = [...context];
|
|
120
|
+
for (const hyp of expression.sequent.context) {
|
|
121
|
+
if (hyp.kind !== "Fact") return undefined;
|
|
122
|
+
const found = search(hyp.expression, [...inner]);
|
|
123
|
+
if (found) return found;
|
|
124
|
+
inner.push(hyp);
|
|
125
|
+
}
|
|
126
|
+
return search(expression.sequent.active, inner);
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
for (const child of getChildren(expression)) {
|
|
130
|
+
const found = search(child);
|
|
131
|
+
if (found) return found;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// The first count indices in reverse, and the rest past the parameters.
|
|
138
|
+
const getReversal = (count: number, paramCount: number) =>
|
|
139
|
+
consAll(
|
|
140
|
+
Array.from({ length: count }, (_item, index): Expr => ({ kind: "Ix", index: index + 1 })),
|
|
141
|
+
shift(count + paramCount),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const getIndices = (count: number) =>
|
|
145
|
+
Array.from({ length: count }, (_item, index): Expr => ({ kind: "Ix", index: count - index }));
|
|
146
|
+
|
|
147
|
+
const instantiate = (
|
|
148
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
149
|
+
expression: Expr,
|
|
150
|
+
args: Expr[],
|
|
151
|
+
): Expr => {
|
|
152
|
+
switch (expression.kind) {
|
|
153
|
+
case "Quant":
|
|
154
|
+
case "Fcn":
|
|
155
|
+
case "SetOf": {
|
|
156
|
+
if (expression.bounds.length !== args.length) {
|
|
157
|
+
const expected = expression.bounds.length;
|
|
158
|
+
const message = `Arity mismatch in subexpression instance.\nExpected ${expected} argument${expected === 1 ? "" : "s"}, but found${args.length < expected ? " only" : ""} ${args.length}.`;
|
|
159
|
+
onWarning(getErrorNote(expression.location, message));
|
|
160
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_inst" });
|
|
161
|
+
}
|
|
162
|
+
const body = expression.kind === "SetOf" ? expression.element : expression.body;
|
|
163
|
+
return substituteExpr(consAll(args, shift(0)), body);
|
|
164
|
+
}
|
|
165
|
+
case "Tquant":
|
|
166
|
+
return instantiate(
|
|
167
|
+
onWarning,
|
|
168
|
+
at(
|
|
169
|
+
{
|
|
170
|
+
kind: "Quant",
|
|
171
|
+
quantifier: expression.quantifier,
|
|
172
|
+
bounds: expression.names.map((name) => ({
|
|
173
|
+
name,
|
|
174
|
+
level: "State",
|
|
175
|
+
domain: { kind: "NoDomain" },
|
|
176
|
+
})),
|
|
177
|
+
body: expression.body,
|
|
178
|
+
},
|
|
179
|
+
expression,
|
|
180
|
+
),
|
|
181
|
+
args,
|
|
182
|
+
);
|
|
183
|
+
case "Lambda":
|
|
184
|
+
return instantiate(
|
|
185
|
+
onWarning,
|
|
186
|
+
at(
|
|
187
|
+
{
|
|
188
|
+
kind: "Quant",
|
|
189
|
+
quantifier: "Forall",
|
|
190
|
+
bounds: expression.params.map((param) => ({
|
|
191
|
+
name: param.name,
|
|
192
|
+
level: "Unknown",
|
|
193
|
+
domain: { kind: "NoDomain" },
|
|
194
|
+
})),
|
|
195
|
+
body: expression.body,
|
|
196
|
+
},
|
|
197
|
+
expression,
|
|
198
|
+
),
|
|
199
|
+
args,
|
|
200
|
+
);
|
|
201
|
+
case "SetSt":
|
|
202
|
+
case "Choose":
|
|
203
|
+
if (args.length !== 1) {
|
|
204
|
+
const message = `Arity mismatch in subexpression instance.\nExpected 1 argument, but found ${args.length}.`;
|
|
205
|
+
onWarning(getErrorNote(expression.location, message));
|
|
206
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_inst" });
|
|
207
|
+
}
|
|
208
|
+
return substituteExpr(consAll(args, shift(0)), expression.body);
|
|
209
|
+
default: {
|
|
210
|
+
const message = "Cannot instantiate non-binding expression";
|
|
211
|
+
onWarning(getErrorNote(expression.location, message));
|
|
212
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_inst" });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const abstract = (
|
|
218
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
219
|
+
definitionCount: number,
|
|
220
|
+
expression: Expr,
|
|
221
|
+
): [Param[], Expr] => {
|
|
222
|
+
const params = ((): Param[] => {
|
|
223
|
+
switch (expression.kind) {
|
|
224
|
+
case "Quant":
|
|
225
|
+
case "Fcn":
|
|
226
|
+
case "SetOf":
|
|
227
|
+
return expression.bounds.map((bound) => ({ name: bound.name, shape: 0 }));
|
|
228
|
+
case "Tquant":
|
|
229
|
+
return expression.names.map((name) => ({ name, shape: 0 }));
|
|
230
|
+
case "Lambda":
|
|
231
|
+
return expression.params;
|
|
232
|
+
case "SetSt":
|
|
233
|
+
case "Choose":
|
|
234
|
+
return [{ name: expression.name, shape: 0 }];
|
|
235
|
+
default: {
|
|
236
|
+
const message = "Cannot abstract over bound variables in a non-binding expression";
|
|
237
|
+
onWarning(getErrorNote(expression.location, message));
|
|
238
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_at" });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
})();
|
|
242
|
+
const shifted = substituteExpr(getReversal(definitionCount, params.length), expression);
|
|
243
|
+
return [params, instantiate(onWarning, shifted, getIndices(params.length))];
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const getNumbered = (
|
|
247
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
248
|
+
definitions: Defn[],
|
|
249
|
+
expression: Expr,
|
|
250
|
+
position: number,
|
|
251
|
+
origin: Expr,
|
|
252
|
+
message: string,
|
|
253
|
+
): [Defn[], Expr] => {
|
|
254
|
+
const checkAtMost = (count: number) => {
|
|
255
|
+
if (position <= count) return;
|
|
256
|
+
onWarning(getErrorNote(origin.location, message));
|
|
257
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_num" });
|
|
258
|
+
};
|
|
259
|
+
const pick = (items: Expr[]) => {
|
|
260
|
+
checkAtMost(items.length);
|
|
261
|
+
return items[position - 1];
|
|
262
|
+
};
|
|
263
|
+
const result = ((): [Defn[], Expr] => {
|
|
264
|
+
switch (expression.kind) {
|
|
265
|
+
case "Sequent": {
|
|
266
|
+
const sequent = expression.sequent;
|
|
267
|
+
checkAtMost(sequent.context.length + 1);
|
|
268
|
+
const hyp = sequent.context[position - 1];
|
|
269
|
+
if (hyp && hyp.kind !== "Fact") {
|
|
270
|
+
const hypMessage = "subexpression reference does not resolve to an expression";
|
|
271
|
+
onWarning(getErrorNote(expression.location, hypMessage));
|
|
272
|
+
throw new TlaProofError(hypMessage, { failure: "Expr.Deref.deref_num" });
|
|
273
|
+
}
|
|
274
|
+
const shifted = substituteExpr(
|
|
275
|
+
shift(-(position - 1)),
|
|
276
|
+
hyp ? hyp.expression : sequent.active,
|
|
277
|
+
);
|
|
278
|
+
if (hasNegativeIndex(shifted)) {
|
|
279
|
+
onWarning(
|
|
280
|
+
getErrorNote(
|
|
281
|
+
expression.location,
|
|
282
|
+
`subexpression of ASSUME/PROVE uses identifiers declared therein\nTlapm does not handle yet such subexpression namings.`,
|
|
283
|
+
),
|
|
284
|
+
);
|
|
285
|
+
throw new TlaProofError(
|
|
286
|
+
"Subexpression of ASSUME/PROVE uses identifiers declared therein\n\nTlapm does not handle yet such subexpression namings.",
|
|
287
|
+
{ failure: "Expr.Deref.deref_num" },
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return [definitions, shifted];
|
|
291
|
+
}
|
|
292
|
+
case "Apply":
|
|
293
|
+
return [definitions, pick(expression.args)];
|
|
294
|
+
case "With":
|
|
295
|
+
return getNumbered(
|
|
296
|
+
onWarning,
|
|
297
|
+
definitions,
|
|
298
|
+
expression.expression,
|
|
299
|
+
position,
|
|
300
|
+
origin,
|
|
301
|
+
message,
|
|
302
|
+
);
|
|
303
|
+
case "If":
|
|
304
|
+
return [
|
|
305
|
+
definitions,
|
|
306
|
+
pick([expression.condition, expression.consequent, expression.alternative]),
|
|
307
|
+
];
|
|
308
|
+
case "List":
|
|
309
|
+
return [definitions, pick(expression.items)];
|
|
310
|
+
case "SetEnum":
|
|
311
|
+
case "Tuple":
|
|
312
|
+
return [definitions, pick(expression.elements)];
|
|
313
|
+
case "Product":
|
|
314
|
+
return [definitions, pick(expression.factors)];
|
|
315
|
+
case "Let":
|
|
316
|
+
checkAtMost(1);
|
|
317
|
+
return [[...definitions, ...expression.definitions], expression.body];
|
|
318
|
+
case "Quant":
|
|
319
|
+
case "SetOf":
|
|
320
|
+
case "Fcn":
|
|
321
|
+
return [
|
|
322
|
+
definitions,
|
|
323
|
+
pick(
|
|
324
|
+
expression.bounds.flatMap((bound) =>
|
|
325
|
+
bound.domain.kind === "Domain" ? [bound.domain.expression] : [],
|
|
326
|
+
),
|
|
327
|
+
),
|
|
328
|
+
];
|
|
329
|
+
case "Choose":
|
|
330
|
+
case "SetSt":
|
|
331
|
+
if (!expression.domain) break;
|
|
332
|
+
return [definitions, pick([expression.domain])];
|
|
333
|
+
case "Arrow":
|
|
334
|
+
return [definitions, pick([expression.domain, expression.range])];
|
|
335
|
+
case "FcnApp": {
|
|
336
|
+
const argument =
|
|
337
|
+
expression.args.length === 1
|
|
338
|
+
? expression.args[0]
|
|
339
|
+
: at({ kind: "Tuple", elements: expression.args }, expression);
|
|
340
|
+
return [definitions, pick([expression.function, argument])];
|
|
341
|
+
}
|
|
342
|
+
case "Rect":
|
|
343
|
+
case "Record":
|
|
344
|
+
return [definitions, pick(expression.fields.map((field) => field.value))];
|
|
345
|
+
case "Dot":
|
|
346
|
+
return [
|
|
347
|
+
definitions,
|
|
348
|
+
pick([
|
|
349
|
+
expression.expression,
|
|
350
|
+
at({ kind: "String", value: expression.field }, expression),
|
|
351
|
+
]),
|
|
352
|
+
];
|
|
353
|
+
case "Except":
|
|
354
|
+
return [
|
|
355
|
+
definitions,
|
|
356
|
+
pick([expression.function, ...expression.specs.map((spec) => spec.value)]),
|
|
357
|
+
];
|
|
358
|
+
case "Sub":
|
|
359
|
+
case "Tsub":
|
|
360
|
+
return [definitions, pick([expression.action, expression.subscript])];
|
|
361
|
+
case "Fair":
|
|
362
|
+
return [definitions, pick([expression.subscript, expression.action])];
|
|
363
|
+
case "Case":
|
|
364
|
+
return [
|
|
365
|
+
definitions,
|
|
366
|
+
pick([
|
|
367
|
+
...expression.arms.map((arm) =>
|
|
368
|
+
at({ kind: "Tuple", elements: [arm.condition, arm.body] }, arm.body),
|
|
369
|
+
),
|
|
370
|
+
...(expression.other
|
|
371
|
+
? [at({ kind: "Tuple", elements: [BAD, expression.other] }, expression.other)]
|
|
372
|
+
: []),
|
|
373
|
+
]),
|
|
374
|
+
];
|
|
375
|
+
}
|
|
376
|
+
const emptyMessage = "Expression has no subexpressions";
|
|
377
|
+
onWarning(getErrorNote(expression.location, emptyMessage));
|
|
378
|
+
throw new TlaProofError(emptyMessage, { failure: "Expr.Deref.deref_num" });
|
|
379
|
+
})();
|
|
380
|
+
if (isBad(result[1])) {
|
|
381
|
+
const badMessage = `Cannot resolve subexpression reference !${position}`;
|
|
382
|
+
onWarning(getErrorNote(expression.location, badMessage));
|
|
383
|
+
throw new TlaProofError(badMessage, { failure: "Expr.Deref.deref_num" });
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Reorders label arguments given in source order to the order of the label's de Bruijn indices.
|
|
389
|
+
const permute = (indices: number[], args: Expr[]) => {
|
|
390
|
+
const count = indices.length;
|
|
391
|
+
let current = indices;
|
|
392
|
+
for (let offset = count - 1; offset >= 0; offset--) {
|
|
393
|
+
if (current.includes(count - offset)) continue;
|
|
394
|
+
current = current.map((index) => (index + offset < count ? index : index - 1));
|
|
395
|
+
}
|
|
396
|
+
return current.map((index) => args[count - index]);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const resolveLabel = (
|
|
400
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
401
|
+
definitionCount: number,
|
|
402
|
+
expression: Expr,
|
|
403
|
+
label: string,
|
|
404
|
+
labelArgs: Expr[],
|
|
405
|
+
): Deref => {
|
|
406
|
+
const found = findLabel(expression, label, []);
|
|
407
|
+
if (!found) {
|
|
408
|
+
onWarning(getErrorNote(expression.location, `Could not find label ${label}`));
|
|
409
|
+
return { params: [], definitions: [], expression: BAD };
|
|
410
|
+
}
|
|
411
|
+
const indices = found.indices;
|
|
412
|
+
if (indices.length !== labelArgs.length && labelArgs.length > 0) {
|
|
413
|
+
const plural = indices.length === 1 ? "" : "s";
|
|
414
|
+
const message = `Expected ${indices.length} argument${plural} for labelled reference !${label}, but found ${labelArgs.length}`;
|
|
415
|
+
onWarning(getErrorNote(found.expression.location, message));
|
|
416
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_lsub" });
|
|
417
|
+
}
|
|
418
|
+
const params =
|
|
419
|
+
labelArgs.length === 0 ? indices.map((index) => ({ name: index.name, shape: 0 })) : [];
|
|
420
|
+
const args = labelArgs.length === 0 ? getIndices(indices.length) : labelArgs;
|
|
421
|
+
const reified = found.context.reduceRight((inner: Expr, hyp): Expr => {
|
|
422
|
+
if (hyp.kind === "Defn")
|
|
423
|
+
return at({ kind: "Let", definitions: [hyp.definition], body: inner }, inner);
|
|
424
|
+
if (hyp.kind === "Fresh") {
|
|
425
|
+
return at(
|
|
426
|
+
{
|
|
427
|
+
kind: "Quant",
|
|
428
|
+
quantifier: "Forall",
|
|
429
|
+
bounds: [{ name: hyp.name, level: "Constant", domain: { kind: "NoDomain" } }],
|
|
430
|
+
body: inner,
|
|
431
|
+
},
|
|
432
|
+
inner,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return substituteExpr(shift(-1), inner);
|
|
436
|
+
}, found.expression);
|
|
437
|
+
const shifted = substituteExpr(getReversal(definitionCount, params.length), reified);
|
|
438
|
+
const definitions: Defn[] = [];
|
|
439
|
+
let body = shifted;
|
|
440
|
+
let remaining = permute(
|
|
441
|
+
indices.map((index) => index.index),
|
|
442
|
+
args,
|
|
443
|
+
);
|
|
444
|
+
while (remaining.length > 0) {
|
|
445
|
+
if (body.kind === "Let" && body.definitions.length === 1) {
|
|
446
|
+
definitions.push(body.definitions[0]);
|
|
447
|
+
body = body.body;
|
|
448
|
+
remaining = remaining.map((arg) => substituteExpr(shift(1), arg));
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (body.kind !== "Quant" || body.bounds.length !== 1) {
|
|
452
|
+
throw new TlapmBugError(
|
|
453
|
+
'File "src/expr/e_deref.ml", line 477, characters 21-33: Assertion failed',
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
body = substituteExpr(consSubstitution(remaining[0], shift(0)), body.body);
|
|
457
|
+
remaining = remaining.slice(1);
|
|
458
|
+
}
|
|
459
|
+
return { params, definitions, expression: body };
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const resolveLetOperator = (
|
|
463
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
464
|
+
definitionCount: number,
|
|
465
|
+
expression: Expr,
|
|
466
|
+
label: string,
|
|
467
|
+
args: Expr[],
|
|
468
|
+
): Deref => {
|
|
469
|
+
if (expression.kind !== "Let") {
|
|
470
|
+
onWarning(getErrorNote(expression.location, `not a LET expression`));
|
|
471
|
+
throw new TlaProofError("not a LET expression", { failure: "Expr.Deref.deref_letop" });
|
|
472
|
+
}
|
|
473
|
+
const index = expression.definitions.findIndex(
|
|
474
|
+
(definition) => definition.kind === "Operator" && definition.name === label,
|
|
475
|
+
);
|
|
476
|
+
const definition = expression.definitions[index];
|
|
477
|
+
if (!definition || definition.kind !== "Operator") {
|
|
478
|
+
const message = `no definition of operator ${label}`;
|
|
479
|
+
onWarning(getErrorNote(expression.location, message));
|
|
480
|
+
throw new TlaProofError(message, { failure: "Expr.Deref.deref_letop" });
|
|
481
|
+
}
|
|
482
|
+
const body = definition.body;
|
|
483
|
+
const labelled =
|
|
484
|
+
body.kind === "Lambda"
|
|
485
|
+
? at(
|
|
486
|
+
{
|
|
487
|
+
...body,
|
|
488
|
+
body: at(
|
|
489
|
+
{
|
|
490
|
+
kind: "Parens",
|
|
491
|
+
expression: body.body,
|
|
492
|
+
form: {
|
|
493
|
+
kind: "Xlabel",
|
|
494
|
+
label,
|
|
495
|
+
indices: body.params.map((param, position) => ({
|
|
496
|
+
name: param.name,
|
|
497
|
+
index: body.params.length - position,
|
|
498
|
+
})),
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
body.body,
|
|
502
|
+
),
|
|
503
|
+
},
|
|
504
|
+
body,
|
|
505
|
+
)
|
|
506
|
+
: at(
|
|
507
|
+
{ kind: "Parens", expression: body, form: { kind: "Xlabel", label, indices: [] } },
|
|
508
|
+
body,
|
|
509
|
+
);
|
|
510
|
+
return resolveLabel(
|
|
511
|
+
onWarning,
|
|
512
|
+
definitionCount,
|
|
513
|
+
at({ kind: "Let", definitions: expression.definitions.slice(0, index), body: labelled }, body),
|
|
514
|
+
label,
|
|
515
|
+
args,
|
|
516
|
+
);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const deref = (
|
|
520
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
521
|
+
isLabel: boolean,
|
|
522
|
+
state: Deref,
|
|
523
|
+
selectors: Selector[],
|
|
524
|
+
): Expr => {
|
|
525
|
+
const [selector, ...rest] = selectors;
|
|
526
|
+
if (!selector) {
|
|
527
|
+
const withDefinitions =
|
|
528
|
+
state.definitions.length === 0
|
|
529
|
+
? state.expression
|
|
530
|
+
: at(
|
|
531
|
+
{ kind: "Let", definitions: state.definitions, body: state.expression },
|
|
532
|
+
state.expression,
|
|
533
|
+
);
|
|
534
|
+
const abstracted =
|
|
535
|
+
state.params.length === 0
|
|
536
|
+
? withDefinitions
|
|
537
|
+
: at({ kind: "Lambda", params: state.params, body: withDefinitions }, withDefinitions);
|
|
538
|
+
return { ...clean(abstracted), isAtomic: true };
|
|
539
|
+
}
|
|
540
|
+
if (state.expression.isAtomic) {
|
|
541
|
+
onWarning(getErrorNote(state.expression.location, `Expression has no subexpressions`));
|
|
542
|
+
return BAD;
|
|
543
|
+
}
|
|
544
|
+
const expression = isLabel ? state.expression : cleanParens(state.expression);
|
|
545
|
+
const count = state.definitions.length;
|
|
546
|
+
const next = ((): Deref & { rest: Selector[]; isLabel: boolean } => {
|
|
547
|
+
const numbered = (position: number, message: string) => {
|
|
548
|
+
const [definitions, inner] = getNumbered(
|
|
549
|
+
onWarning,
|
|
550
|
+
[],
|
|
551
|
+
expression,
|
|
552
|
+
position,
|
|
553
|
+
state.expression,
|
|
554
|
+
message,
|
|
555
|
+
);
|
|
556
|
+
return { params: [], definitions, expression: inner, rest, isLabel: false };
|
|
557
|
+
};
|
|
558
|
+
switch (selector.kind) {
|
|
559
|
+
case "Down": {
|
|
560
|
+
const [label, ...after] = rest;
|
|
561
|
+
if (expression.kind === "Let" && label?.kind === "Label" && isLabel) {
|
|
562
|
+
const result = resolveLetOperator(onWarning, count, expression, label.label, label.args);
|
|
563
|
+
return { ...result, rest: after, isLabel: true };
|
|
564
|
+
}
|
|
565
|
+
if (isLabel) return { params: [], definitions: [], expression, rest, isLabel: false };
|
|
566
|
+
throw new TlaProofError("!: is forbidden for this expression.\n", {
|
|
567
|
+
failure: "Expr.Deref.deref: !:",
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
case "Number":
|
|
571
|
+
return numbered(selector.value, `Cannot resolve !${selector.value}`);
|
|
572
|
+
case "Left":
|
|
573
|
+
return numbered(1, "Cannot resolve !<<");
|
|
574
|
+
case "Right":
|
|
575
|
+
return numbered(2, "Cannot resolve !>>");
|
|
576
|
+
case "Instance":
|
|
577
|
+
return {
|
|
578
|
+
params: [],
|
|
579
|
+
definitions: [],
|
|
580
|
+
expression: instantiate(onWarning, expression, selector.args),
|
|
581
|
+
rest,
|
|
582
|
+
isLabel: false,
|
|
583
|
+
};
|
|
584
|
+
case "Label": {
|
|
585
|
+
const result =
|
|
586
|
+
expression.kind === "Let" && !isLabel
|
|
587
|
+
? resolveLetOperator(onWarning, count, expression, selector.label, selector.args)
|
|
588
|
+
: resolveLabel(onWarning, count, expression, selector.label, selector.args);
|
|
589
|
+
return { ...result, rest, isLabel: true };
|
|
590
|
+
}
|
|
591
|
+
case "At": {
|
|
592
|
+
const [params, inner] = abstract(onWarning, count, expression);
|
|
593
|
+
return { params, definitions: [], expression: inner, rest, isLabel: false };
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
})();
|
|
597
|
+
return deref(
|
|
598
|
+
onWarning,
|
|
599
|
+
next.isLabel,
|
|
600
|
+
{
|
|
601
|
+
params: [...state.params, ...next.params],
|
|
602
|
+
definitions: [...state.definitions, ...next.definitions],
|
|
603
|
+
expression: next.expression,
|
|
604
|
+
},
|
|
605
|
+
next.rest.map((item) =>
|
|
606
|
+
mapSelectorArgs(item, (arg) => substituteExpr(shift(next.definitions.length), arg)),
|
|
607
|
+
),
|
|
608
|
+
);
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const getInitialDeref = (
|
|
612
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
613
|
+
operator: Expr,
|
|
614
|
+
args: Expr[],
|
|
615
|
+
): Deref => {
|
|
616
|
+
if (args.length > 0) {
|
|
617
|
+
return { params: [], definitions: [], expression: instantiate(onWarning, operator, args) };
|
|
618
|
+
}
|
|
619
|
+
if (operator.kind !== "Lambda") return { params: [], definitions: [], expression: operator };
|
|
620
|
+
const [params, expression] = abstract(onWarning, 0, operator);
|
|
621
|
+
return { params, definitions: [], expression };
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
export const resolveSubexpression = (
|
|
625
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
626
|
+
operator: Expr,
|
|
627
|
+
args: Expr[],
|
|
628
|
+
selectors: Selector[],
|
|
629
|
+
) => {
|
|
630
|
+
const result = deref(onWarning, true, getInitialDeref(onWarning, operator, args), selectors);
|
|
631
|
+
if (isBad(result)) {
|
|
632
|
+
const message = "Could not resolve subexpression reference";
|
|
633
|
+
onWarning(getErrorNote(operator.location, message));
|
|
634
|
+
throw new TlaProofError(message, { failure: "Expr.Anon: 2" });
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
637
|
+
};
|