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.
- package/README.md +34 -41
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +166 -30
- package/dist/index.d.ts +166 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +2 -1
- package/dist/run-cli.js +2 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +4 -2
- package/src/ast.ts +82 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +904 -230
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +22 -0
- package/src/evaluate.ts +486 -136
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +2 -2
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +97 -27
- package/src/liveness.ts +47 -91
- package/src/parallel-worker.ts +57 -28
- package/src/parallel.ts +116 -62
- package/src/parser.ts +703 -183
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +142 -44
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +65 -21
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +4 -4
- 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
|
-
|
|
14
|
-
InstanceSubstitution,
|
|
15
|
+
NewSymbol,
|
|
15
16
|
OperatorParam,
|
|
16
|
-
|
|
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)
|
|
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
|
|
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,
|
|
197
|
-
|
|
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" :
|
|
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 = ()
|
|
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
|
-
|
|
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
|
-
(
|
|
472
|
+
isStandalone(token);
|
|
332
473
|
if (isOperatorAlone) {
|
|
333
474
|
cursor.index++;
|
|
334
|
-
return
|
|
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) && (
|
|
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
|
-
|
|
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(()
|
|
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(()
|
|
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
|
-
|
|
473
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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")
|
|
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
|
-
|
|
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
|
|
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:
|
|
564
|
-
: { kind: "Identifier", name
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 = ()
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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:
|
|
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(()
|
|
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
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
|
907
|
-
const start =
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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: "
|
|
925
|
-
|
|
926
|
-
|
|
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 = (
|
|
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")
|
|
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))
|
|
967
|
-
|
|
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(
|
|
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
|
-
|
|
1540
|
+
declareRecursive();
|
|
1002
1541
|
continue;
|
|
1003
1542
|
case "INSTANCE":
|
|
1004
|
-
module.instances.push(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|