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.
Files changed (71) hide show
  1. package/README.md +81 -42
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +215 -30
  4. package/dist/index.d.ts +215 -30
  5. package/dist/index.js +1 -1
  6. package/dist/parallel-worker.cjs +2 -2
  7. package/dist/parallel-worker.js +2 -2
  8. package/dist/parallel.cjs +1 -1
  9. package/dist/parallel.js +2 -2
  10. package/dist/run-cli.cjs +20 -1
  11. package/dist/run-cli.js +20 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +6 -2
  15. package/src/ast.ts +122 -5
  16. package/src/bags.ts +161 -71
  17. package/src/builtins.ts +203 -23
  18. package/src/bundled-modules.ts +63 -32
  19. package/src/check.ts +945 -238
  20. package/src/cli-options.ts +58 -0
  21. package/src/codegen.ts +52 -18
  22. package/src/config.ts +39 -11
  23. package/src/environment.ts +485 -140
  24. package/src/errors.ts +59 -1
  25. package/src/evaluate.ts +489 -137
  26. package/src/exit-codes.ts +75 -0
  27. package/src/fingerprints.ts +2 -2
  28. package/src/index.ts +9 -4
  29. package/src/instances.ts +221 -66
  30. package/src/java-overrides.ts +241 -0
  31. package/src/json.ts +153 -72
  32. package/src/levels.ts +850 -0
  33. package/src/lexer.ts +103 -28
  34. package/src/liveness.ts +47 -91
  35. package/src/obligation-action.ts +384 -0
  36. package/src/obligation-anon.ts +575 -0
  37. package/src/obligation-deref.ts +637 -0
  38. package/src/obligation-expression.ts +1060 -0
  39. package/src/obligation-format.ts +406 -0
  40. package/src/obligation-generate.ts +1074 -0
  41. package/src/obligation-levels.ts +329 -0
  42. package/src/obligation-module.ts +1042 -0
  43. package/src/obligation-printer.ts +889 -0
  44. package/src/obligation-proof.ts +76 -0
  45. package/src/obligation-source.ts +1084 -0
  46. package/src/obligation-tuples.ts +184 -0
  47. package/src/obligations.ts +316 -0
  48. package/src/parallel-worker.ts +78 -30
  49. package/src/parallel.ts +116 -62
  50. package/src/parser.ts +958 -205
  51. package/src/pretty-print.ts +259 -0
  52. package/src/randomization.ts +24 -113
  53. package/src/resolve.ts +472 -41
  54. package/src/run-cli.ts +427 -61
  55. package/src/runtime.ts +63 -28
  56. package/src/spec-description.ts +509 -0
  57. package/src/standard-levels.ts +105 -0
  58. package/src/standard-modules.ts +223 -92
  59. package/src/subexpressions.ts +21 -35
  60. package/src/substitution-levels.ts +184 -0
  61. package/src/successors.ts +261 -46
  62. package/src/symmetry.ts +65 -21
  63. package/src/tableau.ts +43 -37
  64. package/src/tlc-ext.ts +120 -33
  65. package/src/tlc-module.ts +71 -47
  66. package/src/tlc-modules.ts +1022 -0
  67. package/src/tlc-order.ts +497 -0
  68. package/src/tlc-random.ts +411 -0
  69. package/src/tlc-summary.ts +109 -0
  70. package/src/transfer.ts +26 -5
  71. package/src/values.ts +1231 -176
package/src/lexer.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import { TlaSyntaxError } from "./errors";
2
2
 
3
- export type TokenKind =
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
- const operator = BACKSLASH_OPERATORS.get(word);
266
- return operator === undefined ? undefined : { text: operator, length: word.length + 1 };
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
- tokens.push({ kind, text, line: cursor.line, column: getColumn() });
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 (rest.startsWith("\\*")) {
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 (rest.startsWith("(*")) {
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 (rest.startsWith("----")) {
351
- const length = /^-+/.exec(source.slice(cursor.index))?.[0].length ?? 0;
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 (rest.startsWith("====")) {
363
- const length = /^=+/.exec(source.slice(cursor.index))?.[0].length ?? 0;
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 > 0) continue;
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
- const replacement = { n: "\n", t: "\t", r: "\r", f: "\f" }[escaped] ?? escaped;
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({ kind: "String", text: characters.join(""), line, column });
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
- const base = NUMBER_BASES.get(basedNumber[1][0].toLowerCase());
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 = /^[A-Za-z0-9_]+/.exec(source.slice(cursor.index))?.[0] ?? "";
471
+ const word = source.slice(
472
+ cursor.index,
473
+ skipWhile(source, cursor.index, isIdentifierCharacter),
474
+ );
403
475
  if (/^\d+$/.test(word)) {
404
- pushToken("Number", word, word.length);
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
- tokens.push({ kind: "End", text: "", line: cursor.line, column: getColumn() });
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, SourceLocation } from "./ast";
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
- export interface SafetyViolation {
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): number | undefined => {
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): number | undefined => {
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): number => {
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
- return expandTemporal(expression.body, scope, environment, expanding);
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 expandTemporal(binding.expression, binding.scope, environment, expanding);
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 (!binding && definition?.kind === "Operator" && definition.params.length === 0) {
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 expandTemporal(
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 expandTemporal(
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 (expression.kind === "Forall" && isUnrolled) {
251
+ if (isUnrolled) {
256
252
  const expanded: ScopedExpression[] = [];
257
253
  forEachBinding(expression.bounds, context, (boundScope) => {
258
- expanded.push(...expandTemporal(expression.body, boundScope, environment, expanding));
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 ? TRUE : FALSE;
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
- ): LiveExpression => {
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 ? TRUE : FALSE;
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 id = ids[position];
915
- if (known[id] === -1) {
866
+ const actionId = ids[position];
867
+ if (known[actionId] === -1) {
916
868
  const predicate = solution.actionChecks[position];
917
- known[id] = evaluateLive(predicate, state, successor, environment, stateCache) ? 1 : 0;
869
+ known[actionId] = evaluateLive(predicate, state, successor, environment, stateCache)
870
+ ? 1
871
+ : 0;
918
872
  }
919
- actions[edgeIndex * actionCount + position] = known[id];
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: targets.map((list) => list.flatMap((target, edgeIndex) => [target, edgeIndex])),
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) => (witness === undefined ? [] : [witness])),
973
+ ...stateWitnesses.flatMap((witness) => witness ?? []),
1018
974
  ...actionWitnesses.flatMap((witness) => witness ?? []),
1019
- ...promiseWitnesses.flatMap((witness) => (witness === undefined ? [] : [witness])),
975
+ ...promiseWitnesses.flatMap((witness) => witness ?? []),
1020
976
  ];
1021
977
  const cycle = [entry];
1022
978
  for (const waypoint of waypoints) {