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
@@ -1,5 +1,6 @@
1
- import { TlaEvaluationError } from "./errors";
1
+ import { TlaEvaluationError, TlaModuleError } from "./errors";
2
2
  import {
3
+ createOverflowError,
3
4
  defineArithmetic,
4
5
  defineBinary,
5
6
  defineComparison,
@@ -7,12 +8,14 @@ import {
7
8
  defineHigherOrderOperator,
8
9
  defineOperator,
9
10
  getBoolean,
10
- getElements,
11
11
  getNumber,
12
12
  getOperator,
13
- getSequence,
14
13
  getSet,
14
+ getIntegersSignature,
15
+ getJavaInteger,
15
16
  getValue,
17
+ isOutOfRange,
18
+ overrideWithJava,
16
19
  } from "./builtins";
17
20
  import type { Builtin, BuiltinArgument } from "./evaluate";
18
21
  import type { CheckerRuntime } from "./runtime";
@@ -21,10 +24,10 @@ import { createJsonOperators, type FileSystem } from "./json";
21
24
  import { createRandomizationOperators } from "./randomization";
22
25
  import { createPossibleOperators, createTlcExtOperators } from "./tlc-ext";
23
26
  import { createTlcOperators } from "./tlc-module";
27
+ import { TLC_JAVA_METHODS } from "./tlc-modules";
24
28
  import {
25
- checkInteger,
26
29
  createSet,
27
- createSortedSet,
30
+ describeKind,
28
31
  createTuple,
29
32
  differenceSorted,
30
33
  formatValue,
@@ -32,16 +35,24 @@ import {
32
35
  getDomain,
33
36
  intersectSorted,
34
37
  isElementOf,
38
+ getSequenceElements,
35
39
  isEmptySet,
36
40
  isEnumerable,
41
+ isFiniteSet,
37
42
  isFunctionValue,
38
43
  isSetValue,
39
44
  toFiniteSet,
40
45
  unionSorted,
41
- valuesEqual,
46
+ isEqualLikeTlc,
47
+ createEnumeratedSet,
48
+ EMPTY_SET,
49
+ isEnumeratedSet,
50
+ type IntervalValue,
51
+ type MixedSetValue,
42
52
  type SetOperationValue,
43
53
  type SetValue,
44
54
  type Value,
55
+ rankString,
45
56
  } from "./values";
46
57
 
47
58
  interface StandardModule {
@@ -53,30 +64,67 @@ const isSubsetOf = (left: Value[], right: Value) =>
53
64
  left.every((element) => isElementOf(element, right));
54
65
 
55
66
  const BOOLEAN_SET = createSet([false, true]);
56
- const EMPTY_SET = createSortedSet([]);
57
- const MERGED_UNION_LIMIT = 8;
67
+
68
+ // Merging the sets pairwise in a balanced tree compares each element about log2 of their count
69
+ // times.
70
+ const unionInTree = (sets: SetValue[], start: number, end: number): SetValue => {
71
+ if (end - start === 1) return sets[start];
72
+ const middle = (start + end) >> 1;
73
+ return unionSorted(unionInTree(sets, start, middle), unionInTree(sets, middle, end));
74
+ };
58
75
 
59
76
  const SET_OPERATORS: SetOperationValue["operator"][] = ["\\cup", "\\cap", "\\"];
60
77
 
78
+ type ReducibleSet = SetValue | MixedSetValue | IntervalValue;
79
+
80
+ const isReducible = (value: Value): value is ReducibleSet =>
81
+ typeof value === "object" &&
82
+ (value.kind === "Set" || value.kind === "MixedSet" || value.kind === "Interval");
83
+
84
+ const isMixedSet = (value: Value) => typeof value === "object" && value.kind === "MixedSet";
85
+
86
+ const getReducibleElements = (set: ReducibleSet) =>
87
+ set.kind === "Interval" ? toFiniteSet(set).elements : set.elements;
88
+
89
+ // TLC combines a set enumeration or an interval with another set element by element, testing each
90
+ // element for membership in the other set.
91
+ const combineReducible = (
92
+ operator: SetOperationValue["operator"],
93
+ set: ReducibleSet,
94
+ other: Value,
95
+ ): Value => {
96
+ const elements = getReducibleElements(set);
97
+ if (operator !== "\\cup") {
98
+ const isKept = operator === "\\cap";
99
+ return createEnumeratedSet(
100
+ elements.filter((element) => isElementOf(element, other) === isKept),
101
+ );
102
+ }
103
+ if (elements.length === 0) return other;
104
+ if (!isReducible(other)) return { kind: "SetOperation", operator, left: set, right: other };
105
+ const added = toFiniteSet(other).elements.filter((element) => !isElementOf(element, set));
106
+ return createEnumeratedSet([...elements, ...added]);
107
+ };
108
+
61
109
  export const CORE_OPERATORS: Builtin[] = [
62
110
  defineConstant("TRUE", true),
63
111
  defineConstant("FALSE", false),
64
112
  defineConstant("BOOLEAN", BOOLEAN_SET),
65
113
  defineConstant("STRING", { kind: "InfiniteSet", name: "STRING" }),
66
- defineBinary("=", valuesEqual),
67
- defineBinary("#", (left, right) => !valuesEqual(left, right)),
114
+ defineBinary("=", isEqualLikeTlc),
115
+ defineBinary("#", (left, right) => !isEqualLikeTlc(left, right)),
68
116
  defineOperator("~", 1, (args) => !getBoolean(args, 0, "~")),
69
117
  defineOperator("<=>", 2, (args) => getBoolean(args, 0, "<=>") === getBoolean(args, 1, "<=>")),
70
118
  defineOperator("/\\", 2, (args) => getBoolean(args, 0, "/\\") && getBoolean(args, 1, "/\\")),
71
119
  defineOperator("\\/", 2, (args) => getBoolean(args, 0, "\\/") || getBoolean(args, 1, "\\/")),
72
120
  defineOperator("=>", 2, (args) => !getBoolean(args, 0, "=>") || getBoolean(args, 1, "=>")),
73
121
  defineOperator("\\in", 2, (args) =>
74
- isElementOf(getValue(args, 0, "\\in"), getSet(args, 1, "\\in")),
122
+ isElementOf(getValue(args, 0, "\\in"), getValue(args, 1, "\\in")),
75
123
  ),
76
124
  defineOperator(
77
125
  "\\notin",
78
126
  2,
79
- (args) => !isElementOf(getValue(args, 0, "\\notin"), getSet(args, 1, "\\notin")),
127
+ (args) => !isElementOf(getValue(args, 0, "\\notin"), getValue(args, 1, "\\notin")),
80
128
  ),
81
129
  defineOperator("\\subseteq", 2, (args) => {
82
130
  const left = getSet(args, 0, "\\subseteq");
@@ -88,13 +136,14 @@ export const CORE_OPERATORS: Builtin[] = [
88
136
  right.kind === "Subset" &&
89
137
  isEnumerable(left.base);
90
138
  if (isPowerSets) return isSubsetOf(toFiniteSet(left.base).elements, right.base);
91
- return isSubsetOf(getElements(args, 0, "\\subseteq"), right);
139
+ return isSubsetOf(toFiniteSet(left).elements, right);
92
140
  }),
93
141
  ...SET_OPERATORS.map((operator) =>
94
142
  defineOperator(operator, 2, (args) => {
95
143
  const left = getSet(args, 0, operator);
96
144
  const right = getSet(args, 1, operator);
97
- if (isEnumerable(left) && isEnumerable(right)) {
145
+ if (isReducible(left) && isReducible(right) && !isMixedSet(left) && !isMixedSet(right)) {
146
+ if (operator === "\\cup" && isEmptySet(left)) return right;
98
147
  const merge =
99
148
  operator === "\\cup"
100
149
  ? unionSorted
@@ -103,35 +152,28 @@ export const CORE_OPERATORS: Builtin[] = [
103
152
  : differenceSorted;
104
153
  return merge(toFiniteSet(left), toFiniteSet(right));
105
154
  }
106
- const isReducible = (value: Value) =>
107
- typeof value === "object" && (value.kind === "Set" || value.kind === "Interval");
108
- const isSwapped = operator === "\\cup" && !isReducible(left) && isReducible(right);
109
- const set: Value = isSwapped
110
- ? { kind: "SetOperation", operator, left: right, right: left }
111
- : { kind: "SetOperation", operator, left, right };
112
- return isEnumerable(set) ? toFiniteSet(set) : set;
155
+ if (isReducible(left)) return combineReducible(operator, left, right);
156
+ if (operator !== "\\" && isReducible(right)) return combineReducible(operator, right, left);
157
+ return { kind: "SetOperation", operator, left, right };
113
158
  }),
114
159
  ),
115
160
  defineOperator("SUBSET", 1, (args) => ({ kind: "Subset", base: getSet(args, 0, "SUBSET") })),
116
161
  defineOperator("UNION", 1, (args) => {
117
- const sets = getElements(args, 0, "UNION");
118
- sets.forEach((element) => {
119
- if (!isSetValue(element)) {
120
- throw new TlaEvaluationError(
121
- `UNION expects a set of sets but found ${formatValue(element)}`,
122
- );
123
- }
124
- });
125
- if (sets.every((element) => typeof element === "object" && element.kind === "Set")) {
126
- if (sets.length > MERGED_UNION_LIMIT) {
127
- return createSet(sets.flatMap((element) => toFiniteSet(element).elements));
128
- }
129
- return sets.reduce<SetValue>(
130
- (union, element) => unionSorted(union, toFiniteSet(element)),
131
- EMPTY_SET,
162
+ const outer = getSet(args, 0, "UNION");
163
+ const sets = isEnumeratedSet(outer) ? outer.elements.filter(isEnumeratedSet) : [];
164
+ if (!isEnumeratedSet(outer) || sets.length !== outer.elements.length) {
165
+ return { kind: "Union", base: outer };
166
+ }
167
+ if (isMixedSet(outer) || sets.some(isMixedSet)) {
168
+ const union: MixedSetValue = { kind: "MixedSet", elements: [] };
169
+ sets.forEach((set) =>
170
+ set.elements.forEach((member) => {
171
+ if (!isElementOf(member, union)) union.elements.push(member);
172
+ }),
132
173
  );
174
+ return createEnumeratedSet(union.elements);
133
175
  }
134
- return sets.reduce((left, right) => ({ kind: "SetOperation", operator: "\\cup", left, right }));
176
+ return sets.length === 0 ? EMPTY_SET : unionInTree(sets.map(toFiniteSet), 0, sets.length);
135
177
  }),
136
178
  defineOperator("DOMAIN", 1, (args) => {
137
179
  const value = getValue(args, 0, "DOMAIN");
@@ -142,24 +184,36 @@ export const CORE_OPERATORS: Builtin[] = [
142
184
  }),
143
185
  ];
144
186
 
145
- const NATURALS_OPERATORS: Builtin[] = [
187
+ const DOT_DOT_SIGNATURE = getIntegersSignature("DotDot", "IntervalValue", 2);
188
+
189
+ const NEG_SIGNATURE = getIntegersSignature("Neg", "IntValue", 1);
190
+
191
+ const NATURALS_OPERATORS = [
146
192
  defineConstant("Nat", { kind: "InfiniteSet", name: "Nat" }),
147
- defineArithmetic("+", (left, right) => left + right),
148
- defineArithmetic("-", (left, right) => left - right),
149
- defineArithmetic("*", (left, right) => left * right),
150
- defineArithmetic("^", (base, exponent) => {
151
- if (exponent < 0 || (base === 0 && exponent === 0)) {
152
- throw new TlaEvaluationError(`${base}^${exponent} is undefined`);
193
+ defineArithmetic("+", "Plus", (left, right) => left + right),
194
+ defineArithmetic("-", "Minus", (left, right) => left - right),
195
+ defineArithmetic("*", "Times", (left, right) => left * right),
196
+ defineArithmetic("^", "Expt", (base, exponent) => {
197
+ if (exponent < 0) {
198
+ throw new TlaModuleError(
199
+ `The second argument of ^ should be a natural number, but instead it is:\n${exponent}`,
200
+ );
153
201
  }
202
+ if (base === 0 && exponent === 0) throw new TlaModuleError("0^0 is undefined.");
154
203
  return base ** exponent;
155
204
  }),
156
- defineArithmetic("\\div", (left, right) => {
157
- if (right === 0) throw new TlaEvaluationError(`Division by zero in ${left} \\div 0`);
158
- return Math.floor(left / right);
205
+ defineArithmetic("\\div", "Divide", (left, right) => {
206
+ if (right === 0) throw new TlaModuleError("The second argument of \\div is 0.");
207
+ const quotient = Math.floor(left / right);
208
+ if (isOutOfRange(quotient)) throw createOverflowError(`${left} \\div ${right}`);
209
+ return quotient;
159
210
  }),
160
- defineArithmetic("%", (left, right) => {
161
- if (right <= 0)
162
- throw new TlaEvaluationError(`The second argument of % must be positive, but was ${right}`);
211
+ defineArithmetic("%", "Mod", (left, right) => {
212
+ if (right <= 0) {
213
+ throw new TlaModuleError(
214
+ `The second argument of % should be a positive number, but instead it is:\n${right}`,
215
+ );
216
+ }
163
217
  return ((left % right) + right) % right;
164
218
  }),
165
219
  defineComparison("<", (left, right) => left < right),
@@ -168,85 +222,162 @@ const NATURALS_OPERATORS: Builtin[] = [
168
222
  defineComparison(">=", (left, right) => left >= right),
169
223
  defineOperator("..", 2, (args) => ({
170
224
  kind: "Interval",
171
- low: getNumber(args, 0, ".."),
172
- high: getNumber(args, 1, ".."),
225
+ low: getJavaInteger(getValue(args, 0, ".."), DOT_DOT_SIGNATURE),
226
+ high: getJavaInteger(getValue(args, 1, ".."), DOT_DOT_SIGNATURE),
173
227
  })),
174
228
  ];
175
229
 
176
- const INTEGERS_OPERATORS: Builtin[] = [
230
+ const INTEGERS_OPERATORS = [
177
231
  defineConstant("Int", { kind: "InfiniteSet", name: "Int" }),
178
- defineOperator("-.", 1, (args) => checkInteger(-getNumber(args, 0, "-"))),
232
+ defineOperator("-.", 1, (args) => {
233
+ const value = getJavaInteger(getValue(args, 0, "-"), NEG_SIGNATURE);
234
+ if (isOutOfRange(-value)) throw createOverflowError(`-${value}`);
235
+ return -value;
236
+ }),
179
237
  ];
180
238
 
181
- const getSequenceOrString = (args: BuiltinArgument[], index: number, name: string) => {
182
- const value = getValue(args, index, name);
183
- return typeof value === "string" ? value : getSequence(args, index, name);
239
+ const getSequenceArgument = (args: BuiltinArgument[], index: number, message: string) => {
240
+ const value = getValue(args, index, message);
241
+ const elements = getSequenceElements(value);
242
+ if (!elements) throw new TlaModuleError(`${message}\n${formatValue(value)}`);
243
+ return elements;
184
244
  };
185
245
 
186
- const SEQUENCES_OPERATORS: Builtin[] = [
187
- defineOperator("Seq", 1, (args) => ({ kind: "SequenceSet", base: getSet(args, 0, "Seq") })),
188
- defineOperator("Len", 1, (args) => getSequenceOrString(args, 0, "Len").length),
246
+ const getSequenceOrString = (args: BuiltinArgument[], index: number, message: string) => {
247
+ const value = getValue(args, index, message);
248
+ return typeof value === "string" ? value : getSequenceArgument(args, index, message);
249
+ };
250
+
251
+ const sliceSequence = (sequence: string | Value[], start: number, end?: number) =>
252
+ typeof sequence === "string"
253
+ ? rankString(sequence.slice(start, end))
254
+ : createTuple(sequence.slice(start, end));
255
+
256
+ const getSequenceMessage = (position: string, name: string) =>
257
+ `The ${position}argument of ${name} should be a sequence, but instead it is:`;
258
+
259
+ const getEvaluatingMessage = (form: string, kind: string) =>
260
+ `Evaluating an expression of the form ${form} when s is not a ${kind}:`;
261
+
262
+ const withJavaMethods = (module: string, operators: Builtin[]) => {
263
+ const methods = TLC_JAVA_METHODS.get(module);
264
+ if (!methods) return operators;
265
+ return operators.map((operator) => {
266
+ const signature = methods.get(operator.name);
267
+ return signature ? overrideWithJava(operator, signature) : operator;
268
+ });
269
+ };
270
+
271
+ const SEQUENCES_OPERATORS = withJavaMethods("Sequences", [
272
+ defineOperator("Seq", 1, (args) => ({ kind: "SequenceSet", base: getValue(args, 0, "Seq") })),
273
+ defineOperator(
274
+ "Len",
275
+ 1,
276
+ (args) => getSequenceOrString(args, 0, getSequenceMessage("", "Len")).length,
277
+ ),
189
278
  defineOperator("\\o", 2, (args) => {
190
- const left = getSequenceOrString(args, 0, "\\o");
191
- const right = getSequenceOrString(args, 1, "\\o");
192
- if (typeof left === "string" && typeof right === "string") return left + right;
193
- if (typeof left === "string" || typeof right === "string") {
194
- throw new TlaEvaluationError("\\o cannot concatenate a string with a sequence");
279
+ const left = getSequenceOrString(args, 0, getEvaluatingMessage("s \\o t", "sequence"));
280
+ if (typeof left !== "string") {
281
+ const right = getSequenceArgument(args, 1, getEvaluatingMessage("t \\o s", "sequence"));
282
+ return createTuple([...left, ...right]);
195
283
  }
196
- return createTuple([...left, ...right]);
284
+ const right = getValue(args, 1, "\\o");
285
+ if (typeof right !== "string") {
286
+ throw new TlaModuleError(
287
+ `${getEvaluatingMessage("t \\o s", "string")}\n${formatValue(right)}`,
288
+ );
289
+ }
290
+ return rankString(left + right);
197
291
  }),
198
292
  defineOperator("Append", 2, (args) =>
199
- createTuple([...getSequence(args, 0, "Append"), getValue(args, 1, "Append")]),
293
+ createTuple([
294
+ ...getSequenceArgument(args, 0, getEvaluatingMessage("Append(s, v)", "sequence")),
295
+ getValue(args, 1, "Append"),
296
+ ]),
200
297
  ),
201
298
  defineOperator("Head", 1, (args) => {
202
- const elements = getSequence(args, 0, "Head");
299
+ const elements = getSequenceArgument(args, 0, getSequenceMessage("", "Head"));
203
300
  if (elements.length === 0)
204
- throw new TlaEvaluationError("Attempted to apply Head to the empty sequence");
301
+ throw new TlaModuleError("Attempted to apply Head to the empty sequence.");
205
302
  return elements[0];
206
303
  }),
207
304
  defineOperator("Tail", 1, (args) => {
208
- const sequence = getSequenceOrString(args, 0, "Tail");
305
+ const sequence = getSequenceOrString(args, 0, getSequenceMessage("", "Tail"));
209
306
  if (sequence.length === 0)
210
- throw new TlaEvaluationError("Attempted to apply Tail to the empty sequence");
211
- return typeof sequence === "string" ? sequence.slice(1) : createTuple(sequence.slice(1));
307
+ throw new TlaModuleError("Attempted to apply Tail to the empty sequence.");
308
+ return sliceSequence(sequence, 1);
212
309
  }),
213
310
  defineOperator("SubSeq", 3, (args) => {
214
- const sequence = getSequenceOrString(args, 0, "SubSeq");
311
+ const sequence = getSequenceOrString(args, 0, getSequenceMessage("first ", "SubSeq"));
215
312
  const start = getNumber(args, 1, "SubSeq");
216
313
  const end = getNumber(args, 2, "SubSeq");
217
- if (start > end) return typeof sequence === "string" ? "" : createTuple([]);
218
- if (start < 1 || end > sequence.length) {
219
- throw new TlaEvaluationError(
220
- `SubSeq bounds ${start}..${end} are outside 1..${sequence.length}`,
314
+ if (start > end) return sliceSequence(sequence, 0, 0);
315
+ const outOfDomain =
316
+ start < 1
317
+ ? { position: "second", value: start }
318
+ : end > sequence.length
319
+ ? { position: "third", value: end }
320
+ : undefined;
321
+ if (outOfDomain) {
322
+ throw new TlaModuleError(
323
+ `The ${outOfDomain.position} argument of SubSeq must be in the domain of its first argument:\n${formatValue(getValue(args, 0, "SubSeq"))}\n, but instead it is\n${outOfDomain.value}`,
221
324
  );
222
325
  }
223
- return typeof sequence === "string"
224
- ? sequence.slice(start - 1, end)
225
- : createTuple(sequence.slice(start - 1, end));
326
+ return sliceSequence(sequence, start - 1, end);
226
327
  }),
227
328
  defineHigherOrderOperator("SelectSeq", 2, (args) => {
228
329
  const test = getOperator(args, 1, "SelectSeq");
229
330
  return createTuple(
230
- getSequence(args, 0, "SelectSeq").filter((element) => {
331
+ getSequenceArgument(args, 0, getSequenceMessage("first ", "SelectSeq")).filter((element) => {
231
332
  const result = test.call([element]);
232
- if (typeof result !== "boolean")
233
- throw new TlaEvaluationError("The test of SelectSeq must return a boolean");
333
+ if (typeof result !== "boolean") {
334
+ throw new TlaModuleError(
335
+ `The second argument of SelectSeq should be a operator, but instead it is:\n${formatValue(result)}`,
336
+ );
337
+ }
234
338
  return result;
235
339
  }),
236
340
  );
237
341
  }),
238
- ];
342
+ ]);
239
343
 
240
- const FINITE_SETS_OPERATORS: Builtin[] = [
241
- defineOperator("IsFiniteSet", 1, (args) => isEnumerable(getSet(args, 0, "IsFiniteSet"))),
344
+ const FINITE_SETS_OPERATORS = withJavaMethods("FiniteSets", [
345
+ defineOperator("IsFiniteSet", 1, (args) => {
346
+ const value = getValue(args, 0, "IsFiniteSet");
347
+ if (isSetValue(value)) return isFiniteSet(value);
348
+ if (typeof value === "object" && value.kind === "Function") return true;
349
+ throw new TlaEvaluationError(
350
+ typeof value === "object" && value.kind === "LazyFunction"
351
+ ? `Attempted to check if the function:\n${formatValue(value)}\nis a finite set.`
352
+ : `Attempted to check if the ${describeKind(value)} ${formatValue(value)} is a finite set.`,
353
+ );
354
+ }),
242
355
  defineOperator("Cardinality", 1, (args) => {
243
- const set = getSet(args, 0, "Cardinality");
244
- if (isEmptySet(set)) return 0;
245
- if (!isEnumerable(set))
246
- throw new TlaEvaluationError(`Cardinality of the infinite set ${formatValue(set)}`);
356
+ const set = getValue(args, 0, "Cardinality");
357
+ if (
358
+ !isSetValue(set) ||
359
+ (typeof set === "object" && (set.kind === "InfiniteSet" || set.kind === "SequenceSet"))
360
+ ) {
361
+ throw new TlaModuleError(
362
+ `Attempted to compute cardinality of the value\n${formatValue(set)}`,
363
+ );
364
+ }
247
365
  return getCardinality(set);
248
366
  }),
249
- ];
367
+ ]);
368
+
369
+ // The modules that each standard module loads through EXTENDS and LOCAL INSTANCE in TLC.
370
+ export const STANDARD_DEPENDENCIES = new Map([
371
+ ["Integers", ["Naturals"]],
372
+ ["Sequences", ["Naturals"]],
373
+ ["FiniteSets", ["Naturals", "Sequences"]],
374
+ ["TLC", ["Naturals", "Sequences", "FiniteSets"]],
375
+ ["Randomization", ["Naturals", "FiniteSets", "Sequences"]],
376
+ ["_Possible", ["TLC", "TLCExt", "Naturals", "Sequences", "FiniteSets", "Integers"]],
377
+ ["Json", ["Sequences", "FiniteSets", "TLC", "Naturals"]],
378
+ ["Bags", ["Naturals"]],
379
+ ["TLCExt", ["TLC", "Integers", "Naturals", "Sequences", "FiniteSets"]],
380
+ ]);
250
381
 
251
382
  export const getStandardModule = (
252
383
  name: string,
@@ -264,7 +395,7 @@ export const getStandardModule = (
264
395
  case "FiniteSets":
265
396
  return { extends: [], operators: FINITE_SETS_OPERATORS };
266
397
  case "TLC":
267
- return { extends: [], operators: createTlcOperators(print, runtime) };
398
+ return { extends: [], operators: withJavaMethods("TLC", createTlcOperators(print, runtime)) };
268
399
  case "Randomization":
269
400
  return { extends: [], operators: createRandomizationOperators(runtime) };
270
401
  case "_Possible":
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  Bound,
3
- BoundPattern,
4
3
  Definition,
5
4
  Expression,
6
5
  Instance,
@@ -11,7 +10,8 @@ import type {
11
10
  } from "./ast";
12
11
  import { TlaSyntaxError } from "./errors";
13
12
  import { getChildren } from "./evaluate";
14
- import { mapChildren, mapDefinition } from "./instances";
13
+ import { getSourceName, mapChildren, mapDefinition } from "./instances";
14
+ import { getArity, getBoundNames, getPatternNames, LOCAL_RESOLUTION } from "./resolve";
15
15
 
16
16
  type Lookup = (name: string) => Definition | undefined;
17
17
 
@@ -27,21 +27,10 @@ interface Selection {
27
27
  location: SourceLocation;
28
28
  }
29
29
 
30
- const LOCAL_RESOLUTION = { kind: "Local" } as const;
31
-
32
30
  const SELECTION_NAME = "$subexpression";
33
31
 
34
- const describeLocation = (location: SourceLocation) =>
35
- `line ${location.line}, column ${location.column}`;
36
-
37
32
  const createError = (message: string, location: SourceLocation) =>
38
- new TlaSyntaxError(`${message} at ${describeLocation(location)}`);
39
-
40
- const getPatternNames = (pattern: BoundPattern) =>
41
- pattern.kind === "Name" ? [pattern.name] : pattern.names;
42
-
43
- const getBoundNames = (bounds: Bound[]) =>
44
- bounds.flatMap((bound) => getPatternNames(bound.pattern));
33
+ new TlaSyntaxError(`${message} at line ${location.line}, column ${location.column}`);
45
34
 
46
35
  const getBoundSets = (bounds: Bound[]) =>
47
36
  bounds
@@ -164,11 +153,8 @@ const findLabel = (
164
153
  return undefined;
165
154
  };
166
155
 
167
- const takeArguments = (
168
- selector: Selector & { kind: "Name" | "Bound" },
169
- count: number,
170
- pool: Expression[],
171
- ) => (selector.args.length === 0 && pool.length > 0 ? pool.splice(0, count) : selector.args);
156
+ const takeArguments = (args: Expression[], count: number, pool: Expression[]) =>
157
+ args.length === 0 && pool.length > 0 ? pool.splice(0, count) : args;
172
158
 
173
159
  const wrapLets = (body: Expression, lets: Definition[][], location: SourceLocation) =>
174
160
  lets.reduceRight<Expression>(
@@ -221,7 +207,7 @@ const createApplication = (selection: Selection, body: Expression): Expression =
221
207
  };
222
208
  };
223
209
 
224
- const applyDefinition = (definition: Definition, selection: Selection): Expression => {
210
+ const applyDefinition = (definition: Definition, selection: Selection) => {
225
211
  const location = selection.location;
226
212
  const params =
227
213
  definition.kind === "Operator"
@@ -267,11 +253,11 @@ const selectSubexpression = (
267
253
  names.push(selector.name);
268
254
  args.push(...selector.args);
269
255
  const name = names.join("!");
270
- const definition = local ? local.find((candidate) => candidate.name === name) : lookup(name);
256
+ const definition = local
257
+ ? local.find((candidate) => getSourceName(candidate) === name)
258
+ : lookup(name);
271
259
  if (definition) {
272
- const arity = definition.kind === "Operator" ? definition.params.length : 0;
273
- const given = args.length === 0 && pool.length > 0 ? pool.splice(0, arity) : args;
274
- return { definition, args: given };
260
+ return { definition, args: takeArguments(args, getArity(definition), pool) };
275
261
  }
276
262
  state.index++;
277
263
  }
@@ -281,7 +267,7 @@ const selectSubexpression = (
281
267
  const enterDefinition = (local: Definition[] | undefined) => {
282
268
  const found = findDefinition(local);
283
269
  const definition = found.definition;
284
- const arity = definition.kind === "Operator" ? definition.params.length : 0;
270
+ const arity = getArity(definition);
285
271
  const isLast = state.index === selectors.length - 1;
286
272
  if (found.args.length !== arity && found.args.length !== 0) {
287
273
  throw createError(`The operator ${definition.name} requires ${arity} arguments`, location);
@@ -298,13 +284,14 @@ const selectSubexpression = (
298
284
  };
299
285
 
300
286
  const followLabels = () => {
301
- while (state.index < selectors.length && selectors[state.index].kind === "Name") {
302
- const selector = selectors[state.index] as Selector & { kind: "Name" };
287
+ while (state.index < selectors.length) {
288
+ const selector = selectors[state.index];
289
+ if (selector.kind !== "Name") return;
303
290
  const found = state.current && findLabel(state.current, selector.name, []);
304
291
  if (!found) throw createError(`Cannot find label ${selector.name}`, location);
305
292
  selection.lets.push(...found.lets);
306
293
  selection.params.push(...found.label.params.map((name) => ({ name, arity: 0 })));
307
- selection.args.push(...takeArguments(selector, found.label.params.length, pool));
294
+ selection.args.push(...takeArguments(selector.args, found.label.params.length, pool));
308
295
  state.current = found.label.body;
309
296
  state.index++;
310
297
  }
@@ -331,7 +318,7 @@ const selectSubexpression = (
331
318
  if (binder && (selector.kind === "Bound" || selector.kind === "At")) {
332
319
  selection.params.push(...binder.names.map((name) => ({ name, arity: 0 })));
333
320
  if (selector.kind === "Bound") {
334
- selection.args.push(...takeArguments(selector, binder.names.length, pool));
321
+ selection.args.push(...takeArguments(selector.args, binder.names.length, pool));
335
322
  }
336
323
  state.current = binder.body;
337
324
  return;
@@ -372,8 +359,7 @@ const selectSubexpression = (
372
359
  throw createError("A name selector here must be from a LET clause", location);
373
360
  }
374
361
  selection.lets.push(state.current.definitions);
375
- const definitions = state.current.definitions;
376
- const last = enterDefinition(definitions);
362
+ const last = enterDefinition(state.current.definitions);
377
363
  if (last) {
378
364
  return applyDefinition(last.definition, {
379
365
  ...selection,
@@ -407,10 +393,10 @@ const containsReference = (expression: Expression): boolean =>
407
393
  (expression.kind === "Apply" && expression.operator.includes("!")) ||
408
394
  getChildren(expression).some(containsReference);
409
395
 
410
- export const resolveSubexpressions = (expression: Expression, lookup: Lookup): Expression => {
396
+ const resolveSubexpressions = (expression: Expression, lookup: Lookup): Expression => {
411
397
  if (!containsReference(expression)) return expression;
412
398
  if (expression.kind === "Let") {
413
- const local: Lookup = (name: string) =>
399
+ const local: Lookup = (name) =>
414
400
  expression.definitions.find((definition) => definition.name === name) ?? lookup(name);
415
401
  return mapChildren(expression, (child) => resolveSubexpressions(child, local));
416
402
  }
@@ -446,10 +432,10 @@ export const resolveInstanceSubexpressions = (instance: Instance, module: Module
446
432
 
447
433
  export const resolveModuleSubexpressions = (module: Module, available: Definition[]): Module => {
448
434
  const lookup = createLookup([...available, ...module.definitions]);
449
- const isResolved =
435
+ const hasReferences =
450
436
  module.definitions.some((definition) => containsReference(definition.body)) ||
451
437
  module.assumptions.some((assumption) => containsReference(assumption.expression));
452
- if (!isResolved) return module;
438
+ if (!hasReferences) return module;
453
439
  return {
454
440
  ...module,
455
441
  definitions: module.definitions.map((definition) =>