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/tlc-order.ts
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
// TLC orders strings and model values by when it first interned their text, and compares a
|
|
2
|
+
// state's variables in the order of SANY's symbol table. This reproduces both orders.
|
|
3
|
+
import type { Module, SourceLocation } from "./ast";
|
|
4
|
+
import type { ConfigValue, ModelConfig } from "./config";
|
|
5
|
+
import { tokenize, type Token } from "./lexer";
|
|
6
|
+
import { extractModule, parseModule } from "./parser";
|
|
7
|
+
import { isBefore } from "./resolve";
|
|
8
|
+
import {
|
|
9
|
+
SANY_BUILTINS,
|
|
10
|
+
SANY_NODE_NAMES,
|
|
11
|
+
SANY_OPERATOR_SYNONYMS,
|
|
12
|
+
TLC_MODULE_SOURCES,
|
|
13
|
+
TLC_RUNTIME_STRINGS,
|
|
14
|
+
TLC_STARTUP_STRINGS,
|
|
15
|
+
} from "./tlc-modules";
|
|
16
|
+
|
|
17
|
+
type SymbolKind = "builtin" | "constant" | "variable" | "definition" | "theorem" | "module";
|
|
18
|
+
|
|
19
|
+
interface SymbolEntry {
|
|
20
|
+
key: string;
|
|
21
|
+
name: string;
|
|
22
|
+
kind: SymbolKind;
|
|
23
|
+
isLocal: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SymbolTable {
|
|
27
|
+
put: (entry: SymbolEntry) => void;
|
|
28
|
+
inserted: SymbolEntry[];
|
|
29
|
+
enumerate: () => SymbolEntry[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ParsedModule {
|
|
33
|
+
tokens: Token[];
|
|
34
|
+
start: number;
|
|
35
|
+
end: number;
|
|
36
|
+
getModule: () => Module;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ModuleLoader = ((name: string) => string | undefined) | undefined;
|
|
40
|
+
|
|
41
|
+
interface ModuleUnit {
|
|
42
|
+
location: SourceLocation;
|
|
43
|
+
apply: (table: SymbolTable) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SEMANTIC_PRELUDE = ["LAMBDA", "$$InAssume", "nullODN", "nullOpArg", "nullLabelNode"];
|
|
47
|
+
|
|
48
|
+
const THEOREM_KEYWORDS = new Set(["THEOREM", "LEMMA", "COROLLARY", "PROPOSITION"]);
|
|
49
|
+
|
|
50
|
+
const CANONICAL_OPERATORS = new Map(
|
|
51
|
+
SANY_OPERATOR_SYNONYMS.flatMap((synonyms) => synonyms.map((name) => [name, synonyms[0]])),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const getCanonicalName = (name: string) => CANONICAL_OPERATORS.get(name) ?? name;
|
|
55
|
+
|
|
56
|
+
const getJavaHash = (text: string) => {
|
|
57
|
+
let hash = 0;
|
|
58
|
+
for (let index = 0; index < text.length; index++) {
|
|
59
|
+
hash = (Math.imul(hash, 31) + text.charCodeAt(index)) | 0;
|
|
60
|
+
}
|
|
61
|
+
return hash;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// java.util.Hashtable as SANY's Context uses it: default capacity 11, load factor 0.75, new
|
|
65
|
+
// entries at the head of their bucket, and enumeration from the last bucket to the first.
|
|
66
|
+
const createSymbolTable = (): SymbolTable => {
|
|
67
|
+
const table = { buckets: Array.from({ length: 11 }, (): SymbolEntry[] => []), threshold: 8 };
|
|
68
|
+
const keys = new Set<string>();
|
|
69
|
+
const inserted: SymbolEntry[] = [];
|
|
70
|
+
const getBucket = (entry: SymbolEntry, capacity: number) =>
|
|
71
|
+
(getJavaHash(entry.name) & 0x7fffffff) % capacity;
|
|
72
|
+
const rehash = () => {
|
|
73
|
+
const capacity = table.buckets.length * 2 + 1;
|
|
74
|
+
const buckets = Array.from({ length: capacity }, (): SymbolEntry[] => []);
|
|
75
|
+
for (let index = table.buckets.length - 1; index >= 0; index--) {
|
|
76
|
+
for (const entry of table.buckets[index]) buckets[getBucket(entry, capacity)].unshift(entry);
|
|
77
|
+
}
|
|
78
|
+
table.buckets = buckets;
|
|
79
|
+
table.threshold = Math.floor(capacity * 0.75);
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
put: (entry) => {
|
|
83
|
+
if (keys.has(entry.key)) return;
|
|
84
|
+
if (inserted.length >= table.threshold) rehash();
|
|
85
|
+
table.buckets[getBucket(entry, table.buckets.length)].unshift(entry);
|
|
86
|
+
keys.add(entry.key);
|
|
87
|
+
inserted.push(entry);
|
|
88
|
+
},
|
|
89
|
+
inserted,
|
|
90
|
+
enumerate: () => table.buckets.toReversed().flat(),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const createEntry = (name: string, kind: SymbolKind, isLocal: boolean) => ({
|
|
95
|
+
key: kind === "module" ? `module ${name}` : name,
|
|
96
|
+
name,
|
|
97
|
+
kind,
|
|
98
|
+
isLocal,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const getDeclaredName = (tokens: Token[], start: number) => {
|
|
102
|
+
const first = tokens[start];
|
|
103
|
+
const second = tokens[start + 1];
|
|
104
|
+
if (first.text === "_" && second.kind === "Operator") {
|
|
105
|
+
return tokens[start + 2]?.text === "_"
|
|
106
|
+
? { name: getCanonicalName(second.text), end: start + 3 }
|
|
107
|
+
: { name: second.text, end: start + 2 };
|
|
108
|
+
}
|
|
109
|
+
if (first.kind === "Operator") {
|
|
110
|
+
return { name: first.text === "-" ? "-." : getCanonicalName(first.text), end: start + 2 };
|
|
111
|
+
}
|
|
112
|
+
if (second?.text !== "(") return { name: first.text, end: start + 1 };
|
|
113
|
+
const close = tokens.findIndex((token, index) => index > start && token.text === ")");
|
|
114
|
+
return { name: first.text, end: close + 1 };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getDeclarations = (tokens: Token[], start: number) => {
|
|
118
|
+
const names: string[] = [];
|
|
119
|
+
for (let index = start; ;) {
|
|
120
|
+
const declared = getDeclaredName(tokens, index);
|
|
121
|
+
names.push(declared.name);
|
|
122
|
+
if (tokens[declared.end]?.text !== ",") return names;
|
|
123
|
+
index = declared.end + 1;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const getLocation = (token: Token) => ({ line: token.line, column: token.column });
|
|
128
|
+
|
|
129
|
+
interface ModuleReferences {
|
|
130
|
+
key: string;
|
|
131
|
+
unit: string | undefined;
|
|
132
|
+
extends: string[];
|
|
133
|
+
instances: string[];
|
|
134
|
+
inner: ModuleReferences[];
|
|
135
|
+
allExtends: Set<string>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type ModuleSearch = (
|
|
139
|
+
module: ModuleReferences,
|
|
140
|
+
visited: Set<string>,
|
|
141
|
+
) => [ModuleReferences, string] | undefined;
|
|
142
|
+
|
|
143
|
+
// The modules a module extends and instantiates, with its inner modules, as SANY's SpecObj
|
|
144
|
+
// walks them to decide which module to parse next.
|
|
145
|
+
const getModuleReferences = (
|
|
146
|
+
tokens: Token[],
|
|
147
|
+
unit: string | undefined,
|
|
148
|
+
innerModules: Map<string, ModuleReferences>,
|
|
149
|
+
) => {
|
|
150
|
+
const allExtends = new Set<string>();
|
|
151
|
+
const read = (start: number, key: string): { references: ModuleReferences; end: number } => {
|
|
152
|
+
const references: ModuleReferences = {
|
|
153
|
+
key,
|
|
154
|
+
unit,
|
|
155
|
+
extends: [],
|
|
156
|
+
instances: [],
|
|
157
|
+
inner: [],
|
|
158
|
+
allExtends,
|
|
159
|
+
};
|
|
160
|
+
for (let index = start + 1; index < tokens.length; index++) {
|
|
161
|
+
const token = tokens[index];
|
|
162
|
+
if (token.kind === "ModuleEnd") return { references, end: index };
|
|
163
|
+
if (token.kind === "Separator" && tokens[index + 1]?.text === "MODULE") {
|
|
164
|
+
const name = tokens[index + 2].text;
|
|
165
|
+
const inner = read(index, `${key}/${name}`);
|
|
166
|
+
innerModules.set(name, inner.references);
|
|
167
|
+
references.inner.push(inner.references);
|
|
168
|
+
index = inner.end;
|
|
169
|
+
} else if (token.text === "EXTENDS") {
|
|
170
|
+
const names = getDeclarations(tokens, index + 1);
|
|
171
|
+
references.extends.push(...names);
|
|
172
|
+
names.forEach((name) => allExtends.add(name));
|
|
173
|
+
} else if (token.text === "INSTANCE" && tokens[index + 1]?.kind === "Identifier") {
|
|
174
|
+
references.instances.push(tokens[index + 1].text);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { references, end: tokens.length };
|
|
178
|
+
};
|
|
179
|
+
return read(0, unit ?? "").references;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const isAdjacent = (left: Token, right: Token | undefined) =>
|
|
183
|
+
right !== undefined &&
|
|
184
|
+
right.line === left.line &&
|
|
185
|
+
right.column === left.column + left.text.length;
|
|
186
|
+
|
|
187
|
+
// The texts SANY interns while parsing, in order. SANY reads a proof step label such as <1>2 and
|
|
188
|
+
// a module header such as ---- MODULE as one token, and interns a string literal's contents only
|
|
189
|
+
// during semantic analysis.
|
|
190
|
+
const getParsedTexts = (tokens: Token[]) => {
|
|
191
|
+
const texts: string[] = [];
|
|
192
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
193
|
+
const token = tokens[index];
|
|
194
|
+
const isStepLabel =
|
|
195
|
+
token.text === "<" &&
|
|
196
|
+
isAdjacent(token, tokens[index + 1]) &&
|
|
197
|
+
/^(\d+|\*|\+)$/.test(tokens[index + 1].text) &&
|
|
198
|
+
tokens[index + 2]?.text === ">" &&
|
|
199
|
+
isAdjacent(tokens[index + 1], tokens[index + 2]);
|
|
200
|
+
if (isStepLabel) {
|
|
201
|
+
index += isAdjacent(tokens[index + 2], tokens[index + 3]) ? 3 : 2;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const isHeader = token.text === "MODULE" && tokens[index - 1]?.kind === "Separator";
|
|
205
|
+
if (token.kind !== "String" && !isHeader) texts.push(token.text);
|
|
206
|
+
}
|
|
207
|
+
return texts;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const getRanges = (tokens: Token[]) => {
|
|
211
|
+
const ranges = new Map<string, { start: number; end: number }>();
|
|
212
|
+
const open: { name: string; start: number }[] = [];
|
|
213
|
+
tokens.forEach((token, index) => {
|
|
214
|
+
if (token.kind === "Separator" && tokens[index + 1]?.text === "MODULE") {
|
|
215
|
+
open.push({ name: tokens[index + 2].text, start: index });
|
|
216
|
+
}
|
|
217
|
+
const closed = token.kind === "ModuleEnd" ? open.pop() : undefined;
|
|
218
|
+
if (closed && !ranges.has(closed.name)) {
|
|
219
|
+
ranges.set(closed.name, { start: closed.start, end: index + 1 });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
return ranges;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const findModule = (modules: Module[], name: string): Module | undefined =>
|
|
226
|
+
modules.find((module) => module.name === name) ??
|
|
227
|
+
modules.map((module) => findModule(module.modules, name)).find(Boolean);
|
|
228
|
+
|
|
229
|
+
// The modules TLC reads for a spec: the root module, TLC's standard modules, the files the loader
|
|
230
|
+
// returns, the modules that follow the root module, and the modules nested in any of these. A
|
|
231
|
+
// module without a source has no tokens.
|
|
232
|
+
const createModuleSet = (source: string, loadModule: ModuleLoader) => {
|
|
233
|
+
const files = new Map<string, ParsedModule>();
|
|
234
|
+
const nestedModules = new Map<string, ParsedModule>();
|
|
235
|
+
const getFile = (name: string | undefined) => {
|
|
236
|
+
const cached = files.get(name ?? "");
|
|
237
|
+
if (cached) return cached;
|
|
238
|
+
const text =
|
|
239
|
+
name === undefined
|
|
240
|
+
? source
|
|
241
|
+
: (TLC_MODULE_SOURCES.get(name) ?? loadModule?.(name) ?? extractModule(source, name));
|
|
242
|
+
const tokens = text === undefined ? [] : tokenize(text);
|
|
243
|
+
const ranges = getRanges(tokens);
|
|
244
|
+
const range = ranges.get(tokens[2]?.text ?? "") ?? { start: 0, end: 0 };
|
|
245
|
+
const parsed = { module: undefined as Module | undefined };
|
|
246
|
+
const file = {
|
|
247
|
+
tokens,
|
|
248
|
+
...range,
|
|
249
|
+
getModule: () => (parsed.module ??= parseModule(text ?? "")),
|
|
250
|
+
};
|
|
251
|
+
files.set(name ?? "", file);
|
|
252
|
+
ranges.forEach((nestedRange, nestedName) => {
|
|
253
|
+
if (nestedRange.start === range.start || nestedModules.has(nestedName)) return;
|
|
254
|
+
nestedModules.set(nestedName, {
|
|
255
|
+
tokens,
|
|
256
|
+
...nestedRange,
|
|
257
|
+
getModule: () => findModule(file.getModule().modules, nestedName) ?? file.getModule(),
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
return file;
|
|
261
|
+
};
|
|
262
|
+
getFile(undefined);
|
|
263
|
+
return (name: string | undefined): ParsedModule =>
|
|
264
|
+
(name === undefined ? undefined : nestedModules.get(name)) ?? getFile(name);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// The variables in the order TLC compares them: the reverse of the root module's context
|
|
268
|
+
// enumeration, as ModuleNode.getVariableDecls returns them.
|
|
269
|
+
export const getTlcVariableOrder = (source: string, loadModule: ModuleLoader): string[] => {
|
|
270
|
+
const getParsed = createModuleSet(source, loadModule);
|
|
271
|
+
const getUnits = (unit: ParsedModule) => {
|
|
272
|
+
const tokens = unit.tokens;
|
|
273
|
+
const module = unit.getModule();
|
|
274
|
+
const units: ModuleUnit[] = [];
|
|
275
|
+
const statementLocations: SourceLocation[] = [];
|
|
276
|
+
const declare = (index: number, kind: SymbolKind) => {
|
|
277
|
+
const names = getDeclarations(tokens, index + 1);
|
|
278
|
+
units.push({
|
|
279
|
+
location: getLocation(tokens[index]),
|
|
280
|
+
apply: (table) => names.forEach((name) => table.put(createEntry(name, kind, false))),
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
const recursiveNames = new Set(
|
|
284
|
+
module.definitions.filter((definition) => definition.isRecursive).map((item) => item.name),
|
|
285
|
+
);
|
|
286
|
+
const depth = { value: 0 };
|
|
287
|
+
tokens.slice(unit.start, unit.end).forEach((_, offset) => {
|
|
288
|
+
const index = unit.start + offset;
|
|
289
|
+
const token = tokens[index];
|
|
290
|
+
if (token.kind === "Separator" && tokens[index + 1]?.text === "MODULE") {
|
|
291
|
+
depth.value++;
|
|
292
|
+
if (depth.value === 2) {
|
|
293
|
+
const name = tokens[index + 2].text;
|
|
294
|
+
units.push({
|
|
295
|
+
location: getLocation(token),
|
|
296
|
+
apply: (table) => table.put(createEntry(name, "module", false)),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (token.kind === "ModuleEnd") depth.value--;
|
|
302
|
+
if (depth.value !== 1 || token.kind !== "Keyword") return;
|
|
303
|
+
if (token.text === "CONSTANT" || token.text === "CONSTANTS") declare(index, "constant");
|
|
304
|
+
if (token.text === "VARIABLE" || token.text === "VARIABLES") declare(index, "variable");
|
|
305
|
+
if (token.text === "RECURSIVE") {
|
|
306
|
+
const names = getDeclarations(tokens, index + 1);
|
|
307
|
+
if (names.every((name) => recursiveNames.has(name))) declare(index, "definition");
|
|
308
|
+
}
|
|
309
|
+
const isNamedTheorem =
|
|
310
|
+
THEOREM_KEYWORDS.has(token.text) &&
|
|
311
|
+
tokens[index + 1]?.kind === "Identifier" &&
|
|
312
|
+
tokens[index + 2]?.text === "==";
|
|
313
|
+
if (isNamedTheorem) {
|
|
314
|
+
statementLocations.push(getLocation(token));
|
|
315
|
+
units.push({
|
|
316
|
+
location: getLocation(token),
|
|
317
|
+
apply: (table) => table.put(createEntry(tokens[index + 1].text, "theorem", false)),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
module.assumptions.forEach((assumption) => {
|
|
322
|
+
statementLocations.push(assumption.location);
|
|
323
|
+
const name = assumption.name;
|
|
324
|
+
if (name === undefined) return;
|
|
325
|
+
units.push({
|
|
326
|
+
location: assumption.location,
|
|
327
|
+
apply: (table) => table.put(createEntry(name, "theorem", false)),
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
const isStatement = (location: SourceLocation) =>
|
|
331
|
+
statementLocations.some(
|
|
332
|
+
(statement) => statement.line === location.line && statement.column === location.column,
|
|
333
|
+
);
|
|
334
|
+
module.definitions.forEach((definition) => {
|
|
335
|
+
if (isStatement(definition.location)) return;
|
|
336
|
+
const name = getCanonicalName(definition.name);
|
|
337
|
+
units.push({
|
|
338
|
+
location: definition.location,
|
|
339
|
+
apply: (table) => table.put(createEntry(name, "definition", definition.isLocal)),
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
module.instances.forEach((instance) => {
|
|
343
|
+
units.push({
|
|
344
|
+
location: instance.location,
|
|
345
|
+
apply: (table) => {
|
|
346
|
+
const instancee = getContext(instance.module).enumerate();
|
|
347
|
+
const prefix = instance.name === undefined ? "" : `${instance.name}!`;
|
|
348
|
+
const definitions = instancee.filter((entry) => entry.kind === "definition");
|
|
349
|
+
const theorems = instancee.filter((entry) => entry.kind === "theorem");
|
|
350
|
+
[...definitions, ...theorems].forEach((entry) => {
|
|
351
|
+
if (entry.isLocal) return;
|
|
352
|
+
table.put(createEntry(`${prefix}${entry.name}`, entry.kind, instance.isLocal));
|
|
353
|
+
});
|
|
354
|
+
if (instance.name !== undefined) {
|
|
355
|
+
table.put(createEntry(instance.name, "definition", instance.isLocal));
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
return units.sort((left, right) =>
|
|
361
|
+
isBefore(left.location, right.location)
|
|
362
|
+
? -1
|
|
363
|
+
: isBefore(right.location, left.location)
|
|
364
|
+
? 1
|
|
365
|
+
: 0,
|
|
366
|
+
);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const contexts = new Map<string, SymbolTable>();
|
|
370
|
+
const getContext = (name: string | undefined): SymbolTable => {
|
|
371
|
+
const cached = contexts.get(name ?? "");
|
|
372
|
+
if (cached) return cached;
|
|
373
|
+
const table = createSymbolTable();
|
|
374
|
+
contexts.set(name ?? "", table);
|
|
375
|
+
SANY_BUILTINS.toReversed().forEach((builtin) =>
|
|
376
|
+
table.put(createEntry(builtin, "builtin", false)),
|
|
377
|
+
);
|
|
378
|
+
const unit = getParsed(name);
|
|
379
|
+
unit.getModule().extends.forEach((extended) =>
|
|
380
|
+
getContext(extended).inserted.forEach((entry) => {
|
|
381
|
+
if (!entry.isLocal) table.put(entry);
|
|
382
|
+
}),
|
|
383
|
+
);
|
|
384
|
+
getUnits(unit).forEach((item) => item.apply(table));
|
|
385
|
+
return table;
|
|
386
|
+
};
|
|
387
|
+
return getContext(undefined)
|
|
388
|
+
.enumerate()
|
|
389
|
+
.filter((entry) => entry.kind === "variable")
|
|
390
|
+
.map((entry) => entry.name)
|
|
391
|
+
.reverse();
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// Every string TLC interns before model checking starts, in order: values in the config, TLC's
|
|
395
|
+
// startup strings, each module's tokens in the order SpecObj.loadSpec parses the modules, string
|
|
396
|
+
// literals in semantic analysis order, and the names TLC interns for TLCGet.
|
|
397
|
+
export const getTlcStringOrder = (
|
|
398
|
+
source: string,
|
|
399
|
+
config: ModelConfig,
|
|
400
|
+
loadModule: ModuleLoader,
|
|
401
|
+
): string[] => {
|
|
402
|
+
const getParsed = createModuleSet(source, loadModule);
|
|
403
|
+
const order = new Set<string>();
|
|
404
|
+
const intern = (text: string) => {
|
|
405
|
+
order.add(text);
|
|
406
|
+
};
|
|
407
|
+
const internConfigValue = (value: ConfigValue): void => {
|
|
408
|
+
if (value.kind === "ModelValue") intern(value.name);
|
|
409
|
+
if (value.kind === "String") intern(value.value);
|
|
410
|
+
if (value.kind === "Set") value.elements.forEach(internConfigValue);
|
|
411
|
+
};
|
|
412
|
+
config.constants.forEach((constant) => {
|
|
413
|
+
constant.args?.forEach(internConfigValue);
|
|
414
|
+
internConfigValue(constant.value);
|
|
415
|
+
});
|
|
416
|
+
TLC_STARTUP_STRINGS.forEach(intern);
|
|
417
|
+
const getUnitTokens = (name: string | undefined) => {
|
|
418
|
+
const unit = getParsed(name);
|
|
419
|
+
return unit.tokens.slice(unit.start, unit.end);
|
|
420
|
+
};
|
|
421
|
+
const innerModules = new Map<string, ModuleReferences>();
|
|
422
|
+
const unitReferences = new Map<string | undefined, ModuleReferences>();
|
|
423
|
+
const getUnitReferences = (name: string | undefined) => {
|
|
424
|
+
const cached = unitReferences.get(name);
|
|
425
|
+
if (cached) return cached;
|
|
426
|
+
const references = getModuleReferences(getUnitTokens(name), name, innerModules);
|
|
427
|
+
unitReferences.set(name, references);
|
|
428
|
+
return references;
|
|
429
|
+
};
|
|
430
|
+
const getReferenced = (name: string) => innerModules.get(name) ?? getUnitReferences(name);
|
|
431
|
+
const relations = new Set<string>();
|
|
432
|
+
const isResolved = (module: ModuleReferences, name: string) =>
|
|
433
|
+
innerModules.has(name) || relations.has(`${module.key}>${name}`);
|
|
434
|
+
const searchReferences = (
|
|
435
|
+
module: ModuleReferences,
|
|
436
|
+
visited: Set<string>,
|
|
437
|
+
search: ModuleSearch,
|
|
438
|
+
) => {
|
|
439
|
+
const nested = [
|
|
440
|
+
...module.extends.map(getReferenced),
|
|
441
|
+
...module.instances.filter((name) => isResolved(module, name)).map(getReferenced),
|
|
442
|
+
...module.inner,
|
|
443
|
+
];
|
|
444
|
+
for (const item of nested) {
|
|
445
|
+
const found = search(item, visited);
|
|
446
|
+
if (found) return found;
|
|
447
|
+
}
|
|
448
|
+
return undefined;
|
|
449
|
+
};
|
|
450
|
+
const createSearch = (getNames: (module: ModuleReferences) => string[]) => {
|
|
451
|
+
const search: ModuleSearch = (module, visited) => {
|
|
452
|
+
if (visited.has(module.key)) return undefined;
|
|
453
|
+
visited.add(module.key);
|
|
454
|
+
const name = getNames(module).find((referenced) => !isResolved(module, referenced));
|
|
455
|
+
return name === undefined ? searchReferences(module, visited, search) : [module, name];
|
|
456
|
+
};
|
|
457
|
+
return search;
|
|
458
|
+
};
|
|
459
|
+
const findExtension = createSearch((module) => module.extends);
|
|
460
|
+
const findInstantiation = createSearch((module) => module.instances);
|
|
461
|
+
const parsedUnits = new Set<string | undefined>([undefined]);
|
|
462
|
+
const dependencies = new Map<string | undefined, string[]>();
|
|
463
|
+
getParsedTexts(getUnitTokens(undefined)).forEach((text, index) => {
|
|
464
|
+
if (index === 3) SANY_NODE_NAMES.forEach(intern);
|
|
465
|
+
intern(text);
|
|
466
|
+
});
|
|
467
|
+
const rootReferences = getUnitReferences(undefined);
|
|
468
|
+
while (true) {
|
|
469
|
+
const found =
|
|
470
|
+
findExtension(rootReferences, new Set()) ?? findInstantiation(rootReferences, new Set());
|
|
471
|
+
if (!found) break;
|
|
472
|
+
const [module, name] = found;
|
|
473
|
+
relations.add(`${module.key}>${name}`);
|
|
474
|
+
dependencies.set(module.unit, [...(dependencies.get(module.unit) ?? []), name]);
|
|
475
|
+
if (parsedUnits.has(name)) continue;
|
|
476
|
+
parsedUnits.add(name);
|
|
477
|
+
getParsedTexts(getUnitTokens(name)).forEach(intern);
|
|
478
|
+
}
|
|
479
|
+
SEMANTIC_PRELUDE.forEach(intern);
|
|
480
|
+
const analyzed = new Set<string | undefined>();
|
|
481
|
+
const analyze = (name: string | undefined) => {
|
|
482
|
+
if (analyzed.has(name)) return;
|
|
483
|
+
analyzed.add(name);
|
|
484
|
+
const unitDependencies = dependencies.get(name) ?? [];
|
|
485
|
+
const extendees = getUnitReferences(name).allExtends;
|
|
486
|
+
[
|
|
487
|
+
...unitDependencies.filter((item) => extendees.has(item)),
|
|
488
|
+
...unitDependencies.filter((item) => !extendees.has(item)),
|
|
489
|
+
].forEach(analyze);
|
|
490
|
+
getUnitTokens(name).forEach((token) => {
|
|
491
|
+
if (token.kind === "String") intern(token.text);
|
|
492
|
+
});
|
|
493
|
+
};
|
|
494
|
+
analyze(undefined);
|
|
495
|
+
TLC_RUNTIME_STRINGS.forEach(intern);
|
|
496
|
+
return [...order];
|
|
497
|
+
};
|