tla-ts 0.0.1 → 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 +81 -42
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +215 -30
- package/dist/index.d.ts +215 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- 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 -1
- package/dist/run-cli.js +20 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +6 -2
- package/src/ast.ts +122 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +945 -238
- package/src/cli-options.ts +58 -0
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +59 -1
- package/src/evaluate.ts +489 -137
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +2 -2
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +103 -28
- package/src/liveness.ts +47 -91
- 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 +78 -30
- package/src/parallel.ts +116 -62
- package/src/parser.ts +958 -205
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +427 -61
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +65 -21
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +26 -5
- package/src/values.ts +1231 -176
package/src/resolve.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AssumeProve,
|
|
2
3
|
Bound,
|
|
3
4
|
BoundPattern,
|
|
4
5
|
Definition,
|
|
5
6
|
Expression,
|
|
6
7
|
Instance,
|
|
7
8
|
Module,
|
|
9
|
+
NewSymbol,
|
|
8
10
|
OperatorParam,
|
|
11
|
+
Proof,
|
|
12
|
+
ProofStep,
|
|
9
13
|
SourceLocation,
|
|
14
|
+
Theorem,
|
|
10
15
|
} from "./ast";
|
|
11
16
|
import { TlaSyntaxError } from "./errors";
|
|
12
17
|
import { getChildren, isLocallyResolved } from "./evaluate";
|
|
@@ -14,6 +19,7 @@ import { getChildren, isLocallyResolved } from "./evaluate";
|
|
|
14
19
|
export interface ModuleSymbol {
|
|
15
20
|
key: string;
|
|
16
21
|
arity: number;
|
|
22
|
+
declaration?: "Constant" | "Variable";
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export type Symbols = Map<string, ModuleSymbol>;
|
|
@@ -30,14 +36,21 @@ interface Unit {
|
|
|
30
36
|
getKey: (definition: Definition) => string;
|
|
31
37
|
location: SourceLocation;
|
|
32
38
|
self: Definition | undefined;
|
|
39
|
+
facts: Set<string>;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
const LOCAL_RESOLUTION = { kind: "Local" } as const;
|
|
42
|
+
export const LOCAL_RESOLUTION = { kind: "Local" } as const;
|
|
36
43
|
|
|
37
44
|
const renamedArities = new WeakMap<Expression, number>();
|
|
38
45
|
|
|
39
46
|
export const KEY_SEPARATOR = "@";
|
|
40
47
|
|
|
48
|
+
// A key is a name, the separator, and an id, so the operator @@ is not one.
|
|
49
|
+
const isKey = (name: string) => {
|
|
50
|
+
const index = name.lastIndexOf(KEY_SEPARATOR);
|
|
51
|
+
return index > 0 && index < name.length - 1;
|
|
52
|
+
};
|
|
53
|
+
|
|
41
54
|
const CORE_SYMBOLS: Symbols = new Map(
|
|
42
55
|
["~>", "-+->", "\\cdot"].map((name) => [name, { key: name, arity: 2 }]),
|
|
43
56
|
);
|
|
@@ -45,7 +58,7 @@ const CORE_SYMBOLS: Symbols = new Map(
|
|
|
45
58
|
export const isBefore = (left: SourceLocation, right: SourceLocation) =>
|
|
46
59
|
left.line < right.line || (left.line === right.line && left.column < right.column);
|
|
47
60
|
|
|
48
|
-
const getArity = (definition: Definition) =>
|
|
61
|
+
export const getArity = (definition: Definition) =>
|
|
49
62
|
definition.kind === "Operator" ? definition.params.length : 0;
|
|
50
63
|
|
|
51
64
|
const isInstantiated = (definition: Definition) => definition.name.includes("!");
|
|
@@ -60,9 +73,44 @@ const lookupLocal = (scope: LocalName | undefined, name: string) => {
|
|
|
60
73
|
const bindNames = (scope: LocalName | undefined, names: string[], arity = 0) =>
|
|
61
74
|
names.reduce<LocalName | undefined>((parent, name) => ({ name, arity, parent }), scope);
|
|
62
75
|
|
|
63
|
-
const
|
|
76
|
+
const bindParams = (scope: LocalName | undefined, params: OperatorParam[]) =>
|
|
77
|
+
params.reduce<LocalName | undefined>(
|
|
78
|
+
(parent, param) => ({ name: param.name, arity: param.arity, parent }),
|
|
79
|
+
scope,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const getPatternNames = (pattern: BoundPattern) =>
|
|
64
83
|
pattern.kind === "Name" ? [pattern.name] : pattern.names;
|
|
65
84
|
|
|
85
|
+
export const getBoundNames = (bounds: Bound[]) =>
|
|
86
|
+
bounds.flatMap((bound) => getPatternNames(bound.pattern));
|
|
87
|
+
|
|
88
|
+
const getVisibleDefinition = (name: string, unit: Unit) => {
|
|
89
|
+
const definition = unit.definitions.get(name);
|
|
90
|
+
const isVisible =
|
|
91
|
+
definition !== undefined &&
|
|
92
|
+
(definition === unit.self
|
|
93
|
+
? definition.kind === "Function" || definition.isRecursive
|
|
94
|
+
: definition.isRecursive || isBefore(definition.location, unit.location));
|
|
95
|
+
return isVisible ? definition : undefined;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const findSymbol = (name: string, unit: Unit): ModuleSymbol | undefined => {
|
|
99
|
+
const definition = getVisibleDefinition(name, unit);
|
|
100
|
+
return definition
|
|
101
|
+
? { key: unit.getKey(definition), arity: getArity(definition) }
|
|
102
|
+
: unit.imported.get(name);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// SANY names an undefined operator symbol such as + differently from an undefined identifier.
|
|
106
|
+
const isOperatorSymbol = (name: string) =>
|
|
107
|
+
name[0].toLowerCase() === name[0].toUpperCase() &&
|
|
108
|
+
name[0] !== "_" &&
|
|
109
|
+
(name[0] < "0" || name[0] > "9");
|
|
110
|
+
|
|
111
|
+
const isStepName = (name: string) =>
|
|
112
|
+
name.startsWith("<") && ((name[1] >= "0" && name[1] <= "9") || name[1] === "*");
|
|
113
|
+
|
|
66
114
|
const getSymbolArity = (
|
|
67
115
|
expression: Expression & { kind: "Identifier" | "Apply" | "OperatorReference" },
|
|
68
116
|
name: string,
|
|
@@ -72,28 +120,34 @@ const getSymbolArity = (
|
|
|
72
120
|
const location = expression.location;
|
|
73
121
|
const renamed = renamedArities.get(expression);
|
|
74
122
|
if (renamed !== undefined) return renamed;
|
|
75
|
-
if (name
|
|
123
|
+
if (isKey(name)) return expression.kind === "Apply" ? expression.args.length : 0;
|
|
124
|
+
if (isStepName(name)) return 0;
|
|
76
125
|
const local = lookupLocal(scope, name);
|
|
77
126
|
if (local && expression.kind !== "OperatorReference") expression.resolution = LOCAL_RESOLUTION;
|
|
78
127
|
if (local) return local.arity;
|
|
79
|
-
const
|
|
80
|
-
const isVisible =
|
|
81
|
-
definition !== undefined &&
|
|
82
|
-
(definition === unit.self
|
|
83
|
-
? definition.kind === "Function" || definition.isRecursive
|
|
84
|
-
: definition.isRecursive || isBefore(definition.location, unit.location));
|
|
85
|
-
const symbol =
|
|
86
|
-
definition && isVisible
|
|
87
|
-
? { key: unit.getKey(definition), arity: getArity(definition) }
|
|
88
|
-
: unit.imported.get(name);
|
|
128
|
+
const symbol = findSymbol(name, unit);
|
|
89
129
|
if (symbol) {
|
|
90
130
|
if (expression.kind === "Identifier") expression.name = symbol.key;
|
|
91
131
|
else expression.operator = symbol.key;
|
|
92
132
|
renamedArities.set(expression, symbol.arity);
|
|
93
133
|
return symbol.arity;
|
|
94
134
|
}
|
|
135
|
+
// A name such as Inv!P1 selects the label P1 of a definition or theorem, which takes its own
|
|
136
|
+
// arguments.
|
|
137
|
+
const prefix = name.slice(0, name.indexOf("!"));
|
|
138
|
+
const isSelected =
|
|
139
|
+
prefix !== "" &&
|
|
140
|
+
(lookupLocal(scope, prefix) !== undefined ||
|
|
141
|
+
findSymbol(prefix, unit) !== undefined ||
|
|
142
|
+
unit.facts.has(prefix));
|
|
143
|
+
if (isSelected) {
|
|
144
|
+
return expression.kind === "Apply" ? expression.args.length : 0;
|
|
145
|
+
}
|
|
146
|
+
const position = `at line ${location.line}, column ${location.column}`;
|
|
95
147
|
throw new TlaSyntaxError(
|
|
96
|
-
|
|
148
|
+
isOperatorSymbol(name)
|
|
149
|
+
? `Could not find declaration or definition of symbol '${name}' ${position}`
|
|
150
|
+
: `Unknown operator: \`${name}' ${position}`,
|
|
97
151
|
);
|
|
98
152
|
};
|
|
99
153
|
|
|
@@ -123,11 +177,7 @@ const resolveDefinition = (definition: Definition, scope: LocalName | undefined,
|
|
|
123
177
|
markRecursiveConstructions(definition.body, definition.name);
|
|
124
178
|
return;
|
|
125
179
|
}
|
|
126
|
-
|
|
127
|
-
(parent, param) => ({ name: param.name, arity: param.arity, parent }),
|
|
128
|
-
self,
|
|
129
|
-
);
|
|
130
|
-
resolveExpression(definition.body, params, unit);
|
|
180
|
+
resolveExpression(definition.body, bindParams(self, definition.params), unit);
|
|
131
181
|
};
|
|
132
182
|
|
|
133
183
|
const resolveDefinitions = (
|
|
@@ -147,22 +197,67 @@ const resolveDefinitions = (
|
|
|
147
197
|
}, recursiveScope);
|
|
148
198
|
};
|
|
149
199
|
|
|
200
|
+
// OpDefNode#match: a parameter of arity 0 takes an expression, and a parameter of arity n takes an
|
|
201
|
+
// operator of arity n whose own parameters take expressions.
|
|
202
|
+
const checkArgument = (
|
|
203
|
+
definition: Definition,
|
|
204
|
+
index: number,
|
|
205
|
+
arg: Expression,
|
|
206
|
+
argArity: number,
|
|
207
|
+
argDefinition: Definition | undefined,
|
|
208
|
+
) => {
|
|
209
|
+
const param = definition.kind === "Operator" ? definition.params[index] : undefined;
|
|
210
|
+
const location = arg.location;
|
|
211
|
+
if (param?.arity === 0 && argArity > 0) {
|
|
212
|
+
throw new TlaSyntaxError(
|
|
213
|
+
`Operator used in argument number ${index + 1} has incorrect number of arguments at line ${location.line}, column ${location.column}`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const isHigherOrder =
|
|
217
|
+
argDefinition?.kind === "Operator" && argDefinition.params.some((inner) => inner.arity > 0);
|
|
218
|
+
if (param !== undefined && param.arity > 0 && (argArity !== param.arity || isHigherOrder)) {
|
|
219
|
+
throw new TlaSyntaxError(
|
|
220
|
+
`Argument number ${index + 1} to operator '${definition.name}' should be a ${param.arity}-parameter operator at line ${location.line}, column ${location.column}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const getArgumentName = (arg: Expression) =>
|
|
226
|
+
arg.kind === "Identifier"
|
|
227
|
+
? arg.name
|
|
228
|
+
: arg.kind === "OperatorReference"
|
|
229
|
+
? arg.operator
|
|
230
|
+
: undefined;
|
|
231
|
+
|
|
150
232
|
const resolveApply = (
|
|
151
233
|
expression: Expression & { kind: "Apply" },
|
|
152
234
|
scope: LocalName | undefined,
|
|
153
235
|
unit: Unit,
|
|
154
236
|
) => {
|
|
237
|
+
const callee = lookupLocal(scope, expression.operator)
|
|
238
|
+
? undefined
|
|
239
|
+
: getVisibleDefinition(expression.operator, unit);
|
|
155
240
|
const arity = getSymbolArity(expression, expression.operator, scope, unit);
|
|
156
241
|
if (arity !== expression.args.length) {
|
|
157
242
|
throw new TlaSyntaxError(
|
|
158
243
|
`The operator ${expression.operator} requires ${arity} arguments at line ${expression.location.line}, column ${expression.location.column}`,
|
|
159
244
|
);
|
|
160
245
|
}
|
|
161
|
-
expression.args.forEach((arg) =>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
246
|
+
expression.args.forEach((arg, index) => {
|
|
247
|
+
const name = getArgumentName(arg);
|
|
248
|
+
const argDefinition =
|
|
249
|
+
name === undefined || lookupLocal(scope, name) ? undefined : getVisibleDefinition(name, unit);
|
|
250
|
+
const argArity = resolveArgument(arg, scope, unit);
|
|
251
|
+
if (callee) checkArgument(callee, index, arg, argArity, argDefinition);
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// An argument may name an operator that takes arguments of its own.
|
|
256
|
+
const resolveArgument = (arg: Expression, scope: LocalName | undefined, unit: Unit) => {
|
|
257
|
+
if (arg.kind === "Identifier") return getSymbolArity(arg, arg.name, scope, unit);
|
|
258
|
+
if (arg.kind === "OperatorReference") return getSymbolArity(arg, arg.operator, scope, unit);
|
|
259
|
+
resolveExpression(arg, scope, unit);
|
|
260
|
+
return arg.kind === "Lambda" ? arg.params.length : 0;
|
|
166
261
|
};
|
|
167
262
|
|
|
168
263
|
const resolveExpression = (
|
|
@@ -174,6 +269,7 @@ const resolveExpression = (
|
|
|
174
269
|
expressions.forEach((child) => resolveExpression(child, scope, unit));
|
|
175
270
|
switch (expression.kind) {
|
|
176
271
|
case "Number":
|
|
272
|
+
case "Decimal":
|
|
177
273
|
case "String":
|
|
178
274
|
case "ExceptAt":
|
|
179
275
|
return;
|
|
@@ -288,24 +384,338 @@ const resolveExpression = (
|
|
|
288
384
|
resolveExpression(expression.body, scope, unit);
|
|
289
385
|
return;
|
|
290
386
|
case "Subexpression":
|
|
291
|
-
|
|
292
|
-
|
|
387
|
+
expression.selectors
|
|
388
|
+
.flatMap((selector) => ("args" in selector ? selector.args : []))
|
|
389
|
+
.forEach((arg) => resolveArgument(arg, scope, unit));
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const resolveNewSymbol = (symbol: NewSymbol, scope: LocalName | undefined, unit: Unit) => {
|
|
394
|
+
if (symbol.set) resolveExpression(symbol.set, scope, unit);
|
|
395
|
+
return { name: symbol.name, arity: symbol.arity, parent: scope };
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// The NEW symbols of an ASSUME stay in scope for its conclusion and the proof that follows.
|
|
399
|
+
const resolveAssumeProve = (
|
|
400
|
+
assumeProve: AssumeProve,
|
|
401
|
+
scope: LocalName | undefined,
|
|
402
|
+
unit: Unit,
|
|
403
|
+
): LocalName | undefined => {
|
|
404
|
+
const assumed = assumeProve.assumptions.reduce((current, assumption) => {
|
|
405
|
+
if (assumption.kind === "NewSymbol") return resolveNewSymbol(assumption, current, unit);
|
|
406
|
+
if (assumption.kind === "AssumeProve") resolveAssumeProve(assumption, current, unit);
|
|
407
|
+
else resolveExpression(assumption, current, unit);
|
|
408
|
+
return current;
|
|
409
|
+
}, scope);
|
|
410
|
+
resolveExpression(assumeProve.conclusion, assumed, unit);
|
|
411
|
+
return assumed;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const resolveStatement = (
|
|
415
|
+
statement: Expression | AssumeProve,
|
|
416
|
+
scope: LocalName | undefined,
|
|
417
|
+
unit: Unit,
|
|
418
|
+
) => {
|
|
419
|
+
if (statement.kind === "AssumeProve") return resolveAssumeProve(statement, scope, unit);
|
|
420
|
+
resolveExpression(statement, scope, unit);
|
|
421
|
+
return scope;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// SANY resolves the expressions of a proof, while its facts and DEF clauses may also name
|
|
425
|
+
// theorems, assumptions, and modules.
|
|
426
|
+
const resolveProof = (proof: Proof | undefined, scope: LocalName | undefined, unit: Unit) => {
|
|
427
|
+
if (proof?.kind !== "Steps") return;
|
|
428
|
+
proof.steps.reduce((current, step) => resolveStep(step, current, unit), scope);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const resolveStep = (
|
|
432
|
+
step: ProofStep,
|
|
433
|
+
scope: LocalName | undefined,
|
|
434
|
+
unit: Unit,
|
|
435
|
+
): LocalName | undefined => {
|
|
436
|
+
const body = step.body;
|
|
437
|
+
switch (body.kind) {
|
|
438
|
+
case "Assert": {
|
|
439
|
+
const assumed = resolveStatement(body.statement, scope, unit);
|
|
440
|
+
resolveProof(step.proof, assumed, unit);
|
|
441
|
+
return body.isSuffices ? assumed : scope;
|
|
442
|
+
}
|
|
443
|
+
case "Define":
|
|
444
|
+
return resolveDefinitions(
|
|
445
|
+
body.definitions.flatMap((definition) =>
|
|
446
|
+
definition.kind === "Instance" ? [] : [definition],
|
|
447
|
+
),
|
|
448
|
+
scope,
|
|
449
|
+
unit,
|
|
293
450
|
);
|
|
451
|
+
case "Have":
|
|
452
|
+
case "Case":
|
|
453
|
+
resolveExpression(body.expression, scope, unit);
|
|
454
|
+
resolveProof(step.proof, scope, unit);
|
|
455
|
+
return scope;
|
|
456
|
+
case "Witness":
|
|
457
|
+
body.expressions.forEach((expression) => resolveExpression(expression, scope, unit));
|
|
458
|
+
return scope;
|
|
459
|
+
case "Take":
|
|
460
|
+
return bindNames(resolveBounds(body.bounds, scope, unit), body.names);
|
|
461
|
+
case "Pick": {
|
|
462
|
+
const picked = bindNames(resolveBounds(body.bounds, scope, unit), body.names);
|
|
463
|
+
resolveExpression(body.body, picked, unit);
|
|
464
|
+
resolveProof(step.proof, scope, unit);
|
|
465
|
+
return picked;
|
|
466
|
+
}
|
|
467
|
+
default:
|
|
468
|
+
resolveProof(step.proof, scope, unit);
|
|
469
|
+
return scope;
|
|
294
470
|
}
|
|
295
471
|
};
|
|
296
472
|
|
|
473
|
+
const resolveTheorem = (theorem: Theorem, unit: Unit) => {
|
|
474
|
+
const isDefinition = theorem.name !== undefined && theorem.statement.kind !== "AssumeProve";
|
|
475
|
+
const scope = isDefinition ? undefined : resolveStatement(theorem.statement, undefined, unit);
|
|
476
|
+
resolveProof(theorem.proof, scope, unit);
|
|
477
|
+
};
|
|
478
|
+
|
|
297
479
|
interface ModuleUnit {
|
|
298
480
|
imported: Symbols;
|
|
481
|
+
importedAt: Map<string, SourceLocation>;
|
|
299
482
|
definitions: Map<string, Definition>;
|
|
300
483
|
getKey: (definition: Definition) => string;
|
|
301
484
|
}
|
|
302
485
|
|
|
486
|
+
interface ModuleName {
|
|
487
|
+
name: string;
|
|
488
|
+
kind: "Definition" | "Constant" | "Variable";
|
|
489
|
+
location: SourceLocation;
|
|
490
|
+
isRecursive: boolean;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
type IsDefined = (name: string) => boolean;
|
|
494
|
+
|
|
495
|
+
const createRedefinitionError = (name: string, location: SourceLocation) =>
|
|
496
|
+
new TlaSyntaxError(
|
|
497
|
+
`Multiply-defined symbol '${name}' at line ${location.line}, column ${location.column}`,
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
const bindUnique = (
|
|
501
|
+
scope: LocalName | undefined,
|
|
502
|
+
names: string[],
|
|
503
|
+
isDefined: IsDefined,
|
|
504
|
+
location: SourceLocation,
|
|
505
|
+
) =>
|
|
506
|
+
names.reduce<LocalName | undefined>((parent, name) => {
|
|
507
|
+
if (lookupLocal(parent, name) || isDefined(name)) throw createRedefinitionError(name, location);
|
|
508
|
+
return { name, arity: 0, parent };
|
|
509
|
+
}, scope);
|
|
510
|
+
|
|
511
|
+
const checkBoundNames = (
|
|
512
|
+
bounds: Bound[],
|
|
513
|
+
scope: LocalName | undefined,
|
|
514
|
+
isDefined: IsDefined,
|
|
515
|
+
location: SourceLocation,
|
|
516
|
+
) => {
|
|
517
|
+
bounds.forEach((bound) => checkNames(bound.set, scope, isDefined));
|
|
518
|
+
return bounds.reduce(
|
|
519
|
+
(current, bound) => bindUnique(current, getPatternNames(bound.pattern), isDefined, location),
|
|
520
|
+
scope,
|
|
521
|
+
);
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const checkDefinitionNames = (
|
|
525
|
+
definition: Definition,
|
|
526
|
+
scope: LocalName | undefined,
|
|
527
|
+
isDefined: IsDefined,
|
|
528
|
+
) => {
|
|
529
|
+
if (definition.kind === "Function") {
|
|
530
|
+
const self = bindNames(scope, [definition.name]);
|
|
531
|
+
const bound = checkBoundNames(definition.bounds, self, isDefined, definition.location);
|
|
532
|
+
checkNames(definition.body, bound, isDefined);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const params = bindUnique(
|
|
536
|
+
scope,
|
|
537
|
+
definition.params.map((param) => param.name),
|
|
538
|
+
isDefined,
|
|
539
|
+
definition.location,
|
|
540
|
+
);
|
|
541
|
+
checkNames(definition.body, params, isDefined);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const checkNames = (
|
|
545
|
+
expression: Expression,
|
|
546
|
+
scope: LocalName | undefined,
|
|
547
|
+
isDefined: IsDefined,
|
|
548
|
+
): void => {
|
|
549
|
+
const location = expression.location;
|
|
550
|
+
switch (expression.kind) {
|
|
551
|
+
case "Lambda":
|
|
552
|
+
checkNames(
|
|
553
|
+
expression.body,
|
|
554
|
+
bindUnique(scope, expression.params, isDefined, location),
|
|
555
|
+
isDefined,
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
case "Let": {
|
|
559
|
+
const recursive = expression.definitions
|
|
560
|
+
.filter((definition) => definition.isRecursive)
|
|
561
|
+
.map((definition) => definition.name);
|
|
562
|
+
const inner = expression.definitions.reduce((current, definition) => {
|
|
563
|
+
if (lookupLocal(current, definition.name) || isDefined(definition.name)) {
|
|
564
|
+
throw createRedefinitionError(definition.name, definition.location);
|
|
565
|
+
}
|
|
566
|
+
checkDefinitionNames(definition, bindNames(current, recursive), isDefined);
|
|
567
|
+
return bindNames(current, [definition.name]);
|
|
568
|
+
}, scope);
|
|
569
|
+
checkNames(
|
|
570
|
+
expression.body,
|
|
571
|
+
bindUnique(
|
|
572
|
+
inner,
|
|
573
|
+
(expression.instances ?? []).flatMap((instance) =>
|
|
574
|
+
instance.name === undefined ? [] : [instance.name],
|
|
575
|
+
),
|
|
576
|
+
isDefined,
|
|
577
|
+
location,
|
|
578
|
+
),
|
|
579
|
+
isDefined,
|
|
580
|
+
);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
case "Forall":
|
|
584
|
+
case "Exists":
|
|
585
|
+
case "FunctionConstruction":
|
|
586
|
+
checkNames(
|
|
587
|
+
expression.body,
|
|
588
|
+
checkBoundNames(expression.bounds, scope, isDefined, location),
|
|
589
|
+
isDefined,
|
|
590
|
+
);
|
|
591
|
+
return;
|
|
592
|
+
case "SetFilter":
|
|
593
|
+
checkNames(
|
|
594
|
+
expression.predicate,
|
|
595
|
+
checkBoundNames([expression.bound], scope, isDefined, location),
|
|
596
|
+
isDefined,
|
|
597
|
+
);
|
|
598
|
+
return;
|
|
599
|
+
case "SetMap":
|
|
600
|
+
checkNames(
|
|
601
|
+
expression.expression,
|
|
602
|
+
checkBoundNames(expression.bounds, scope, isDefined, location),
|
|
603
|
+
isDefined,
|
|
604
|
+
);
|
|
605
|
+
return;
|
|
606
|
+
case "UnboundedQuantifier":
|
|
607
|
+
checkNames(
|
|
608
|
+
expression.body,
|
|
609
|
+
bindUnique(scope, expression.names, isDefined, location),
|
|
610
|
+
isDefined,
|
|
611
|
+
);
|
|
612
|
+
return;
|
|
613
|
+
case "Choose":
|
|
614
|
+
if (expression.set) checkNames(expression.set, scope, isDefined);
|
|
615
|
+
checkNames(
|
|
616
|
+
expression.body,
|
|
617
|
+
bindUnique(scope, getPatternNames(expression.pattern), isDefined, location),
|
|
618
|
+
isDefined,
|
|
619
|
+
);
|
|
620
|
+
return;
|
|
621
|
+
default:
|
|
622
|
+
getChildren(expression).forEach((child) => checkNames(child, scope, isDefined));
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const getModuleNames = (module: Module) => [
|
|
627
|
+
...module.definitions.map((definition): ModuleName => ({
|
|
628
|
+
name: definition.name,
|
|
629
|
+
kind: "Definition",
|
|
630
|
+
location: definition.location,
|
|
631
|
+
isRecursive: definition.isRecursive,
|
|
632
|
+
})),
|
|
633
|
+
...module.instances.flatMap((instance): ModuleName[] =>
|
|
634
|
+
instance.name === undefined
|
|
635
|
+
? []
|
|
636
|
+
: [
|
|
637
|
+
{
|
|
638
|
+
name: instance.name,
|
|
639
|
+
kind: "Definition",
|
|
640
|
+
location: instance.location,
|
|
641
|
+
isRecursive: false,
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
),
|
|
645
|
+
...module.constants.map((constant): ModuleName => ({
|
|
646
|
+
name: constant.name,
|
|
647
|
+
kind: "Constant",
|
|
648
|
+
location: constant.location,
|
|
649
|
+
isRecursive: false,
|
|
650
|
+
})),
|
|
651
|
+
...module.variables.map((variable): ModuleName => ({
|
|
652
|
+
name: variable.name,
|
|
653
|
+
kind: "Variable",
|
|
654
|
+
location: variable.location,
|
|
655
|
+
isRecursive: false,
|
|
656
|
+
})),
|
|
657
|
+
];
|
|
658
|
+
|
|
659
|
+
export const checkRedefinitions = (module: Module, unit: ModuleUnit) => {
|
|
660
|
+
const byName = new Map<string, ModuleName[]>();
|
|
661
|
+
getModuleNames(module)
|
|
662
|
+
.sort((left, right) => (isBefore(left.location, right.location) ? -1 : 1))
|
|
663
|
+
.forEach((entry) => byName.set(entry.name, [...(byName.get(entry.name) ?? []), entry]));
|
|
664
|
+
const isImportedBefore = (name: string, location: SourceLocation) => {
|
|
665
|
+
if (!unit.imported.has(name) && !CORE_SYMBOLS.has(name)) return false;
|
|
666
|
+
const importedAt = unit.importedAt.get(name);
|
|
667
|
+
return importedAt === undefined || isBefore(importedAt, location);
|
|
668
|
+
};
|
|
669
|
+
byName.forEach((entries) =>
|
|
670
|
+
entries.forEach((entry, index) => {
|
|
671
|
+
const isDuplicate = entries
|
|
672
|
+
.slice(0, index)
|
|
673
|
+
.some(
|
|
674
|
+
(earlier) =>
|
|
675
|
+
earlier.kind === "Definition" ||
|
|
676
|
+
entry.kind === "Definition" ||
|
|
677
|
+
earlier.kind !== entry.kind,
|
|
678
|
+
);
|
|
679
|
+
// A module may declare again a constant or variable that it extends or that encloses it.
|
|
680
|
+
const isRedeclared = unit.imported.get(entry.name)?.declaration === entry.kind;
|
|
681
|
+
if (isDuplicate || (!isRedeclared && isImportedBefore(entry.name, entry.location)))
|
|
682
|
+
throw createRedefinitionError(entry.name, entry.location);
|
|
683
|
+
}),
|
|
684
|
+
);
|
|
685
|
+
const getIsDefined =
|
|
686
|
+
(location: SourceLocation): IsDefined =>
|
|
687
|
+
(name) =>
|
|
688
|
+
(byName.get(name) ?? []).some(
|
|
689
|
+
(entry) => entry.isRecursive || isBefore(entry.location, location),
|
|
690
|
+
) || isImportedBefore(name, location);
|
|
691
|
+
module.definitions.forEach((definition) =>
|
|
692
|
+
checkDefinitionNames(definition, undefined, getIsDefined(definition.location)),
|
|
693
|
+
);
|
|
694
|
+
module.assumptions.forEach((assumption) =>
|
|
695
|
+
checkNames(assumption.expression, undefined, getIsDefined(assumption.location)),
|
|
696
|
+
);
|
|
697
|
+
module.instances.forEach((instance) => {
|
|
698
|
+
const isDefined = getIsDefined(instance.location);
|
|
699
|
+
const params = bindUnique(
|
|
700
|
+
undefined,
|
|
701
|
+
instance.params.map((param) => param.name),
|
|
702
|
+
isDefined,
|
|
703
|
+
instance.location,
|
|
704
|
+
);
|
|
705
|
+
instance.substitutions.forEach((substitution) =>
|
|
706
|
+
checkNames(substitution.expression, params, isDefined),
|
|
707
|
+
);
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
|
|
303
711
|
const createSymbols = (module: Module, imported: Symbols) => {
|
|
304
712
|
const symbols = new Map([...CORE_SYMBOLS, ...imported]);
|
|
305
713
|
module.constants.forEach((constant) =>
|
|
306
714
|
symbols.set(constant.name, { key: constant.name, arity: constant.arity }),
|
|
307
715
|
);
|
|
308
|
-
module.variables.forEach((variable) =>
|
|
716
|
+
module.variables.forEach((variable) =>
|
|
717
|
+
symbols.set(variable.name, { key: variable.name, arity: 0 }),
|
|
718
|
+
);
|
|
309
719
|
return symbols;
|
|
310
720
|
};
|
|
311
721
|
|
|
@@ -315,48 +725,69 @@ export const resolveSubstitutions = (
|
|
|
315
725
|
module: Module,
|
|
316
726
|
unit: ModuleUnit,
|
|
317
727
|
) => {
|
|
318
|
-
const scope =
|
|
319
|
-
undefined,
|
|
320
|
-
instance.params.map((param) => param.name),
|
|
321
|
-
);
|
|
728
|
+
const scope = bindParams(undefined, instance.params);
|
|
322
729
|
const context: Unit = {
|
|
323
730
|
imported: createSymbols(module, unit.imported),
|
|
324
731
|
definitions: unit.definitions,
|
|
325
732
|
getKey: unit.getKey,
|
|
326
733
|
location: instance.location,
|
|
327
734
|
self: undefined,
|
|
735
|
+
facts: new Set(),
|
|
328
736
|
};
|
|
329
737
|
const explicit = new Map(instance.substitutions.map((item) => [item.name, item.expression]));
|
|
738
|
+
const position = `in the instance of ${instance.module} at line ${instance.location.line}, column ${instance.location.column}`;
|
|
739
|
+
explicit.forEach((_, name) => {
|
|
740
|
+
if (declarations.every((declaration) => declaration.name !== name)) {
|
|
741
|
+
throw new TlaSyntaxError(
|
|
742
|
+
`Identifier '${name}' is not a legal target of a substitution ${position}`,
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
330
746
|
return new Map(
|
|
331
747
|
declarations.map((declaration): [string, Expression] => {
|
|
332
|
-
const
|
|
748
|
+
const name = declaration.name;
|
|
749
|
+
if (!explicit.has(name) && !lookupLocal(scope, name) && !findSymbol(name, context)) {
|
|
750
|
+
throw new TlaSyntaxError(`Substitution missing for symbol ${name} ${position}`);
|
|
751
|
+
}
|
|
752
|
+
const expression: Expression = explicit.get(name) ?? {
|
|
333
753
|
kind: "Identifier",
|
|
334
|
-
name
|
|
754
|
+
name,
|
|
335
755
|
location: instance.location,
|
|
336
756
|
};
|
|
337
|
-
if (declaration.arity
|
|
338
|
-
getSymbolArity(expression, expression.name, scope, context);
|
|
339
|
-
} else {
|
|
757
|
+
if (declaration.arity === 0 || expression.kind !== "Identifier") {
|
|
340
758
|
resolveExpression(expression, scope, context);
|
|
759
|
+
return [name, expression];
|
|
341
760
|
}
|
|
342
|
-
|
|
761
|
+
if (getSymbolArity(expression, expression.name, scope, context) !== declaration.arity) {
|
|
762
|
+
throw new TlaSyntaxError(
|
|
763
|
+
`An operator must be substituted for symbol '${name}', and it must have arity ${declaration.arity} ${position}`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return [name, expression];
|
|
343
767
|
}),
|
|
344
768
|
);
|
|
345
769
|
};
|
|
346
770
|
|
|
347
771
|
export const resolveModule = (module: Module, unit: ModuleUnit) => {
|
|
348
772
|
const symbols = createSymbols(module, unit.imported);
|
|
349
|
-
const
|
|
773
|
+
const facts = new Set(
|
|
774
|
+
[...module.theorems, ...module.assumptions].flatMap((fact) => fact.name ?? []),
|
|
775
|
+
);
|
|
776
|
+
const createUnit = (location: SourceLocation, self: Definition | undefined) => ({
|
|
350
777
|
imported: symbols,
|
|
351
778
|
definitions: unit.definitions,
|
|
352
779
|
getKey: unit.getKey,
|
|
353
780
|
location,
|
|
354
781
|
self,
|
|
782
|
+
facts,
|
|
355
783
|
});
|
|
356
784
|
module.definitions.forEach((definition) =>
|
|
357
|
-
resolveDefinition(definition, undefined,
|
|
785
|
+
resolveDefinition(definition, undefined, createUnit(definition.location, definition)),
|
|
358
786
|
);
|
|
359
787
|
module.assumptions.forEach((assumption) =>
|
|
360
|
-
resolveExpression(assumption.expression, undefined,
|
|
788
|
+
resolveExpression(assumption.expression, undefined, createUnit(assumption.location, undefined)),
|
|
789
|
+
);
|
|
790
|
+
module.theorems.forEach((theorem) =>
|
|
791
|
+
resolveTheorem(theorem, createUnit(theorem.location, undefined)),
|
|
361
792
|
);
|
|
362
793
|
};
|