tla-ts 0.0.1 → 0.0.2
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 +34 -41
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +166 -30
- package/dist/index.d.ts +166 -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 +2 -1
- package/dist/run-cli.js +2 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +4 -2
- package/src/ast.ts +82 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +904 -230
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +22 -0
- package/src/evaluate.ts +486 -136
- 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 +97 -27
- package/src/liveness.ts +47 -91
- package/src/parallel-worker.ts +57 -28
- package/src/parallel.ts +116 -62
- package/src/parser.ts +703 -183
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +142 -44
- 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 +4 -4
- package/src/values.ts +1186 -164
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) => {
|
|
@@ -320,18 +384,17 @@ export const tokenize = (source: string): Token[] => {
|
|
|
320
384
|
|
|
321
385
|
while (cursor.index < source.length) {
|
|
322
386
|
const character = source[cursor.index];
|
|
323
|
-
const rest = source.slice(cursor.index, cursor.index + 8);
|
|
324
387
|
|
|
325
388
|
if (character === "\n" || character === " " || character === "\t" || character === "\r") {
|
|
326
389
|
advanceOver(1);
|
|
327
390
|
continue;
|
|
328
391
|
}
|
|
329
|
-
if (
|
|
392
|
+
if (source.startsWith("\\*", cursor.index)) {
|
|
330
393
|
const lineEnd = source.indexOf("\n", cursor.index);
|
|
331
394
|
advanceOver((lineEnd === -1 ? source.length : lineEnd) - cursor.index);
|
|
332
395
|
continue;
|
|
333
396
|
}
|
|
334
|
-
if (
|
|
397
|
+
if (source.startsWith("(*", cursor.index)) {
|
|
335
398
|
const depth = { value: 0 };
|
|
336
399
|
do {
|
|
337
400
|
if (cursor.index >= source.length) throw createError("Unterminated comment");
|
|
@@ -347,8 +410,8 @@ export const tokenize = (source: string): Token[] => {
|
|
|
347
410
|
} while (depth.value > 0);
|
|
348
411
|
continue;
|
|
349
412
|
}
|
|
350
|
-
if (
|
|
351
|
-
const length =
|
|
413
|
+
if (source.startsWith("----", cursor.index)) {
|
|
414
|
+
const length = skipWhile(source, cursor.index, (next) => next === "-") - cursor.index;
|
|
352
415
|
if (
|
|
353
416
|
source
|
|
354
417
|
.slice(cursor.index + length)
|
|
@@ -359,14 +422,11 @@ export const tokenize = (source: string): Token[] => {
|
|
|
359
422
|
pushToken("Separator", "----", length);
|
|
360
423
|
continue;
|
|
361
424
|
}
|
|
362
|
-
if (
|
|
363
|
-
const length =
|
|
425
|
+
if (source.startsWith("====", cursor.index)) {
|
|
426
|
+
const length = skipWhile(source, cursor.index, (next) => next === "=") - cursor.index;
|
|
364
427
|
pushToken("ModuleEnd", "====", length);
|
|
365
428
|
cursor.moduleDepth--;
|
|
366
|
-
if (cursor.moduleDepth
|
|
367
|
-
const nextHeader = MODULE_HEADER.exec(source.slice(cursor.index));
|
|
368
|
-
if (!nextHeader) break;
|
|
369
|
-
advanceOver(nextHeader.index);
|
|
429
|
+
if (cursor.moduleDepth <= 0) break;
|
|
370
430
|
continue;
|
|
371
431
|
}
|
|
372
432
|
if (character === '"') {
|
|
@@ -380,8 +440,7 @@ export const tokenize = (source: string): Token[] => {
|
|
|
380
440
|
}
|
|
381
441
|
if (source[cursor.index] === "\\") {
|
|
382
442
|
const escaped = source[cursor.index + 1];
|
|
383
|
-
|
|
384
|
-
characters.push(replacement);
|
|
443
|
+
characters.push({ n: "\n", t: "\t", r: "\r", f: "\f" }[escaped] ?? escaped);
|
|
385
444
|
advanceOver(2);
|
|
386
445
|
continue;
|
|
387
446
|
}
|
|
@@ -389,19 +448,28 @@ export const tokenize = (source: string): Token[] => {
|
|
|
389
448
|
advanceOver(1);
|
|
390
449
|
}
|
|
391
450
|
advanceOver(1);
|
|
392
|
-
tokens.push({
|
|
451
|
+
tokens.push({
|
|
452
|
+
kind: "String",
|
|
453
|
+
text: characters.join(""),
|
|
454
|
+
line,
|
|
455
|
+
column,
|
|
456
|
+
endColumn: getColumn() - 1,
|
|
457
|
+
});
|
|
393
458
|
continue;
|
|
394
459
|
}
|
|
395
460
|
const basedNumber = BASED_NUMBER.exec(source.slice(cursor.index, cursor.index + 64));
|
|
396
461
|
if (basedNumber) {
|
|
397
|
-
|
|
398
|
-
pushToken("Number", String(parseInt(basedNumber[1].slice(1), base)), basedNumber[0].length);
|
|
462
|
+
pushToken("Number", basedNumber[0], basedNumber[0].length);
|
|
399
463
|
continue;
|
|
400
464
|
}
|
|
401
465
|
if (isIdentifierCharacter(character)) {
|
|
402
|
-
const word =
|
|
466
|
+
const word = source.slice(
|
|
467
|
+
cursor.index,
|
|
468
|
+
skipWhile(source, cursor.index, isIdentifierCharacter),
|
|
469
|
+
);
|
|
403
470
|
if (/^\d+$/.test(word)) {
|
|
404
|
-
|
|
471
|
+
const number = /^\d+(\.\d+)?/.exec(source.slice(cursor.index))?.[0] ?? word;
|
|
472
|
+
pushToken(number.includes(".") ? "Decimal" : "Number", number, number.length);
|
|
405
473
|
continue;
|
|
406
474
|
}
|
|
407
475
|
if (word.startsWith("WF_") || word.startsWith("SF_")) {
|
|
@@ -411,18 +479,20 @@ export const tokenize = (source: string): Token[] => {
|
|
|
411
479
|
pushToken(KEYWORDS.has(word) ? "Keyword" : "Identifier", word, word.length);
|
|
412
480
|
continue;
|
|
413
481
|
}
|
|
482
|
+
const stepLength = matchStep(source, cursor.index);
|
|
483
|
+
if (stepLength > 0) {
|
|
484
|
+
pushToken("Step", source.slice(cursor.index, cursor.index + stepLength), stepLength);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
414
487
|
const operator = matchOperator(source, cursor.index);
|
|
415
488
|
if (operator) {
|
|
416
489
|
pushToken("Operator", operator.text, operator.length);
|
|
417
490
|
continue;
|
|
418
491
|
}
|
|
419
|
-
if (character === "\\") {
|
|
420
|
-
const word = /^[A-Za-z]+/.exec(source.slice(cursor.index + 1))?.[0] ?? "";
|
|
421
|
-
throw createError(`Unknown operator \\${word}`);
|
|
422
|
-
}
|
|
423
492
|
throw createError(`Unexpected character ${JSON.stringify(character)}`);
|
|
424
493
|
}
|
|
425
494
|
|
|
426
|
-
|
|
495
|
+
const endColumn = getColumn();
|
|
496
|
+
tokens.push({ kind: "End", text: "", line: cursor.line, column: endColumn, endColumn });
|
|
427
497
|
return tokens;
|
|
428
498
|
};
|
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) {
|