tla-ts 0.0.1 → 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 +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 +57 -28
- 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/lexer.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { TlaSyntaxError } from "./errors";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type TokenKind =
|
|
4
4
|
| "Identifier"
|
|
5
5
|
| "Number"
|
|
6
|
+
| "Decimal"
|
|
6
7
|
| "String"
|
|
7
8
|
| "Operator"
|
|
8
9
|
| "Keyword"
|
|
10
|
+
| "Step"
|
|
9
11
|
| "Separator"
|
|
10
12
|
| "ModuleEnd"
|
|
11
13
|
| "End";
|
|
@@ -15,22 +17,31 @@ export interface Token {
|
|
|
15
17
|
text: string;
|
|
16
18
|
line: number;
|
|
17
19
|
column: number;
|
|
20
|
+
endColumn: number;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
const KEYWORDS = new Set([
|
|
24
|
+
"ACTION",
|
|
25
|
+
"ACTIONS",
|
|
21
26
|
"ASSUME",
|
|
22
27
|
"ASSUMPTION",
|
|
23
28
|
"AXIOM",
|
|
29
|
+
"BY",
|
|
24
30
|
"CASE",
|
|
25
31
|
"CHOOSE",
|
|
26
32
|
"CONSTANT",
|
|
27
33
|
"CONSTANTS",
|
|
28
34
|
"COROLLARY",
|
|
35
|
+
"DEF",
|
|
36
|
+
"DEFINE",
|
|
37
|
+
"DEFS",
|
|
29
38
|
"DOMAIN",
|
|
30
39
|
"ELSE",
|
|
31
40
|
"ENABLED",
|
|
32
41
|
"EXCEPT",
|
|
33
42
|
"EXTENDS",
|
|
43
|
+
"HAVE",
|
|
44
|
+
"HIDE",
|
|
34
45
|
"IF",
|
|
35
46
|
"IN",
|
|
36
47
|
"INSTANCE",
|
|
@@ -39,18 +50,32 @@ const KEYWORDS = new Set([
|
|
|
39
50
|
"LET",
|
|
40
51
|
"LOCAL",
|
|
41
52
|
"MODULE",
|
|
53
|
+
"NEW",
|
|
54
|
+
"OBVIOUS",
|
|
55
|
+
"OMITTED",
|
|
56
|
+
"ONLY",
|
|
42
57
|
"OTHER",
|
|
58
|
+
"PICK",
|
|
43
59
|
"PROOF",
|
|
44
60
|
"PROPOSITION",
|
|
61
|
+
"PROVE",
|
|
62
|
+
"QED",
|
|
45
63
|
"RECURSIVE",
|
|
64
|
+
"STATE",
|
|
46
65
|
"SUBSET",
|
|
66
|
+
"SUFFICES",
|
|
67
|
+
"TAKE",
|
|
68
|
+
"TEMPORAL",
|
|
69
|
+
"TEMPORALS",
|
|
47
70
|
"THEN",
|
|
48
71
|
"THEOREM",
|
|
49
72
|
"UNCHANGED",
|
|
50
73
|
"UNION",
|
|
74
|
+
"USE",
|
|
51
75
|
"VARIABLE",
|
|
52
76
|
"VARIABLES",
|
|
53
77
|
"WITH",
|
|
78
|
+
"WITNESS",
|
|
54
79
|
]);
|
|
55
80
|
|
|
56
81
|
const BACKSLASH_OPERATORS = new Map([
|
|
@@ -249,6 +274,12 @@ const NUMBER_BASES = new Map([
|
|
|
249
274
|
["h", 16],
|
|
250
275
|
]);
|
|
251
276
|
|
|
277
|
+
// A numeral token keeps its text as typed, such as \hFF, so errors can quote it as TLC does.
|
|
278
|
+
export const getNumeralValue = (text: string) =>
|
|
279
|
+
text.startsWith("\\")
|
|
280
|
+
? parseInt(text.slice(2), NUMBER_BASES.get(text[1].toLowerCase()))
|
|
281
|
+
: Number(text);
|
|
282
|
+
|
|
252
283
|
const MODULE_HEADER = /-{4,}[ \t]*MODULE\b/;
|
|
253
284
|
|
|
254
285
|
const isIdentifierCharacter = (character: string) => /[A-Za-z0-9_]/.test(character);
|
|
@@ -262,8 +293,13 @@ export const matchOperator = (source: string, index: number): OperatorMatch | un
|
|
|
262
293
|
const character = source[index];
|
|
263
294
|
if (character === "\\" && /[A-Za-z]/.test(source[index + 1] ?? "")) {
|
|
264
295
|
const word = /^[A-Za-z]+/.exec(source.slice(index + 1))?.[0] ?? "";
|
|
265
|
-
|
|
266
|
-
|
|
296
|
+
// Like SANY, read the longest operator that the word starts with, so S\used is S \ used.
|
|
297
|
+
const length = { value: word.length };
|
|
298
|
+
while (length.value > 0 && !BACKSLASH_OPERATORS.has(word.slice(0, length.value))) {
|
|
299
|
+
length.value--;
|
|
300
|
+
}
|
|
301
|
+
const operator = BACKSLASH_OPERATORS.get(word.slice(0, length.value));
|
|
302
|
+
return { text: operator ?? "\\", length: operator === undefined ? 1 : length.value + 1 };
|
|
267
303
|
}
|
|
268
304
|
const unicodeOperator = UNICODE_OPERATORS.get(character);
|
|
269
305
|
if (unicodeOperator) return { text: unicodeOperator, length: character.length };
|
|
@@ -272,6 +308,33 @@ export const matchOperator = (source: string, index: number): OperatorMatch | un
|
|
|
272
308
|
return character === "\\" ? { text: "\\", length: 1 } : undefined;
|
|
273
309
|
};
|
|
274
310
|
|
|
311
|
+
const isDigit = (character: string | undefined) =>
|
|
312
|
+
character !== undefined && character >= "0" && character <= "9";
|
|
313
|
+
|
|
314
|
+
export const skipWhile = (
|
|
315
|
+
source: string,
|
|
316
|
+
index: number,
|
|
317
|
+
isMatch: (character: string) => boolean,
|
|
318
|
+
) => {
|
|
319
|
+
const end = { value: index };
|
|
320
|
+
while (end.value < source.length && isMatch(source[end.value])) end.value++;
|
|
321
|
+
return end.value;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// The length of a step number such as <1>a., <+>, or <2>*., which SANY reads as one token wherever
|
|
325
|
+
// it appears.
|
|
326
|
+
const matchStep = (source: string, index: number) => {
|
|
327
|
+
if (source[index] !== "<") return 0;
|
|
328
|
+
const isImplicit = source[index + 1] === "+" || source[index + 1] === "*";
|
|
329
|
+
const levelEnd = isImplicit ? index + 2 : skipWhile(source, index + 1, isDigit);
|
|
330
|
+
if (levelEnd === index + 1 || source[levelEnd] !== ">") return 0;
|
|
331
|
+
const nameEnd = skipWhile(source, levelEnd + 1, isIdentifierCharacter);
|
|
332
|
+
const isDash = nameEnd === levelEnd + 1 && (source[nameEnd] === "*" || source[nameEnd] === "-");
|
|
333
|
+
return (
|
|
334
|
+
skipWhile(source, isDash ? nameEnd + 1 : nameEnd, (character) => character === ".") - index
|
|
335
|
+
);
|
|
336
|
+
};
|
|
337
|
+
|
|
275
338
|
const TAB_SIZE = 8;
|
|
276
339
|
|
|
277
340
|
const getWidthAfter = (width: number, character: string | undefined) =>
|
|
@@ -303,7 +366,8 @@ export const tokenize = (source: string): Token[] => {
|
|
|
303
366
|
return getWidthAfter(scan.width, source[cursor.index]);
|
|
304
367
|
};
|
|
305
368
|
const pushToken = (kind: TokenKind, text: string, length: number) => {
|
|
306
|
-
|
|
369
|
+
const column = getColumn();
|
|
370
|
+
tokens.push({ kind, text, line: cursor.line, column, endColumn: column + length - 1 });
|
|
307
371
|
cursor.index += length;
|
|
308
372
|
};
|
|
309
373
|
const advanceOver = (length: number) => {
|
|
@@ -316,22 +380,26 @@ export const tokenize = (source: string): Token[] => {
|
|
|
316
380
|
}
|
|
317
381
|
};
|
|
318
382
|
const createError = (message: string) =>
|
|
319
|
-
new TlaSyntaxError(`${message} at line ${cursor.line}, column ${getColumn()}
|
|
383
|
+
new TlaSyntaxError(`${message} at line ${cursor.line}, column ${getColumn()}`, undefined, {
|
|
384
|
+
reason: message,
|
|
385
|
+
line: cursor.line,
|
|
386
|
+
column: getColumn(),
|
|
387
|
+
isEnd: cursor.index >= source.length,
|
|
388
|
+
});
|
|
320
389
|
|
|
321
390
|
while (cursor.index < source.length) {
|
|
322
391
|
const character = source[cursor.index];
|
|
323
|
-
const rest = source.slice(cursor.index, cursor.index + 8);
|
|
324
392
|
|
|
325
393
|
if (character === "\n" || character === " " || character === "\t" || character === "\r") {
|
|
326
394
|
advanceOver(1);
|
|
327
395
|
continue;
|
|
328
396
|
}
|
|
329
|
-
if (
|
|
397
|
+
if (source.startsWith("\\*", cursor.index)) {
|
|
330
398
|
const lineEnd = source.indexOf("\n", cursor.index);
|
|
331
399
|
advanceOver((lineEnd === -1 ? source.length : lineEnd) - cursor.index);
|
|
332
400
|
continue;
|
|
333
401
|
}
|
|
334
|
-
if (
|
|
402
|
+
if (source.startsWith("(*", cursor.index)) {
|
|
335
403
|
const depth = { value: 0 };
|
|
336
404
|
do {
|
|
337
405
|
if (cursor.index >= source.length) throw createError("Unterminated comment");
|
|
@@ -347,8 +415,8 @@ export const tokenize = (source: string): Token[] => {
|
|
|
347
415
|
} while (depth.value > 0);
|
|
348
416
|
continue;
|
|
349
417
|
}
|
|
350
|
-
if (
|
|
351
|
-
const length =
|
|
418
|
+
if (source.startsWith("----", cursor.index)) {
|
|
419
|
+
const length = skipWhile(source, cursor.index, (next) => next === "-") - cursor.index;
|
|
352
420
|
if (
|
|
353
421
|
source
|
|
354
422
|
.slice(cursor.index + length)
|
|
@@ -359,14 +427,11 @@ export const tokenize = (source: string): Token[] => {
|
|
|
359
427
|
pushToken("Separator", "----", length);
|
|
360
428
|
continue;
|
|
361
429
|
}
|
|
362
|
-
if (
|
|
363
|
-
const length =
|
|
430
|
+
if (source.startsWith("====", cursor.index)) {
|
|
431
|
+
const length = skipWhile(source, cursor.index, (next) => next === "=") - cursor.index;
|
|
364
432
|
pushToken("ModuleEnd", "====", length);
|
|
365
433
|
cursor.moduleDepth--;
|
|
366
|
-
if (cursor.moduleDepth
|
|
367
|
-
const nextHeader = MODULE_HEADER.exec(source.slice(cursor.index));
|
|
368
|
-
if (!nextHeader) break;
|
|
369
|
-
advanceOver(nextHeader.index);
|
|
434
|
+
if (cursor.moduleDepth <= 0) break;
|
|
370
435
|
continue;
|
|
371
436
|
}
|
|
372
437
|
if (character === '"') {
|
|
@@ -380,8 +445,7 @@ export const tokenize = (source: string): Token[] => {
|
|
|
380
445
|
}
|
|
381
446
|
if (source[cursor.index] === "\\") {
|
|
382
447
|
const escaped = source[cursor.index + 1];
|
|
383
|
-
|
|
384
|
-
characters.push(replacement);
|
|
448
|
+
characters.push({ n: "\n", t: "\t", r: "\r", f: "\f" }[escaped] ?? escaped);
|
|
385
449
|
advanceOver(2);
|
|
386
450
|
continue;
|
|
387
451
|
}
|
|
@@ -389,19 +453,28 @@ export const tokenize = (source: string): Token[] => {
|
|
|
389
453
|
advanceOver(1);
|
|
390
454
|
}
|
|
391
455
|
advanceOver(1);
|
|
392
|
-
tokens.push({
|
|
456
|
+
tokens.push({
|
|
457
|
+
kind: "String",
|
|
458
|
+
text: characters.join(""),
|
|
459
|
+
line,
|
|
460
|
+
column,
|
|
461
|
+
endColumn: getColumn() - 1,
|
|
462
|
+
});
|
|
393
463
|
continue;
|
|
394
464
|
}
|
|
395
465
|
const basedNumber = BASED_NUMBER.exec(source.slice(cursor.index, cursor.index + 64));
|
|
396
466
|
if (basedNumber) {
|
|
397
|
-
|
|
398
|
-
pushToken("Number", String(parseInt(basedNumber[1].slice(1), base)), basedNumber[0].length);
|
|
467
|
+
pushToken("Number", basedNumber[0], basedNumber[0].length);
|
|
399
468
|
continue;
|
|
400
469
|
}
|
|
401
470
|
if (isIdentifierCharacter(character)) {
|
|
402
|
-
const word =
|
|
471
|
+
const word = source.slice(
|
|
472
|
+
cursor.index,
|
|
473
|
+
skipWhile(source, cursor.index, isIdentifierCharacter),
|
|
474
|
+
);
|
|
403
475
|
if (/^\d+$/.test(word)) {
|
|
404
|
-
|
|
476
|
+
const number = /^\d+(\.\d+)?/.exec(source.slice(cursor.index))?.[0] ?? word;
|
|
477
|
+
pushToken(number.includes(".") ? "Decimal" : "Number", number, number.length);
|
|
405
478
|
continue;
|
|
406
479
|
}
|
|
407
480
|
if (word.startsWith("WF_") || word.startsWith("SF_")) {
|
|
@@ -411,18 +484,20 @@ export const tokenize = (source: string): Token[] => {
|
|
|
411
484
|
pushToken(KEYWORDS.has(word) ? "Keyword" : "Identifier", word, word.length);
|
|
412
485
|
continue;
|
|
413
486
|
}
|
|
487
|
+
const stepLength = matchStep(source, cursor.index);
|
|
488
|
+
if (stepLength > 0) {
|
|
489
|
+
pushToken("Step", source.slice(cursor.index, cursor.index + stepLength), stepLength);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
414
492
|
const operator = matchOperator(source, cursor.index);
|
|
415
493
|
if (operator) {
|
|
416
494
|
pushToken("Operator", operator.text, operator.length);
|
|
417
495
|
continue;
|
|
418
496
|
}
|
|
419
|
-
if (character === "\\") {
|
|
420
|
-
const word = /^[A-Za-z]+/.exec(source.slice(cursor.index + 1))?.[0] ?? "";
|
|
421
|
-
throw createError(`Unknown operator \\${word}`);
|
|
422
|
-
}
|
|
423
497
|
throw createError(`Unexpected character ${JSON.stringify(character)}`);
|
|
424
498
|
}
|
|
425
499
|
|
|
426
|
-
|
|
500
|
+
const endColumn = getColumn();
|
|
501
|
+
tokens.push({ kind: "End", text: "", line: cursor.line, column: endColumn, endColumn });
|
|
427
502
|
return tokens;
|
|
428
503
|
};
|
package/src/liveness.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Definition, Expression
|
|
1
|
+
import type { Definition, Expression } from "./ast";
|
|
2
2
|
import { TlaConfigError, TlaEvaluationError } from "./errors";
|
|
3
3
|
import {
|
|
4
|
+
describeLocation,
|
|
4
5
|
bindDefinitions,
|
|
5
6
|
bindOperatorArguments,
|
|
6
7
|
evaluate,
|
|
@@ -18,16 +19,16 @@ import {
|
|
|
18
19
|
type Thunk,
|
|
19
20
|
} from "./evaluate";
|
|
20
21
|
import { mapChildren } from "./instances";
|
|
22
|
+
import { ACTION_LEVEL, TEMPORAL_LEVEL } from "./levels";
|
|
21
23
|
import { isEnabled } from "./successors";
|
|
22
24
|
import {
|
|
25
|
+
createBoolean,
|
|
23
26
|
createJunction,
|
|
24
27
|
createPair,
|
|
25
28
|
createUnary,
|
|
26
|
-
FALSE,
|
|
27
29
|
getLiveLevel,
|
|
28
30
|
getSolutions,
|
|
29
31
|
isFulfilling,
|
|
30
|
-
TRUE,
|
|
31
32
|
type LiveEnabled,
|
|
32
33
|
type LiveExpression,
|
|
33
34
|
type Solution,
|
|
@@ -55,7 +56,7 @@ export interface Lasso {
|
|
|
55
56
|
cycle: number[];
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
interface SafetyViolation {
|
|
59
60
|
name: string;
|
|
60
61
|
path: number[];
|
|
61
62
|
}
|
|
@@ -88,10 +89,6 @@ interface BehaviorGraph {
|
|
|
88
89
|
parents: number[];
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export const ACTION_LEVEL = 2;
|
|
92
|
-
|
|
93
|
-
export const TEMPORAL_LEVEL = 3;
|
|
94
|
-
|
|
95
92
|
const MAXIMUM_EXPANSION_DEPTH = 100;
|
|
96
93
|
|
|
97
94
|
const TEMPORAL_OPERATORS = new Set(["~>", "-+->"]);
|
|
@@ -107,9 +104,6 @@ const getConstantContext = (environment: Environment, scope: Scope | undefined):
|
|
|
107
104
|
next: undefined,
|
|
108
105
|
});
|
|
109
106
|
|
|
110
|
-
const describeLocation = (expression: Expression) =>
|
|
111
|
-
`line ${expression.location.line}, column ${expression.location.column}`;
|
|
112
|
-
|
|
113
107
|
const createCannotHandleError = (expression: Expression) =>
|
|
114
108
|
new TlaEvaluationError(
|
|
115
109
|
`TLC cannot handle the temporal formula at ${describeLocation(expression)}`,
|
|
@@ -121,7 +115,7 @@ export const getLevel = (
|
|
|
121
115
|
environment: Environment,
|
|
122
116
|
shadowed = new Set<string>(),
|
|
123
117
|
): number => {
|
|
124
|
-
const getBindingLevel = (name: string)
|
|
118
|
+
const getBindingLevel = (name: string) => {
|
|
125
119
|
const binding = lookupScope(scope, name);
|
|
126
120
|
if (!binding) return undefined;
|
|
127
121
|
if (binding.kind === "Value") return 0;
|
|
@@ -129,7 +123,7 @@ export const getLevel = (
|
|
|
129
123
|
const operator = binding.operator;
|
|
130
124
|
return operator.kind === "Closure" ? getLevel(operator.body, operator.scope, environment) : 0;
|
|
131
125
|
};
|
|
132
|
-
const getGlobalLevel = (name: string)
|
|
126
|
+
const getGlobalLevel = (name: string) => {
|
|
133
127
|
if (environment.variableIndex.has(name)) return 1;
|
|
134
128
|
if (environment.constants.has(name)) return 0;
|
|
135
129
|
const definition = getDefinition(name, environment);
|
|
@@ -186,7 +180,7 @@ export const getLevel = (
|
|
|
186
180
|
}
|
|
187
181
|
};
|
|
188
182
|
|
|
189
|
-
const getDefinitionLevel = (definition: Definition, environment: Environment)
|
|
183
|
+
const getDefinitionLevel = (definition: Definition, environment: Environment) => {
|
|
190
184
|
const cached = definitionLevels.get(definition);
|
|
191
185
|
if (cached !== undefined) return cached;
|
|
192
186
|
definitionLevels.set(definition, 0);
|
|
@@ -203,34 +197,40 @@ export const expandTemporal = (
|
|
|
203
197
|
expression: Expression,
|
|
204
198
|
scope: Scope | undefined,
|
|
205
199
|
environment: Environment,
|
|
200
|
+
isUnrollingForall: boolean,
|
|
206
201
|
expanding = new Set<Definition>(),
|
|
207
202
|
): ScopedExpression[] => {
|
|
203
|
+
const expand = (
|
|
204
|
+
child: Expression,
|
|
205
|
+
childScope: Scope | undefined,
|
|
206
|
+
childExpanding = expanding,
|
|
207
|
+
): ScopedExpression[] =>
|
|
208
|
+
expandTemporal(child, childScope, environment, isUnrollingForall, childExpanding);
|
|
208
209
|
if (expression.kind === "And") {
|
|
209
|
-
return expression.operands.flatMap((operand) =>
|
|
210
|
-
expandTemporal(operand, scope, environment, expanding),
|
|
211
|
-
);
|
|
210
|
+
return expression.operands.flatMap((operand) => expand(operand, scope));
|
|
212
211
|
}
|
|
213
|
-
if (expression.kind === "Label")
|
|
214
|
-
|
|
212
|
+
if (expression.kind === "Label") return expand(expression.body, scope);
|
|
213
|
+
if (expression.kind === "Let") {
|
|
214
|
+
return expand(expression.body, bindLetDefinitions(expression, scope, environment));
|
|
215
215
|
}
|
|
216
216
|
if (expression.kind === "Identifier") {
|
|
217
217
|
const binding = isLocallyResolved(expression) ? lookupScope(scope, expression.name) : undefined;
|
|
218
218
|
if (binding?.kind === "Thunk") {
|
|
219
|
-
return
|
|
219
|
+
return expand(binding.expression, binding.scope);
|
|
220
220
|
}
|
|
221
221
|
const definition = environment.constants.has(expression.name)
|
|
222
222
|
? undefined
|
|
223
223
|
: getDefinition(expression.name, environment);
|
|
224
|
-
if (
|
|
224
|
+
if (
|
|
225
|
+
!binding &&
|
|
226
|
+
definition?.kind === "Operator" &&
|
|
227
|
+
definition.params.length === 0 &&
|
|
228
|
+
getDefinitionLevel(definition, environment) > 0
|
|
229
|
+
) {
|
|
225
230
|
if (expanding.has(definition)) {
|
|
226
231
|
throw new TlaConfigError(`The definition of ${expression.name} refers to itself`);
|
|
227
232
|
}
|
|
228
|
-
return
|
|
229
|
-
definition.body,
|
|
230
|
-
undefined,
|
|
231
|
-
environment,
|
|
232
|
-
new Set([...expanding, definition]),
|
|
233
|
-
);
|
|
233
|
+
return expand(definition.body, undefined, new Set([...expanding, definition]));
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
const context = getConstantContext(environment, scope);
|
|
@@ -240,22 +240,18 @@ export const expandTemporal = (
|
|
|
240
240
|
? undefined
|
|
241
241
|
: getDefinition(expression.operator, environment);
|
|
242
242
|
if (operator?.kind === "Closure" && !definition?.isRecursive) {
|
|
243
|
-
return
|
|
244
|
-
operator.body,
|
|
245
|
-
bindOperatorArguments(operator, expression.args, context),
|
|
246
|
-
environment,
|
|
247
|
-
expanding,
|
|
248
|
-
);
|
|
243
|
+
return expand(operator.body, bindOperatorArguments(operator, expression.args, context));
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
246
|
const isUnrolled =
|
|
247
|
+
isUnrollingForall &&
|
|
252
248
|
expression.kind === "Forall" &&
|
|
253
249
|
getLevel(expression.body, scope, environment) === TEMPORAL_LEVEL &&
|
|
254
250
|
expression.bounds.every((bound) => getLevel(bound.set, scope, environment) === 0);
|
|
255
|
-
if (
|
|
251
|
+
if (isUnrolled) {
|
|
256
252
|
const expanded: ScopedExpression[] = [];
|
|
257
253
|
forEachBinding(expression.bounds, context, (boundScope) => {
|
|
258
|
-
expanded.push(...
|
|
254
|
+
expanded.push(...expand(expression.body, boundScope));
|
|
259
255
|
return false;
|
|
260
256
|
});
|
|
261
257
|
return expanded;
|
|
@@ -263,52 +259,6 @@ export const expandTemporal = (
|
|
|
263
259
|
return [{ expression, scope }];
|
|
264
260
|
};
|
|
265
261
|
|
|
266
|
-
const toLiteral = (value: Value, location: SourceLocation): Expression | undefined => {
|
|
267
|
-
if (typeof value === "number") return { kind: "Number", value, location };
|
|
268
|
-
if (typeof value === "string") return { kind: "String", value, location };
|
|
269
|
-
if (typeof value === "boolean") {
|
|
270
|
-
return { kind: "Identifier", name: value ? "TRUE" : "FALSE", location };
|
|
271
|
-
}
|
|
272
|
-
return undefined;
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
export const toUnscopedExpression = (
|
|
276
|
-
expression: Expression,
|
|
277
|
-
scope: Scope | undefined,
|
|
278
|
-
): Expression | undefined => {
|
|
279
|
-
const definitions: Definition[] = [];
|
|
280
|
-
for (let current = scope; current; current = current.parent) {
|
|
281
|
-
const binding = current.binding;
|
|
282
|
-
const closure =
|
|
283
|
-
binding.kind === "Operator" && binding.operator.kind === "Closure"
|
|
284
|
-
? binding.operator
|
|
285
|
-
: undefined;
|
|
286
|
-
const body =
|
|
287
|
-
binding.kind === "Thunk"
|
|
288
|
-
? binding.expression
|
|
289
|
-
: binding.kind === "Value"
|
|
290
|
-
? toLiteral(binding.value, expression.location)
|
|
291
|
-
: closure?.body;
|
|
292
|
-
const isUnscoped =
|
|
293
|
-
binding.kind === "Thunk"
|
|
294
|
-
? binding.scope === undefined
|
|
295
|
-
: binding.kind === "Value" || closure?.scope === undefined;
|
|
296
|
-
if (!body || !isUnscoped) return undefined;
|
|
297
|
-
definitions.unshift({
|
|
298
|
-
kind: "Operator",
|
|
299
|
-
name: current.name,
|
|
300
|
-
params: closure?.params ?? [],
|
|
301
|
-
body,
|
|
302
|
-
isLocal: true,
|
|
303
|
-
isRecursive: false,
|
|
304
|
-
location: body.location,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
return definitions.length === 0
|
|
308
|
-
? expression
|
|
309
|
-
: { kind: "Let", definitions, body: expression, location: expression.location };
|
|
310
|
-
};
|
|
311
|
-
|
|
312
262
|
const bindLetDefinitions = (
|
|
313
263
|
expression: Expression & { kind: "Let" },
|
|
314
264
|
scope: Scope | undefined,
|
|
@@ -346,7 +296,7 @@ const toLiveExpression = (
|
|
|
346
296
|
`TLC expected a boolean in the temporal formula at ${describeLocation(expression)}`,
|
|
347
297
|
);
|
|
348
298
|
}
|
|
349
|
-
return value
|
|
299
|
+
return createBoolean(value);
|
|
350
300
|
}
|
|
351
301
|
if (atomLevel === 1) return { kind: "State", expression, scope };
|
|
352
302
|
return { kind: "Action", expression, unchanged: undefined, scope };
|
|
@@ -375,7 +325,7 @@ const toLiveExpression = (
|
|
|
375
325
|
const convertCall = (
|
|
376
326
|
expression: Expression & { kind: "Identifier" | "Apply" },
|
|
377
327
|
scope: Scope | undefined,
|
|
378
|
-
)
|
|
328
|
+
) => {
|
|
379
329
|
const name = expression.kind === "Identifier" ? expression.name : expression.operator;
|
|
380
330
|
const context = getConstantContext(environment, scope);
|
|
381
331
|
const binding =
|
|
@@ -412,7 +362,7 @@ const toLiveExpression = (
|
|
|
412
362
|
return false;
|
|
413
363
|
});
|
|
414
364
|
const isForall = expression.kind === "Forall";
|
|
415
|
-
if (operands.length === 0) return isForall
|
|
365
|
+
if (operands.length === 0) return createBoolean(isForall);
|
|
416
366
|
const result = createJunction(isForall ? "And" : "Or", operands);
|
|
417
367
|
const level = getLiveLevel(result);
|
|
418
368
|
return level > ACTION_LEVEL ? result : toAtom(expression, scope, level);
|
|
@@ -851,6 +801,7 @@ export const createLivenessChecker = (
|
|
|
851
801
|
};
|
|
852
802
|
|
|
853
803
|
const addState = (index: number, state: Value[], successors: LiveSuccessor[]) => {
|
|
804
|
+
environment.runtime.setLivenessSource(index);
|
|
854
805
|
states[index] = state;
|
|
855
806
|
const edges: LiveSuccessor[] = [];
|
|
856
807
|
const edgesByTarget = new Map<number, LiveSuccessor[]>();
|
|
@@ -893,6 +844,7 @@ export const createLivenessChecker = (
|
|
|
893
844
|
};
|
|
894
845
|
|
|
895
846
|
const computeActionResults = (source: number) => {
|
|
847
|
+
environment.runtime.setLivenessSource(source);
|
|
896
848
|
const state = states[source];
|
|
897
849
|
const successors = edgeStates[source];
|
|
898
850
|
const values = successors.map(() => new Int8Array(actionKeys.size).fill(-1));
|
|
@@ -911,12 +863,14 @@ export const createLivenessChecker = (
|
|
|
911
863
|
successors.forEach((successor, edgeIndex) => {
|
|
912
864
|
const known = values[edgeIndex];
|
|
913
865
|
for (let position = 0; position < actionCount; position++) {
|
|
914
|
-
const
|
|
915
|
-
if (known[
|
|
866
|
+
const actionId = ids[position];
|
|
867
|
+
if (known[actionId] === -1) {
|
|
916
868
|
const predicate = solution.actionChecks[position];
|
|
917
|
-
known[
|
|
869
|
+
known[actionId] = evaluateLive(predicate, state, successor, environment, stateCache)
|
|
870
|
+
? 1
|
|
871
|
+
: 0;
|
|
918
872
|
}
|
|
919
|
-
actions[edgeIndex * actionCount + position] = known[
|
|
873
|
+
actions[edgeIndex * actionCount + position] = known[actionId];
|
|
920
874
|
}
|
|
921
875
|
});
|
|
922
876
|
check.actionResults[source] = actions;
|
|
@@ -932,7 +886,9 @@ export const createLivenessChecker = (
|
|
|
932
886
|
check.product ?? {
|
|
933
887
|
states: states.map((_, index) => index),
|
|
934
888
|
tableauNodes: states.map(() => -1),
|
|
935
|
-
edges:
|
|
889
|
+
edges: Array.from({ length: states.length }, (_, index) =>
|
|
890
|
+
(targets[index] ?? []).flatMap((target, edgeIndex) => [target, edgeIndex]),
|
|
891
|
+
),
|
|
936
892
|
initial,
|
|
937
893
|
parents: [],
|
|
938
894
|
};
|
|
@@ -1014,9 +970,9 @@ export const createLivenessChecker = (
|
|
|
1014
970
|
if (!isViolation) return false;
|
|
1015
971
|
const entry = component[component.length - 1];
|
|
1016
972
|
const waypoints = [
|
|
1017
|
-
...stateWitnesses.flatMap((witness) =>
|
|
973
|
+
...stateWitnesses.flatMap((witness) => witness ?? []),
|
|
1018
974
|
...actionWitnesses.flatMap((witness) => witness ?? []),
|
|
1019
|
-
...promiseWitnesses.flatMap((witness) =>
|
|
975
|
+
...promiseWitnesses.flatMap((witness) => witness ?? []),
|
|
1020
976
|
];
|
|
1021
977
|
const cycle = [entry];
|
|
1022
978
|
for (const waypoint of waypoints) {
|