tla-ts 0.0.1 → 0.0.2

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 (57) hide show
  1. package/README.md +34 -41
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +166 -30
  4. package/dist/index.d.ts +166 -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 +2 -1
  11. package/dist/run-cli.js +2 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +4 -2
  15. package/src/ast.ts +82 -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 +904 -230
  20. package/src/codegen.ts +52 -18
  21. package/src/config.ts +39 -11
  22. package/src/environment.ts +485 -140
  23. package/src/errors.ts +22 -0
  24. package/src/evaluate.ts +486 -136
  25. package/src/exit-codes.ts +75 -0
  26. package/src/fingerprints.ts +2 -2
  27. package/src/index.ts +9 -4
  28. package/src/instances.ts +221 -66
  29. package/src/java-overrides.ts +241 -0
  30. package/src/json.ts +153 -72
  31. package/src/levels.ts +850 -0
  32. package/src/lexer.ts +97 -27
  33. package/src/liveness.ts +47 -91
  34. package/src/parallel-worker.ts +57 -28
  35. package/src/parallel.ts +116 -62
  36. package/src/parser.ts +703 -183
  37. package/src/pretty-print.ts +259 -0
  38. package/src/randomization.ts +24 -113
  39. package/src/resolve.ts +472 -41
  40. package/src/run-cli.ts +142 -44
  41. package/src/runtime.ts +63 -28
  42. package/src/spec-description.ts +509 -0
  43. package/src/standard-levels.ts +105 -0
  44. package/src/standard-modules.ts +223 -92
  45. package/src/subexpressions.ts +21 -35
  46. package/src/substitution-levels.ts +184 -0
  47. package/src/successors.ts +261 -46
  48. package/src/symmetry.ts +65 -21
  49. package/src/tableau.ts +43 -37
  50. package/src/tlc-ext.ts +120 -33
  51. package/src/tlc-module.ts +71 -47
  52. package/src/tlc-modules.ts +1022 -0
  53. package/src/tlc-order.ts +497 -0
  54. package/src/tlc-random.ts +411 -0
  55. package/src/tlc-summary.ts +109 -0
  56. package/src/transfer.ts +4 -4
  57. package/src/values.ts +1186 -164
package/src/parser.ts CHANGED
@@ -1,24 +1,29 @@
1
1
  import type {
2
+ AssumeProve,
2
3
  Assumption,
3
4
  Bound,
4
5
  BoundPattern,
5
6
  CaseArm,
6
7
  Declaration,
8
+ DefineStep,
7
9
  Definition,
8
10
  ExceptPathStep,
9
- ExceptUpdate,
10
11
  Expression,
12
+ Fact,
11
13
  Module,
12
14
  Instance,
13
- OuterScope,
14
- InstanceSubstitution,
15
+ NewSymbol,
15
16
  OperatorParam,
16
- RecordField,
17
+ Proof,
18
+ ProofStep,
17
19
  Selector,
18
20
  SourceLocation,
21
+ SourceRange,
22
+ StepBody,
23
+ UseOrHide,
19
24
  } from "./ast";
20
25
  import { TlaSyntaxError } from "./errors";
21
- import { tokenize, type Token } from "./lexer";
26
+ import { getNumeralValue, tokenize, type Token } from "./lexer";
22
27
 
23
28
  interface InfixOperator {
24
29
  low: number;
@@ -26,7 +31,7 @@ interface InfixOperator {
26
31
  isAssociative: boolean;
27
32
  }
28
33
 
29
- const createInfix = (low: number, high: number, isAssociative: boolean): InfixOperator => ({
34
+ const createInfix = (low: number, high: number, isAssociative: boolean) => ({
30
35
  low,
31
36
  high,
32
37
  isAssociative,
@@ -134,6 +139,20 @@ const POSTFIX_OPERATORS = new Set(["^+", "^*", "^#"]);
134
139
 
135
140
  const PREFIX_OPERATOR_REFERENCES = new Set(["-", "~", "-."]);
136
141
 
142
+ const EXPRESSION_PREFIXES = new Set([
143
+ "-",
144
+ "~",
145
+ "/\\",
146
+ "\\/",
147
+ "SUBSET",
148
+ "UNION",
149
+ "DOMAIN",
150
+ "ENABLED",
151
+ "UNCHANGED",
152
+ "[]",
153
+ "<>",
154
+ ]);
155
+
137
156
  const OPERATOR_ARGUMENTS = new Set([
138
157
  "SUBSET",
139
158
  "UNION",
@@ -146,6 +165,9 @@ const OPERATOR_ARGUMENTS = new Set([
146
165
  ...POSTFIX_OPERATORS,
147
166
  ]);
148
167
 
168
+ const isOperatorName = (text: string) =>
169
+ INFIX_OPERATORS.has(text) || POSTFIX_OPERATORS.has(text) || text === "-.";
170
+
149
171
  const getOperatorApplication = (
150
172
  operator: string,
151
173
  argument: Expression,
@@ -187,27 +209,111 @@ const UNIT_KEYWORDS = new Set([
187
209
 
188
210
  const THEOREM_KEYWORDS = new Set(["THEOREM", "LEMMA", "COROLLARY", "PROPOSITION"]);
189
211
 
190
- const PROOF_WORDS = new Set(["BY", "OBVIOUS", "OMITTED", "QED", "USE", "HIDE"]);
212
+ const PROOF_STARTS = new Set(["BY", "PROOF", "OBVIOUS", "OMITTED"]);
213
+
214
+ const PROVABLE_STEPS = new Set(["Qed", "Pick", "Case", "Assert"]);
215
+
216
+ const UNNAMED_STEP_LABELS = new Set(["", "*", "-"]);
217
+
218
+ const FACT_KEYWORDS = new Set([
219
+ "CASE",
220
+ "CHOOSE",
221
+ "DOMAIN",
222
+ "ENABLED",
223
+ "IF",
224
+ "LAMBDA",
225
+ "LET",
226
+ "SF_",
227
+ "SUBSET",
228
+ "UNCHANGED",
229
+ "UNION",
230
+ "WF_",
231
+ ]);
232
+
233
+ const NEW_SYMBOL_LEVELS = new Map<string, NewSymbol["level"]>([
234
+ ["CONSTANT", "Constant"],
235
+ ["CONSTANTS", "Constant"],
236
+ ["VARIABLE", "Variable"],
237
+ ["VARIABLES", "Variable"],
238
+ ["STATE", "State"],
239
+ ["ACTION", "Action"],
240
+ ["ACTIONS", "Action"],
241
+ ["TEMPORAL", "Temporal"],
242
+ ["TEMPORALS", "Temporal"],
243
+ ]);
244
+
245
+ const NEW_SYMBOL_STARTS = new Set(["NEW", ...NEW_SYMBOL_LEVELS.keys()]);
246
+
247
+ const OPENING_BRACKETS = new Set(["(", "[", "{", "<<"]);
248
+
249
+ const CLOSING_BRACKETS = new Set([")", "]", "}", ">>", "]_", ">>_"]);
250
+
251
+ const BUILTIN_CONSTANTS = new Set(["TRUE", "FALSE", "BOOLEAN", "STRING"]);
191
252
 
192
253
  const NO_OFFSIDE = -1;
193
254
 
255
+ const getModuleHeader = (name: string) => new RegExp(`^-{4,}\\s*MODULE\\s+${name}\\s*-{3,}$`);
256
+
257
+ // The text of a module that starts on its own line in a file, as TLC's monolith extractor reads
258
+ // modules that follow the root module.
259
+ export const extractModule = (source: string, name: string) => {
260
+ const lines = source.split(/\r?\n/);
261
+ const start = lines.findIndex((line) => getModuleHeader(name).test(line));
262
+ if (start === -1) return undefined;
263
+ const end = lines.findIndex((line, index) => index > start && line.startsWith("===="));
264
+ return lines.slice(start, end === -1 ? undefined : end + 1).join("\n");
265
+ };
266
+
194
267
  export const parseModule = (source: string): Module => {
195
268
  const tokens = tokenize(source);
196
- const cursor = { index: 0, limit: tokens.length, offsideColumns: [] as number[] };
197
- const recursiveNames = new Set<string>();
269
+ const cursor = { index: 0, offsideColumns: [] as number[] };
270
+ // A RECURSIVE declaration covers the definitions of its own LET or module.
271
+ const recursiveScopes: Set<string>[] = [];
272
+ const factNames = new Set<string>();
273
+ // The NEW, TAKE, and PICK names in scope, which a DEF clause cannot cite.
274
+ const proofScopes: Set<string>[] = [];
275
+ // A fact may cite an operator of a parameterized instance without the instance's arguments.
276
+ const factStart = { index: -1 };
277
+ const openModules: Module[] = [];
278
+
279
+ const isRecursiveName = (name: string) => recursiveScopes[recursiveScopes.length - 1].has(name);
198
280
 
199
281
  const getLocation = (token: Token): SourceLocation => ({
200
282
  line: token.line,
201
283
  column: token.column,
284
+ range: undefined,
202
285
  });
203
286
 
287
+ const tokenStarts: SourceLocation[] = [];
288
+ const tokenEnds: SourceLocation[] = [];
289
+ const tokenRanges: SourceRange[] = [];
290
+
291
+ const getRange = (startIndex: number) => {
292
+ const endIndex = cursor.index - 1;
293
+ const cached = startIndex === endIndex ? tokenRanges[startIndex] : undefined;
294
+ if (cached) return cached;
295
+ const last = tokens[endIndex];
296
+ tokenStarts[startIndex] ??= getLocation(tokens[startIndex]);
297
+ tokenEnds[endIndex] ??= { line: last.line, column: last.endColumn };
298
+ const range = {
299
+ module: openModules[openModules.length - 1].name,
300
+ start: tokenStarts[startIndex],
301
+ end: tokenEnds[endIndex],
302
+ };
303
+ if (startIndex === endIndex) tokenRanges[startIndex] = range;
304
+ return range;
305
+ };
306
+
307
+ // Each node gets its own location object from getLocation, so the range can be set in place.
308
+ const withRange = <Node extends { location: SourceLocation }>(node: Node, startIndex: number) => {
309
+ node.location.range = getRange(startIndex);
310
+ return node;
311
+ };
312
+
204
313
  const peekRaw = (offset = 0) => tokens[Math.min(cursor.index + offset, tokens.length - 1)];
205
314
 
206
315
  const peek = (offset = 0) => {
207
- const token =
208
- cursor.index + offset >= cursor.limit
209
- ? { ...peekRaw(offset), kind: "End" as const, text: "" }
210
- : peekRaw(offset);
316
+ const token = peekRaw(offset);
211
317
  const offsideColumn = cursor.offsideColumns[cursor.offsideColumns.length - 1];
212
318
  if (offsideColumn === undefined || offsideColumn === NO_OFFSIDE || token.kind === "End") {
213
319
  return token;
@@ -216,9 +322,9 @@ export const parseModule = (source: string): Module => {
216
322
  };
217
323
 
218
324
  const describe = (token: Token) =>
219
- token.kind === "End" ? "end of input" : `${JSON.stringify(token.text)}`;
325
+ token.kind === "End" ? "end of input" : JSON.stringify(token.text);
220
326
 
221
- const createError = (message: string, token = peekRaw()) =>
327
+ const createError = (message: string, token: { line: number; column: number } = peekRaw()) =>
222
328
  new TlaSyntaxError(`${message} at line ${token.line}, column ${token.column}`);
223
329
 
224
330
  const isFieldName = (token: Token) => token.kind === "Identifier" || token.kind === "Keyword";
@@ -304,7 +410,7 @@ export const parseModule = (source: string): Module => {
304
410
  return { kind: "Tuple", names };
305
411
  };
306
412
 
307
- const parseBounds = (): Bound[] =>
413
+ const parseBounds = () =>
308
414
  parseSeparated(() => {
309
415
  const patterns = parseSeparated(parsePattern);
310
416
  expect("\\in");
@@ -315,26 +421,64 @@ export const parseModule = (source: string): Module => {
315
421
  const parseSubscript = (): Expression => {
316
422
  const token = peek();
317
423
  if (token.kind === "Identifier") {
318
- cursor.index++;
319
- return { kind: "Identifier", name: token.text, location: getLocation(token) };
424
+ const start = cursor.index;
425
+ const names = [expectIdentifier()];
426
+ while (isAt("!") && peek(1).kind === "Identifier") {
427
+ advance();
428
+ names.push(expectIdentifier());
429
+ }
430
+ return withRange(
431
+ { kind: "Identifier", name: names.join("!"), location: getLocation(token) },
432
+ start,
433
+ );
320
434
  }
321
435
  if (isAt("<<") || isAt("(")) return parsePrimary();
322
436
  throw createError(`Expected a subscript but found ${describe(token)}`);
323
437
  };
324
438
 
439
+ const isDefinitionAhead = (operator: Token) => {
440
+ const token = peek(1);
441
+ const next = peek(2);
442
+ if (token.kind === "Identifier" && next.text === "==") return true;
443
+ if (token.line === operator.line) return false;
444
+ if (token.text === "-.") return true;
445
+ return (
446
+ token.kind === "Identifier" &&
447
+ (next.text === "(" ||
448
+ next.text === "[" ||
449
+ (next.kind === "Operator" && isAt("==", POSTFIX_OPERATORS.has(next.text) ? 3 : 4)))
450
+ );
451
+ };
452
+
453
+ const isStandalone = (operator: Token) => {
454
+ if (!EXPRESSION_PREFIXES.has(operator.text)) return true;
455
+ const token = peek(1);
456
+ return (
457
+ token.kind === "End" ||
458
+ token.kind === "ModuleEnd" ||
459
+ token.kind === "Separator" ||
460
+ isAt(",", 1) ||
461
+ isAt(")", 1) ||
462
+ (token.kind === "Keyword" && UNIT_KEYWORDS.has(token.text)) ||
463
+ isDefinitionAhead(operator)
464
+ );
465
+ };
466
+
325
467
  const parseArgument = (): Expression => {
326
468
  const token = peek();
327
- const next = peek(1);
328
469
  const isOperatorAlone =
329
470
  token.kind === "Operator" &&
330
471
  (INFIX_OPERATORS.has(token.text) || PREFIX_OPERATOR_REFERENCES.has(token.text)) &&
331
- (next.text === "," || next.text === ")");
472
+ isStandalone(token);
332
473
  if (isOperatorAlone) {
333
474
  cursor.index++;
334
- return { kind: "OperatorReference", operator: token.text, location: getLocation(token) };
475
+ return withRange(
476
+ { kind: "OperatorReference", operator: token.text, location: getLocation(token) },
477
+ cursor.index - 1,
478
+ );
335
479
  }
336
480
  const isPrefixAlone =
337
- OPERATOR_ARGUMENTS.has(token.text) && (next.text === "," || next.text === ")");
481
+ token.kind !== "String" && OPERATOR_ARGUMENTS.has(token.text) && isStandalone(token);
338
482
  if (isPrefixAlone) {
339
483
  cursor.index++;
340
484
  const location = getLocation(token);
@@ -350,10 +494,12 @@ export const parseModule = (source: string): Module => {
350
494
  };
351
495
  }
352
496
  if (isAt("LAMBDA")) {
497
+ const start = cursor.index;
353
498
  advance();
354
499
  const params = parseSeparated(expectIdentifier);
355
500
  expect(":");
356
- return { kind: "Lambda", params, body: parseExpression(), location: getLocation(token) };
501
+ const body = parseExpression();
502
+ return withRange({ kind: "Lambda", params, body, location: getLocation(token) }, start);
357
503
  }
358
504
  return parseExpression();
359
505
  };
@@ -387,7 +533,7 @@ export const parseModule = (source: string): Module => {
387
533
  const location = getLocation(start);
388
534
  if (peek().kind === "Identifier" && (isAt("|->", 1) || isAt(":", 1))) {
389
535
  const kind = isAt("|->", 1) ? "Record" : "RecordSet";
390
- const fields = parseSeparated((): RecordField => {
536
+ const fields = parseSeparated(() => {
391
537
  const name = expectIdentifier();
392
538
  expect(kind === "Record" ? "|->" : ":");
393
539
  return { name, value: parseExpression() };
@@ -409,7 +555,7 @@ export const parseModule = (source: string): Module => {
409
555
  return { kind: "FunctionSet", domain: expression, range, location };
410
556
  }
411
557
  if (accept("EXCEPT")) {
412
- const updates = parseSeparated((): ExceptUpdate => {
558
+ const updates = parseSeparated(() => {
413
559
  expect("!");
414
560
  const path: ExceptPathStep[] = [];
415
561
  while (isAt(".") || isAt("[")) {
@@ -468,10 +614,9 @@ export const parseModule = (source: string): Module => {
468
614
 
469
615
  const parseTuple = (start: Token): Expression => {
470
616
  const location = getLocation(start);
471
- const elements = withoutOffside(() => {
472
- if (isAt(">>") || isAt(">>_")) return [];
473
- return parseSeparated(() => parseExpression());
474
- });
617
+ const elements = withoutOffside(() =>
618
+ isAt(">>") || isAt(">>_") ? [] : parseSeparated(() => parseExpression()),
619
+ );
475
620
  if (accept(">>_")) {
476
621
  if (elements.length !== 1)
477
622
  throw createError("Expected a single action inside <<A>>_v", start);
@@ -497,18 +642,16 @@ export const parseModule = (source: string): Module => {
497
642
 
498
643
  const parseCase = (start: Token): Expression => {
499
644
  const arms: CaseArm[] = [];
500
- const other = { value: undefined as Expression | undefined };
501
645
  do {
502
646
  if (accept("OTHER")) {
503
647
  expect("->");
504
- other.value = parseExpression();
505
- break;
648
+ return { kind: "Case", arms, other: parseExpression(), location: getLocation(start) };
506
649
  }
507
650
  const condition = parseExpression();
508
651
  expect("->");
509
652
  arms.push({ condition, body: parseExpression() });
510
653
  } while (accept("[]"));
511
- return { kind: "Case", arms, other: other.value, location: getLocation(start) };
654
+ return { kind: "Case", arms, other: undefined, location: getLocation(start) };
512
655
  };
513
656
 
514
657
  const isSelectorAhead = () => {
@@ -527,7 +670,7 @@ export const parseModule = (source: string): Module => {
527
670
 
528
671
  const parseSelector = (): Selector => {
529
672
  const token = advance();
530
- if (token.kind === "Number") return { kind: "Operand", operand: Number(token.text) };
673
+ if (token.kind === "Number") return { kind: "Operand", operand: getNumeralValue(token.text) };
531
674
  switch (token.text) {
532
675
  case "<<":
533
676
  case ">>":
@@ -545,25 +688,46 @@ export const parseModule = (source: string): Module => {
545
688
  };
546
689
 
547
690
  const parsePrimary = (): Expression => {
691
+ const isFactStart = cursor.index === factStart.index;
548
692
  const token = advance();
549
693
  const location = getLocation(token);
550
- if (token.kind === "Number") return { kind: "Number", value: Number(token.text), location };
694
+ if (token.kind === "Number") {
695
+ return { kind: "Number", value: getNumeralValue(token.text), text: token.text, location };
696
+ }
697
+ if (token.kind === "Decimal") return { kind: "Decimal", text: token.text, location };
551
698
  if (token.kind === "String") return { kind: "String", value: token.text, location };
552
- if (token.kind === "Identifier") {
699
+ const isStep = token.kind === "Step" && isStepReference(token);
700
+ if (isStep && getProofLevel() < 0 && proofLevels.length <= 1) {
701
+ throw createError("Step number used outside proof", token);
702
+ }
703
+ if (isStep && token.text[1] === "+") {
704
+ throw createError("<+> step number used in an expression", token);
705
+ }
706
+ if (token.kind === "Identifier" || isStep) {
707
+ const name = isStep ? (getStepName(token) ?? token.text) : token.text;
553
708
  if (isLabelAhead()) {
554
709
  const params = accept("(") ? parseSeparated(expectIdentifier) : [];
555
710
  if (params.length > 0) expect(")");
556
711
  expect("::");
557
- return { kind: "Label", name: token.text, params, body: parseExpression(), location };
712
+ return { kind: "Label", name, params, body: parseExpression(), location };
558
713
  }
559
714
  const hasArguments = isAt("(");
560
715
  const args = hasArguments ? parseArguments() : [];
561
716
  if (!isSelectorAhead()) {
562
717
  return hasArguments
563
- ? { kind: "Apply", operator: token.text, args, location }
564
- : { kind: "Identifier", name: token.text, location };
718
+ ? { kind: "Apply", operator: name, args, location }
719
+ : { kind: "Identifier", name, location };
720
+ }
721
+ const instance = openModules
722
+ .flatMap((module) => module.instances)
723
+ .find((candidate) => candidate.name === name);
724
+ if (instance && !isFactStart && instance.params.length !== args.length) {
725
+ throw createError(
726
+ `The operator ${name} requires ${instance.params.length} arguments`,
727
+ token,
728
+ );
565
729
  }
566
- const selectors: Selector[] = [{ kind: "Name", name: token.text, args }];
730
+ const selectors: Selector[] = [{ kind: "Name", name, args }];
567
731
  while (isSelectorAhead()) {
568
732
  advance();
569
733
  selectors.push(parseSelector());
@@ -580,9 +744,7 @@ export const parseModule = (source: string): Module => {
580
744
  ? { kind: "Apply", operator, args: qualifiedArgs, location }
581
745
  : { kind: "Identifier", name: operator, location };
582
746
  }
583
- const isOperatorName =
584
- INFIX_OPERATORS.has(token.text) || POSTFIX_OPERATORS.has(token.text) || token.text === "-.";
585
- if (token.kind === "Operator" && isOperatorName && isAt("("))
747
+ if (token.kind === "Operator" && isOperatorName(token.text) && isAt("("))
586
748
  return { kind: "Apply", operator: token.text, args: parseArguments(), location };
587
749
  switch (token.text) {
588
750
  case "(": {
@@ -615,15 +777,17 @@ export const parseModule = (source: string): Module => {
615
777
  case "LET": {
616
778
  const definitions: Definition[] = [];
617
779
  const instances: Instance[] = [];
780
+ recursiveScopes.push(new Set());
618
781
  while (!accept("IN")) {
619
782
  if (accept("RECURSIVE")) {
620
- parseDeclarations().forEach((declaration) => recursiveNames.add(declaration.name));
783
+ declareRecursive();
621
784
  continue;
622
785
  }
623
786
  const unit = parseDefinition(false);
624
787
  if (unit.kind === "Instance") instances.push(unit);
625
788
  else definitions.push(unit);
626
789
  }
790
+ recursiveScopes.pop();
627
791
  const body = parseExpression();
628
792
  return instances.length === 0
629
793
  ? { kind: "Let", definitions, body, location }
@@ -692,8 +856,10 @@ export const parseModule = (source: string): Module => {
692
856
  };
693
857
 
694
858
  const parseExpression = (minimumPrecedence = 0): Expression => {
859
+ const start = cursor.index;
860
+ const prefix = parsePrefix();
695
861
  const state = {
696
- left: parsePrefix(),
862
+ left: prefix.location.range ? prefix : withRange(prefix, start),
697
863
  previous: undefined as (InfixOperator & { text: string }) | undefined,
698
864
  };
699
865
  while (true) {
@@ -702,24 +868,31 @@ export const parseModule = (source: string): Module => {
702
868
  const location = getLocation(token);
703
869
  if (token.text === "'" && minimumPrecedence <= 15) {
704
870
  advance();
705
- state.left = { kind: "Prime", expression: state.left, location };
871
+ state.left = withRange({ kind: "Prime", expression: state.left, location }, start);
706
872
  continue;
707
873
  }
708
874
  if (POSTFIX_OPERATORS.has(token.text) && minimumPrecedence <= 15) {
709
875
  advance();
710
- state.left = { kind: "Apply", operator: token.text, args: [state.left], location };
876
+ state.left = withRange(
877
+ { kind: "Apply", operator: token.text, args: [state.left], location },
878
+ start,
879
+ );
711
880
  continue;
712
881
  }
713
882
  if (token.text === "[") {
714
883
  advance();
715
884
  const args = withoutOffside(() => parseSeparated(() => parseExpression()));
716
885
  withoutOffside(() => expect("]"));
717
- state.left = { kind: "FunctionApplication", function: state.left, args, location };
886
+ state.left = withRange(
887
+ { kind: "FunctionApplication", function: state.left, args, location },
888
+ start,
889
+ );
718
890
  continue;
719
891
  }
720
892
  if (token.text === "." && isFieldName(peek(1))) {
721
893
  advance();
722
- state.left = { kind: "FieldAccess", record: state.left, field: advance().text, location };
894
+ const field = advance().text;
895
+ state.left = withRange({ kind: "FieldAccess", record: state.left, field, location }, start);
723
896
  continue;
724
897
  }
725
898
  const infix = INFIX_OPERATORS.get(token.text);
@@ -752,44 +925,59 @@ export const parseModule = (source: string): Module => {
752
925
  } else {
753
926
  state.left = { kind: "Apply", operator: token.text, args: [left, right], location };
754
927
  }
928
+ state.left = withRange(state.left, start);
755
929
  state.previous = { ...infix, text: token.text };
756
930
  }
757
931
  return state.left;
758
932
  };
759
933
 
760
- const parseParams = (): OperatorParam[] => {
934
+ const parseParams = () => {
761
935
  if (!accept("(")) return [];
762
936
  const params = parseDeclarations();
763
937
  expect(")");
764
938
  return params;
765
939
  };
766
940
 
941
+ const parseBody = () => {
942
+ const start = getLocation(peek());
943
+ const body = parseExpression();
944
+ const last = tokens[cursor.index - 1];
945
+ const end = { line: last.line, column: last.endColumn };
946
+ return { body, bodyRange: { module: openModules[openModules.length - 1].name, start, end } };
947
+ };
948
+
767
949
  const parseDefinition = (isLocal: boolean): Definition | Instance => {
950
+ const start = cursor.index;
951
+ return withRange(parseDefinitionUnit(isLocal), start);
952
+ };
953
+
954
+ const parseDefinitionUnit = (isLocal: boolean): Definition | Instance => {
768
955
  const token = peek();
769
956
  const location = getLocation(token);
957
+ const parseOperator = (name: string, params: OperatorParam[]): Definition => ({
958
+ kind: "Operator",
959
+ name,
960
+ params,
961
+ ...parseBody(),
962
+ isLocal,
963
+ isRecursive: isRecursiveName(name),
964
+ location,
965
+ });
770
966
  if (
771
967
  token.kind === "Identifier" &&
772
968
  peek(1).kind === "Operator" &&
773
- INFIX_OPERATORS.has(peek(1).text)
969
+ INFIX_OPERATORS.has(peek(1).text) &&
970
+ peek(2).kind === "Identifier" &&
971
+ isAt("==", 3)
774
972
  ) {
775
- if (peek(2).kind === "Identifier" && isAt("==", 3)) {
776
- const left = expectIdentifier();
777
- const operator = advance().text;
778
- const right = expectIdentifier();
779
- expect("==");
780
- return {
781
- kind: "Operator",
782
- name: operator,
783
- params: [
784
- { name: left, arity: 0 },
785
- { name: right, arity: 0 },
786
- ],
787
- body: parseExpression(),
788
- isLocal,
789
- isRecursive: recursiveNames.has(operator),
790
- location,
791
- };
792
- }
973
+ const left = expectIdentifier();
974
+ const operator = advance().text;
975
+ const right = expectIdentifier();
976
+ expect("==");
977
+ return parseOperator(operator, [
978
+ { name: left, arity: 0 },
979
+ { name: right, arity: 0 },
980
+ ]);
793
981
  }
794
982
  const isPrefixDefinition = token.kind === "Operator" && token.text === "-.";
795
983
  const isPostfixDefinition =
@@ -799,15 +987,7 @@ export const parseModule = (source: string): Module => {
799
987
  cursor.index += isPrefixDefinition ? 1 : 2;
800
988
  const param = isPrefixDefinition ? expectIdentifier() : token.text;
801
989
  expect("==");
802
- return {
803
- kind: "Operator",
804
- name: operator,
805
- params: [{ name: param, arity: 0 }],
806
- body: parseExpression(),
807
- isLocal,
808
- isRecursive: recursiveNames.has(operator),
809
- location,
810
- };
990
+ return parseOperator(operator, [{ name: param, arity: 0 }]);
811
991
  }
812
992
  const name = expectIdentifier();
813
993
  if (accept("[")) {
@@ -820,22 +1000,14 @@ export const parseModule = (source: string): Module => {
820
1000
  bounds,
821
1001
  body: parseExpression(),
822
1002
  isLocal,
823
- isRecursive: recursiveNames.has(name),
1003
+ isRecursive: isRecursiveName(name),
824
1004
  location,
825
1005
  };
826
1006
  }
827
1007
  const params = parseParams();
828
1008
  expect("==");
829
1009
  if (isAt("INSTANCE")) return parseInstance(name, params, isLocal, location);
830
- return {
831
- kind: "Operator",
832
- name,
833
- params,
834
- body: parseExpression(),
835
- isLocal,
836
- isRecursive: recursiveNames.has(name),
837
- location,
838
- };
1010
+ return parseOperator(name, params);
839
1011
  };
840
1012
 
841
1013
  const parseInstance = (
@@ -847,7 +1019,7 @@ export const parseModule = (source: string): Module => {
847
1019
  expect("INSTANCE");
848
1020
  const module = expectIdentifier();
849
1021
  const substitutions = accept("WITH")
850
- ? parseSeparated((): InstanceSubstitution => {
1022
+ ? parseSeparated(() => {
851
1023
  const target = advance().text;
852
1024
  expect("<-");
853
1025
  return { name: target, expression: parseArgument() };
@@ -856,101 +1028,444 @@ export const parseModule = (source: string): Module => {
856
1028
  return { kind: "Instance", name, params, module, substitutions, isLocal, location };
857
1029
  };
858
1030
 
859
- const parseDeclarations = (): Declaration[] =>
860
- parseSeparated(() => {
861
- if (isAt("_") && peek(1).kind === "Operator") {
862
- advance();
863
- const operator = advance().text;
864
- return accept("_") ? { name: operator, arity: 2 } : { name: operator, arity: 1 };
865
- }
866
- if (peek().kind === "Operator" && isAt("_", 1)) {
867
- const operator = advance().text;
868
- advance();
869
- return { name: operator === "-" ? "-." : operator, arity: 1 };
870
- }
871
- const name = expectIdentifier();
872
- if (!accept("(")) return { name, arity: 0 };
873
- const placeholders = parseSeparated(() => expect("_"));
874
- expect(")");
875
- return { name, arity: placeholders.length };
876
- });
1031
+ const parseDeclaration = (): Declaration => {
1032
+ const location = getLocation(peek());
1033
+ if (isAt("_") && peek(1).kind === "Operator") {
1034
+ advance();
1035
+ const name = advance().text;
1036
+ return { name, arity: accept("_") ? 2 : 1, location };
1037
+ }
1038
+ if (peek().kind === "Operator" && isAt("_", 1)) {
1039
+ const operator = advance().text;
1040
+ advance();
1041
+ return { name: operator === "-" ? "-." : operator, arity: 1, location };
1042
+ }
1043
+ const name = expectIdentifier();
1044
+ if (!accept("(")) return { name, arity: 0, location };
1045
+ const placeholders = parseSeparated(() => expect("_"));
1046
+ expect(")");
1047
+ return { name, arity: placeholders.length, location };
1048
+ };
877
1049
 
878
- const isUnitStart = (token: Token, next: Token) => {
879
- if (token.column !== 1) return false;
880
- if (token.kind === "Separator" || token.kind === "ModuleEnd" || token.kind === "End")
881
- return true;
882
- if (token.kind === "Keyword") return UNIT_KEYWORDS.has(token.text);
883
- return (
884
- token.kind === "Identifier" && (next.text === "==" || next.text === "(" || next.text === "[")
1050
+ const parseDeclarations = () => parseSeparated(parseDeclaration);
1051
+
1052
+ const declareRecursive = () =>
1053
+ parseDeclarations().forEach((declaration) =>
1054
+ recursiveScopes[recursiveScopes.length - 1].add(declaration.name),
885
1055
  );
1056
+
1057
+ const getClosingOffset = (offset: number) => {
1058
+ const depth = { value: 0, offset };
1059
+ do {
1060
+ const token = peek(depth.offset);
1061
+ if (token.kind === "End") return depth.offset;
1062
+ if (token.kind === "Operator" && OPENING_BRACKETS.has(token.text)) depth.value++;
1063
+ if (token.kind === "Operator" && CLOSING_BRACKETS.has(token.text)) depth.value--;
1064
+ depth.offset++;
1065
+ } while (depth.value > 0);
1066
+ return depth.offset;
1067
+ };
1068
+
1069
+ // SANY inserts a break before each definition, which ends the fact list or step before it.
1070
+ const isDefinitionStart = () => {
1071
+ const token = peek();
1072
+ const next = peek(1);
1073
+ if (token.kind !== "Identifier") {
1074
+ return isAt("-.") && next.kind === "Identifier" && isAt("==", 2);
1075
+ }
1076
+ if (isAt("(", 1) || isAt("[", 1)) return isAt("==", getClosingOffset(1));
1077
+ if (next.kind === "Operator" && POSTFIX_OPERATORS.has(next.text)) return isAt("==", 2);
1078
+ if (next.kind === "Operator" && INFIX_OPERATORS.has(next.text)) {
1079
+ return peek(2).kind === "Identifier" && isAt("==", 3);
1080
+ }
1081
+ return isAt("==", 1);
886
1082
  };
887
1083
 
888
- const skipUnit = (start: Token) => {
889
- while (peekRaw().kind !== "End") {
890
- const token = peekRaw();
891
- if (token.line > start.line && isUnitStart(token, peekRaw(1))) return;
1084
+ // SANY's proof levels, one for each open proof, which stay -1 until a step sets them.
1085
+ const proofLevels: number[] = [];
1086
+
1087
+ const getProofLevel = () => proofLevels[proofLevels.length - 1] ?? -1;
1088
+
1089
+ const getStepLevel = (step: Token) => {
1090
+ if (step.text[1] === "*") return -1;
1091
+ if (step.text[1] === "+") return -2;
1092
+ return Number(step.text.slice(1, step.text.indexOf(">")));
1093
+ };
1094
+
1095
+ const getStepLabel = (step: Token) => {
1096
+ const label = step.text.slice(step.text.indexOf(">") + 1);
1097
+ const end = { value: label.length };
1098
+ while (label[end.value - 1] === ".") end.value--;
1099
+ return label.slice(0, end.value);
1100
+ };
1101
+
1102
+ const isStepReference = (step: Token) =>
1103
+ !step.text.endsWith(".") && !UNNAMED_STEP_LABELS.has(getStepLabel(step));
1104
+
1105
+ const getStepName = (step: Token) => {
1106
+ const label = getStepLabel(step);
1107
+ if (UNNAMED_STEP_LABELS.has(label)) return undefined;
1108
+ const level = getStepLevel(step);
1109
+ if (level >= 0) return `<${level}>${label}`;
1110
+ const current = getProofLevel();
1111
+ const outer = proofLevels[proofLevels.length - 2];
1112
+ return `<${current < 0 && outer !== undefined ? outer : current}>${label}`;
1113
+ };
1114
+
1115
+ const isProofStart = (token: Token) => {
1116
+ if (token.kind === "Keyword") return PROOF_STARTS.has(token.text);
1117
+ if (token.kind !== "Step") return false;
1118
+ if (token.text[1] === "*") return proofLevels.length === 0;
1119
+ if (token.text[1] === "+" || proofLevels.length === 0) return true;
1120
+ const level = getProofLevel();
1121
+ return level >= 0 && getStepLevel(token) > level;
1122
+ };
1123
+
1124
+ const isCorrectLevel = (step: Token) => {
1125
+ const level = getStepLevel(step);
1126
+ const top = proofLevels.length - 1;
1127
+ const outer = proofLevels[top - 1] ?? -1;
1128
+ const isFirst = proofLevels[top] < 0;
1129
+ if (isFirst) proofLevels[top] = level < 0 ? outer + 1 : level;
1130
+ if (level === -1) return true;
1131
+ if (level === -2) return isFirst;
1132
+ return isFirst ? level > outer : proofLevels[top] === level;
1133
+ };
1134
+
1135
+ const isDeclared = (name: string) =>
1136
+ !factNames.has(name) &&
1137
+ openModules.some(
1138
+ (module) =>
1139
+ module.constants.some((constant) => constant.name === name) ||
1140
+ module.variables.some((variable) => variable.name === name) ||
1141
+ module.definitions.some((definition) => definition.name === name),
1142
+ );
1143
+
1144
+ const isHideFact = (fact: Fact) => {
1145
+ if (fact.kind === "ModuleFact" || fact.kind === "Subexpression") return true;
1146
+ if (fact.kind === "Apply") return fact.operator.includes("!");
1147
+ if (fact.kind !== "Identifier") return false;
1148
+ if (fact.name.startsWith("<") || fact.name.includes("!")) return true;
1149
+ return !BUILTIN_CONSTANTS.has(fact.name) && !isDeclared(fact.name);
1150
+ };
1151
+
1152
+ const isFactAhead = () => {
1153
+ const token = peek();
1154
+ if (token.kind === "Step") return isStepReference(token);
1155
+ if (token.kind === "Keyword") return FACT_KEYWORDS.has(token.text);
1156
+ if (token.kind === "End" || token.kind === "ModuleEnd" || token.kind === "Separator") {
1157
+ return false;
1158
+ }
1159
+ return !isDefinitionStart();
1160
+ };
1161
+
1162
+ // SANY reads an infix or postfix operator alone as its name, but a prefix operator such as - as the
1163
+ // start of an expression.
1164
+ const parseFact = (): Fact => {
1165
+ const start = cursor.index;
1166
+ factStart.index = start;
1167
+ const token = peek();
1168
+ if (
1169
+ token.kind === "Operator" &&
1170
+ !EXPRESSION_PREFIXES.has(token.text) &&
1171
+ isOperatorName(token.text)
1172
+ ) {
892
1173
  cursor.index++;
1174
+ return withRange(
1175
+ { kind: "OperatorReference", operator: token.text, location: getLocation(token) },
1176
+ start,
1177
+ );
893
1178
  }
1179
+ if (!accept("MODULE")) return parseExpression();
1180
+ return withRange(
1181
+ { kind: "ModuleFact", name: expectIdentifier(), location: getLocation(token) },
1182
+ start,
1183
+ );
894
1184
  };
895
1185
 
896
- const isProofStart = (index: number, start: Token) => {
897
- const token = tokens[index];
898
- if (token.kind === "End") return true;
899
- if (token.line > start.line && isUnitStart(token, tokens[index + 1])) return true;
900
- if (token.text === "PROOF" || PROOF_WORDS.has(token.text)) return true;
901
- return (
902
- token.text === "<" && tokens[index + 1]?.kind === "Number" && tokens[index + 2]?.text === ">"
1186
+ const declareProofSymbol = (name: string) => proofScopes[proofScopes.length - 1]?.add(name);
1187
+
1188
+ const withProofScope = <Result>(parse: () => Result) => {
1189
+ proofScopes.push(new Set());
1190
+ const result = parse();
1191
+ proofScopes.pop();
1192
+ return result;
1193
+ };
1194
+
1195
+ const isDeclaredSymbol = (name: string) =>
1196
+ proofScopes.some((scope) => scope.has(name)) ||
1197
+ openModules.some(
1198
+ (module) =>
1199
+ module.constants.some((constant) => constant.name === name) ||
1200
+ module.variables.some((variable) => variable.name === name),
903
1201
  );
1202
+
1203
+ const parseDefs = () => {
1204
+ const defs = parseSeparated(parseFact);
1205
+ const invalid = defs.find((def) => def.kind === "Identifier" && isDeclaredSymbol(def.name));
1206
+ if (invalid) {
1207
+ throw createError("DEF clause entry should describe a defined operator", invalid.location);
1208
+ }
1209
+ return defs;
1210
+ };
1211
+
1212
+ const parseFacts = () => ({
1213
+ facts: isFactAhead() ? parseSeparated(parseFact) : [],
1214
+ defs: accept("DEF") || accept("DEFS") ? parseDefs() : [],
1215
+ });
1216
+
1217
+ const parseUseOrHide = (): UseOrHide => {
1218
+ const start = cursor.index;
1219
+ const keyword = advance();
1220
+ const isOnly = keyword.text === "USE" && accept("ONLY");
1221
+ const use: UseOrHide = {
1222
+ kind: keyword.text === "USE" ? "Use" : "Hide",
1223
+ isOnly,
1224
+ ...parseFacts(),
1225
+ location: getLocation(keyword),
1226
+ };
1227
+ const invalid = use.kind === "Hide" ? use.facts.find((fact) => !isHideFact(fact)) : undefined;
1228
+ if (invalid) {
1229
+ throw createError(
1230
+ "The only expression allowed as a fact in a HIDE is the name of a theorem, assumption, or step",
1231
+ invalid.location,
1232
+ );
1233
+ }
1234
+ return withRange(use, start);
904
1235
  };
905
1236
 
906
- const parseTheorem = (): Definition | undefined => {
907
- const start = advance();
908
- const isNamed = peek().kind === "Identifier" && isAt("==", 1) && !isAt("ASSUME", 2);
909
- if (!isNamed) {
910
- skipUnit(start);
911
- return undefined;
1237
+ const parseNewSymbol = (): NewSymbol => {
1238
+ const start = cursor.index;
1239
+ const location = getLocation(peek());
1240
+ const isNew = accept("NEW");
1241
+ const level = peek().kind === "Keyword" ? NEW_SYMBOL_LEVELS.get(peek().text) : undefined;
1242
+ if (!isNew && level === undefined) {
1243
+ throw createError(`Expected a declaration but found ${describe(peek())}`);
912
1244
  }
913
- const name = expectIdentifier();
914
- advance();
915
- const end = { value: cursor.index };
916
- while (!isProofStart(end.value, start)) end.value++;
917
- cursor.limit = end.value;
918
- const body = parseExpression();
919
- const isComplete = cursor.index === end.value;
920
- cursor.limit = tokens.length;
921
- skipUnit(start);
922
- if (!isComplete) return undefined;
1245
+ if (level !== undefined) advance();
1246
+ if (level === "Variable") {
1247
+ const name = expectIdentifier();
1248
+ declareProofSymbol(name);
1249
+ return withRange(
1250
+ { kind: "NewSymbol", name, arity: 0, level, set: undefined, location },
1251
+ start,
1252
+ );
1253
+ }
1254
+ const declaration = parseDeclaration();
1255
+ declareProofSymbol(declaration.name);
1256
+ const isBounded = (level ?? "Constant") === "Constant" && isAt("\\in");
1257
+ if (isBounded && declaration.arity > 0) {
1258
+ throw createError("declared symbol with arguments before \\in");
1259
+ }
1260
+ if (isBounded) advance();
1261
+ return withRange(
1262
+ {
1263
+ kind: "NewSymbol",
1264
+ name: declaration.name,
1265
+ arity: declaration.arity,
1266
+ level: level ?? "Constant",
1267
+ set: isBounded ? parseExpression() : undefined,
1268
+ location,
1269
+ },
1270
+ start,
1271
+ );
1272
+ };
1273
+
1274
+ const parseAssumeProve = (): AssumeProve => {
1275
+ const start = cursor.index;
1276
+ const location = getLocation(expect("ASSUME"));
1277
+ const assumptions = parseSeparated(() => {
1278
+ if (isAt("ASSUME")) return withProofScope(parseAssumeProve);
1279
+ if (peek().kind === "Keyword" && NEW_SYMBOL_STARTS.has(peek().text)) return parseNewSymbol();
1280
+ return parseExpression();
1281
+ });
1282
+ expect("PROVE");
1283
+ return withRange(
1284
+ { kind: "AssumeProve", assumptions, conclusion: parseExpression(), location },
1285
+ start,
1286
+ );
1287
+ };
1288
+
1289
+ const isNameListAhead = (terminator: string) => {
1290
+ const offset = { value: 0 };
1291
+ while (peek(offset.value).kind === "Identifier" && isAt(",", offset.value + 1)) {
1292
+ offset.value += 2;
1293
+ }
1294
+ return peek(offset.value).kind === "Identifier" && isAt(terminator, offset.value + 1);
1295
+ };
1296
+
1297
+ const declareProofSymbols = (names: string[], bounds: Bound[]) => {
1298
+ names.forEach(declareProofSymbol);
1299
+ bounds.forEach((bound) =>
1300
+ (bound.pattern.kind === "Name" ? [bound.pattern.name] : bound.pattern.names).forEach(
1301
+ declareProofSymbol,
1302
+ ),
1303
+ );
1304
+ };
1305
+
1306
+ const parseStepBody = (): StepBody => {
1307
+ const token = peek();
1308
+ if (isAt("USE") || isAt("HIDE")) return parseUseOrHide();
1309
+ if (isAt("INSTANCE")) {
1310
+ return withRange(parseInstance(undefined, [], false, getLocation(token)), cursor.index);
1311
+ }
1312
+ if (isAt("DEFINE") || isDefinitionStart()) {
1313
+ const start = cursor.index;
1314
+ accept("DEFINE");
1315
+ const definitions = [parseDefinition(false)];
1316
+ while (isDefinitionStart()) definitions.push(parseDefinition(false));
1317
+ const define: DefineStep = { kind: "Define", definitions, location: getLocation(token) };
1318
+ return withRange(define, start);
1319
+ }
1320
+ if (accept("HAVE")) return { kind: "Have", expression: parseExpression() };
1321
+ if (accept("TAKE")) {
1322
+ const isBounded = isBoundAhead();
1323
+ const names = isBounded ? [] : parseSeparated(expectIdentifier);
1324
+ const bounds = isBounded ? parseBounds() : [];
1325
+ declareProofSymbols(names, bounds);
1326
+ return { kind: "Take", names, bounds };
1327
+ }
1328
+ if (accept("WITNESS")) {
1329
+ return { kind: "Witness", expressions: parseSeparated(() => parseExpression()) };
1330
+ }
1331
+ if (accept("PICK")) {
1332
+ const isNames = isNameListAhead(":");
1333
+ const names = isNames ? parseSeparated(expectIdentifier) : [];
1334
+ const bounds = isNames ? [] : parseBounds();
1335
+ declareProofSymbols(names, bounds);
1336
+ expect(":");
1337
+ return { kind: "Pick", names, bounds, body: parseExpression() };
1338
+ }
1339
+ if (accept("CASE")) return { kind: "Case", expression: parseExpression() };
923
1340
  return {
924
- kind: "Operator",
925
- name,
926
- params: [],
927
- body,
928
- isLocal: false,
929
- isRecursive: false,
930
- location: getLocation(start),
1341
+ kind: "Assert",
1342
+ isSuffices: accept("SUFFICES"),
1343
+ statement: isAt("ASSUME") ? parseAssumeProve() : parseExpression(),
931
1344
  };
932
1345
  };
933
1346
 
1347
+ const parseStepStart = (isQed: boolean) => {
1348
+ const step = peek();
1349
+ if (step.kind !== "Step") {
1350
+ throw createError(`Expected a step number but found ${describe(step)}`);
1351
+ }
1352
+ if (!isCorrectLevel(step)) {
1353
+ throw createError(`${isQed ? "QED step" : "step"}'s number has bad level`, step);
1354
+ }
1355
+ if (getStepLevel(step) < 0 && getStepName(step) !== undefined) {
1356
+ throw createError("<*> and <+> cannot be used for a named step", step);
1357
+ }
1358
+ cursor.index++;
1359
+ return { name: getStepName(step), location: getLocation(step) };
1360
+ };
1361
+
1362
+ // The names an assertion assumes stay in its own proof, and other steps declare theirs for the rest
1363
+ // of the enclosing proof.
1364
+ const parseStep = (): ProofStep => {
1365
+ const start = cursor.index;
1366
+ const step = parseStepStart(isAt("QED", 1));
1367
+ const scope = new Set<string>();
1368
+ proofScopes.push(scope);
1369
+ const body: StepBody = accept("QED") ? { kind: "Qed" } : parseStepBody();
1370
+ const hasProof = isProofStart(peek());
1371
+ if (hasProof && !PROVABLE_STEPS.has(body.kind)) {
1372
+ throw createError("Proof of a step that does not take a proof");
1373
+ }
1374
+ const proof = hasProof ? parseProof() : undefined;
1375
+ proofScopes.pop();
1376
+ if (body.kind !== "Assert" || body.isSuffices) scope.forEach(declareProofSymbol);
1377
+ return withRange({ ...step, body, proof }, start);
1378
+ };
1379
+
1380
+ const parseProofBody = (): Proof => {
1381
+ const location = getLocation(peek());
1382
+ if (isAt("BY") || (isAt("PROOF") && isAt("BY", 1))) {
1383
+ accept("PROOF");
1384
+ advance();
1385
+ return { kind: "By", isOnly: accept("ONLY"), ...parseFacts(), location };
1386
+ }
1387
+ const terminal = peek(isAt("PROOF") ? 1 : 0);
1388
+ if (
1389
+ terminal.kind === "Keyword" &&
1390
+ (terminal.text === "OBVIOUS" || terminal.text === "OMITTED")
1391
+ ) {
1392
+ accept("PROOF");
1393
+ advance();
1394
+ return { kind: terminal.text === "OBVIOUS" ? "Obvious" : "Omitted", location };
1395
+ }
1396
+ accept("PROOF");
1397
+ const steps = [parseStep()];
1398
+ while (steps[steps.length - 1].body.kind !== "Qed") steps.push(parseStep());
1399
+ return { kind: "Steps", level: getProofLevel(), steps, location };
1400
+ };
1401
+
1402
+ const parseProof = (): Proof => {
1403
+ const start = cursor.index;
1404
+ proofLevels.push(-1);
1405
+ const proof = withProofScope(parseProofBody);
1406
+ proofLevels.pop();
1407
+ return withRange(proof, start);
1408
+ };
1409
+
1410
+ const parseTheorem = (module: Module) => {
1411
+ const start = cursor.index;
1412
+ const keyword = advance();
1413
+ const isNamed = peek().kind === "Identifier" && isAt("==", 1);
1414
+ const name = isNamed ? expectIdentifier() : undefined;
1415
+ if (name !== undefined) {
1416
+ factNames.add(name);
1417
+ advance();
1418
+ }
1419
+ const parsed = withProofScope(() => {
1420
+ const statement = isAt("ASSUME") ? parseAssumeProve() : parseExpression();
1421
+ return { statement, proof: isProofStart(peek()) ? parseProof() : undefined };
1422
+ });
1423
+ const statement = parsed.statement;
1424
+ module.theorems.push(
1425
+ withRange({ name, statement, proof: parsed.proof, location: getLocation(keyword) }, start),
1426
+ );
1427
+ if (name === undefined || statement.kind === "AssumeProve") return;
1428
+ module.definitions.push(
1429
+ withRange(
1430
+ {
1431
+ kind: "Operator",
1432
+ name,
1433
+ params: [],
1434
+ body: statement,
1435
+ isLocal: false,
1436
+ isRecursive: false,
1437
+ location: getLocation(keyword),
1438
+ },
1439
+ start,
1440
+ ),
1441
+ );
1442
+ };
1443
+
934
1444
  const parseAssumption = (): Assumption => {
1445
+ const start = cursor.index;
1446
+ return withRange(parseAssumptionUnit(), start);
1447
+ };
1448
+
1449
+ const parseAssumptionUnit = (): Assumption => {
935
1450
  const keyword = advance();
936
1451
  const location = getLocation(keyword);
937
1452
  const isAxiom = keyword.text === "AXIOM";
938
1453
  if (peek().kind === "Identifier" && isAt("==", 1)) {
939
1454
  const name = expectIdentifier();
1455
+ factNames.add(name);
940
1456
  advance();
941
1457
  return { name, expression: parseExpression(), isAxiom, location };
942
1458
  }
943
1459
  return { name: undefined, expression: parseExpression(), isAxiom, location };
944
1460
  };
945
1461
 
946
- const parseModuleBody = (outer?: OuterScope): Module => {
947
- expect("----");
1462
+ const parseModuleBody = (): Module => {
1463
+ const location = getLocation(expect("----"));
948
1464
  expect("MODULE");
949
1465
  const name = expectIdentifier();
950
1466
  expect("----");
951
1467
  const module: Module = {
952
1468
  name,
953
- outer,
954
1469
  modules: [],
955
1470
  extends: accept("EXTENDS") ? parseSeparated(expectIdentifier) : [],
956
1471
  constants: [],
@@ -958,13 +1473,27 @@ export const parseModule = (source: string): Module => {
958
1473
  definitions: [],
959
1474
  instances: [],
960
1475
  assumptions: [],
1476
+ location,
1477
+ theorems: [],
1478
+ uses: [],
961
1479
  };
1480
+ openModules.push(module);
1481
+ recursiveScopes.push(new Set());
962
1482
  while (true) {
1483
+ const tokenIndex = cursor.index;
963
1484
  const token = peek();
964
- if (token.kind === "ModuleEnd" || token.kind === "End") return module;
1485
+ if (token.kind === "ModuleEnd" || token.kind === "End") {
1486
+ openModules.pop();
1487
+ recursiveScopes.pop();
1488
+ return module;
1489
+ }
965
1490
  if (token.kind === "Separator") {
966
- if (isAt("MODULE", 1)) module.modules.push(parseNestedModule(getOuterScope(module)));
967
- else advance();
1491
+ if (isAt("MODULE", 1)) {
1492
+ module.modules.push(parseModuleBody());
1493
+ expect("====");
1494
+ continue;
1495
+ }
1496
+ advance();
968
1497
  continue;
969
1498
  }
970
1499
  switch (token.text) {
@@ -976,7 +1505,13 @@ export const parseModule = (source: string): Module => {
976
1505
  case "VARIABLE":
977
1506
  case "VARIABLES":
978
1507
  advance();
979
- module.variables.push(...parseSeparated(expectIdentifier));
1508
+ module.variables.push(
1509
+ ...parseSeparated(() => {
1510
+ const start = cursor.index;
1511
+ const location = getLocation(peek());
1512
+ return withRange({ name: expectIdentifier(), arity: 0, location }, start);
1513
+ }),
1514
+ );
980
1515
  continue;
981
1516
  case "ASSUME":
982
1517
  case "ASSUMPTION":
@@ -996,17 +1531,25 @@ export const parseModule = (source: string): Module => {
996
1531
  }
997
1532
  continue;
998
1533
  }
1534
+ case "USE":
1535
+ case "HIDE":
1536
+ module.uses.push(parseUseOrHide());
1537
+ continue;
999
1538
  case "RECURSIVE":
1000
1539
  advance();
1001
- parseDeclarations().forEach((declaration) => recursiveNames.add(declaration.name));
1540
+ declareRecursive();
1002
1541
  continue;
1003
1542
  case "INSTANCE":
1004
- module.instances.push(parseInstance(undefined, [], false, getLocation(token)));
1543
+ module.instances.push(
1544
+ withRange(parseInstance(undefined, [], false, getLocation(token)), tokenIndex),
1545
+ );
1005
1546
  continue;
1006
1547
  case "LOCAL": {
1007
1548
  advance();
1008
1549
  if (isAt("INSTANCE")) {
1009
- module.instances.push(parseInstance(undefined, [], true, getLocation(token)));
1550
+ module.instances.push(
1551
+ withRange(parseInstance(undefined, [], true, getLocation(token)), tokenIndex),
1552
+ );
1010
1553
  continue;
1011
1554
  }
1012
1555
  const unit = parseDefinition(true);
@@ -1016,8 +1559,7 @@ export const parseModule = (source: string): Module => {
1016
1559
  }
1017
1560
  }
1018
1561
  if (token.kind === "Keyword" && THEOREM_KEYWORDS.has(token.text)) {
1019
- const theorem = parseTheorem();
1020
- if (theorem) module.definitions.push(theorem);
1562
+ parseTheorem(module);
1021
1563
  continue;
1022
1564
  }
1023
1565
  const unit = parseDefinition(false);
@@ -1026,27 +1568,5 @@ export const parseModule = (source: string): Module => {
1026
1568
  }
1027
1569
  };
1028
1570
 
1029
- const getOuterScope = (module: Module): OuterScope => ({
1030
- extends: [...(module.outer?.extends ?? []), ...module.extends],
1031
- symbols: [
1032
- ...(module.outer?.symbols ?? []),
1033
- ...module.constants,
1034
- ...module.variables.map((variable) => ({ name: variable, arity: 0 })),
1035
- ...module.definitions.map((definition) => ({
1036
- name: definition.name,
1037
- arity: definition.kind === "Operator" ? definition.params.length : 0,
1038
- })),
1039
- ],
1040
- });
1041
-
1042
- const parseNestedModule = (outer?: OuterScope) => {
1043
- const nested = parseModuleBody(outer);
1044
- accept("====");
1045
- return nested;
1046
- };
1047
-
1048
- const root = parseModuleBody();
1049
- accept("====");
1050
- while (peekRaw().kind === "Separator") root.modules.push(parseNestedModule());
1051
- return root;
1571
+ return parseModuleBody();
1052
1572
  };