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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { Declaration, Definition, Expression, SourceLocation } from "./ast";
|
|
2
|
+
import { TlaSyntaxError } from "./errors";
|
|
3
|
+
import {
|
|
4
|
+
createLevelAnalyzer,
|
|
5
|
+
getArgumentKey,
|
|
6
|
+
MAX_LEVEL,
|
|
7
|
+
parseArgumentParamKey,
|
|
8
|
+
putArgConstraint,
|
|
9
|
+
putConstraint,
|
|
10
|
+
type OperatorLevels,
|
|
11
|
+
type Scope,
|
|
12
|
+
} from "./levels";
|
|
13
|
+
|
|
14
|
+
export interface InstanceCheck {
|
|
15
|
+
module: string;
|
|
16
|
+
instancer: string;
|
|
17
|
+
lets: Definition[];
|
|
18
|
+
substitutions: Map<string, Expression>;
|
|
19
|
+
location: SourceLocation;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ModuleContext {
|
|
23
|
+
constants: Declaration[];
|
|
24
|
+
variables: string[];
|
|
25
|
+
definitions: Definition[];
|
|
26
|
+
assumptions: Expression[];
|
|
27
|
+
getDefinition: (name: string) => Definition | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ModuleLevels {
|
|
31
|
+
isConstant: boolean;
|
|
32
|
+
constraints: Map<string, number>;
|
|
33
|
+
argConstraints: Map<string, number>;
|
|
34
|
+
argParams: Set<string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Substituted {
|
|
38
|
+
name: string;
|
|
39
|
+
maximumLevel: number;
|
|
40
|
+
arity: number;
|
|
41
|
+
level: number;
|
|
42
|
+
operator: OperatorLevels | undefined;
|
|
43
|
+
isLeibniz: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getMaxLevel = (operator: OperatorLevels, index: number) =>
|
|
47
|
+
operator.maxLevels[index] ?? MAX_LEVEL;
|
|
48
|
+
|
|
49
|
+
export const checkInstances = (
|
|
50
|
+
checks: InstanceCheck[],
|
|
51
|
+
getModule: (name: string) => ModuleContext,
|
|
52
|
+
) => {
|
|
53
|
+
const analyzers = new Map<string, ReturnType<typeof createLevelAnalyzer>>();
|
|
54
|
+
const getAnalyzer = (name: string) => {
|
|
55
|
+
const cached = analyzers.get(name);
|
|
56
|
+
if (cached) return cached;
|
|
57
|
+
const module = getModule(name);
|
|
58
|
+
const analyzer = createLevelAnalyzer({
|
|
59
|
+
constants: new Set(module.constants.map((constant) => constant.name)),
|
|
60
|
+
variables: new Set(module.variables),
|
|
61
|
+
getDefinition: module.getDefinition,
|
|
62
|
+
});
|
|
63
|
+
analyzers.set(name, analyzer);
|
|
64
|
+
return analyzer;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const moduleLevels = new Map<string, ModuleLevels>();
|
|
68
|
+
const getModuleLevels = (name: string) => {
|
|
69
|
+
const cached = moduleLevels.get(name);
|
|
70
|
+
if (cached) return cached;
|
|
71
|
+
const module = getModule(name);
|
|
72
|
+
const analyzer = getAnalyzer(name);
|
|
73
|
+
const definitions = module.definitions.map((definition) =>
|
|
74
|
+
analyzer.analyzeDefinition(definition, new Map()),
|
|
75
|
+
);
|
|
76
|
+
const levels: ModuleLevels = {
|
|
77
|
+
isConstant:
|
|
78
|
+
module.variables.length === 0 && definitions.every((definition) => definition.level === 0),
|
|
79
|
+
constraints: new Map(),
|
|
80
|
+
argConstraints: new Map(),
|
|
81
|
+
argParams: new Set(),
|
|
82
|
+
};
|
|
83
|
+
if (!levels.isConstant) {
|
|
84
|
+
module.constants.forEach((constant) => putConstraint(levels.constraints, constant.name, 0));
|
|
85
|
+
}
|
|
86
|
+
const addLevels = (
|
|
87
|
+
data: Omit<ModuleLevels, "isConstant">,
|
|
88
|
+
isKept: (operator: string, param: string) => boolean,
|
|
89
|
+
) => {
|
|
90
|
+
data.constraints.forEach((value, key) => putConstraint(levels.constraints, key, value));
|
|
91
|
+
data.argConstraints.forEach((value, key) =>
|
|
92
|
+
putArgConstraint(levels.argConstraints, key, value),
|
|
93
|
+
);
|
|
94
|
+
data.argParams.forEach((key) => {
|
|
95
|
+
const parameter = parseArgumentParamKey(key);
|
|
96
|
+
if (isKept(parameter.operator, parameter.param)) levels.argParams.add(key);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
definitions.forEach((definition) =>
|
|
100
|
+
addLevels(definition, (operator, param) =>
|
|
101
|
+
definition.formals.every((formal) => formal.name !== operator && formal.name !== param),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
module.assumptions.forEach((assumption) =>
|
|
105
|
+
addLevels(analyzer.analyze(assumption, new Map()), () => true),
|
|
106
|
+
);
|
|
107
|
+
moduleLevels.set(name, levels);
|
|
108
|
+
return levels;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const messages = new Set<string>();
|
|
112
|
+
checks.forEach((check) => {
|
|
113
|
+
const location = `at line ${check.location.line}, column ${check.location.column}`;
|
|
114
|
+
const addMessage = (message: string) => messages.add(`${message} ${location}`);
|
|
115
|
+
const instancee = getModule(check.module);
|
|
116
|
+
const levels = getModuleLevels(check.module);
|
|
117
|
+
const analyzer = getAnalyzer(check.instancer);
|
|
118
|
+
const scope: Scope = new Map();
|
|
119
|
+
check.lets.forEach((definition) =>
|
|
120
|
+
scope.set(definition.name, { kind: "Let", definition, scope }),
|
|
121
|
+
);
|
|
122
|
+
const declarations = [
|
|
123
|
+
...instancee.constants.map((constant) => ({ ...constant, maximumLevel: 0 })),
|
|
124
|
+
...instancee.variables.map((variable) => ({ name: variable, arity: 0, maximumLevel: 1 })),
|
|
125
|
+
];
|
|
126
|
+
const substituted = declarations.flatMap((declaration): Substituted[] => {
|
|
127
|
+
const expression = check.substitutions.get(declaration.name);
|
|
128
|
+
if (!expression) return [];
|
|
129
|
+
if (declaration.arity === 0) {
|
|
130
|
+
const data = analyzer.analyze(expression, scope);
|
|
131
|
+
return [{ ...declaration, level: data.level, operator: undefined, isLeibniz: true }];
|
|
132
|
+
}
|
|
133
|
+
const operand = analyzer.toOperand(expression, scope);
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
...declaration,
|
|
137
|
+
level: operand.data.level,
|
|
138
|
+
operator: operand.operator,
|
|
139
|
+
isLeibniz: operand.operator === undefined || operand.operator.nonLeibniz.size === 0,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
});
|
|
143
|
+
const getLevelMessage = (name: string, level: number) =>
|
|
144
|
+
`Level error in instantiating module '${check.module}': the level of the expression or operator substituted for '${name}' must be at most ${level}`;
|
|
145
|
+
substituted.forEach((item) => {
|
|
146
|
+
if (!levels.isConstant && item.level > item.maximumLevel) {
|
|
147
|
+
addMessage(getLevelMessage(item.name, item.maximumLevel));
|
|
148
|
+
}
|
|
149
|
+
if (!item.isLeibniz) {
|
|
150
|
+
addMessage(
|
|
151
|
+
`Error in instantiating module '${check.module}': a non-Leibniz operator substituted for '${item.name}'`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
substituted.forEach((item) => {
|
|
156
|
+
const maximum = levels.constraints.get(item.name);
|
|
157
|
+
if (maximum !== undefined && item.level > maximum)
|
|
158
|
+
addMessage(getLevelMessage(item.name, maximum));
|
|
159
|
+
const operator = item.operator;
|
|
160
|
+
if (!operator) return;
|
|
161
|
+
Array.from({ length: item.arity }, (_, index) => {
|
|
162
|
+
const minimum = levels.argConstraints.get(getArgumentKey(item.name, index));
|
|
163
|
+
if (minimum !== undefined && getMaxLevel(operator, index) < minimum) {
|
|
164
|
+
addMessage(
|
|
165
|
+
`Level error in instantiating module '${check.module}': the level of the argument ${index + 1} of the operator ${operator.name} must be at least ${minimum}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
levels.argParams.forEach((key) => {
|
|
171
|
+
const parameter = parseArgumentParamKey(key);
|
|
172
|
+
const operator = substituted.find((item) => item.name === parameter.operator)?.operator;
|
|
173
|
+
const argument = substituted.find((item) => item.name === parameter.param);
|
|
174
|
+
if (!operator || !argument) return;
|
|
175
|
+
const maximum = getMaxLevel(operator, parameter.index);
|
|
176
|
+
if (argument.level > maximum) {
|
|
177
|
+
addMessage(
|
|
178
|
+
`Level error when instantiating module '${check.module}': the level of the argument ${parameter.index} of the operator ${parameter.operator}' must be at most ${maximum}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
if (messages.size > 0) throw new TlaSyntaxError([...messages].join("\n"));
|
|
184
|
+
};
|
package/src/successors.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Bound, Definition, Expression } from "./ast";
|
|
2
|
+
import { getReadVariables } from "./dependencies";
|
|
2
3
|
import { TlaEvaluationError } from "./errors";
|
|
4
|
+
import { EXIT_STATUS, setExitCode } from "./exit-codes";
|
|
3
5
|
import {
|
|
6
|
+
describeLocation,
|
|
4
7
|
bindArgument,
|
|
5
8
|
bindDefinitions,
|
|
6
9
|
bindOperatorArguments,
|
|
@@ -10,7 +13,10 @@ import {
|
|
|
10
13
|
expectBoolean,
|
|
11
14
|
forEachBinding,
|
|
12
15
|
getBoundsDomain,
|
|
16
|
+
getChildren,
|
|
13
17
|
getDefinition,
|
|
18
|
+
getEnumeratedElements,
|
|
19
|
+
getReevaluatedDefinitions,
|
|
14
20
|
lookupScope,
|
|
15
21
|
resolveApplyOperator,
|
|
16
22
|
resolveGlobalOperator,
|
|
@@ -20,12 +26,13 @@ import {
|
|
|
20
26
|
type Scope,
|
|
21
27
|
type State,
|
|
22
28
|
} from "./evaluate";
|
|
29
|
+
import { mapChildren } from "./instances";
|
|
23
30
|
import {
|
|
24
31
|
createTuple,
|
|
25
32
|
formatValue,
|
|
33
|
+
getStreamedElements,
|
|
26
34
|
isElementOf,
|
|
27
35
|
isEnumerable,
|
|
28
|
-
toFiniteSet,
|
|
29
36
|
valuesEqual,
|
|
30
37
|
type Value,
|
|
31
38
|
} from "./values";
|
|
@@ -39,6 +46,7 @@ interface Run {
|
|
|
39
46
|
environment: Environment;
|
|
40
47
|
isInit: boolean;
|
|
41
48
|
current: Value[] | undefined;
|
|
49
|
+
isStopped: boolean;
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
type Continuation = (partial: State, action: string | undefined) => void;
|
|
@@ -62,10 +70,7 @@ type Conjunct = { expression: Expression } & (
|
|
|
62
70
|
const initRunners = new WeakMap<Expression, Runner>();
|
|
63
71
|
const nextRunners = new WeakMap<Expression, Runner>();
|
|
64
72
|
|
|
65
|
-
const
|
|
66
|
-
`line ${expression.location.line}, column ${expression.location.column}`;
|
|
67
|
-
|
|
68
|
-
const getContext = (run: Run, scope: Scope | undefined, partial: State): Context =>
|
|
73
|
+
const getContext = (run: Run, scope: Scope | undefined, partial: State) =>
|
|
69
74
|
run.isInit
|
|
70
75
|
? { environment: run.environment, scope, state: partial, next: undefined }
|
|
71
76
|
: { environment: run.environment, scope, state: run.current, next: partial };
|
|
@@ -79,7 +84,12 @@ const assign = (partial: State, index: number, value: Value) => {
|
|
|
79
84
|
const holds = (expression: Expression, context: Context) => {
|
|
80
85
|
const value = evaluate(expression, context);
|
|
81
86
|
if (typeof value === "boolean") return value;
|
|
82
|
-
|
|
87
|
+
try {
|
|
88
|
+
return expectBoolean(value, `the action at ${describeLocation(expression)}`);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// TLC fails with an unexpected exception when the next-state relation isn't a boolean.
|
|
91
|
+
throw context.next === undefined ? error : setExitCode(error, EXIT_STATUS.error);
|
|
92
|
+
}
|
|
83
93
|
};
|
|
84
94
|
|
|
85
95
|
const isAssignmentOperator = (operator: string) =>
|
|
@@ -188,16 +198,12 @@ const ACTION_KINDS = new Set([
|
|
|
188
198
|
"FunctionApplication",
|
|
189
199
|
]);
|
|
190
200
|
|
|
191
|
-
const isGuard = (expression: Expression, environment: Environment, isInit: boolean)
|
|
201
|
+
const isGuard = (expression: Expression, environment: Environment, isInit: boolean) => {
|
|
192
202
|
if (ACTION_KINDS.has(expression.kind)) return false;
|
|
193
203
|
if (expression.kind === "Identifier") {
|
|
194
|
-
|
|
195
|
-
environment.variableIndex.has(expression.name) ||
|
|
196
|
-
|
|
197
|
-
) {
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
return false;
|
|
204
|
+
return (
|
|
205
|
+
environment.variableIndex.has(expression.name) || environment.constants.has(expression.name)
|
|
206
|
+
);
|
|
201
207
|
}
|
|
202
208
|
if (expression.kind !== "Apply") return true;
|
|
203
209
|
if (expression.operator === "=>") return false;
|
|
@@ -212,6 +218,56 @@ const isGuard = (expression: Expression, environment: Environment, isInit: boole
|
|
|
212
218
|
return definition === undefined && environment.builtins.has(expression.operator);
|
|
213
219
|
};
|
|
214
220
|
|
|
221
|
+
const NO_NAMES: ReadonlySet<string> = new Set();
|
|
222
|
+
|
|
223
|
+
// The local names an expression reads from enclosing scopes, or undefined when it applies a local
|
|
224
|
+
// operator, whose body may assign variables.
|
|
225
|
+
const getReadLocals = (
|
|
226
|
+
expression: Expression,
|
|
227
|
+
bound: ReadonlySet<string>,
|
|
228
|
+
): string[] | undefined => {
|
|
229
|
+
if (isLocallyResolved(expression)) {
|
|
230
|
+
if (expression.kind !== "Identifier") return undefined;
|
|
231
|
+
return bound.has(expression.name) ? [] : [expression.name];
|
|
232
|
+
}
|
|
233
|
+
const names: string[] = [];
|
|
234
|
+
const result = { isReadOnly: true };
|
|
235
|
+
mapChildren(expression, (child, childNames) => {
|
|
236
|
+
const childLocals = result.isReadOnly
|
|
237
|
+
? getReadLocals(child, childNames.length === 0 ? bound : new Set([...bound, ...childNames]))
|
|
238
|
+
: [];
|
|
239
|
+
if (childLocals) names.push(...childLocals);
|
|
240
|
+
else result.isReadOnly = false;
|
|
241
|
+
return child;
|
|
242
|
+
});
|
|
243
|
+
return result.isReadOnly ? names : undefined;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// TLC generates a successor for every disjunct and witness that holds, so an expression only
|
|
247
|
+
// checks like a guard when it has neither.
|
|
248
|
+
const hasBranches = (
|
|
249
|
+
expression: Expression,
|
|
250
|
+
environment: Environment,
|
|
251
|
+
visited: Set<Definition>,
|
|
252
|
+
): boolean => {
|
|
253
|
+
if (expression.kind === "Or" || expression.kind === "Exists") return true;
|
|
254
|
+
const name =
|
|
255
|
+
expression.kind === "Identifier"
|
|
256
|
+
? expression.name
|
|
257
|
+
: expression.kind === "Apply"
|
|
258
|
+
? expression.operator
|
|
259
|
+
: undefined;
|
|
260
|
+
const definition =
|
|
261
|
+
name === undefined || isLocallyResolved(expression)
|
|
262
|
+
? undefined
|
|
263
|
+
: getDefinition(name, environment);
|
|
264
|
+
if (definition && !visited.has(definition)) {
|
|
265
|
+
visited.add(definition);
|
|
266
|
+
if (hasBranches(definition.body, environment, visited)) return true;
|
|
267
|
+
}
|
|
268
|
+
return getChildren(expression).some((child) => hasBranches(child, environment, visited));
|
|
269
|
+
};
|
|
270
|
+
|
|
215
271
|
const toConjunct = (
|
|
216
272
|
expression: Expression,
|
|
217
273
|
environment: Environment,
|
|
@@ -332,8 +388,8 @@ const compileMembership = (
|
|
|
332
388
|
`In computing next states, the right side of \\in is not enumerable: ${formatValue(value)} at ${describeLocation(expression)}`,
|
|
333
389
|
);
|
|
334
390
|
}
|
|
335
|
-
const elements =
|
|
336
|
-
for (let position = 0; position < elements.length; position++) {
|
|
391
|
+
const elements = getStreamedElements(value);
|
|
392
|
+
for (let position = 0; position < elements.length && !run.isStopped; position++) {
|
|
337
393
|
onComplete(assign(partial, index, elements[position]), action);
|
|
338
394
|
}
|
|
339
395
|
};
|
|
@@ -542,6 +598,104 @@ const compileIdentifier = (
|
|
|
542
598
|
};
|
|
543
599
|
};
|
|
544
600
|
|
|
601
|
+
// A PlusCal action starts with a guard such as pc[self] = "L1", and a process is a disjunction of
|
|
602
|
+
// its actions. The labels an expression requires of one variable at one bound name let a
|
|
603
|
+
// disjunction skip the operands whose guard is false without evaluating them.
|
|
604
|
+
interface LabelGuard {
|
|
605
|
+
variable: number;
|
|
606
|
+
argument: string;
|
|
607
|
+
labels: Set<string>;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const getLabelGuard = (
|
|
611
|
+
expression: Expression,
|
|
612
|
+
environment: Environment,
|
|
613
|
+
visited: Set<Expression>,
|
|
614
|
+
): LabelGuard | undefined => {
|
|
615
|
+
if (visited.has(expression)) return undefined;
|
|
616
|
+
visited.add(expression);
|
|
617
|
+
if (expression.kind === "And") {
|
|
618
|
+
const first = expression.operands[0];
|
|
619
|
+
if (first.kind !== "Apply" || first.operator !== "=" || isLocallyResolved(first)) {
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
const target = first.args[0];
|
|
623
|
+
const label = first.args[1];
|
|
624
|
+
if (target.kind !== "FunctionApplication" || label.kind !== "String") return undefined;
|
|
625
|
+
const argument = target.args.length === 1 ? target.args[0] : undefined;
|
|
626
|
+
if (argument?.kind !== "Identifier" || !isLocallyResolved(argument)) return undefined;
|
|
627
|
+
const variable = getStaticVariableIndex(target.function, environment, false);
|
|
628
|
+
if (variable === undefined) return undefined;
|
|
629
|
+
return { variable, argument: argument.name, labels: new Set([label.value]) };
|
|
630
|
+
}
|
|
631
|
+
if (expression.kind === "Or") {
|
|
632
|
+
const guards = expression.operands.map((operand) =>
|
|
633
|
+
getLabelGuard(operand, environment, visited),
|
|
634
|
+
);
|
|
635
|
+
const first = guards[0];
|
|
636
|
+
const isShared = guards.every(
|
|
637
|
+
(guard) =>
|
|
638
|
+
guard !== undefined &&
|
|
639
|
+
guard.variable === first?.variable &&
|
|
640
|
+
guard.argument === first.argument,
|
|
641
|
+
);
|
|
642
|
+
if (!first || !isShared) return undefined;
|
|
643
|
+
const labels = new Set(guards.flatMap((guard) => [...(guard?.labels ?? [])]));
|
|
644
|
+
return { variable: first.variable, argument: first.argument, labels };
|
|
645
|
+
}
|
|
646
|
+
if (expression.kind !== "Apply" || isLocallyResolved(expression)) return undefined;
|
|
647
|
+
const closure = resolveGlobalOperator(expression.operator, environment);
|
|
648
|
+
if (closure?.kind !== "Closure" || closure.scope !== undefined) return undefined;
|
|
649
|
+
const guard = getLabelGuard(closure.body, environment, visited);
|
|
650
|
+
const position = closure.params.findIndex(
|
|
651
|
+
(param) => param.arity === 0 && param.name === guard?.argument,
|
|
652
|
+
);
|
|
653
|
+
const argument = expression.args[position];
|
|
654
|
+
if (!guard || argument?.kind !== "Identifier" || !isLocallyResolved(argument)) {
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|
|
657
|
+
return { variable: guard.variable, argument: argument.name, labels: guard.labels };
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const getLabel = (run: Run, scope: Scope | undefined, guard: LabelGuard) => {
|
|
661
|
+
const binding = lookupScope(scope, guard.argument);
|
|
662
|
+
const functionValue = run.current?.[guard.variable];
|
|
663
|
+
if (
|
|
664
|
+
binding?.kind !== "Value" ||
|
|
665
|
+
typeof functionValue !== "object" ||
|
|
666
|
+
functionValue.kind !== "Function"
|
|
667
|
+
) {
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
const position = functionValue.domain.indexOf(binding.value);
|
|
671
|
+
const label = position === -1 ? undefined : functionValue.range[position];
|
|
672
|
+
return typeof label === "string" ? label : undefined;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
const compileDisjunction = (
|
|
676
|
+
operands: Expression[],
|
|
677
|
+
environment: Environment,
|
|
678
|
+
isInit: boolean,
|
|
679
|
+
): Runner => {
|
|
680
|
+
const runners = operands.map((operand) => getRunner(operand, environment, isInit));
|
|
681
|
+
const guards = isInit
|
|
682
|
+
? []
|
|
683
|
+
: operands.map((operand) => getLabelGuard(operand, environment, new Set()));
|
|
684
|
+
const dispatch = guards.find((guard) => guard !== undefined);
|
|
685
|
+
const labels = guards.map((guard) =>
|
|
686
|
+
guard?.variable === dispatch?.variable && guard?.argument === dispatch?.argument
|
|
687
|
+
? guard?.labels
|
|
688
|
+
: undefined,
|
|
689
|
+
);
|
|
690
|
+
return (run, scope, partial, action, isFixed, onComplete) => {
|
|
691
|
+
const label = dispatch && getLabel(run, scope, dispatch);
|
|
692
|
+
for (let index = 0; index < runners.length && !run.isStopped; index++) {
|
|
693
|
+
if (label !== undefined && labels[index]?.has(label) === false) continue;
|
|
694
|
+
runners[index](run, scope, partial, action, isFixed, onComplete);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
};
|
|
698
|
+
|
|
545
699
|
const compileAction = (
|
|
546
700
|
expression: Expression,
|
|
547
701
|
environment: Environment,
|
|
@@ -551,26 +705,19 @@ const compileAction = (
|
|
|
551
705
|
switch (expression.kind) {
|
|
552
706
|
case "And":
|
|
553
707
|
return compileConjunction(expression.operands, environment, isInit);
|
|
554
|
-
case "Or":
|
|
555
|
-
|
|
556
|
-
return (run, scope, partial, action, isFixed, onComplete) => {
|
|
557
|
-
for (const operand of operands) operand(run, scope, partial, action, isFixed, onComplete);
|
|
558
|
-
};
|
|
559
|
-
}
|
|
708
|
+
case "Or":
|
|
709
|
+
return compileDisjunction(expression.operands, environment, isInit);
|
|
560
710
|
case "Exists": {
|
|
561
711
|
const body = child(expression.body);
|
|
562
712
|
const bound = expression.bounds[0];
|
|
563
713
|
if (expression.bounds.length === 1 && bound.pattern.kind === "Name") {
|
|
564
714
|
const name = bound.pattern.name;
|
|
565
715
|
return (run, scope, partial, action, isFixed, onComplete) => {
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
const elements = toFiniteSet(set).elements;
|
|
573
|
-
for (let index = 0; index < elements.length; index++) {
|
|
716
|
+
const elements = getEnumeratedElements(
|
|
717
|
+
evaluate(bound.set, getContext(run, scope, partial)),
|
|
718
|
+
bound.set,
|
|
719
|
+
);
|
|
720
|
+
for (let index = 0; index < elements.length && !run.isStopped; index++) {
|
|
574
721
|
body(
|
|
575
722
|
run,
|
|
576
723
|
bindValue(scope, name, elements[index]),
|
|
@@ -585,12 +732,18 @@ const compileAction = (
|
|
|
585
732
|
return (run, scope, partial, action, isFixed, onComplete) => {
|
|
586
733
|
forEachBinding(expression.bounds, getContext(run, scope, partial), (boundScope) => {
|
|
587
734
|
body(run, boundScope, partial, action, isFixed, onComplete);
|
|
588
|
-
return
|
|
735
|
+
return run.isStopped;
|
|
589
736
|
});
|
|
590
737
|
};
|
|
591
738
|
}
|
|
592
739
|
case "Forall": {
|
|
593
740
|
const body = child(expression.body);
|
|
741
|
+
const readLocals =
|
|
742
|
+
!isInit &&
|
|
743
|
+
getReadVariables(expression, environment) !== undefined &&
|
|
744
|
+
!hasBranches(expression.body, environment, new Set())
|
|
745
|
+
? getReadLocals(expression, NO_NAMES)
|
|
746
|
+
: undefined;
|
|
594
747
|
return (run, scope, partial, action, _isFixed, onComplete) => {
|
|
595
748
|
const boundScopes: (Scope | undefined)[] = [];
|
|
596
749
|
forEachBinding(expression.bounds, getContext(run, scope, partial), (boundScope) => {
|
|
@@ -598,6 +751,16 @@ const compileAction = (
|
|
|
598
751
|
return false;
|
|
599
752
|
});
|
|
600
753
|
const scopes = [...boundScopes.slice(0, 1), ...boundScopes.slice(1).reverse()];
|
|
754
|
+
const isBoundToValues = readLocals?.every(
|
|
755
|
+
(name) => lookupScope(scope, name)?.kind === "Value",
|
|
756
|
+
);
|
|
757
|
+
if (isBoundToValues) {
|
|
758
|
+
const isSatisfied = scopes.every((boundScope) =>
|
|
759
|
+
holds(expression.body, getContext(run, boundScope, partial)),
|
|
760
|
+
);
|
|
761
|
+
if (isSatisfied) onComplete(partial, action);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
601
764
|
const visit = (index: number, nextPartial: State, nextAction: string | undefined): void => {
|
|
602
765
|
if (index === scopes.length) {
|
|
603
766
|
onComplete(nextPartial, nextAction);
|
|
@@ -647,10 +810,11 @@ const compileAction = (
|
|
|
647
810
|
}
|
|
648
811
|
case "Let": {
|
|
649
812
|
const body = child(expression.body);
|
|
813
|
+
const reevaluated = getReevaluatedDefinitions(expression.definitions, environment);
|
|
650
814
|
return (run, scope, partial, action, isFixed, onComplete) =>
|
|
651
815
|
body(
|
|
652
816
|
run,
|
|
653
|
-
bindDefinitions(expression.definitions, getContext(run, scope, partial)),
|
|
817
|
+
bindDefinitions(expression.definitions, getContext(run, scope, partial), reevaluated),
|
|
654
818
|
partial,
|
|
655
819
|
action,
|
|
656
820
|
isFixed,
|
|
@@ -751,7 +915,7 @@ export const isEnabled = (expression: Expression, context: Context): boolean =>
|
|
|
751
915
|
const environment = context.environment;
|
|
752
916
|
const found = { value: false };
|
|
753
917
|
getRunner(expression, environment, false)(
|
|
754
|
-
{ environment, isInit: false, current: state },
|
|
918
|
+
{ environment, isInit: false, current: state, isStopped: false },
|
|
755
919
|
context.scope,
|
|
756
920
|
getEmptyState(environment),
|
|
757
921
|
undefined,
|
|
@@ -763,48 +927,99 @@ export const isEnabled = (expression: Expression, context: Context): boolean =>
|
|
|
763
927
|
return found.value;
|
|
764
928
|
};
|
|
765
929
|
|
|
766
|
-
export const
|
|
767
|
-
|
|
768
|
-
|
|
930
|
+
export const forEachInitialState = (
|
|
931
|
+
expression: Expression,
|
|
932
|
+
environment: Environment,
|
|
933
|
+
onState: (state: Value[]) => boolean,
|
|
934
|
+
scope?: Scope,
|
|
935
|
+
) => {
|
|
936
|
+
const run: Run = { environment, isInit: true, current: undefined, isStopped: false };
|
|
769
937
|
getRunner(expression, environment, true)(
|
|
770
938
|
run,
|
|
771
|
-
|
|
939
|
+
scope,
|
|
772
940
|
getEmptyState(environment),
|
|
773
941
|
undefined,
|
|
774
942
|
false,
|
|
775
943
|
(partial) => {
|
|
944
|
+
if (run.isStopped) return;
|
|
776
945
|
if (!isComplete(partial)) {
|
|
777
|
-
|
|
778
|
-
|
|
946
|
+
// TLC reports an initial state that isn't completely specified as an illegal state, not as
|
|
947
|
+
// an evaluation failure.
|
|
948
|
+
throw setExitCode(
|
|
949
|
+
new TlaEvaluationError(
|
|
950
|
+
`The initial predicate does not assign ${getMissingVariables(partial, environment).join(", ")}`,
|
|
951
|
+
),
|
|
952
|
+
EXIT_STATUS.error,
|
|
779
953
|
);
|
|
780
954
|
}
|
|
781
|
-
|
|
955
|
+
run.isStopped = onState(partial);
|
|
956
|
+
},
|
|
957
|
+
);
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
export const getInitialStates = (
|
|
961
|
+
expression: Expression,
|
|
962
|
+
environment: Environment,
|
|
963
|
+
scope?: Scope,
|
|
964
|
+
): Value[][] => {
|
|
965
|
+
const states: Value[][] = [];
|
|
966
|
+
forEachInitialState(
|
|
967
|
+
expression,
|
|
968
|
+
environment,
|
|
969
|
+
(state) => {
|
|
970
|
+
states.push(state);
|
|
971
|
+
return false;
|
|
782
972
|
},
|
|
973
|
+
scope,
|
|
783
974
|
);
|
|
784
975
|
return states;
|
|
785
976
|
};
|
|
786
977
|
|
|
787
|
-
export const
|
|
978
|
+
export const forEachSuccessor = (
|
|
788
979
|
expression: Expression,
|
|
789
980
|
state: Value[],
|
|
790
981
|
environment: Environment,
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
982
|
+
onSuccessor: (successor: Successor) => boolean,
|
|
983
|
+
scope?: Scope,
|
|
984
|
+
) => {
|
|
985
|
+
const run: Run = { environment, isInit: false, current: state, isStopped: false };
|
|
986
|
+
const counter = { successors: 0 };
|
|
794
987
|
getRunner(expression, environment, false)(
|
|
795
988
|
run,
|
|
796
|
-
|
|
989
|
+
scope,
|
|
797
990
|
getEmptyState(environment),
|
|
798
991
|
undefined,
|
|
799
992
|
false,
|
|
800
993
|
(partial, action) => {
|
|
994
|
+
if (run.isStopped) return;
|
|
801
995
|
if (!isComplete(partial)) {
|
|
802
996
|
throw new TlaEvaluationError(
|
|
803
997
|
`Successor state is not completely specified by action ${action ?? "Next"}. The following variables are not assigned: ${getMissingVariables(partial, environment).join(", ")}`,
|
|
804
998
|
);
|
|
805
999
|
}
|
|
806
|
-
successors
|
|
1000
|
+
counter.successors++;
|
|
1001
|
+
run.isStopped = onSuccessor({ state: partial, action });
|
|
1002
|
+
},
|
|
1003
|
+
);
|
|
1004
|
+
return counter.successors;
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
export const getSuccessors = (
|
|
1008
|
+
expression: Expression,
|
|
1009
|
+
state: Value[],
|
|
1010
|
+
environment: Environment,
|
|
1011
|
+
scope?: Scope,
|
|
1012
|
+
): Successor[] => {
|
|
1013
|
+
const successors: Successor[] = [];
|
|
1014
|
+
forEachSuccessor(
|
|
1015
|
+
expression,
|
|
1016
|
+
state,
|
|
1017
|
+
environment,
|
|
1018
|
+
(successor) => {
|
|
1019
|
+
successors.push(successor);
|
|
1020
|
+
return false;
|
|
807
1021
|
},
|
|
1022
|
+
scope,
|
|
808
1023
|
);
|
|
809
1024
|
return successors;
|
|
810
1025
|
};
|