tla-ts 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +34 -41
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +166 -30
  4. package/dist/index.d.ts +166 -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 +2 -1
  11. package/dist/run-cli.js +2 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +4 -2
  15. package/src/ast.ts +82 -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 +904 -230
  20. package/src/codegen.ts +52 -18
  21. package/src/config.ts +39 -11
  22. package/src/environment.ts +485 -140
  23. package/src/errors.ts +22 -0
  24. package/src/evaluate.ts +486 -136
  25. package/src/exit-codes.ts +75 -0
  26. package/src/fingerprints.ts +2 -2
  27. package/src/index.ts +9 -4
  28. package/src/instances.ts +221 -66
  29. package/src/java-overrides.ts +241 -0
  30. package/src/json.ts +153 -72
  31. package/src/levels.ts +850 -0
  32. package/src/lexer.ts +97 -27
  33. package/src/liveness.ts +47 -91
  34. package/src/parallel-worker.ts +57 -28
  35. package/src/parallel.ts +116 -62
  36. package/src/parser.ts +703 -183
  37. package/src/pretty-print.ts +259 -0
  38. package/src/randomization.ts +24 -113
  39. package/src/resolve.ts +472 -41
  40. package/src/run-cli.ts +142 -44
  41. package/src/runtime.ts +63 -28
  42. package/src/spec-description.ts +509 -0
  43. package/src/standard-levels.ts +105 -0
  44. package/src/standard-modules.ts +223 -92
  45. package/src/subexpressions.ts +21 -35
  46. package/src/substitution-levels.ts +184 -0
  47. package/src/successors.ts +261 -46
  48. package/src/symmetry.ts +65 -21
  49. package/src/tableau.ts +43 -37
  50. package/src/tlc-ext.ts +120 -33
  51. package/src/tlc-module.ts +71 -47
  52. package/src/tlc-modules.ts +1022 -0
  53. package/src/tlc-order.ts +497 -0
  54. package/src/tlc-random.ts +411 -0
  55. package/src/tlc-summary.ts +109 -0
  56. package/src/transfer.ts +4 -4
  57. package/src/values.ts +1186 -164
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.2",
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,9 @@
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",
72
74
  "publint": "publint",
73
75
  "test": "vp test",
74
76
  "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
 
@@ -266,6 +280,7 @@ export interface SubexpressionExpression {
266
280
 
267
281
  export type Expression =
268
282
  | NumberExpression
283
+ | DecimalExpression
269
284
  | StringExpression
270
285
  | IdentifierExpression
271
286
  | ApplyExpression
@@ -309,6 +324,7 @@ export interface OperatorDefinition {
309
324
  name: string;
310
325
  params: OperatorParam[];
311
326
  body: Expression;
327
+ bodyRange?: SourceRange;
312
328
  isLocal: boolean;
313
329
  isRecursive: boolean;
314
330
  location: SourceLocation;
@@ -329,6 +345,7 @@ export type Definition = OperatorDefinition | FunctionDefinition;
329
345
  export interface Declaration {
330
346
  name: string;
331
347
  arity: number;
348
+ location: SourceLocation;
332
349
  }
333
350
 
334
351
  export interface Assumption {
@@ -353,19 +370,79 @@ export interface Instance {
353
370
  location: SourceLocation;
354
371
  }
355
372
 
356
- export interface OuterScope {
357
- extends: string[];
358
- symbols: Declaration[];
373
+ export interface NewSymbol {
374
+ kind: "NewSymbol";
375
+ name: string;
376
+ arity: number;
377
+ level: "Constant" | "Variable" | "State" | "Action" | "Temporal";
378
+ set: Expression | undefined;
379
+ location: SourceLocation;
380
+ }
381
+
382
+ export interface AssumeProve {
383
+ kind: "AssumeProve";
384
+ assumptions: (Expression | NewSymbol | AssumeProve)[];
385
+ conclusion: Expression;
386
+ location: SourceLocation;
387
+ }
388
+
389
+ export type Fact = Expression | { kind: "ModuleFact"; name: string; location: SourceLocation };
390
+
391
+ export interface UseOrHide {
392
+ kind: "Use" | "Hide";
393
+ isOnly: boolean;
394
+ facts: Fact[];
395
+ defs: Fact[];
396
+ location: SourceLocation;
397
+ }
398
+
399
+ export type Proof =
400
+ | { kind: "Obvious" | "Omitted"; location: SourceLocation }
401
+ | { kind: "By"; isOnly: boolean; facts: Fact[]; defs: Fact[]; location: SourceLocation }
402
+ | { kind: "Steps"; level: number; steps: ProofStep[]; location: SourceLocation };
403
+
404
+ export interface DefineStep {
405
+ kind: "Define";
406
+ definitions: (Definition | Instance)[];
407
+ location: SourceLocation;
408
+ }
409
+
410
+ export type StepBody =
411
+ | UseOrHide
412
+ | Instance
413
+ | DefineStep
414
+ | { kind: "Have"; expression: Expression }
415
+ | { kind: "Take"; names: string[]; bounds: Bound[] }
416
+ | { kind: "Witness"; expressions: Expression[] }
417
+ | { kind: "Pick"; names: string[]; bounds: Bound[]; body: Expression }
418
+ | { kind: "Case"; expression: Expression }
419
+ | { kind: "Assert"; isSuffices: boolean; statement: Expression | AssumeProve }
420
+ | { kind: "Qed" };
421
+
422
+ export interface ProofStep {
423
+ name: string | undefined;
424
+ body: StepBody;
425
+ proof: Proof | undefined;
426
+ location: SourceLocation;
427
+ }
428
+
429
+ export interface Theorem {
430
+ name: string | undefined;
431
+ statement: Expression | AssumeProve;
432
+ proof: Proof | undefined;
433
+ location: SourceLocation;
359
434
  }
360
435
 
361
436
  export interface Module {
362
437
  name: string;
363
- outer: OuterScope | undefined;
364
438
  modules: Module[];
365
439
  extends: string[];
366
440
  constants: Declaration[];
367
- variables: string[];
441
+ variables: Declaration[];
368
442
  definitions: Definition[];
369
443
  instances: Instance[];
370
444
  assumptions: Assumption[];
445
+ location: SourceLocation;
446
+ theorems: Theorem[];
447
+ uses: UseOrHide[];
371
448
  }
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
  }),