tla-ts 0.0.1 → 0.0.3-dev.763cc71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +78 -30
- 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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface OptionSpec {
|
|
2
|
+
flag: string;
|
|
3
|
+
isRequired?: boolean;
|
|
4
|
+
isRepeatable?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ParsedOption {
|
|
8
|
+
name: string;
|
|
9
|
+
values: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ParsedArgs {
|
|
13
|
+
options: ParsedOption[];
|
|
14
|
+
positionals: string[];
|
|
15
|
+
unknown: string[];
|
|
16
|
+
incomplete: ParsedOption | undefined;
|
|
17
|
+
has: (name: string) => boolean;
|
|
18
|
+
get: (name: string) => string[] | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const formatOption = (spec: OptionSpec) => {
|
|
22
|
+
const text = spec.isRequired ? spec.flag : `[${spec.flag}]`;
|
|
23
|
+
return spec.isRepeatable ? `${text}...` : text;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Each <name> in a flag such as "--toolbox <start> <end>" is one value the option takes, and
|
|
27
|
+
// {options} in the usage stands for the options.
|
|
28
|
+
export const defineCommand = (usage: string, specs: OptionSpec[]) => {
|
|
29
|
+
const arities = new Map(
|
|
30
|
+
specs.map((spec) => {
|
|
31
|
+
const [name, ...values] = spec.flag.split(" ");
|
|
32
|
+
return [name, values.length];
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
const parse = (args: string[]): ParsedArgs => {
|
|
36
|
+
const options: ParsedOption[] = [];
|
|
37
|
+
const positionals: string[] = [];
|
|
38
|
+
const unknown: string[] = [];
|
|
39
|
+
for (let index = 0; index < args.length; index++) {
|
|
40
|
+
const arity = arities.get(args[index]);
|
|
41
|
+
if (arity === undefined) {
|
|
42
|
+
(args[index].startsWith("-") ? unknown : positionals).push(args[index]);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
options.push({ name: args[index], values: args.slice(index + 1, index + 1 + arity) });
|
|
46
|
+
index += arity;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
options,
|
|
50
|
+
positionals,
|
|
51
|
+
unknown,
|
|
52
|
+
incomplete: options.find((option) => option.values.length < (arities.get(option.name) ?? 0)),
|
|
53
|
+
has: (name) => options.some((option) => option.name === name),
|
|
54
|
+
get: (name) => options.findLast((option) => option.name === name)?.values,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
return { usage: usage.replace("{options}", specs.map(formatOption).join(" ")), parse };
|
|
58
|
+
};
|
package/src/codegen.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import type { Expression } from "./ast";
|
|
2
2
|
import { TlaEvaluationError } from "./errors";
|
|
3
3
|
import {
|
|
4
|
+
describeLocation,
|
|
4
5
|
bindValue,
|
|
5
6
|
expectBoolean,
|
|
6
7
|
findGlobalName,
|
|
7
8
|
getChildren,
|
|
8
9
|
getEnumeratedElements,
|
|
10
|
+
getConditionError,
|
|
11
|
+
getConjunctError,
|
|
12
|
+
getDomainOperator,
|
|
13
|
+
getSubsetOperator,
|
|
9
14
|
getEvaluator,
|
|
15
|
+
getFieldError,
|
|
10
16
|
isClosedConstant,
|
|
11
17
|
isLocallyResolved,
|
|
18
|
+
isTlcInteger,
|
|
12
19
|
isValueArgument,
|
|
13
20
|
isWorthCaching,
|
|
21
|
+
keepLazy,
|
|
14
22
|
resolveGlobalOperator,
|
|
15
23
|
type Context,
|
|
16
24
|
type Environment,
|
|
@@ -18,9 +26,11 @@ import {
|
|
|
18
26
|
} from "./evaluate";
|
|
19
27
|
import {
|
|
20
28
|
applyFunction,
|
|
21
|
-
|
|
29
|
+
compareValues,
|
|
30
|
+
createEnumeratedSet,
|
|
22
31
|
createSortedFunction,
|
|
23
32
|
createTuple,
|
|
33
|
+
createWrittenRecord,
|
|
24
34
|
valuesEqual,
|
|
25
35
|
type Value,
|
|
26
36
|
} from "./values";
|
|
@@ -56,15 +66,21 @@ const GENERATED_KINDS = new Set([
|
|
|
56
66
|
"Exists",
|
|
57
67
|
]);
|
|
58
68
|
|
|
59
|
-
const describeLocation = (expression: Expression) =>
|
|
60
|
-
`line ${expression.location.line}, column ${expression.location.column}`;
|
|
61
|
-
|
|
62
69
|
const createRuntime = () => ({
|
|
63
70
|
bool: expectBoolean,
|
|
71
|
+
condition: (value: Value, expression: Expression) => {
|
|
72
|
+
if (typeof value !== "boolean") throw getConditionError(expression, value);
|
|
73
|
+
return value;
|
|
74
|
+
},
|
|
75
|
+
conjunct: (value: Value, expression: Expression & { kind: "And" | "Or" }, index: number) => {
|
|
76
|
+
if (typeof value !== "boolean") throw getConjunctError(expression, index, value);
|
|
77
|
+
return value;
|
|
78
|
+
},
|
|
64
79
|
apply: applyFunction,
|
|
65
80
|
tuple: createTuple,
|
|
66
|
-
set:
|
|
81
|
+
set: createEnumeratedSet,
|
|
67
82
|
record: createSortedFunction,
|
|
83
|
+
writtenRecord: createWrittenRecord,
|
|
68
84
|
equal: valuesEqual,
|
|
69
85
|
enumerate: getEnumeratedElements,
|
|
70
86
|
unassigned: (name: string, expression: Expression): never => {
|
|
@@ -103,7 +119,7 @@ const addReference = (generator: Generator, value: unknown) => {
|
|
|
103
119
|
|
|
104
120
|
const createLocal = (generator: Generator) => `local${generator.counter++}`;
|
|
105
121
|
|
|
106
|
-
const getFreeNames = (expression: Expression, names = new Set<string>())
|
|
122
|
+
const getFreeNames = (expression: Expression, names = new Set<string>()) => {
|
|
107
123
|
if (expression.kind === "Identifier") names.add(expression.name);
|
|
108
124
|
if (expression.kind === "Apply") names.add(expression.operator);
|
|
109
125
|
getChildren(expression).forEach((child) => getFreeNames(child, names));
|
|
@@ -131,7 +147,7 @@ const generateQuantifier = (
|
|
|
131
147
|
const description = JSON.stringify(isForall ? "the body of \\A" : "the body of \\E");
|
|
132
148
|
const name = `quantifier${generator.counter++}`;
|
|
133
149
|
const outer = [...frame.locals.values()];
|
|
134
|
-
const inner
|
|
150
|
+
const inner = {
|
|
135
151
|
context: "context",
|
|
136
152
|
state: "state",
|
|
137
153
|
next: "next",
|
|
@@ -174,6 +190,15 @@ const generateApply = (
|
|
|
174
190
|
if (isLocallyResolved(expression)) return generateDelegate(generator, expression, frame);
|
|
175
191
|
const builtin = resolveGlobalOperator(expression.operator, generator.environment);
|
|
176
192
|
if (builtin?.kind !== "Builtin") return generateDelegate(generator, expression, frame);
|
|
193
|
+
if (builtin.name === "DOMAIN") {
|
|
194
|
+
const domain = addReference(generator, getDomainOperator(expression));
|
|
195
|
+
return `${domain}(${generateExpression(generator, args[0], frame)})`;
|
|
196
|
+
}
|
|
197
|
+
if (builtin.name === "TLCEval") return generateDelegate(generator, expression, frame);
|
|
198
|
+
if (builtin.name === "\\subseteq" && args.length === 2) {
|
|
199
|
+
const subset = addReference(generator, getSubsetOperator(expression, builtin));
|
|
200
|
+
return `${subset}(${generateExpression(generator, args[0], frame)}, ${generateExpression(generator, args[1], frame)})`;
|
|
201
|
+
}
|
|
177
202
|
const environment = generator.environment;
|
|
178
203
|
if (builtin.evaluateUnary && args.length === 1 && isValueArgument(args[0], environment)) {
|
|
179
204
|
const operation = addReference(generator, builtin.evaluateUnary);
|
|
@@ -198,7 +223,7 @@ const generateExpression = (generator: Generator, expression: Expression, frame:
|
|
|
198
223
|
}
|
|
199
224
|
switch (expression.kind) {
|
|
200
225
|
case "Number":
|
|
201
|
-
if (
|
|
226
|
+
if (isTlcInteger(expression.value)) {
|
|
202
227
|
return String(expression.value);
|
|
203
228
|
}
|
|
204
229
|
return generateDelegate(generator, expression, frame);
|
|
@@ -219,22 +244,26 @@ const generateExpression = (generator: Generator, expression: Expression, frame:
|
|
|
219
244
|
}
|
|
220
245
|
case "Apply":
|
|
221
246
|
return generateApply(generator, expression, frame);
|
|
222
|
-
case "And":
|
|
223
|
-
|
|
247
|
+
case "And": {
|
|
248
|
+
const node = addReference(generator, expression);
|
|
249
|
+
return `(${expression.operands.map((operand, index) => `conjunct(${generate(operand)}, ${node}, ${index})`).join(" && ")})`;
|
|
250
|
+
}
|
|
224
251
|
case "Or":
|
|
225
252
|
return `(${expression.operands.map((operand) => `bool(${generate(operand)}, "a disjunct")`).join(" || ")})`;
|
|
226
253
|
case "If":
|
|
227
|
-
return `(
|
|
254
|
+
return `(condition(${generate(expression.condition)}, ${addReference(generator, expression)}) ? ${generate(expression.consequent)} : ${generate(expression.alternative)})`;
|
|
228
255
|
case "FunctionApplication": {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
256
|
+
keepLazy(expression.function);
|
|
257
|
+
const functionCode = generate(expression.function);
|
|
258
|
+
if (expression.args.length === 1)
|
|
259
|
+
return `apply(${functionCode}, ${generate(expression.args[0])})`;
|
|
260
|
+
return `apply(${functionCode}, tuple([${expression.args.map((arg) => generate(arg)).join(", ")}]))`;
|
|
232
261
|
}
|
|
233
262
|
case "FieldAccess":
|
|
234
|
-
return `apply(${generate(expression.record)}, ${JSON.stringify(expression.field)})`;
|
|
263
|
+
return `apply(${generate(expression.record)}, ${JSON.stringify(expression.field)}, ${addReference(generator, getFieldError(expression))})`;
|
|
235
264
|
case "Record": {
|
|
236
265
|
const fields = [...expression.fields].sort((left, right) =>
|
|
237
|
-
left.name
|
|
266
|
+
compareValues(left.name, right.name),
|
|
238
267
|
);
|
|
239
268
|
const hasDuplicates = fields.some(
|
|
240
269
|
(field, index) => index > 0 && fields[index - 1].name === field.name,
|
|
@@ -244,7 +273,12 @@ const generateExpression = (generator: Generator, expression: Expression, frame:
|
|
|
244
273
|
generator,
|
|
245
274
|
fields.map((field) => field.name),
|
|
246
275
|
);
|
|
247
|
-
|
|
276
|
+
const values = `[${fields.map((field) => generate(field.value)).join(", ")}]`;
|
|
277
|
+
const writtenNames = expression.fields.map((field) => field.name);
|
|
278
|
+
if (writtenNames.every((name, index) => name === fields[index].name)) {
|
|
279
|
+
return `record(${names}, ${values})`;
|
|
280
|
+
}
|
|
281
|
+
return `writtenRecord(${names}, ${values}, ${addReference(generator, writtenNames)})`;
|
|
248
282
|
}
|
|
249
283
|
case "Tuple":
|
|
250
284
|
return `tuple([${expression.elements.map((element) => generate(element)).join(", ")}])`;
|
|
@@ -253,7 +287,7 @@ const generateExpression = (generator: Generator, expression: Expression, frame:
|
|
|
253
287
|
case "Prime": {
|
|
254
288
|
const node = addReference(generator, expression);
|
|
255
289
|
if (frame.isPrimed) return `primeOutsideAction(${node})`;
|
|
256
|
-
const primed
|
|
290
|
+
const primed = {
|
|
257
291
|
context: `(primed ??= prime(${frame.context}))`,
|
|
258
292
|
state: frame.next,
|
|
259
293
|
next: "undefined",
|
package/src/config.ts
CHANGED
|
@@ -34,8 +34,11 @@ export interface ModelConfig {
|
|
|
34
34
|
checkDeadlock: boolean;
|
|
35
35
|
symmetry: string | undefined;
|
|
36
36
|
view: string | undefined;
|
|
37
|
+
alias: string | undefined;
|
|
37
38
|
postconditions: string[];
|
|
38
39
|
possible: string[];
|
|
40
|
+
periodic: string | undefined;
|
|
41
|
+
rlReward: string | undefined;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
interface ConfigToken {
|
|
@@ -65,11 +68,13 @@ const KEYWORDS = new Set([
|
|
|
65
68
|
"POSTCONDITION",
|
|
66
69
|
"POSTCONDITIONS",
|
|
67
70
|
"_POSSIBLE",
|
|
71
|
+
"_PERIODIC",
|
|
72
|
+
"_RL_REWARD",
|
|
68
73
|
]);
|
|
69
74
|
|
|
70
75
|
const SYMBOLS = ["<-", "<<", ">>", "=", "{", "}", ",", "[", "]", "(", ")"];
|
|
71
76
|
|
|
72
|
-
const tokenizeConfig = (source: string)
|
|
77
|
+
const tokenizeConfig = (source: string) => {
|
|
73
78
|
const tokens: ConfigToken[] = [];
|
|
74
79
|
const cursor = { index: 0, line: 1 };
|
|
75
80
|
while (cursor.index < source.length) {
|
|
@@ -92,7 +97,7 @@ const tokenizeConfig = (source: string): ConfigToken[] => {
|
|
|
92
97
|
cursor.index += end + 2;
|
|
93
98
|
continue;
|
|
94
99
|
}
|
|
95
|
-
const number =
|
|
100
|
+
const number = /^\d+(?![A-Za-z_])/.exec(rest)?.[0];
|
|
96
101
|
if (number) {
|
|
97
102
|
tokens.push({ kind: "Number", text: number, line: cursor.line });
|
|
98
103
|
cursor.index += number.length;
|
|
@@ -106,7 +111,7 @@ const tokenizeConfig = (source: string): ConfigToken[] => {
|
|
|
106
111
|
}
|
|
107
112
|
const string = /^"((?:[^"\\]|\\.)*)"/.exec(rest);
|
|
108
113
|
if (string) {
|
|
109
|
-
tokens.push({ kind: "String", text:
|
|
114
|
+
tokens.push({ kind: "String", text: string[1], line: cursor.line });
|
|
110
115
|
cursor.index += string[0].length;
|
|
111
116
|
continue;
|
|
112
117
|
}
|
|
@@ -144,10 +149,14 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
144
149
|
checkDeadlock: true,
|
|
145
150
|
symmetry: undefined,
|
|
146
151
|
view: undefined,
|
|
152
|
+
alias: undefined,
|
|
147
153
|
postconditions: [],
|
|
148
154
|
possible: [],
|
|
155
|
+
periodic: undefined,
|
|
156
|
+
rlReward: undefined,
|
|
149
157
|
};
|
|
150
158
|
|
|
159
|
+
const deadlock = { isSet: false };
|
|
151
160
|
const peek = () => tokens[cursor.index];
|
|
152
161
|
const advance = () => tokens[cursor.index++];
|
|
153
162
|
const isKeyword = (token: ConfigToken) => token.kind === "Word" && KEYWORDS.has(token.text);
|
|
@@ -168,13 +177,22 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
168
177
|
}
|
|
169
178
|
return token.text;
|
|
170
179
|
};
|
|
180
|
+
const isNameAhead = () => peek().kind === "Word" && !isKeyword(peek());
|
|
171
181
|
const parseNames = () => {
|
|
172
182
|
const names: string[] = [];
|
|
173
|
-
while (
|
|
183
|
+
while (isNameAhead()) names.push(advance().text);
|
|
174
184
|
return names;
|
|
175
185
|
};
|
|
176
186
|
const setSingle = (
|
|
177
|
-
key:
|
|
187
|
+
key:
|
|
188
|
+
| "init"
|
|
189
|
+
| "next"
|
|
190
|
+
| "specification"
|
|
191
|
+
| "symmetry"
|
|
192
|
+
| "view"
|
|
193
|
+
| "alias"
|
|
194
|
+
| "periodic"
|
|
195
|
+
| "rlReward",
|
|
178
196
|
keyword: string,
|
|
179
197
|
) => {
|
|
180
198
|
if (config[key] !== undefined) throw new TlaConfigError(`The keyword ${keyword} appears twice`);
|
|
@@ -189,11 +207,12 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
189
207
|
return { kind: "Boolean", value: token.text === "TRUE" };
|
|
190
208
|
if (token.text === "{") {
|
|
191
209
|
const elements: ConfigValue[] = [];
|
|
192
|
-
|
|
210
|
+
if (peek().text !== "}") elements.push(parseValue());
|
|
211
|
+
while (peek().text === ",") {
|
|
212
|
+
advance();
|
|
193
213
|
elements.push(parseValue());
|
|
194
|
-
if (peek().text !== "}") expect(",");
|
|
195
214
|
}
|
|
196
|
-
|
|
215
|
+
expect("}");
|
|
197
216
|
return { kind: "Set", elements };
|
|
198
217
|
}
|
|
199
218
|
if (token.kind === "Word" && !isKeyword(token)) return { kind: "ModelValue", name: token.text };
|
|
@@ -211,7 +230,7 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
211
230
|
};
|
|
212
231
|
|
|
213
232
|
const parseConstants = () => {
|
|
214
|
-
while (
|
|
233
|
+
while (isNameAhead()) {
|
|
215
234
|
const name = advance().text;
|
|
216
235
|
if (peek().text === "(") {
|
|
217
236
|
advance();
|
|
@@ -222,7 +241,8 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
222
241
|
}
|
|
223
242
|
advance();
|
|
224
243
|
expect("=");
|
|
225
|
-
|
|
244
|
+
const module = parseModuleScope();
|
|
245
|
+
config.constants.push({ name, value: parseValue(), args, ...(module && { module }) });
|
|
226
246
|
continue;
|
|
227
247
|
}
|
|
228
248
|
if (peek().text === "<-") {
|
|
@@ -276,7 +296,7 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
276
296
|
config.actionConstraints.push(...parseNames());
|
|
277
297
|
break;
|
|
278
298
|
case "ALIAS":
|
|
279
|
-
|
|
299
|
+
setSingle("alias", token.text);
|
|
280
300
|
break;
|
|
281
301
|
case "POSTCONDITION":
|
|
282
302
|
case "POSTCONDITIONS":
|
|
@@ -285,7 +305,15 @@ export const parseConfig = (source: string): ModelConfig => {
|
|
|
285
305
|
case "_POSSIBLE":
|
|
286
306
|
config.possible.push(...parseNames());
|
|
287
307
|
break;
|
|
308
|
+
case "_PERIODIC":
|
|
309
|
+
setSingle("periodic", token.text);
|
|
310
|
+
break;
|
|
311
|
+
case "_RL_REWARD":
|
|
312
|
+
setSingle("rlReward", token.text);
|
|
313
|
+
break;
|
|
288
314
|
case "CHECK_DEADLOCK": {
|
|
315
|
+
if (deadlock.isSet) throw new TlaConfigError("The keyword CHECK_DEADLOCK appears twice");
|
|
316
|
+
deadlock.isSet = true;
|
|
289
317
|
const value = advance().text;
|
|
290
318
|
if (value !== "TRUE" && value !== "FALSE") {
|
|
291
319
|
throw new TlaConfigError(
|