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/src/standard-modules.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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("=",
|
|
67
|
-
defineBinary("#", (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"),
|
|
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"),
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
118
|
-
sets.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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.
|
|
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
|
|
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
|
|
152
|
-
throw new
|
|
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
|
|
158
|
-
|
|
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
|
|
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:
|
|
172
|
-
high:
|
|
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
|
|
230
|
+
const INTEGERS_OPERATORS = [
|
|
177
231
|
defineConstant("Int", { kind: "InfiniteSet", name: "Int" }),
|
|
178
|
-
defineOperator("-.", 1, (args) =>
|
|
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
|
|
182
|
-
const value = getValue(args, index,
|
|
183
|
-
|
|
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
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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([
|
|
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 =
|
|
299
|
+
const elements = getSequenceArgument(args, 0, getSequenceMessage("", "Head"));
|
|
203
300
|
if (elements.length === 0)
|
|
204
|
-
throw new
|
|
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
|
|
211
|
-
return
|
|
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
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
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
|
-
|
|
331
|
+
getSequenceArgument(args, 0, getSequenceMessage("first ", "SelectSeq")).filter((element) => {
|
|
231
332
|
const result = test.call([element]);
|
|
232
|
-
if (typeof result !== "boolean")
|
|
233
|
-
throw new
|
|
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
|
|
241
|
-
defineOperator("IsFiniteSet", 1, (args) =>
|
|
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 =
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
|
|
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":
|
package/src/subexpressions.ts
CHANGED
|
@@ -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 ${
|
|
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
|
-
|
|
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)
|
|
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
|
|
256
|
+
const definition = local
|
|
257
|
+
? local.find((candidate) => getSourceName(candidate) === name)
|
|
258
|
+
: lookup(name);
|
|
271
259
|
if (definition) {
|
|
272
|
-
|
|
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
|
|
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
|
|
302
|
-
const selector = selectors[state.index]
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
435
|
+
const hasReferences =
|
|
450
436
|
module.definitions.some((definition) => containsReference(definition.body)) ||
|
|
451
437
|
module.assumptions.some((assumption) => containsReference(assumption.expression));
|
|
452
|
-
if (!
|
|
438
|
+
if (!hasReferences) return module;
|
|
453
439
|
return {
|
|
454
440
|
...module,
|
|
455
441
|
definitions: module.definitions.map((definition) =>
|