tla-ts 0.0.1 → 0.0.3-dev.6979965

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 +85 -40
  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 +48 -13
  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 +121 -123
  49. package/src/parallel.ts +167 -83
  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/builtins.ts CHANGED
@@ -1,22 +1,60 @@
1
- import { TlaEvaluationError } from "./errors";
1
+ import { TlaEvaluationError, TlaModuleError } from "./errors";
2
2
  import type { Builtin, BuiltinArgument, Context, OperatorValue } from "./evaluate";
3
3
  import {
4
- checkInteger,
5
4
  formatValue,
6
5
  getSequenceElements,
7
6
  isEnumerable,
7
+ isFunctionValue,
8
8
  isSetValue,
9
+ toFiniteFunction,
10
+ MAXIMUM_INTEGER,
11
+ MINIMUM_INTEGER,
9
12
  toFiniteSet,
10
13
  type Value,
11
14
  } from "./values";
12
15
 
13
- export const isOperatorValue = (argument: BuiltinArgument): argument is OperatorValue =>
16
+ // TLC evaluates these operators in its core, or its Java code for them takes integers, so a wrong
17
+ // argument is an evaluation failure. Every other standard-module operator checks its arguments in
18
+ // Java and raises an EvalException.
19
+ const CORE_OPERATOR_NAMES = new Set([
20
+ "=",
21
+ "#",
22
+ "~",
23
+ "<=>",
24
+ "/\\",
25
+ "\\/",
26
+ "=>",
27
+ "\\in",
28
+ "\\notin",
29
+ "\\subseteq",
30
+ "\\cup",
31
+ "\\cap",
32
+ "\\",
33
+ "SUBSET",
34
+ "UNION",
35
+ "DOMAIN",
36
+ "+",
37
+ "-",
38
+ "*",
39
+ "^",
40
+ "\\div",
41
+ "%",
42
+ "..",
43
+ "Seq",
44
+ "IsFiniteSet",
45
+ "RandomElement",
46
+ ]);
47
+
48
+ const createArgumentError = (name: string, message: string) =>
49
+ CORE_OPERATOR_NAMES.has(name) ? new TlaEvaluationError(message) : new TlaModuleError(message);
50
+
51
+ const isOperatorValue = (argument: BuiltinArgument): argument is OperatorValue =>
14
52
  typeof argument === "object" && argument.kind === "Operator";
15
53
 
16
54
  export const getValue = (args: BuiltinArgument[], index: number, name: string): Value => {
17
55
  const argument = args[index];
18
56
  if (argument === undefined || isOperatorValue(argument)) {
19
- throw new TlaEvaluationError(`${name} expects a value as argument ${index + 1}`);
57
+ throw createArgumentError(name, `${name} expects a value as argument ${index + 1}`);
20
58
  }
21
59
  return argument;
22
60
  };
@@ -24,7 +62,10 @@ export const getValue = (args: BuiltinArgument[], index: number, name: string):
24
62
  export const getNumber = (args: BuiltinArgument[], index: number, name: string) => {
25
63
  const value = getValue(args, index, name);
26
64
  if (typeof value !== "number") {
27
- throw new TlaEvaluationError(`${name} expects an integer but received ${formatValue(value)}`);
65
+ throw createArgumentError(
66
+ name,
67
+ `${name} expects an integer but received ${formatValue(value)}`,
68
+ );
28
69
  }
29
70
  return value;
30
71
  };
@@ -32,7 +73,7 @@ export const getNumber = (args: BuiltinArgument[], index: number, name: string)
32
73
  export const getBoolean = (args: BuiltinArgument[], index: number, name: string) => {
33
74
  const value = getValue(args, index, name);
34
75
  if (typeof value !== "boolean") {
35
- throw new TlaEvaluationError(`${name} expects a boolean but received ${formatValue(value)}`);
76
+ throw createArgumentError(name, `${name} expects a boolean but received ${formatValue(value)}`);
36
77
  }
37
78
  return value;
38
79
  };
@@ -40,20 +81,24 @@ export const getBoolean = (args: BuiltinArgument[], index: number, name: string)
40
81
  export const getSet = (args: BuiltinArgument[], index: number, name: string) => {
41
82
  const value = getValue(args, index, name);
42
83
  if (!isSetValue(value))
43
- throw new TlaEvaluationError(`${name} expects a set but received ${formatValue(value)}`);
84
+ throw createArgumentError(name, `${name} expects a set but received ${formatValue(value)}`);
44
85
  return value;
45
86
  };
46
87
 
47
- export const getFiniteSet = (args: BuiltinArgument[], index: number, name: string) => {
88
+ export const getEnumerableSet = (args: BuiltinArgument[], index: number, name: string) => {
48
89
  const set = getSet(args, index, name);
49
90
  if (!isEnumerable(set)) {
50
- throw new TlaEvaluationError(
91
+ throw createArgumentError(
92
+ name,
51
93
  `${name} cannot enumerate the non-enumerable set ${formatValue(set)}`,
52
94
  );
53
95
  }
54
- return toFiniteSet(set);
96
+ return set;
55
97
  };
56
98
 
99
+ const getFiniteSet = (args: BuiltinArgument[], index: number, name: string) =>
100
+ toFiniteSet(getEnumerableSet(args, index, name));
101
+
57
102
  export const getElements = (args: BuiltinArgument[], index: number, name: string) =>
58
103
  getFiniteSet(args, index, name).elements;
59
104
 
@@ -61,14 +106,28 @@ export const getSequence = (args: BuiltinArgument[], index: number, name: string
61
106
  const value = getValue(args, index, name);
62
107
  const elements = getSequenceElements(value);
63
108
  if (!elements)
64
- throw new TlaEvaluationError(`${name} expects a sequence but received ${formatValue(value)}`);
109
+ throw createArgumentError(
110
+ name,
111
+ `${name} expects a sequence but received ${formatValue(value)}`,
112
+ );
65
113
  return elements;
66
114
  };
67
115
 
116
+ export const getFunction = (args: BuiltinArgument[], index: number, name: string) => {
117
+ const value = getValue(args, index, name);
118
+ if (!isFunctionValue(value)) {
119
+ throw createArgumentError(
120
+ name,
121
+ `${name} expects a function but received ${formatValue(value)}`,
122
+ );
123
+ }
124
+ return toFiniteFunction(value);
125
+ };
126
+
68
127
  export const getOperator = (args: BuiltinArgument[], index: number, name: string) => {
69
128
  const argument = args[index];
70
129
  if (argument === undefined || !isOperatorValue(argument)) {
71
- throw new TlaEvaluationError(`${name} expects an operator as argument ${index + 1}`);
130
+ throw createArgumentError(name, `${name} expects an operator as argument ${index + 1}`);
72
131
  }
73
132
  return argument;
74
133
  };
@@ -122,13 +181,6 @@ export const defineStateOperator = (
122
181
  isStateLevel: true,
123
182
  });
124
183
 
125
- export const toNumber = (value: Value, name: string) => {
126
- if (typeof value !== "number") {
127
- throw new TlaEvaluationError(`${name} expects an integer but received ${formatValue(value)}`);
128
- }
129
- return value;
130
- };
131
-
132
184
  export const defineBinary = (
133
185
  name: string,
134
186
  compute: (left: Value, right: Value) => Value,
@@ -141,13 +193,141 @@ export const defineBinary = (
141
193
  evaluateBinary: compute,
142
194
  });
143
195
 
144
- export const defineArithmetic = (name: string, compute: (left: number, right: number) => number) =>
145
- defineBinary(name, (left, right) =>
146
- checkInteger(compute(toNumber(left, name), toNumber(right, name))),
196
+ export const isOutOfRange = (value: number) => value > MAXIMUM_INTEGER || value < MINIMUM_INTEGER;
197
+
198
+ export const createOverflowError = (computation: string) =>
199
+ new TlaModuleError(`Overflow when computing ${computation}`);
200
+
201
+ const JAVA_CLASSES: Record<string, string> = {
202
+ ModelValue: "ModelValue",
203
+ Set: "SetEnumValue",
204
+ Interval: "IntervalValue",
205
+ InfiniteSet: "UserValue",
206
+ SequenceSet: "UserValue",
207
+ Subset: "SubsetValue",
208
+ FunctionSet: "SetOfFcnsValue",
209
+ RecordSet: "SetOfRcdsValue",
210
+ ProductSet: "SetOfTuplesValue",
211
+ FilteredSet: "SetPredValue",
212
+ LazyFunction: "FcnLambdaValue",
213
+ Union: "UnionValue",
214
+ MixedSet: "SetEnumValue",
215
+ };
216
+
217
+ const SET_OPERATION_CLASSES = {
218
+ "\\cup": "SetCupValue",
219
+ "\\cap": "SetCapValue",
220
+ "\\": "SetDiffValue",
221
+ };
222
+
223
+ const KIND_STRINGS: Record<string, string> = {
224
+ BoolValue: "a Boolean value",
225
+ IntValue: "an integer",
226
+ StringValue: "a string",
227
+ RecordValue: "a record",
228
+ SetEnumValue: "a set of the form {e1, ... ,eN}",
229
+ SetPredValue: "a set of the form {x \\in S : expr}",
230
+ TupleValue: "a tuple",
231
+ FcnLambdaValue: "a function of the form [x \\in S |-> expr]",
232
+ FcnRcdValue: "a function of the form (d1 :> e1 @@ ... @@ dN :> eN)",
233
+ SetOfFcnsValue: "a set of the form [S -> T]",
234
+ SetOfRcdsValue: "a set of the form [d1 : S1, ... , dN : SN]",
235
+ SetOfTuplesValue: "a cartesian product",
236
+ SubsetValue: "a set of the form SUBSET S",
237
+ SetDiffValue: "a set of the form S \\ T",
238
+ SetCapValue: "a set of the form S \\cap T",
239
+ SetCupValue: "a set of the form S \\cup T",
240
+ ModelValue: "a model value",
241
+ UserValue: "a special set constant",
242
+ IntervalValue: "a set of the form i..j",
243
+ UnionValue: "a set of the form UNION S",
244
+ };
245
+
246
+ // TLC stores a function as a TupleValue or a RecordValue when it has that shape, so the
247
+ // shape stands in for how TLC built the value.
248
+ export const getJavaClassName = (value: Value) => {
249
+ if (typeof value === "number") return "IntValue";
250
+ if (typeof value === "string") return "StringValue";
251
+ if (typeof value === "boolean") return "BoolValue";
252
+ if (value.kind === "SetOperation") return SET_OPERATION_CLASSES[value.operator];
253
+ if (value.kind !== "Function") return JAVA_CLASSES[value.kind];
254
+ const domain = value.domain;
255
+ if (domain.every((key, index) => key === index + 1)) return "TupleValue";
256
+ return domain.every((key) => typeof key === "string") ? "RecordValue" : "FcnRcdValue";
257
+ };
258
+
259
+ export const getKindString = (value: Value) => KIND_STRINGS[getJavaClassName(value)];
260
+
261
+ export const createJavaOverrideError = (signature: string, message: string) =>
262
+ new TlaEvaluationError(
263
+ `Attempted to apply the operator overridden by the Java method\n${signature},\nbut it produced the following error:\n${message}`,
147
264
  );
148
265
 
266
+ // TLC reports a failure inside an operator's Java method as a failure of the method, except an
267
+ // EvalException that the method raises itself. A method that evaluates its own arguments reports
268
+ // every failure that way.
269
+ export const overrideWithJava = (builtin: Builtin, signature: string): Builtin => {
270
+ const guard =
271
+ <Arguments extends unknown[]>(compute: (...args: Arguments) => Value) =>
272
+ (...args: Arguments) => {
273
+ try {
274
+ return compute(...args);
275
+ } catch (error) {
276
+ const isWrapped =
277
+ error instanceof TlaEvaluationError &&
278
+ (builtin.isLazy === true || !(error instanceof TlaModuleError));
279
+ throw isWrapped ? createJavaOverrideError(signature, error.message) : error;
280
+ }
281
+ };
282
+ return {
283
+ ...builtin,
284
+ evaluate: guard(builtin.evaluate),
285
+ evaluateUnary: builtin.evaluateUnary && guard(builtin.evaluateUnary),
286
+ evaluateBinary: builtin.evaluateBinary && guard(builtin.evaluateBinary),
287
+ };
288
+ };
289
+
290
+ export const getJavaInteger = (value: Value, signature: string) => {
291
+ if (typeof value !== "number") {
292
+ throw createJavaOverrideError(
293
+ signature,
294
+ `Cannot cast tlc2.value.impl.${getJavaClassName(value)} to tlc2.value.impl.IntValue`,
295
+ );
296
+ }
297
+ return value;
298
+ };
299
+
300
+ export const getIntegersSignature = (method: string, result: string, arity: number) =>
301
+ `public static tlc2.value.impl.${result} tlc2.module.Integers.${method}(${Array.from({ length: arity }, () => "tlc2.value.impl.IntValue").join(",")})`;
302
+
303
+ export const defineArithmetic = (
304
+ name: string,
305
+ method: string,
306
+ compute: (left: number, right: number) => number,
307
+ ) => {
308
+ const signature = getIntegersSignature(method, "IntValue", 2);
309
+ return defineBinary(name, (left, right) => {
310
+ const leftNumber = getJavaInteger(left, signature);
311
+ const rightNumber = getJavaInteger(right, signature);
312
+ const result = compute(leftNumber, rightNumber);
313
+ if (isOutOfRange(result)) throw createOverflowError(`${leftNumber}${name}${rightNumber}`);
314
+ return result;
315
+ });
316
+ };
317
+
318
+ const getIntegerArgument = (value: Value, position: string, name: string) => {
319
+ if (typeof value !== "number") {
320
+ throw new TlaModuleError(
321
+ `The ${position} argument of ${name} should be an integer, but instead it is:\n${formatValue(value)}`,
322
+ );
323
+ }
324
+ return value;
325
+ };
326
+
149
327
  export const defineComparison = (name: string, compare: (left: number, right: number) => boolean) =>
150
- defineBinary(name, (left, right) => compare(toNumber(left, name), toNumber(right, name)));
328
+ defineBinary(name, (left, right) =>
329
+ compare(getIntegerArgument(left, "first", name), getIntegerArgument(right, "second", name)),
330
+ );
151
331
 
152
332
  export const defineLazyOperator = (
153
333
  name: string,
@@ -1,48 +1,79 @@
1
- const REALS = `---- MODULE Reals ----
1
+ // TLC's Reals.tla, RealTime.tla, and Toolbox.tla, verbatim, so that errors inside them name the same
2
+ // lines as TLC's errors.
3
+ const REALS = `-------------------------------- MODULE Reals -------------------------------
4
+ (***************************************************************************)
5
+ (* This module provides dummy definitions of the operators that are *)
6
+ (* defined by the real Reals module. It is expected that any tool that *)
7
+ (* handles specifications that use real numbers will provide its own *)
8
+ (* implementations of these operators. TLC currently does not handle real *)
9
+ (* numbers and produces an error if a specification requires TLC to *)
10
+ (* evaluate the operators defined here or to evaluate any operator defined *)
11
+ (* in the Integers module that is applied to non-integer real numbers. *)
12
+ (* *)
13
+ (* See the book "Specifying Systems" for the real Reals module. *)
14
+ (***************************************************************************)
2
15
  EXTENDS Integers
3
16
 
4
- Real == "Reals"
17
+ Real == "Reals"
18
+ a / b == CHOOSE m \\in Real : m * b = a
19
+ Infinity == CHOOSE i : (i \\notin Real) /\\ (\\A r \\in Real : i > r)
20
+ =============================================================================`;
5
21
 
6
- a / b == CHOOSE quotient \\in Real : quotient * b = a
7
-
8
- Infinity == CHOOSE value : (value \\notin Real) /\\ (\\A real \\in Real : value > real)
9
- ====`;
22
+ const REAL_TIME = `----------------------------- MODULE RealTime -------------------------------
23
+ (***************************************************************************)
24
+ (* This standard module is described in Chapter 9 of the book "Specifying *)
25
+ (* Systems". The type of specification described there cannot be handled *)
26
+ (* by the TLC model checker. *)
27
+ (***************************************************************************)
28
+ EXTENDS Reals
29
+ VARIABLE now
10
30
 
11
- const REAL_TIME = `---- MODULE RealTime ----
12
- EXTENDS Reals
31
+ RTBound(A, v, D, E) ==
32
+ LET TNext(t) == t' = IF <<A>>_v \\/ ~(ENABLED <<A>>_v)'
33
+ THEN 0
34
+ ELSE t + (now'-now)
13
35
 
14
- VARIABLE now
36
+ Timer(t) == (t=0) /\\ [][TNext(t)]_<<t, v, now>>
15
37
 
16
- RTBound(A, v, D, E) ==
17
- LET TNext(timer) == timer' = IF <<A>>_v \\/ ~(ENABLED <<A>>_v)'
18
- THEN 0
19
- ELSE timer + (now' - now)
20
- Timer(timer) == (timer = 0) /\\ [][TNext(timer)]_<<timer, v, now>>
21
- MaxTime(timer) == [](timer \\leq E)
22
- MinTime(timer) == [][A => timer \\geq D]_v
23
- IN \\EE timer : Timer(timer) /\\ MaxTime(timer) /\\ MinTime(timer)
38
+ MaxTime(t) == [](t \\leq E)
24
39
 
25
- RTnow(v) == LET NowNext == /\\ now' \\in {real \\in Real : real > now}
40
+ MinTime(t) == [][A => t \\geq D]_v
41
+ IN \\EE t : Timer(t) /\\ MaxTime(t) /\\ MinTime(t)
42
+ -----------------------------------------------------------------------------
43
+ RTnow(v) == LET NowNext == /\\ now' \\in {r \\in Real : r > now}
26
44
  /\\ UNCHANGED v
27
- IN /\\ now \\in Real
28
- /\\ [][NowNext]_now
29
- /\\ \\A real \\in Real : WF_now(NowNext /\\ (now' > real))
30
- ====`;
45
+ IN /\\ now \\in Real
46
+ /\\ [][NowNext]_now
47
+ /\\ \\A r \\in Real : WF_now(NowNext /\\ (now'>r))
48
+ =============================================================================`;
31
49
 
32
- const TOOLBOX = `---- MODULE Toolbox ----
33
- LOCAL INSTANCE Naturals
34
- LOCAL INSTANCE FiniteSets
35
- LOCAL INSTANCE TLC
50
+ const TOOLBOX = `----------------------------- MODULE Toolbox --------------------------------
51
+ (***************************************************************************)
52
+ (* *)
53
+ (***************************************************************************)
54
+ LOCAL INSTANCE Naturals \\* a + b
55
+ LOCAL INSTANCE FiniteSets \\* Cardinality
56
+ LOCAL INSTANCE TLC \\* TLCGet
36
57
 
37
- _TETrace == CHOOSE trace \\in [(Nat \\ {0}) -> STRING] : TRUE
58
+ -----------------------------------------------------------------------------
59
+
60
+ (* Trace Exploration *)
61
+
62
+ _TETrace ==
63
+ CHOOSE f \\in [ (Nat \\ {0}) -> STRING ] : TRUE
38
64
 
39
65
  LOCAL _TETraceLength == Cardinality(DOMAIN _TETrace)
40
66
 
41
- _TEPosition ==
42
- IF TLCGet("level") >= _TETraceLength
43
- THEN _TETraceLength
44
- ELSE IF TLCGet("level") = 0 THEN 1 ELSE TLCGet("level")
45
- ====`;
67
+ \\* When evaluated as part of a TLC ALIAS expression, \`_TEPosition\` should be
68
+ \\* redefined as \`TLCGet("level"), or replace with \`TLCGet(level)\` right away.
69
+ \\* The definition below accounts for several "peculiarities" of the
70
+ \\* Toolbox's trace exploration.
71
+ _TEPosition ==
72
+ IF TLCGet("level") >= _TETraceLength
73
+ THEN _TETraceLength
74
+ ELSE IF TLCGet("level") = 0 THEN 1 ELSE TLCGet("level")
75
+
76
+ =============================================================================`;
46
77
 
47
78
  export const BUNDLED_MODULES = new Map([
48
79
  ["Reals", REALS],