tla-ts 0.0.1 → 0.0.3-dev.3cf2ad0

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.
Files changed (71) hide show
  1. package/README.md +85 -40
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +216 -30
  4. package/dist/index.d.ts +216 -30
  5. package/dist/index.js +1 -1
  6. package/dist/parallel-worker.cjs +2 -2
  7. package/dist/parallel-worker.js +2 -2
  8. package/dist/parallel.cjs +1 -1
  9. package/dist/parallel.js +2 -2
  10. package/dist/run-cli.cjs +20 -1
  11. package/dist/run-cli.js +20 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +6 -2
  15. package/src/ast.ts +122 -5
  16. package/src/bags.ts +161 -71
  17. package/src/builtins.ts +203 -23
  18. package/src/bundled-modules.ts +63 -32
  19. package/src/check.ts +945 -238
  20. package/src/cli-options.ts +58 -0
  21. package/src/codegen.ts +52 -18
  22. package/src/config.ts +39 -11
  23. package/src/environment.ts +485 -140
  24. package/src/errors.ts +59 -1
  25. package/src/evaluate.ts +489 -137
  26. package/src/exit-codes.ts +75 -0
  27. package/src/fingerprints.ts +48 -13
  28. package/src/index.ts +9 -4
  29. package/src/instances.ts +221 -66
  30. package/src/java-overrides.ts +241 -0
  31. package/src/json.ts +153 -72
  32. package/src/levels.ts +850 -0
  33. package/src/lexer.ts +103 -28
  34. package/src/liveness.ts +47 -91
  35. package/src/obligation-action.ts +384 -0
  36. package/src/obligation-anon.ts +575 -0
  37. package/src/obligation-deref.ts +637 -0
  38. package/src/obligation-expression.ts +1060 -0
  39. package/src/obligation-format.ts +406 -0
  40. package/src/obligation-generate.ts +1074 -0
  41. package/src/obligation-levels.ts +329 -0
  42. package/src/obligation-module.ts +1042 -0
  43. package/src/obligation-printer.ts +889 -0
  44. package/src/obligation-proof.ts +76 -0
  45. package/src/obligation-source.ts +1084 -0
  46. package/src/obligation-tuples.ts +184 -0
  47. package/src/obligations.ts +316 -0
  48. package/src/parallel-worker.ts +183 -202
  49. package/src/parallel.ts +163 -84
  50. package/src/parser.ts +958 -205
  51. package/src/pretty-print.ts +259 -0
  52. package/src/randomization.ts +24 -113
  53. package/src/resolve.ts +472 -41
  54. package/src/run-cli.ts +427 -61
  55. package/src/runtime.ts +63 -28
  56. package/src/spec-description.ts +509 -0
  57. package/src/standard-levels.ts +105 -0
  58. package/src/standard-modules.ts +223 -92
  59. package/src/subexpressions.ts +21 -35
  60. package/src/substitution-levels.ts +184 -0
  61. package/src/successors.ts +261 -46
  62. package/src/symmetry.ts +70 -22
  63. package/src/tableau.ts +43 -37
  64. package/src/tlc-ext.ts +120 -33
  65. package/src/tlc-module.ts +71 -47
  66. package/src/tlc-modules.ts +1022 -0
  67. package/src/tlc-order.ts +497 -0
  68. package/src/tlc-random.ts +411 -0
  69. package/src/tlc-summary.ts +109 -0
  70. package/src/transfer.ts +26 -5
  71. package/src/values.ts +1234 -176
@@ -12,18 +12,40 @@ import { BUNDLED_MODULES } from "./bundled-modules";
12
12
  import type { ConfigValue, ModelConfig } from "./config";
13
13
  import type { FileSystem } from "./json";
14
14
  import { TlaConfigError, TlaEvaluationError, TlaSyntaxError } from "./errors";
15
- import { getChildren, isConstantLevel, type Builtin, type Environment } from "./evaluate";
15
+ import { EXIT_STATUS, setExitCode } from "./exit-codes";
16
+ import {
17
+ createBigNumberError,
18
+ createRealNumberError,
19
+ getChildren,
20
+ isConstantLevel,
21
+ isTlcInteger,
22
+ type Builtin,
23
+ type Environment,
24
+ } from "./evaluate";
16
25
  import {
17
26
  createInstantiation,
18
27
  getEnabledVariableName,
19
28
  renameLocals,
20
29
  withLetInstances,
21
30
  } from "./instances";
22
- import { parseModule } from "./parser";
23
- import { KEY_SEPARATOR, resolveModule, resolveSubstitutions, type Symbols } from "./resolve";
31
+ import { overrideDefinitions } from "./java-overrides";
32
+ import { extractModule, parseModule } from "./parser";
33
+ import {
34
+ checkRedefinitions,
35
+ getArity,
36
+ isBefore,
37
+ KEY_SEPARATOR,
38
+ LOCAL_RESOLUTION,
39
+ resolveModule,
40
+ resolveSubstitutions,
41
+ type ModuleSymbol,
42
+ type Symbols,
43
+ } from "./resolve";
24
44
  import { createRuntime } from "./runtime";
25
- import { CORE_OPERATORS, getStandardModule } from "./standard-modules";
45
+ import { CORE_OPERATORS, getStandardModule, STANDARD_DEPENDENCIES } from "./standard-modules";
26
46
  import { resolveInstanceSubexpressions, resolveModuleSubexpressions } from "./subexpressions";
47
+ import { checkModuleLevels } from "./levels";
48
+ import { checkInstances, type InstanceCheck } from "./substitution-levels";
27
49
  import {
28
50
  checkInteger,
29
51
  createModelValue,
@@ -33,21 +55,36 @@ import {
33
55
  type Value,
34
56
  } from "./values";
35
57
 
36
- export interface EnvironmentOptions {
58
+ interface EnvironmentOptions {
59
+ source: string;
37
60
  loadModule: ((name: string) => string | undefined) | undefined;
61
+ loadClass?: (name: string) => Uint8Array | undefined;
38
62
  print: (text: string) => void;
39
63
  files: FileSystem;
64
+ seed?: bigint;
40
65
  }
41
66
 
42
- export interface Specification {
67
+ interface Specification {
43
68
  environment: Environment;
44
69
  assumptions: Assumption[];
70
+ variableLocations: SourceLocation[];
71
+ instanceActions: Map<string, InstanceAction>;
72
+ }
73
+
74
+ export interface InstanceAction {
75
+ name: string;
76
+ location: SourceLocation;
45
77
  }
46
78
 
47
79
  const toValue = (value: ConfigValue): Value => {
48
80
  switch (value.kind) {
49
81
  case "Number":
50
- return checkInteger(value.value);
82
+ try {
83
+ return checkInteger(value.value);
84
+ } catch (error) {
85
+ // TLC fails with an unexpected exception on a configuration number too big for it.
86
+ throw setExitCode(error, EXIT_STATUS.error);
87
+ }
51
88
  case "String":
52
89
  case "Boolean":
53
90
  return value.value;
@@ -62,11 +99,29 @@ interface LinkedModule {
62
99
  module: Module;
63
100
  constants: Declaration[];
64
101
  variables: string[];
102
+ variableLocations: SourceLocation[];
65
103
  visible: Symbols;
66
104
  exported: Symbols;
105
+ modules: Map<string, LinkedModule>;
106
+ extended: LinkedModule[];
107
+ }
108
+
109
+ interface ModuleScope {
110
+ id: string;
111
+ symbols: Symbols;
112
+ modules: Map<string, LinkedModule>;
113
+ nested: Module[];
67
114
  }
68
115
 
69
- const LOCAL_RESOLUTION = { kind: "Local" } as const;
116
+ const getDeclarations = (linked: LinkedModule) => [
117
+ ...linked.constants,
118
+ ...linked.variables.map((variable) => ({ name: variable, arity: 0 })),
119
+ ];
120
+
121
+ const createModuleRedefinedError = (module: Module) =>
122
+ new TlaSyntaxError(
123
+ `Multiply-defined module '${module.name}' at line ${module.location.line}, column ${module.location.column}`,
124
+ );
70
125
 
71
126
  const createLocalFunction = (definition: Definition, key: string): Definition => ({
72
127
  kind: "Operator",
@@ -88,37 +143,42 @@ const createLocalFunction = (definition: Definition, key: string): Definition =>
88
143
  location: definition.location,
89
144
  });
90
145
 
91
- const createOperatorConstant = (name: string, rows: [Value[], Value][]): Builtin => ({
92
- kind: "Builtin",
93
- name,
94
- arity: rows[0][0].length,
95
- evaluate: (args) => {
96
- const row = rows.find((candidate) =>
97
- candidate[0].every((value, index) => {
98
- const argument = args[index];
99
- return (
100
- !(typeof argument === "object" && argument.kind === "Operator") &&
101
- valuesEqual(value, argument)
102
- );
103
- }),
104
- );
105
- if (row) return row[1];
106
- throw new TlaEvaluationError(
107
- `Attempted to apply operator:\n${name}\nto arguments (${args.map((argument) => (typeof argument === "object" && argument.kind === "Operator" ? argument.name : formatValue(argument))).join(", ")}), which is undefined.`,
108
- );
109
- },
110
- evaluateUnary: undefined,
111
- evaluateBinary: undefined,
112
- });
146
+ const createOperatorConstant = (name: string, arity: number, rows: [Value[], Value][]): Builtin => {
147
+ const table = `{ ${rows.map((row) => `<${[...row[0], row[1]].map((value) => formatValue(value)).join(", ")}>`).join(", ")}}`;
148
+ return {
149
+ kind: "Builtin",
150
+ name,
151
+ arity,
152
+ evaluate: (args) => {
153
+ const row = rows.find((candidate) => {
154
+ if (candidate[0].length !== args.length) {
155
+ throw new TlaEvaluationError(
156
+ `Attempted to apply the operator ${table}\nwith wrong number of arguments.`,
157
+ );
158
+ }
159
+ return candidate[0].every((value, index) => {
160
+ const argument = args[index];
161
+ return (
162
+ !(typeof argument === "object" && argument.kind === "Operator") &&
163
+ valuesEqual(value, argument)
164
+ );
165
+ });
166
+ });
167
+ if (row) return row[1];
168
+ throw new TlaEvaluationError(
169
+ `Attempted to apply operator:\n${table}\nto arguments (${args.map((argument) => (typeof argument === "object" && argument.kind === "Operator" ? argument.name : formatValue(argument))).join(", ")}), which is undefined.`,
170
+ );
171
+ },
172
+ evaluateUnary: undefined,
173
+ evaluateBinary: undefined,
174
+ };
175
+ };
113
176
 
114
177
  const mentionsName = (expression: Expression, names: Set<string>): boolean =>
115
178
  (expression.kind === "Identifier" && names.has(expression.name)) ||
116
179
  (expression.kind === "Apply" && names.has(expression.operator)) ||
117
180
  getChildren(expression).some((child) => mentionsName(child, names));
118
181
 
119
- const getArity = (definition: Definition) =>
120
- definition.kind === "Operator" ? definition.params.length : 0;
121
-
122
182
  const getVariableIndex = (variables: string[], declared: string[]) => {
123
183
  const index = new Map(variables.map((name, position) => [name, position]));
124
184
  new Set(declared).forEach((name) =>
@@ -127,20 +187,53 @@ const getVariableIndex = (variables: string[], declared: string[]) => {
127
187
  return index;
128
188
  };
129
189
 
190
+ const getSubstitutions = (instances: Instance[]) =>
191
+ instances.flatMap((instance) =>
192
+ instance.substitutions.map((substitution) => substitution.expression),
193
+ );
194
+
195
+ const rejectLiterals = (expressions: Expression[]): void =>
196
+ expressions.forEach((expression) => {
197
+ if (expression.kind === "Decimal") throw createRealNumberError(expression);
198
+ if (expression.kind === "Number" && !isTlcInteger(expression.value)) {
199
+ throw createBigNumberError(expression);
200
+ }
201
+ rejectLiterals(
202
+ expression.kind === "Let"
203
+ ? [...getChildren(expression), ...getSubstitutions(expression.instances ?? [])]
204
+ : getChildren(expression),
205
+ );
206
+ });
207
+
130
208
  export const createEnvironment = (
131
- root: Module,
132
209
  config: ModelConfig,
133
210
  options: EnvironmentOptions,
134
211
  ): Specification => {
212
+ const root = parseModule(options.source);
135
213
  const definitions = new Map<string, Definition>();
136
214
  const parameterized = new Map<string, boolean>();
137
215
  const builtins = new Map<string, Builtin>(
138
216
  CORE_OPERATORS.map((operator) => [operator.name, operator]),
139
217
  );
140
218
  const standardExports = new Map<string, Symbols>();
141
- const runtime = createRuntime(config.checkDeadlock);
219
+ const runtime = createRuntime(config.checkDeadlock, options.seed);
142
220
  const linkedModules = new Map<string, LinkedModule>();
221
+ const nestedModules = new Map<string, LinkedModule>();
222
+ const linkedList: LinkedModule[] = [];
223
+ const dependencies = new Map<string, Set<string>>();
224
+ const loading: Set<string>[] = [];
225
+ // The top-level modules being linked, to report a cycle of EXTENDS or INSTANCE.
226
+ const linking: string[] = [];
227
+
228
+ const useModule = (name: string, used: Iterable<string>) =>
229
+ loading.forEach((closure) => {
230
+ closure.add(name);
231
+ [...used].forEach((dependency) => closure.add(dependency));
232
+ });
143
233
  const copyCount = { value: 0 };
234
+ const createName = (name: string) => `${name}#${copyCount.value++}`;
235
+ const instanceChecks: InstanceCheck[] = [];
236
+ const instanceActions = new Map<string, InstanceAction>();
144
237
 
145
238
  const addStandardModule = (name: string): Symbols | undefined => {
146
239
  const cached = standardExports.get(name);
@@ -161,65 +254,108 @@ export const createEnvironment = (
161
254
  return exported;
162
255
  };
163
256
 
164
- const nestedModules = new Map<string, Module>();
165
- const outerModules = new Map<string, Module>();
166
- const addNestedModules = (module: Module): Module => {
167
- module.modules.forEach((nested) => {
168
- if (nestedModules.has(nested.name)) return;
169
- outerModules.set(nested.name, module);
170
- nestedModules.set(nested.name, addNestedModules(nested));
171
- });
257
+ const getStandardSymbols = (name: string) => {
258
+ const symbols = addStandardModule(name);
259
+ if (symbols) useModule(name, STANDARD_DEPENDENCIES.get(name) ?? []);
260
+ return symbols;
261
+ };
262
+
263
+ // Like TLC, reject a decimal or a big integer anywhere in a module, even where nothing evaluates it.
264
+ const rejectModuleLiterals = (module: Module): Module => {
265
+ rejectLiterals([
266
+ ...module.definitions.flatMap((definition) =>
267
+ definition.kind === "Function"
268
+ ? [...definition.bounds.map((bound) => bound.set), definition.body]
269
+ : [definition.body],
270
+ ),
271
+ ...module.assumptions.map((assumption) => assumption.expression),
272
+ ...module.theorems.flatMap((theorem) =>
273
+ theorem.statement.kind === "AssumeProve" ? [] : [theorem.statement],
274
+ ),
275
+ ...getSubstitutions(module.instances),
276
+ ]);
277
+ module.modules.forEach(rejectModuleLiterals);
172
278
  return module;
173
279
  };
174
- addNestedModules(root);
280
+ rejectModuleLiterals(root);
175
281
 
176
- const loadModule = (name: string) => {
177
- const nested = nestedModules.get(name);
178
- if (nested) return nested;
179
- const source = options.loadModule?.(name) ?? BUNDLED_MODULES.get(name);
282
+ const loadModule = (name: string, importer: string) => {
283
+ const source =
284
+ options.loadModule?.(name) ??
285
+ BUNDLED_MODULES.get(name) ??
286
+ extractModule(options.source, name);
180
287
  if (source === undefined)
181
- throw new TlaSyntaxError(`Cannot find source file for module ${name}`);
182
- return addNestedModules(parseModule(source));
288
+ throw new TlaSyntaxError(
289
+ `Cannot find source file for module ${name} imported in module ${importer}.`,
290
+ );
291
+ const module = parseModule(source);
292
+ if (module.name !== name) {
293
+ throw new TlaSyntaxError(
294
+ `File name '${name}' does not match the name '${module.name}' of the top level module it contains.`,
295
+ );
296
+ }
297
+ return rejectModuleLiterals(module);
183
298
  };
184
299
 
185
- const getDefinitionKey = (module: Module, name: string) =>
186
- module === root ? name : `${name}${KEY_SEPARATOR}${module.name}`;
187
-
188
- const getOuterKey = (module: Module, name: string): string => {
189
- const outer = outerModules.get(module.name);
190
- if (!outer || !module.outer) return name;
191
- if (outer.definitions.some((definition) => definition.name === name)) {
192
- return getDefinitionKey(outer, name);
300
+ const linkTopLevel = (parsed: Module) => {
301
+ const cycleStart = linking.indexOf(parsed.name);
302
+ if (cycleStart !== -1) {
303
+ const cycle = [...linking.slice(cycleStart), parsed.name].map((name) => `${name}.tla`);
304
+ throw new TlaSyntaxError(
305
+ `Circular dependency among .tla files; dependency cycle is: ${cycle.join(" --> ")}`,
306
+ );
193
307
  }
194
- const isDeclared =
195
- outer.constants.some((constant) => constant.name === name) || outer.variables.includes(name);
196
- return isDeclared ? name : getOuterKey(outer, name);
308
+ const closure = new Set<string>();
309
+ const nested: Module[] = [];
310
+ linking.push(parsed.name);
311
+ loading.push(closure);
312
+ const linked = linkModule(parsed, {
313
+ id: parsed.name,
314
+ symbols: new Map(),
315
+ modules: new Map(),
316
+ nested,
317
+ });
318
+ loading.pop();
319
+ linking.pop();
320
+ const duplicate = nested.find((module) => closure.has(module.name));
321
+ if (duplicate) throw createModuleRedefinedError(duplicate);
322
+ return { linked, closure };
197
323
  };
198
324
 
199
- const getLinkedModule = (name: string) => linkedModules.get(name) ?? linkModule(loadModule(name));
325
+ const getLinkedModule = (name: string, importer: string): LinkedModule => {
326
+ const cached = linkedModules.get(name);
327
+ if (cached) {
328
+ useModule(name, dependencies.get(name) ?? []);
329
+ return cached;
330
+ }
331
+ const top = linkTopLevel(loadModule(name, importer));
332
+ linkedModules.set(name, top.linked);
333
+ dependencies.set(name, top.closure);
334
+ useModule(name, top.closure);
335
+ return top.linked;
336
+ };
200
337
 
201
338
  const instantiate = (
202
339
  instance: Instance,
203
340
  module: Module,
204
341
  unit: {
205
342
  imported: Symbols;
343
+ importedAt: Map<string, SourceLocation>;
206
344
  definitions: Map<string, Definition>;
207
345
  getKey: (definition: Definition) => string;
208
346
  },
209
347
  isParameterFree: boolean,
348
+ nested: LinkedModule | undefined,
210
349
  ): Symbols => {
211
- const standardSymbols = addStandardModule(instance.module);
350
+ const standardSymbols = nested ? undefined : getStandardSymbols(instance.module);
212
351
  if (standardSymbols) {
213
352
  if (instance.name === undefined) return standardSymbols;
214
353
  return new Map(
215
354
  [...standardSymbols].map(([name, symbol]) => [`${instance.name}!${name}`, symbol]),
216
355
  );
217
356
  }
218
- const linked = getLinkedModule(instance.module);
219
- const declarations = [
220
- ...linked.constants,
221
- ...linked.variables.map((variable) => ({ name: variable, arity: 0 })),
222
- ];
357
+ const linked = nested ?? getLinkedModule(instance.module, module.name);
358
+ const declarations = getDeclarations(linked);
223
359
  const suffix = `#${copyCount.value++}`;
224
360
  const renamed = new Map(instance.params.map((param) => [param.name, `${param.name}${suffix}`]));
225
361
  const substitutions = resolveSubstitutions(
@@ -228,6 +364,13 @@ export const createEnvironment = (
228
364
  module,
229
365
  unit,
230
366
  );
367
+ instanceChecks.push({
368
+ module: instance.module,
369
+ instancer: module.name,
370
+ lets: [],
371
+ substitutions,
372
+ location: instance.location,
373
+ });
231
374
  const instantiation = createInstantiation({
232
375
  params: instance.params.map((param) => ({ ...param, name: `${param.name}${suffix}` })),
233
376
  substitutions: new Map(
@@ -237,22 +380,30 @@ export const createEnvironment = (
237
380
  getDefinition: (key) => definitions.get(key),
238
381
  isParameterized: (key) => parameterized.get(key) === true,
239
382
  addDefinition: (name, create) => {
240
- const key = `${name}@${module.name}#${copyCount.value++}`;
383
+ const key = createName(`${name}@${module.name}`);
241
384
  definitions.set(key, create(key));
242
385
  parameterized.set(key, !isParameterFree);
243
386
  return key;
244
387
  },
388
+ createName,
245
389
  });
246
390
  const declared = new Set(declarations.map((declaration) => declaration.name));
247
391
  const symbols: Symbols = new Map();
248
392
  linked.exported.forEach((symbol, name) => {
249
393
  if (declared.has(name)) return;
250
394
  if (instance.name === undefined) {
251
- symbols.set(name, { key: instantiation.getKey(symbol.key), arity: symbol.arity });
395
+ const key = instantiation.getKey(symbol.key);
396
+ if (declarations.length > 0)
397
+ instanceActions.set(key, { name, location: instance.location });
398
+ symbols.set(name, { key, arity: symbol.arity });
252
399
  return;
253
400
  }
401
+ const key = instantiation.getQualifiedKey(symbol.key);
402
+ if (declarations.length > 0) {
403
+ instanceActions.set(key, { name: `${instance.name}!${name}`, location: instance.location });
404
+ }
254
405
  symbols.set(`${instance.name}!${name}`, {
255
- key: instantiation.getQualifiedKey(symbol.key),
406
+ key,
256
407
  arity: symbol.arity + instance.params.length,
257
408
  });
258
409
  });
@@ -291,7 +442,7 @@ export const createEnvironment = (
291
442
 
292
443
  const getSubstitutionBody = (
293
444
  replacement: Expression,
294
- declaration: Declaration,
445
+ declaration: OperatorParam,
295
446
  location: SourceLocation,
296
447
  ): { params: OperatorParam[]; body: Expression } => {
297
448
  if (declaration.arity === 0) return { params: [], body: replacement };
@@ -314,10 +465,14 @@ export const createEnvironment = (
314
465
  };
315
466
  };
316
467
 
317
- const instantiateLocally = (instance: Instance): Definition[] => {
468
+ const instantiateLocally = (
469
+ instance: Instance,
470
+ lets: Definition[],
471
+ instancer: string,
472
+ nested: LinkedModule | undefined,
473
+ ): Definition[] => {
318
474
  const location = instance.location;
319
- const instanceParams = instance.params;
320
- const standardSymbols = addStandardModule(instance.module);
475
+ const standardSymbols = nested ? undefined : getStandardSymbols(instance.module);
321
476
  if (standardSymbols) {
322
477
  if (instance.name === undefined) return [];
323
478
  return [...standardSymbols].map(([name, symbol]) => {
@@ -325,23 +480,29 @@ export const createEnvironment = (
325
480
  return createLocalAlias(
326
481
  `${instance.name}!${name}`,
327
482
  symbol.key,
328
- [...instanceParams, ...forwarded],
483
+ [...instance.params, ...forwarded],
329
484
  forwarded,
330
485
  location,
331
486
  );
332
487
  });
333
488
  }
334
- const linked = getLinkedModule(instance.module);
489
+ const linked = nested ?? getLinkedModule(instance.module, instancer);
335
490
  const suffix = `#${copyCount.value++}`;
336
491
  const explicit = new Map(instance.substitutions.map((item) => [item.name, item.expression]));
337
- const declarations = [
338
- ...linked.constants,
339
- ...linked.variables.map((variable) => ({ name: variable, arity: 0 })),
340
- ];
341
- const getReplacement = (declaration: Declaration): Expression =>
492
+ const declarations = getDeclarations(linked);
493
+ const getReplacement = (declaration: OperatorParam): Expression =>
342
494
  explicit.get(declaration.name) ?? { kind: "Identifier", name: declaration.name, location };
343
- const instanceNames = new Set(instanceParams.map((param) => param.name));
344
- const isParameterDependent = (declaration: Declaration) =>
495
+ instanceChecks.push({
496
+ module: instance.module,
497
+ instancer,
498
+ lets,
499
+ substitutions: new Map(
500
+ declarations.map((declaration) => [declaration.name, getReplacement(declaration)]),
501
+ ),
502
+ location,
503
+ });
504
+ const instanceNames = new Set(instance.params.map((param) => param.name));
505
+ const isParameterDependent = (declaration: OperatorParam) =>
345
506
  mentionsName(getReplacement(declaration), instanceNames);
346
507
  const substitutionDefinitions = declarations.map((declaration): Definition => {
347
508
  const substitution = getSubstitutionBody(getReplacement(declaration), declaration, location);
@@ -349,7 +510,7 @@ export const createEnvironment = (
349
510
  kind: "Operator",
350
511
  name: `${declaration.name}${suffix}`,
351
512
  params: [
352
- ...(isParameterDependent(declaration) ? instanceParams : []),
513
+ ...(isParameterDependent(declaration) ? instance.params : []),
353
514
  ...substitution.params,
354
515
  ],
355
516
  body: substitution.body,
@@ -360,7 +521,7 @@ export const createEnvironment = (
360
521
  });
361
522
  const copies: Definition[] = [];
362
523
  const instantiation = createInstantiation({
363
- params: instanceParams,
524
+ params: instance.params,
364
525
  substitutions: new Map(
365
526
  declarations.map((declaration): [string, Expression] => [
366
527
  declaration.name,
@@ -374,7 +535,7 @@ export const createEnvironment = (
374
535
  : {
375
536
  kind: "Apply",
376
537
  operator: `${declaration.name}${suffix}`,
377
- args: getLocalArguments(instanceParams, location),
538
+ args: getLocalArguments(instance.params, location),
378
539
  location,
379
540
  resolution: LOCAL_RESOLUTION,
380
541
  },
@@ -393,6 +554,7 @@ export const createEnvironment = (
393
554
  );
394
555
  return key;
395
556
  },
557
+ createName,
396
558
  });
397
559
  const declared = new Set(declarations.map((declaration) => declaration.name));
398
560
  const aliases = [...linked.exported]
@@ -400,7 +562,7 @@ export const createEnvironment = (
400
562
  .map(([name, symbol]) => {
401
563
  const key = instantiation.getKey(symbol.key);
402
564
  const own = getArguments(symbol.arity);
403
- const params = [...instanceParams, ...own];
565
+ const params = [...instance.params, ...own];
404
566
  return createLocalAlias(
405
567
  instance.name === undefined ? name : `${instance.name}!${name}`,
406
568
  key,
@@ -418,61 +580,139 @@ export const createEnvironment = (
418
580
  return definition ? [{ ...definition, name }] : [];
419
581
  });
420
582
 
421
- const linkModule = (parsed: Module): LinkedModule => {
422
- const visible: Symbols = new Map(
423
- CORE_OPERATORS.map((operator) => [
583
+ const linkModule = (unlinked: Module, scope: ModuleScope): LinkedModule => {
584
+ const parsed = overrideDefinitions(unlinked, options.loadClass?.(unlinked.name));
585
+ const visible: Symbols = new Map([
586
+ ...CORE_OPERATORS.map((operator): [string, ModuleSymbol] => [
424
587
  operator.name,
425
588
  { key: operator.name, arity: operator.arity },
426
589
  ]),
427
- );
590
+ ...scope.symbols,
591
+ ]);
428
592
  const exported: Symbols = new Map();
593
+ const modules = new Map<string, LinkedModule>();
594
+ const extended: LinkedModule[] = [];
429
595
  const constants = [...parsed.constants];
430
- const variables = [...parsed.variables];
596
+ const variables = parsed.variables.map((variable) => variable.name);
597
+ const variableLocations = parsed.variables.map((variable) => variable.location);
431
598
  const addSymbols = (symbols: Symbols, isExported: boolean) =>
432
599
  symbols.forEach((symbol, name) => {
433
600
  visible.set(name, symbol);
434
601
  if (isExported) exported.set(name, symbol);
435
602
  });
436
- parsed.outer?.extends.forEach((name) =>
437
- addSymbols(addStandardModule(name) ?? getLinkedModule(name).exported, false),
438
- );
439
- parsed.outer?.symbols.forEach((symbol) =>
440
- visible.set(symbol.name, { key: getOuterKey(parsed, symbol.name), arity: symbol.arity }),
441
- );
442
603
  parsed.extends.forEach((name) => {
443
- const standardSymbols = addStandardModule(name);
604
+ const nested = scope.modules.get(name);
605
+ const standardSymbols = nested ? undefined : getStandardSymbols(name);
444
606
  if (standardSymbols) {
445
607
  addSymbols(standardSymbols, true);
446
608
  return;
447
609
  }
448
- const linked = getLinkedModule(name);
610
+ const linked = nested ?? getLinkedModule(name, parsed.name);
449
611
  constants.push(
450
612
  ...linked.constants.filter((constant) =>
451
613
  constants.every((existing) => existing.name !== constant.name),
452
614
  ),
453
615
  );
454
- variables.push(...linked.variables.filter((variable) => !variables.includes(variable)));
616
+ linked.variables.forEach((variable, index) => {
617
+ if (variables.includes(variable)) return;
618
+ variables.push(variable);
619
+ variableLocations.push(linked.variableLocations[index]);
620
+ });
455
621
  addSymbols(linked.exported, true);
622
+ linked.modules.forEach((module, moduleName) => modules.set(moduleName, module));
623
+ extended.push(linked);
456
624
  });
457
625
  parsed.constants.forEach((constant) =>
458
- exported.set(constant.name, { key: constant.name, arity: constant.arity }),
626
+ exported.set(constant.name, {
627
+ key: constant.name,
628
+ arity: constant.arity,
629
+ declaration: "Constant",
630
+ }),
631
+ );
632
+ parsed.variables.forEach((variable) =>
633
+ exported.set(variable.name, { key: variable.name, arity: 0, declaration: "Variable" }),
459
634
  );
460
- parsed.variables.forEach((variable) => exported.set(variable, { key: variable, arity: 0 }));
461
635
  const isParameterFree = constants.length === 0 && variables.length === 0;
462
- const getKey = (definition: Definition) => getDefinitionKey(parsed, definition.name);
636
+ const getKey = (definition: Definition) =>
637
+ parsed === root ? definition.name : `${definition.name}${KEY_SEPARATOR}${scope.id}`;
638
+ const importedAt = new Map<string, SourceLocation>();
463
639
  const getUnit = (module: Module) => ({
464
640
  imported: visible,
641
+ importedAt,
465
642
  definitions: new Map(module.definitions.map((definition) => [definition.name, definition])),
466
643
  getKey,
467
644
  });
468
- parsed.instances.forEach((instance) =>
469
- addSymbols(
470
- instantiate(instance, parsed, getUnit(parsed), isParameterFree),
471
- !instance.isLocal,
472
- ),
473
- );
645
+ const getVisibleModules = () => new Map([...scope.modules, ...modules]);
646
+ const getSymbolsBefore = (location: SourceLocation): Symbols =>
647
+ new Map([
648
+ ...visible,
649
+ ...[
650
+ ...parsed.constants.map((constant) => ({ ...constant, kind: "Constant" as const })),
651
+ ...parsed.variables.map((variable) => ({ ...variable, kind: "Variable" as const })),
652
+ ]
653
+ .filter((declaration) => isBefore(declaration.location, location))
654
+ .map((declaration): [string, ModuleSymbol] => [
655
+ declaration.name,
656
+ { key: declaration.name, arity: declaration.arity, declaration: declaration.kind },
657
+ ]),
658
+ ...parsed.definitions
659
+ .filter((definition) => isBefore(definition.location, location))
660
+ .map((definition): [string, ModuleSymbol] => [
661
+ definition.name,
662
+ { key: getKey(definition), arity: getArity(definition) },
663
+ ]),
664
+ ]);
665
+ const linkNested = (nested: Module) => {
666
+ const visibleModules = getVisibleModules();
667
+ if (visibleModules.has(nested.name)) throw createModuleRedefinedError(nested);
668
+ scope.nested.push(nested);
669
+ const linked = linkModule(nested, {
670
+ id: `${scope.id}/${nested.name}`,
671
+ symbols: getSymbolsBefore(nested.location),
672
+ modules: visibleModules,
673
+ nested: scope.nested,
674
+ });
675
+ modules.set(nested.name, linked);
676
+ if (!nestedModules.has(nested.name)) nestedModules.set(nested.name, linked);
677
+ };
678
+ [
679
+ ...parsed.instances.map((instance) => ({
680
+ location: instance.location,
681
+ link: () => {
682
+ const symbols = instantiate(
683
+ instance,
684
+ parsed,
685
+ getUnit(parsed),
686
+ isParameterFree,
687
+ getVisibleModules().get(instance.module),
688
+ );
689
+ symbols.forEach((_, name) => {
690
+ if (!visible.has(name)) importedAt.set(name, instance.location);
691
+ });
692
+ addSymbols(symbols, !instance.isLocal);
693
+ },
694
+ })),
695
+ ...parsed.modules.map((nested) => ({
696
+ location: nested.location,
697
+ link: () => linkNested(nested),
698
+ })),
699
+ ]
700
+ .sort((left, right) => (isBefore(left.location, right.location) ? -1 : 1))
701
+ .forEach((unit) => unit.link());
702
+ checkRedefinitions(parsed, getUnit(parsed));
703
+ const isInstancedAfterDefinition = (nested: Module) => {
704
+ const first = parsed.instances.find((instance) => instance.module === nested.name);
705
+ return first !== undefined && isBefore(nested.location, first.location);
706
+ };
707
+ const getLetModule = (instance: Instance) => {
708
+ const own = parsed.modules.find((nested) => nested.name === instance.module);
709
+ if (!own) return getVisibleModules().get(instance.module);
710
+ return isInstancedAfterDefinition(own) ? modules.get(own.name) : undefined;
711
+ };
474
712
  const module = resolveModuleSubexpressions(
475
- withLetInstances(parsed, instantiateLocally),
713
+ withLetInstances(parsed, (instance, lets) =>
714
+ instantiateLocally(instance, lets, parsed.name, getLetModule(instance)),
715
+ ),
476
716
  getAvailableDefinitions(visible),
477
717
  );
478
718
  resolveModule(module, getUnit(module));
@@ -484,12 +724,26 @@ export const createEnvironment = (
484
724
  visible.set(definition.name, symbol);
485
725
  if (!definition.isLocal) exported.set(definition.name, symbol);
486
726
  });
487
- const linked = { module, constants, variables, visible, exported };
488
- linkedModules.set(parsed.name, linked);
727
+ checkModuleLevels(module, {
728
+ constants: new Set(constants.map((constant) => constant.name)),
729
+ variables: new Set(variables),
730
+ getDefinition: (symbol) => definitions.get(visible.get(symbol)?.key ?? symbol),
731
+ });
732
+ const linked = {
733
+ module,
734
+ constants,
735
+ variables,
736
+ variableLocations,
737
+ visible,
738
+ exported,
739
+ modules,
740
+ extended,
741
+ };
742
+ linkedList.push(linked);
489
743
  return linked;
490
744
  };
491
745
 
492
- const linkedRoot = linkModule(root);
746
+ const linkedRoot = linkTopLevel(root).linked;
493
747
  const getRootKey = (name: string) => linkedRoot.visible.get(name)?.key ?? name;
494
748
  linkedRoot.visible.forEach((symbol, name) => {
495
749
  const definition = definitions.get(symbol.key);
@@ -497,47 +751,80 @@ export const createEnvironment = (
497
751
  });
498
752
 
499
753
  const assumptions: Assumption[] = [];
500
- const addAssumptions = (module: Module, visited: Set<string>) => {
501
- if (visited.has(module.name)) return;
502
- visited.add(module.name);
503
- module.extends.forEach((name) => {
504
- const linked = linkedModules.get(name);
505
- if (linked) addAssumptions(linked.module, visited);
506
- });
507
- assumptions.push(...module.assumptions);
754
+ const addAssumptions = (linked: LinkedModule, visited: Set<LinkedModule>) => {
755
+ if (visited.has(linked)) return;
756
+ visited.add(linked);
757
+ linked.extended.forEach((extended) => addAssumptions(extended, visited));
758
+ assumptions.push(...linked.module.assumptions);
508
759
  };
509
- addAssumptions(linkedRoot.module, new Set());
760
+ addAssumptions(linkedRoot, new Set());
510
761
  const declaredConstants = linkedRoot.constants.map((constant) => constant.name);
511
762
  const variables = linkedRoot.variables;
512
763
 
513
- const getScopedKeys = (item: { name: string; module?: string }): string[] => {
764
+ const getScopedKeys = (item: { name: string; module?: string }) => {
514
765
  if (item.module === undefined) return [item.name, getRootKey(item.name)];
515
766
  if (item.module === root.name || declaredConstants.includes(item.name)) return [];
516
- return [linkedModules.get(item.module)?.visible.get(item.name)?.key ?? item.name];
767
+ const linked = linkedModules.get(item.module) ?? nestedModules.get(item.module);
768
+ return [linked?.visible.get(item.name)?.key ?? item.name];
769
+ };
770
+ const getKeyArity = (key: string) => {
771
+ const definition = definitions.get(key);
772
+ return definition?.kind === "Operator"
773
+ ? definition.params.length
774
+ : (linkedRoot.constants.find((constant) => constant.name === key)?.arity ?? 0);
517
775
  };
518
776
  const constants = new Map<string, Value>();
777
+ const operatorKeys = new Set<string>();
778
+ const setOperator = (key: string, operator: Builtin) => {
779
+ builtins.set(key, operator);
780
+ definitions.delete(key);
781
+ constants.delete(key);
782
+ operatorKeys.add(key);
783
+ };
519
784
  config.constants
520
785
  .filter((assignment) => assignment.args === undefined)
521
786
  .forEach((assignment) => {
522
787
  const value = toValue(assignment.value);
523
- getScopedKeys(assignment).forEach((key) => constants.set(key, value));
788
+ getScopedKeys(assignment).forEach((key) => {
789
+ const arity = getKeyArity(key);
790
+ if (arity === 0) constants.set(key, value);
791
+ else
792
+ setOperator(key, {
793
+ kind: "Builtin",
794
+ name: key,
795
+ arity,
796
+ evaluate: () => value,
797
+ evaluateUnary: undefined,
798
+ evaluateBinary: undefined,
799
+ isLazy: true,
800
+ });
801
+ });
524
802
  });
525
- const operatorConstants = new Map<string, [Value[], Value][]>();
803
+ const tables = new Map<string, { name: string; module?: string; rows: [Value[], Value][] }>();
526
804
  config.constants.forEach((assignment) => {
527
805
  if (assignment.args === undefined) return;
528
- const rows = operatorConstants.get(assignment.name) ?? [];
529
- if (rows.length > 0 && rows[0][0].length !== assignment.args.length) {
806
+ const tableKey = `${assignment.module ?? ""}!${assignment.name}`;
807
+ const table = tables.get(tableKey) ?? {
808
+ name: assignment.name,
809
+ module: assignment.module,
810
+ rows: [],
811
+ };
812
+ if (table.rows.length > 0 && table.rows[0][0].length !== assignment.args.length) {
530
813
  throw new TlaConfigError(
531
- `The operator ${assignment.name} is assigned values with inconsistent arities`,
814
+ `The arity of the operator ${assignment.name} is inconsistent in the configuration file.`,
532
815
  );
533
816
  }
534
- rows.push([assignment.args.map(toValue), toValue(assignment.value)]);
535
- operatorConstants.set(assignment.name, rows);
536
- });
537
- operatorConstants.forEach((rows, name) => {
538
- builtins.set(name, createOperatorConstant(name, rows));
539
- constants.delete(name);
817
+ table.rows.push([assignment.args.map(toValue), toValue(assignment.value)]);
818
+ tables.set(tableKey, table);
540
819
  });
820
+ tables.forEach((table) =>
821
+ getScopedKeys(table).forEach((key) =>
822
+ setOperator(
823
+ key,
824
+ createOperatorConstant(table.name, getKeyArity(key) || table.rows[0][0].length, table.rows),
825
+ ),
826
+ ),
827
+ );
541
828
  const substitutions = new Map(
542
829
  config.substitutions.flatMap((substitution) =>
543
830
  getScopedKeys(substitution).map((key): [string, string] => [
@@ -546,8 +833,47 @@ export const createEnvironment = (
546
833
  ]),
547
834
  ),
548
835
  );
836
+ config.substitutions
837
+ .filter((substitution) => substitution.module === undefined)
838
+ .forEach((substitution) => {
839
+ const name = substitution.name;
840
+ const target = substitution.target;
841
+ if (!declaredConstants.includes(name) && !linkedRoot.visible.has(name)) {
842
+ throw new TlaConfigError(
843
+ `In the configuration file, the identifier ${name} does not appear in the specification.`,
844
+ );
845
+ }
846
+ if (config.substitutions.some((other) => other.name === target)) {
847
+ throw new TlaConfigError(
848
+ `In the configuration file, the identifier ${target} appears\non the right-hand side of a <- after already appearing on the\nleft-hand side of one.`,
849
+ );
850
+ }
851
+ const targetKey = getRootKey(target);
852
+ const definition = definitions.get(targetKey);
853
+ const isDefined =
854
+ definition !== undefined ||
855
+ builtins.has(targetKey) ||
856
+ constants.has(targetKey) ||
857
+ declaredConstants.includes(target) ||
858
+ variables.includes(target);
859
+ if (!isDefined) {
860
+ throw new TlaConfigError(
861
+ `The configuration file substitutes for ${name} with the undefined identifier ${target}.`,
862
+ );
863
+ }
864
+ if (
865
+ definition?.kind === "Operator" &&
866
+ definition.params.length !==
867
+ (linkedRoot.constants.find((constant) => constant.name === name)?.arity ??
868
+ linkedRoot.visible.get(name)?.arity)
869
+ ) {
870
+ throw new TlaConfigError(
871
+ `The configuration file substitutes for ${name} with ${target} of different number of arguments.`,
872
+ );
873
+ }
874
+ });
549
875
  declaredConstants.forEach((name) => {
550
- if (!constants.has(name) && !substitutions.has(name) && !operatorConstants.has(name)) {
876
+ if (!constants.has(name) && !substitutions.has(name) && !operatorKeys.has(name)) {
551
877
  throw new TlaConfigError(`The constant ${name} is not assigned a value by the configuration`);
552
878
  }
553
879
  });
@@ -561,12 +887,26 @@ export const createEnvironment = (
561
887
  variables,
562
888
  variableIndex: getVariableIndex(
563
889
  variables,
564
- [...linkedModules.values()].flatMap((linked) => linked.module.variables),
890
+ linkedList.flatMap((linked) => linked.module.variables.map((variable) => variable.name)),
565
891
  ),
566
892
  definitionValues: new Map(),
567
893
  constantLevels: new Map(),
568
894
  evaluators: new WeakMap(),
569
895
  };
896
+ checkInstances(instanceChecks, (name) => {
897
+ const linked = linkedModules.get(name) ?? nestedModules.get(name) ?? linkedRoot;
898
+ return {
899
+ constants: linked.constants,
900
+ variables: linked.variables,
901
+ definitions: [
902
+ ...new Set(
903
+ [...linked.visible.values()].flatMap((symbol) => definitions.get(symbol.key) ?? []),
904
+ ),
905
+ ],
906
+ assumptions: linked.module.assumptions.map((assumption) => assumption.expression),
907
+ getDefinition: (symbol) => definitions.get(linked.visible.get(symbol)?.key ?? symbol),
908
+ };
909
+ });
570
910
  substitutions.forEach((target, name) => {
571
911
  const definition = definitions.get(target);
572
912
  if (
@@ -579,5 +919,10 @@ export const createEnvironment = (
579
919
  );
580
920
  }
581
921
  });
582
- return { environment, assumptions };
922
+ return {
923
+ environment,
924
+ assumptions,
925
+ variableLocations: linkedRoot.variableLocations,
926
+ instanceActions,
927
+ };
583
928
  };