tla-ts 0.0.1 → 0.0.3-dev.a50fc89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +57 -28
  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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tla-ts",
3
- "version": "0.0.1",
4
- "description": "A TLA+ interpreter and model checker in TypeScript, 2.4× faster than TLC",
3
+ "version": "0.0.3-dev.a50fc89",
4
+ "description": "A TLA+ interpreter and model checker in TypeScript, 2× faster than TLC",
5
5
  "keywords": [
6
6
  "formal verification",
7
7
  "model checker",
@@ -68,7 +68,11 @@
68
68
  "bench:corpus": "pnpm build && tsx scripts/benchmark-corpus.ts",
69
69
  "conformance": "tsx scripts/conformance.ts",
70
70
  "conformance:fetch": "tsx scripts/fetch-corpora.ts",
71
+ "conformance:sany": "tsx scripts/proof-conformance.ts",
71
72
  "conformance:record": "tsx scripts/record-tlc.ts",
73
+ "conformance:record-sany": "tsx scripts/record-sany.ts",
74
+ "conformance:record-tlapm": "tsx scripts/record-tlapm.ts",
75
+ "conformance:tlapm": "tsx scripts/tlapm-conformance.ts",
72
76
  "publint": "publint",
73
77
  "test": "vp test",
74
78
  "typecheck": "tsc --noEmit"
package/src/ast.ts CHANGED
@@ -5,11 +5,25 @@ export interface LocalResolution {
5
5
  export interface SourceLocation {
6
6
  line: number;
7
7
  column: number;
8
+ range?: SourceRange;
9
+ }
10
+
11
+ export interface SourceRange {
12
+ module: string;
13
+ start: SourceLocation;
14
+ end: SourceLocation;
8
15
  }
9
16
 
10
17
  export interface NumberExpression {
11
18
  kind: "Number";
12
19
  value: number;
20
+ text?: string;
21
+ location: SourceLocation;
22
+ }
23
+
24
+ export interface DecimalExpression {
25
+ kind: "Decimal";
26
+ text: string;
13
27
  location: SourceLocation;
14
28
  }
15
29
 
@@ -30,6 +44,9 @@ export interface ApplyExpression {
30
44
  kind: "Apply";
31
45
  operator: string;
32
46
  args: Expression[];
47
+ // The number of args each part of a name like F(x)!l(y) takes, when a part before the last takes
48
+ // some.
49
+ argumentCounts?: number[];
33
50
  location: SourceLocation;
34
51
  resolution?: LocalResolution;
35
52
  }
@@ -50,6 +67,7 @@ export interface LambdaExpression {
50
67
  export interface JunctionExpression {
51
68
  kind: "And" | "Or";
52
69
  operands: Expression[];
70
+ isBulleted?: boolean;
53
71
  location: SourceLocation;
54
72
  }
55
73
 
@@ -115,6 +133,7 @@ export interface QuantifierExpression {
115
133
 
116
134
  export interface UnboundedQuantifierExpression {
117
135
  kind: "UnboundedQuantifier";
136
+ quantifier: "Forall" | "Exists";
118
137
  names: string[];
119
138
  isTemporal?: boolean;
120
139
  body: Expression;
@@ -266,6 +285,7 @@ export interface SubexpressionExpression {
266
285
 
267
286
  export type Expression =
268
287
  | NumberExpression
288
+ | DecimalExpression
269
289
  | StringExpression
270
290
  | IdentifierExpression
271
291
  | ApplyExpression
@@ -302,6 +322,7 @@ export type Expression =
302
322
  export interface OperatorParam {
303
323
  name: string;
304
324
  arity: number;
325
+ location?: SourceLocation;
305
326
  }
306
327
 
307
328
  export interface OperatorDefinition {
@@ -309,6 +330,7 @@ export interface OperatorDefinition {
309
330
  name: string;
310
331
  params: OperatorParam[];
311
332
  body: Expression;
333
+ bodyRange?: SourceRange;
312
334
  isLocal: boolean;
313
335
  isRecursive: boolean;
314
336
  location: SourceLocation;
@@ -329,6 +351,7 @@ export type Definition = OperatorDefinition | FunctionDefinition;
329
351
  export interface Declaration {
330
352
  name: string;
331
353
  arity: number;
354
+ location: SourceLocation;
332
355
  }
333
356
 
334
357
  export interface Assumption {
@@ -351,21 +374,115 @@ export interface Instance {
351
374
  substitutions: InstanceSubstitution[];
352
375
  isLocal: boolean;
353
376
  location: SourceLocation;
377
+ instanceRange: SourceRange;
378
+ definitionRange?: SourceRange;
354
379
  }
355
380
 
356
- export interface OuterScope {
357
- extends: string[];
358
- symbols: Declaration[];
381
+ export interface NewSymbol {
382
+ kind: "NewSymbol";
383
+ name: string;
384
+ arity: number;
385
+ level: "Constant" | "Variable" | "State" | "Action" | "Temporal";
386
+ set: Expression | undefined;
387
+ location: SourceLocation;
388
+ }
389
+
390
+ export interface AssumeProve {
391
+ kind: "AssumeProve";
392
+ assumptions: (Expression | NewSymbol | AssumeProve)[];
393
+ conclusion: Expression;
394
+ location: SourceLocation;
395
+ }
396
+
397
+ export type Fact = Expression | { kind: "ModuleFact"; name: string; location: SourceLocation };
398
+
399
+ // keyword is the range of the USE or HIDE token and any ONLY after it.
400
+ export interface UseOrHide {
401
+ kind: "Use" | "Hide";
402
+ isOnly: boolean;
403
+ facts: Fact[];
404
+ defs: Fact[];
405
+ keyword: SourceRange;
406
+ location: SourceLocation;
359
407
  }
360
408
 
409
+ // keyword is the range of the BY, OBVIOUS, or OMITTED token.
410
+ export type Proof =
411
+ | { kind: "Obvious" | "Omitted"; keyword: SourceRange; location: SourceLocation }
412
+ | {
413
+ kind: "By";
414
+ isOnly: boolean;
415
+ facts: Fact[];
416
+ defs: Fact[];
417
+ keyword: SourceRange;
418
+ location: SourceLocation;
419
+ }
420
+ | {
421
+ kind: "Steps";
422
+ level: number;
423
+ steps: ProofStep[];
424
+ location: SourceLocation;
425
+ };
426
+
427
+ export interface DefineStep {
428
+ kind: "Define";
429
+ definitions: (Definition | Instance)[];
430
+ location: SourceLocation;
431
+ }
432
+
433
+ export type StepBody =
434
+ | UseOrHide
435
+ | Instance
436
+ | DefineStep
437
+ | { kind: "Have"; expression: Expression }
438
+ | { kind: "Take"; names: string[]; bounds: Bound[] }
439
+ | { kind: "Witness"; expressions: Expression[] }
440
+ | { kind: "Pick"; names: string[]; bounds: Bound[]; body: Expression }
441
+ | { kind: "Case"; expression: Expression }
442
+ | { kind: "Assert"; isSuffices: boolean; statement: Expression | AssumeProve }
443
+ | { kind: "Qed" };
444
+
445
+ // head is the range of the step number and its statement, without the proof.
446
+ export interface ProofStep {
447
+ name: string | undefined;
448
+ token: string;
449
+ body: StepBody;
450
+ head: SourceRange;
451
+ proof: Proof | undefined;
452
+ location: SourceLocation;
453
+ }
454
+
455
+ export interface Theorem {
456
+ name: string | undefined;
457
+ statement: Expression | AssumeProve;
458
+ proof: Proof | undefined;
459
+ location: SourceLocation;
460
+ }
461
+
462
+ // The units of a module in source order, for consumers that need the order across kinds.
463
+ export type ModuleUnit =
464
+ | { kind: "Constants"; declarations: Declaration[] }
465
+ | { kind: "Variables"; declarations: Declaration[] }
466
+ | { kind: "Recursive"; declarations: Declaration[] }
467
+ | { kind: "Definition"; definition: Definition }
468
+ | { kind: "Instance"; instance: Instance }
469
+ | { kind: "Assumption"; assumption: Assumption }
470
+ | { kind: "Theorem"; theorem: Theorem }
471
+ | { kind: "Use"; use: UseOrHide }
472
+ | { kind: "Module"; module: Module };
473
+
361
474
  export interface Module {
362
475
  name: string;
363
- outer: OuterScope | undefined;
364
476
  modules: Module[];
365
477
  extends: string[];
478
+ extendsRanges: SourceRange[];
366
479
  constants: Declaration[];
367
- variables: string[];
480
+ variables: Declaration[];
368
481
  definitions: Definition[];
369
482
  instances: Instance[];
370
483
  assumptions: Assumption[];
484
+ location: SourceLocation;
485
+ theorems: Theorem[];
486
+ uses: UseOrHide[];
487
+ units: ModuleUnit[];
371
488
  }
package/src/bags.ts CHANGED
@@ -1,75 +1,118 @@
1
1
  import {
2
+ createJavaOverrideError,
3
+ createOverflowError,
2
4
  defineConstant,
3
5
  defineHigherOrderOperator,
4
6
  defineOperator,
5
- getElements,
7
+ getJavaClassName,
6
8
  getOperator,
7
9
  getValue,
10
+ isOutOfRange,
8
11
  } from "./builtins";
9
- import { TlaEvaluationError } from "./errors";
12
+ import { TlaEvaluationError, TlaModuleError } from "./errors";
10
13
  import type { Builtin, BuiltinArgument } from "./evaluate";
11
14
  import {
12
- checkInteger,
13
15
  createFunction,
16
+ createSet,
14
17
  createSortedFunction,
15
- createSortedSet,
16
18
  formatValue,
19
+ getDomain,
17
20
  isEnumerable,
18
21
  isFunctionValue,
22
+ isSetValue,
19
23
  toFiniteFunction,
24
+ toFiniteSet,
20
25
  valuesEqual,
21
26
  type FunctionValue,
22
27
  type Value,
23
28
  } from "./values";
24
29
 
25
- const describeWrongValue = (name: string, expected: string, value: Value) =>
26
- new TlaEvaluationError(
30
+ const createWrongValueError = (name: string, expected: string, value: Value) =>
31
+ new TlaModuleError(
27
32
  `Applying ${name} to the following value,\nwhich is not ${expected}:\n${formatValue(value)}`,
28
33
  );
29
34
 
30
- const describeWrongArgument = (position: string, name: string, expected: string, value: Value) =>
31
- new TlaEvaluationError(
35
+ const createWrongArgumentError = (position: string, name: string, expected: string, value: Value) =>
36
+ new TlaModuleError(
32
37
  `The ${position} argument of ${name} should be a ${expected}, but instead it is:\n${formatValue(value)}`,
33
38
  );
34
39
 
35
- const toFunction = (value: Value): FunctionValue | undefined =>
36
- isFunctionValue(value) && (value.kind === "Function" || isEnumerable(value.domain))
37
- ? toFiniteFunction(value)
38
- : undefined;
40
+ const toFunction = (value: Value) => (isFunctionValue(value) ? toFiniteFunction(value) : undefined);
39
41
 
40
42
  const getFunction = (args: BuiltinArgument[], index: number, name: string) => {
41
43
  const value = getValue(args, index, name);
42
- const fn = toFunction(value);
43
- if (!fn) throw describeWrongValue(name, "a function with a finite domain", value);
44
- return fn;
44
+ const functionValue = toFunction(value);
45
+ if (!functionValue) throw createWrongValueError(name, "a function with a finite domain", value);
46
+ return functionValue;
45
47
  };
46
48
 
47
- const isBag = (fn: FunctionValue) =>
48
- fn.range.every((count) => typeof count === "number" && count > 0);
49
+ const getBagArgument = (args: BuiltinArgument[], index: number, name: string) => {
50
+ const value = getValue(args, index, name);
51
+ const functionValue = toFunction(value);
52
+ if (!functionValue)
53
+ throw createWrongArgumentError(index === 0 ? "first" : "second", name, "bag", value);
54
+ return functionValue;
55
+ };
49
56
 
50
- const getCount = (value: Value, name: string, bag: Value) => {
51
- if (typeof value !== "number") throw describeWrongValue(name, "a bag", bag);
52
- return value;
57
+ const getFiniteElements = (set: Value, describeError: () => TlaModuleError) => {
58
+ if (!isSetValue(set) || !isEnumerable(set)) throw describeError();
59
+ return toFiniteSet(set).elements;
53
60
  };
54
61
 
55
- const findCount = (fn: FunctionValue, element: Value) => {
56
- const position = fn.domain.findIndex((key) => valuesEqual(key, element));
57
- return position === -1 ? undefined : fn.range[position];
62
+ const isBag = (functionValue: FunctionValue) =>
63
+ functionValue.range.every((count) => typeof count === "number" && count > 0);
64
+
65
+ const findCount = (functionValue: FunctionValue, element: Value) => {
66
+ const position = functionValue.domain.findIndex((key) => valuesEqual(key, element));
67
+ return position === -1 ? undefined : functionValue.range[position];
58
68
  };
59
69
 
60
- const addCounts = (left: number, right: number) => {
61
- const sum = left + right;
62
- if (sum > 2147483647) throw new TlaEvaluationError(`Overflow when computing ${left}+${right}`);
70
+ const getMethod = (name: string, result: string, arity: number) =>
71
+ `public static ${result} tlc2.module.Bags.${name}(${Array.from({ length: arity }, () => "tlc2.value.impl.Value").join(",")})`;
72
+
73
+ const BAG_DIFF = getMethod("BagDiff", "tlc2.value.impl.Value", 2);
74
+ const SQ_SUBSETEQ = getMethod("SqSubseteq", "tlc2.value.IBoolValue", 2);
75
+ const BAG_UNION = getMethod("BagUnion", "tlc2.value.impl.Value", 1);
76
+ const BAG_OF_ALL = getMethod("BagOfAll", "tlc2.value.impl.Value", 2);
77
+ const BAG_CUP = getMethod("BagCup", "tlc2.value.impl.Value", 2);
78
+ const BAG_CARDINALITY = getMethod("BagCardinality", "tlc2.value.impl.IntValue", 1);
79
+
80
+ // Bags.java casts each count to IntValue, so a count that isn't an integer fails with Java's
81
+ // ClassCastException.
82
+ const toCount = (value: Value, method: string) => {
83
+ if (typeof value === "number") return value;
84
+ const javaClass = `tlc2.value.impl.${getJavaClassName(value)}`;
85
+ throw createJavaOverrideError(
86
+ method,
87
+ `class ${javaClass} cannot be cast to class tlc2.value.impl.IntValue (${javaClass} and tlc2.value.impl.IntValue are in unnamed module of loader 'app')`,
88
+ );
89
+ };
90
+
91
+ const addCounts = (left: Value, right: Value, method: string) => {
92
+ const sum = toCount(left, method) + toCount(right, method);
93
+ if (isOutOfRange(sum)) throw createOverflowError(`${left}+${right}`);
63
94
  return sum;
64
95
  };
65
96
 
66
- const mergeBags = (bags: FunctionValue[]) =>
97
+ // TLC subtracts counts as Java ints, which wrap on overflow.
98
+ const subtractCounts = (
99
+ functionValue: FunctionValue,
100
+ index: number,
101
+ other: FunctionValue,
102
+ method: string,
103
+ ) => {
104
+ const removed = findCount(other, functionValue.domain[index]);
105
+ const count = toCount(functionValue.range[index], method);
106
+ return removed === undefined ? count : (count - toCount(removed, method)) | 0;
107
+ };
108
+
109
+ const mergeBags = (bags: FunctionValue[], method: string) =>
67
110
  createFunction(
68
111
  bags.reduce<[Value, Value][]>((pairs, bag) => {
69
112
  bag.domain.forEach((key, index) => {
70
113
  const existing = pairs.find((pair) => valuesEqual(pair[0], key));
71
114
  if (existing) {
72
- existing[1] = addCounts(Number(existing[1]), Number(bag.range[index]));
115
+ existing[1] = addCounts(existing[1], bag.range[index], method);
73
116
  return;
74
117
  }
75
118
  pairs.push([key, bag.range[index]]);
@@ -78,107 +121,154 @@ const mergeBags = (bags: FunctionValue[]) =>
78
121
  }, []),
79
122
  );
80
123
 
124
+ const EMPTY_BAG = createSortedFunction([], []);
125
+
81
126
  export const BAGS_OPERATORS: Builtin[] = [
82
- defineConstant("EmptyBag", createSortedFunction([], [])),
127
+ defineConstant("EmptyBag", EMPTY_BAG),
83
128
  defineOperator("IsABag", 1, (args) => {
84
- const fn = toFunction(getValue(args, 0, "IsABag"));
85
- return fn !== undefined && isBag(fn);
129
+ const functionValue = toFunction(getValue(args, 0, "IsABag"));
130
+ return functionValue !== undefined && isBag(functionValue);
86
131
  }),
87
132
  defineOperator("BagCardinality", 1, (args) => {
88
133
  const bag = getValue(args, 0, "BagCardinality");
89
- const fn = getFunction(args, 0, "BagCardinality");
90
- return checkInteger(
91
- fn.range.reduce<number>((total, count) => {
92
- const copies = getCount(count, "BagCardinality", bag);
93
- if (copies <= 0) throw describeWrongValue("BagCardinality", "a bag", bag);
94
- return addCounts(total, copies);
95
- }, 0),
96
- );
134
+ const functionValue = getFunction(args, 0, "BagCardinality");
135
+ return functionValue.range.reduce<number>((total, count) => {
136
+ if (typeof count !== "number" || count <= 0) {
137
+ throw createWrongValueError("BagCardinality", "a bag", bag);
138
+ }
139
+ return addCounts(total, count, BAG_CARDINALITY);
140
+ }, 0);
97
141
  }),
98
142
  defineOperator("BagIn", 2, (args) => {
99
143
  const bag = getValue(args, 1, "BagIn");
100
144
  const count = findCount(getFunction(args, 1, "BagIn"), getValue(args, 0, "BagIn"));
101
145
  if (count === undefined) return false;
102
- if (typeof count !== "number") throw describeWrongArgument("second", "BagIn", "bag", bag);
146
+ if (typeof count !== "number") throw createWrongArgumentError("second", "BagIn", "bag", bag);
103
147
  return count > 0;
104
148
  }),
105
149
  defineOperator("CopiesIn", 2, (args) => {
106
150
  const bag = getValue(args, 1, "CopiesIn");
107
151
  const count = findCount(getFunction(args, 1, "CopiesIn"), getValue(args, 0, "CopiesIn"));
108
152
  if (count === undefined) return 0;
109
- if (typeof count !== "number") throw describeWrongArgument("second", "CopiesIn", "bag", bag);
153
+ if (typeof count !== "number") throw createWrongArgumentError("second", "CopiesIn", "bag", bag);
110
154
  return count;
111
155
  }),
112
156
  defineOperator("\\oplus", 2, (args) => {
113
157
  const bags = [0, 1].map((index) => {
114
- const fn = getFunction(args, index, "(+)");
115
- if (!isBag(fn)) {
116
- throw describeWrongArgument(
158
+ const functionValue = getBagArgument(args, index, "(+)");
159
+ if (!isBag(functionValue)) {
160
+ throw createWrongArgumentError(
117
161
  index === 0 ? "first" : "second",
118
162
  "(+)",
119
163
  "bag",
120
164
  getValue(args, index, "(+)"),
121
165
  );
122
166
  }
123
- return fn;
167
+ return functionValue;
124
168
  });
125
- return mergeBags(bags);
169
+ return mergeBags(bags, BAG_CUP);
126
170
  }),
127
171
  defineOperator("\\ominus", 2, (args) => {
128
- const left = getFunction(args, 0, "(-)");
129
- const right = getFunction(args, 1, "(-)");
172
+ const left = getBagArgument(args, 0, "(-)");
173
+ const right = getBagArgument(args, 1, "(-)");
130
174
  return createFunction(
131
175
  left.domain.flatMap((key, index): [Value, Value][] => {
132
- const removed = findCount(right, key);
133
- const count = Number(left.range[index]) - (removed === undefined ? 0 : Number(removed));
176
+ const count = subtractCounts(left, index, right, BAG_DIFF);
134
177
  return count > 0 ? [[key, count]] : [];
135
178
  }),
136
179
  );
137
180
  }),
138
181
  defineOperator("BagUnion", 1, (args) => {
139
182
  const set = getValue(args, 0, "BagUnion");
140
- const bags = getElements(args, 0, "BagUnion").map((element) => {
141
- const fn = toFunction(element);
142
- if (!fn) {
143
- throw new TlaEvaluationError(
183
+ const elements = getFiniteElements(set, () =>
184
+ createWrongValueError("BagUnion", "a finite enumerable set", set),
185
+ );
186
+ if (elements.length < 2) return elements[0] ?? EMPTY_BAG;
187
+ const bags = elements.map((element) => {
188
+ const functionValue = toFunction(element);
189
+ if (!functionValue) {
190
+ throw new TlaModuleError(
144
191
  `Attempted to apply BagUnion to the following set, whose\nelement is not a bag:\n${formatValue(set)}`,
145
192
  );
146
193
  }
147
- return fn;
194
+ return functionValue;
148
195
  });
149
- if (bags.length === 1) return bags[0];
150
- return mergeBags(bags);
196
+ return mergeBags(bags, BAG_UNION);
151
197
  }),
152
198
  defineOperator("\\sqsubseteq", 2, (args) => {
153
199
  const left = getFunction(args, 0, "\\sqsubseteq");
154
200
  const right = getFunction(args, 1, "\\sqsubseteq");
155
- return left.domain.every((key, index) => {
156
- const count = findCount(right, key);
157
- return Number(left.range[index]) - (count === undefined ? 0 : Number(count)) <= 0;
158
- });
201
+ return left.domain.every((_, index) => subtractCounts(left, index, right, SQ_SUBSETEQ) <= 0);
159
202
  }),
160
203
  defineHigherOrderOperator("BagOfAll", 2, (args) => {
161
204
  const operator = getOperator(args, 0, "BagOfAll");
205
+ // TLC passes an operator whose body is a number or string literal to Bags.java as that literal.
206
+ if (operator.literalBody !== undefined) {
207
+ throw new TlaModuleError(
208
+ `The first argument of BagOfAll should be an operator, but instead it is:\n${formatValue(operator.literalBody)}`,
209
+ );
210
+ }
162
211
  const value = getValue(args, 1, "BagOfAll");
163
- const fn = toFunction(value);
164
- if (!fn) {
165
- throw describeWrongArgument("second", "BagOfAll", "function with a finite domain", value);
212
+ const functionValue = toFunction(value);
213
+ if (!functionValue) {
214
+ throw createWrongArgumentError("second", "BagOfAll", "function with a finite domain", value);
166
215
  }
167
216
  return mergeBags(
168
- fn.domain.map((key, index) =>
169
- createSortedFunction([operator.call([key])], [fn.range[index]]),
217
+ functionValue.domain.map((key, index) =>
218
+ createSortedFunction([operator.call([key])], [functionValue.range[index]]),
170
219
  ),
220
+ BAG_OF_ALL,
171
221
  );
172
222
  }),
173
223
  defineOperator("SubBag", 1, (args) => {
174
- getFunction(args, 0, "SubBag");
175
- throw new TlaEvaluationError("SubBag is not implemented.");
224
+ const value = getValue(args, 0, "SubBag");
225
+ if (!isFunctionValue(value)) {
226
+ throw new TlaEvaluationError(`DOMAIN expects a function but received ${formatValue(value)}`);
227
+ }
228
+ const functionValue = toFiniteFunction(value);
229
+ const range = createSet(functionValue.range).elements;
230
+ const invalid = range.find((count) => typeof count !== "number");
231
+ if (invalid !== undefined) {
232
+ const isFirst = typeof range[0] !== "number";
233
+ throw new TlaEvaluationError(
234
+ `The ${isFirst ? "first" : "second"} argument of >= should be an integer, but instead it is:\n${formatValue(isFirst ? range[0] : invalid)}`,
235
+ );
236
+ }
237
+ const counts = functionValue.range.filter((count) => typeof count === "number");
238
+ if (counts.some((count) => count < 1)) {
239
+ throw new TlaEvaluationError(
240
+ `Attempted to apply a function to ${formatValue(functionValue)}, which is not in its domain`,
241
+ );
242
+ }
243
+ return createSet(
244
+ functionValue.domain
245
+ .reduce<[Value, Value][][]>(
246
+ (subBags, key, index) =>
247
+ subBags.flatMap((entries) => [
248
+ entries,
249
+ ...Array.from({ length: counts[index] }, (_, copies): [Value, Value][] => [
250
+ ...entries,
251
+ [key, copies + 1],
252
+ ]),
253
+ ]),
254
+ [[]],
255
+ )
256
+ .map((entries) =>
257
+ createSortedFunction(
258
+ entries.map((entry) => entry[0]),
259
+ entries.map((entry) => entry[1]),
260
+ ),
261
+ ),
262
+ );
176
263
  }),
177
- defineOperator("BagToSet", 1, (args) => createSortedSet(getFunction(args, 0, "BagToSet").domain)),
264
+ defineOperator("BagToSet", 1, (args) => getDomain(getFunction(args, 0, "BagToSet"))),
178
265
  defineOperator("SetToBag", 1, (args) => {
179
- const elements = getElements(args, 0, "SetToBag");
266
+ const set = getValue(args, 0, "SetToBag");
267
+ const elements = getFiniteElements(set, () =>
268
+ createWrongValueError("BagToSet", "a function with a finite domain", set),
269
+ );
180
270
  return createSortedFunction(
181
- elements,
271
+ [...elements],
182
272
  elements.map(() => 1),
183
273
  );
184
274
  }),