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.
- package/README.md +81 -42
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +215 -30
- package/dist/index.d.ts +215 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -1
- package/dist/run-cli.js +20 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +6 -2
- package/src/ast.ts +122 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +945 -238
- package/src/cli-options.ts +58 -0
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +59 -1
- package/src/evaluate.ts +489 -137
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +2 -2
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +103 -28
- package/src/liveness.ts +47 -91
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parallel-worker.ts +57 -28
- package/src/parallel.ts +116 -62
- package/src/parser.ts +958 -205
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +427 -61
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +65 -21
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +26 -5
- 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.
|
|
4
|
-
"description": "A TLA+ interpreter and model checker in TypeScript, 2
|
|
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
|
|
357
|
-
|
|
358
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
26
|
-
new
|
|
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
|
|
31
|
-
new
|
|
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)
|
|
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
|
|
43
|
-
if (!
|
|
44
|
-
return
|
|
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
|
|
48
|
-
|
|
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
|
|
51
|
-
if (
|
|
52
|
-
return
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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(
|
|
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",
|
|
127
|
+
defineConstant("EmptyBag", EMPTY_BAG),
|
|
83
128
|
defineOperator("IsABag", 1, (args) => {
|
|
84
|
-
const
|
|
85
|
-
return
|
|
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
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
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
|
|
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
|
|
115
|
-
if (!isBag(
|
|
116
|
-
throw
|
|
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
|
|
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 =
|
|
129
|
-
const right =
|
|
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
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
194
|
+
return functionValue;
|
|
148
195
|
});
|
|
149
|
-
|
|
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((
|
|
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
|
|
164
|
-
if (!
|
|
165
|
-
throw
|
|
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
|
-
|
|
169
|
-
createSortedFunction([operator.call([key])], [
|
|
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
|
-
|
|
175
|
-
|
|
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) =>
|
|
264
|
+
defineOperator("BagToSet", 1, (args) => getDomain(getFunction(args, 0, "BagToSet"))),
|
|
178
265
|
defineOperator("SetToBag", 1, (args) => {
|
|
179
|
-
const
|
|
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
|
}),
|