tla-ts 0.0.1 → 0.0.3-dev.3cf2ad0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -40
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +216 -30
- package/dist/index.d.ts +216 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -1
- package/dist/run-cli.js +20 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +6 -2
- package/src/ast.ts +122 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +945 -238
- package/src/cli-options.ts +58 -0
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +59 -1
- package/src/evaluate.ts +489 -137
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +48 -13
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +103 -28
- package/src/liveness.ts +47 -91
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parallel-worker.ts +183 -202
- package/src/parallel.ts +163 -84
- package/src/parser.ts +958 -205
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +427 -61
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +70 -22
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +26 -5
- package/src/values.ts +1234 -176
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,22 @@ 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
|
+
|
|
156
|
+
const CIRCLED_OPERATORS = new Set(["+", "-", "/", "\\X"]);
|
|
157
|
+
|
|
137
158
|
const OPERATOR_ARGUMENTS = new Set([
|
|
138
159
|
"SUBSET",
|
|
139
160
|
"UNION",
|
|
@@ -146,6 +167,9 @@ const OPERATOR_ARGUMENTS = new Set([
|
|
|
146
167
|
...POSTFIX_OPERATORS,
|
|
147
168
|
]);
|
|
148
169
|
|
|
170
|
+
const isOperatorName = (text: string) =>
|
|
171
|
+
INFIX_OPERATORS.has(text) || POSTFIX_OPERATORS.has(text) || text === "-.";
|
|
172
|
+
|
|
149
173
|
const getOperatorApplication = (
|
|
150
174
|
operator: string,
|
|
151
175
|
argument: Expression,
|
|
@@ -187,27 +211,148 @@ const UNIT_KEYWORDS = new Set([
|
|
|
187
211
|
|
|
188
212
|
const THEOREM_KEYWORDS = new Set(["THEOREM", "LEMMA", "COROLLARY", "PROPOSITION"]);
|
|
189
213
|
|
|
190
|
-
const
|
|
214
|
+
const PROOF_STARTS = new Set(["BY", "PROOF", "OBVIOUS", "OMITTED"]);
|
|
215
|
+
|
|
216
|
+
const PROVABLE_STEPS = new Set(["Qed", "Pick", "Case", "Assert"]);
|
|
217
|
+
|
|
218
|
+
const TLAPM_PUNCTUATION = new Set([
|
|
219
|
+
",",
|
|
220
|
+
".",
|
|
221
|
+
"_",
|
|
222
|
+
"(",
|
|
223
|
+
")",
|
|
224
|
+
"[",
|
|
225
|
+
"]",
|
|
226
|
+
"{",
|
|
227
|
+
"}",
|
|
228
|
+
"<<",
|
|
229
|
+
">>",
|
|
230
|
+
"]_",
|
|
231
|
+
">>_",
|
|
232
|
+
"==",
|
|
233
|
+
"!",
|
|
234
|
+
"@",
|
|
235
|
+
":",
|
|
236
|
+
"::",
|
|
237
|
+
";",
|
|
238
|
+
"->",
|
|
239
|
+
"<-",
|
|
240
|
+
"|->",
|
|
241
|
+
"\\A",
|
|
242
|
+
"\\AA",
|
|
243
|
+
"\\E",
|
|
244
|
+
"\\EE",
|
|
245
|
+
"\\forall",
|
|
246
|
+
"\\exists",
|
|
247
|
+
"WF_",
|
|
248
|
+
"SF_",
|
|
249
|
+
"(*{",
|
|
250
|
+
"}*)",
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
const UNNAMED_STEP_LABELS = new Set(["", "*", "-"]);
|
|
254
|
+
|
|
255
|
+
const FACT_KEYWORDS = new Set([
|
|
256
|
+
"CASE",
|
|
257
|
+
"CHOOSE",
|
|
258
|
+
"DOMAIN",
|
|
259
|
+
"ENABLED",
|
|
260
|
+
"IF",
|
|
261
|
+
"LAMBDA",
|
|
262
|
+
"LET",
|
|
263
|
+
"SF_",
|
|
264
|
+
"SUBSET",
|
|
265
|
+
"UNCHANGED",
|
|
266
|
+
"UNION",
|
|
267
|
+
"WF_",
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
const NEW_SYMBOL_LEVELS = new Map<string, NewSymbol["level"]>([
|
|
271
|
+
["CONSTANT", "Constant"],
|
|
272
|
+
["CONSTANTS", "Constant"],
|
|
273
|
+
["VARIABLE", "Variable"],
|
|
274
|
+
["VARIABLES", "Variable"],
|
|
275
|
+
["STATE", "State"],
|
|
276
|
+
["ACTION", "Action"],
|
|
277
|
+
["ACTIONS", "Action"],
|
|
278
|
+
["TEMPORAL", "Temporal"],
|
|
279
|
+
["TEMPORALS", "Temporal"],
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
const NEW_SYMBOL_STARTS = new Set(["NEW", ...NEW_SYMBOL_LEVELS.keys()]);
|
|
283
|
+
|
|
284
|
+
const OPENING_BRACKETS = new Set(["(", "[", "{", "<<"]);
|
|
285
|
+
|
|
286
|
+
const CLOSING_BRACKETS = new Set([")", "]", "}", ">>", "]_", ">>_"]);
|
|
287
|
+
|
|
288
|
+
const BUILTIN_CONSTANTS = new Set(["TRUE", "FALSE", "BOOLEAN", "STRING"]);
|
|
191
289
|
|
|
192
290
|
const NO_OFFSIDE = -1;
|
|
193
291
|
|
|
194
|
-
|
|
292
|
+
const getModuleHeader = (name: string) => new RegExp(`^-{4,}\\s*MODULE\\s+${name}\\s*-{3,}$`);
|
|
293
|
+
|
|
294
|
+
// The text of a module that starts on its own line in a file, as TLC's monolith extractor reads
|
|
295
|
+
// modules that follow the root module.
|
|
296
|
+
export const extractModule = (source: string, name: string) => {
|
|
297
|
+
const lines = source.split(/\r?\n/);
|
|
298
|
+
const start = lines.findIndex((line) => getModuleHeader(name).test(line));
|
|
299
|
+
if (start === -1) return undefined;
|
|
300
|
+
const end = lines.findIndex((line, index) => index > start && line.startsWith("===="));
|
|
301
|
+
return lines.slice(start, end === -1 ? undefined : end + 1).join("\n");
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// isTlapm parses as tlapm does for proof obligations, which accepts and rejects some modules that
|
|
305
|
+
// SANY does not.
|
|
306
|
+
const parse = (source: string, isTlapm: boolean): Module => {
|
|
195
307
|
const tokens = tokenize(source);
|
|
196
|
-
const cursor = { index: 0,
|
|
197
|
-
|
|
308
|
+
const cursor = { index: 0, offsideColumns: [] as number[] };
|
|
309
|
+
// A RECURSIVE declaration covers the definitions of its own LET or module.
|
|
310
|
+
const recursiveScopes: Set<string>[] = [];
|
|
311
|
+
const factNames = new Set<string>();
|
|
312
|
+
// The NEW, TAKE, and PICK names in scope, which a DEF clause cannot cite.
|
|
313
|
+
const proofScopes: Set<string>[] = [];
|
|
314
|
+
// A fact may cite an operator of a parameterized instance without the instance's arguments.
|
|
315
|
+
const factStart = { index: -1 };
|
|
316
|
+
const openModules: Module[] = [];
|
|
317
|
+
|
|
318
|
+
const isRecursiveName = (name: string) => recursiveScopes[recursiveScopes.length - 1].has(name);
|
|
198
319
|
|
|
199
320
|
const getLocation = (token: Token): SourceLocation => ({
|
|
200
321
|
line: token.line,
|
|
201
|
-
column: token.column,
|
|
322
|
+
column: isTlapm && token.kind === "String" ? token.endColumn : token.column,
|
|
323
|
+
range: undefined,
|
|
202
324
|
});
|
|
203
325
|
|
|
326
|
+
const tokenStarts: SourceLocation[] = [];
|
|
327
|
+
const tokenEnds: SourceLocation[] = [];
|
|
328
|
+
const tokenRanges: SourceRange[] = [];
|
|
329
|
+
|
|
330
|
+
const getRange = (startIndex: number) => {
|
|
331
|
+
const endIndex = cursor.index - 1;
|
|
332
|
+
const cached = startIndex === endIndex ? tokenRanges[startIndex] : undefined;
|
|
333
|
+
if (cached) return cached;
|
|
334
|
+
const last = tokens[endIndex];
|
|
335
|
+
tokenStarts[startIndex] ??= getLocation(tokens[startIndex]);
|
|
336
|
+
tokenEnds[endIndex] ??= { line: last.line, column: last.endColumn };
|
|
337
|
+
const range = {
|
|
338
|
+
module: openModules[openModules.length - 1].name,
|
|
339
|
+
start: tokenStarts[startIndex],
|
|
340
|
+
end: tokenEnds[endIndex],
|
|
341
|
+
};
|
|
342
|
+
if (startIndex === endIndex) tokenRanges[startIndex] = range;
|
|
343
|
+
return range;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Each node gets its own location object from getLocation, so the range can be set in place.
|
|
347
|
+
const withRange = <Node extends { location: SourceLocation }>(node: Node, startIndex: number) => {
|
|
348
|
+
node.location.range = getRange(startIndex);
|
|
349
|
+
return node;
|
|
350
|
+
};
|
|
351
|
+
|
|
204
352
|
const peekRaw = (offset = 0) => tokens[Math.min(cursor.index + offset, tokens.length - 1)];
|
|
205
353
|
|
|
206
354
|
const peek = (offset = 0) => {
|
|
207
|
-
const token =
|
|
208
|
-
cursor.index + offset >= cursor.limit
|
|
209
|
-
? { ...peekRaw(offset), kind: "End" as const, text: "" }
|
|
210
|
-
: peekRaw(offset);
|
|
355
|
+
const token = peekRaw(offset);
|
|
211
356
|
const offsideColumn = cursor.offsideColumns[cursor.offsideColumns.length - 1];
|
|
212
357
|
if (offsideColumn === undefined || offsideColumn === NO_OFFSIDE || token.kind === "End") {
|
|
213
358
|
return token;
|
|
@@ -216,12 +361,80 @@ export const parseModule = (source: string): Module => {
|
|
|
216
361
|
};
|
|
217
362
|
|
|
218
363
|
const describe = (token: Token) =>
|
|
219
|
-
token.kind === "End" ? "end of input" :
|
|
364
|
+
token.kind === "End" ? "end of input" : JSON.stringify(token.text);
|
|
365
|
+
|
|
366
|
+
const unitStart = { index: 0 };
|
|
367
|
+
|
|
368
|
+
const getDefinitionEnd = (fallback: { line: number; column: number }) => {
|
|
369
|
+
const equals = tokens
|
|
370
|
+
.slice(unitStart.index, cursor.index)
|
|
371
|
+
.find((token) => token.text === "==" && token.kind !== "String");
|
|
372
|
+
return equals ? { line: equals.line, column: equals.endColumn + 1 } : fallback;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// tlapm reads TRUE, FALSE, BOOLEAN, STRING and the other keywords only as keywords.
|
|
376
|
+
const isTlapmKeyword = (token: Token) =>
|
|
377
|
+
isTlapm && (token.kind === "Keyword" || BUILTIN_CONSTANTS.has(token.text));
|
|
378
|
+
|
|
379
|
+
const getPreviousEnd = (position: { line: number; column: number }) => {
|
|
380
|
+
const previous = tokens.findLast(
|
|
381
|
+
(token) =>
|
|
382
|
+
token.kind !== "End" &&
|
|
383
|
+
(token.line < position.line ||
|
|
384
|
+
(token.line === position.line && token.column < position.column)),
|
|
385
|
+
);
|
|
386
|
+
return previous ? { line: previous.line, column: previous.endColumn + 1 } : position;
|
|
387
|
+
};
|
|
220
388
|
|
|
221
|
-
const
|
|
222
|
-
|
|
389
|
+
const getTlapmName = (token: Token) => {
|
|
390
|
+
if (/^-{4,}$/.test(token.text)) return "----";
|
|
391
|
+
if (/^={4,}$/.test(token.text)) return "====";
|
|
392
|
+
if (TLAPM_PUNCTUATION.has(token.text) || token.kind === "Number" || token.kind === "Decimal")
|
|
393
|
+
return token.text;
|
|
394
|
+
if (token.kind === "String") return JSON.stringify(token.text);
|
|
395
|
+
if (isTlapmKeyword(token)) return `keyword ${token.text}`;
|
|
396
|
+
return token.kind === "Identifier" ? `identifier ${token.text}` : `operator ${token.text}`;
|
|
397
|
+
};
|
|
223
398
|
|
|
224
|
-
const
|
|
399
|
+
const createError = (
|
|
400
|
+
message: string,
|
|
401
|
+
token: { line: number; column: number } = peekRaw(),
|
|
402
|
+
unexpected?: Token,
|
|
403
|
+
) => {
|
|
404
|
+
const end = tokens[tokens.length - 1];
|
|
405
|
+
const isEnd = token.line === end.line && token.column === end.column;
|
|
406
|
+
return new TlaSyntaxError(
|
|
407
|
+
`${message} at line ${token.line}, column ${token.column}`,
|
|
408
|
+
undefined,
|
|
409
|
+
getFailure(message, token, isEnd, unexpected),
|
|
410
|
+
);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const getFailure = (
|
|
414
|
+
message: string,
|
|
415
|
+
token: { line: number; column: number },
|
|
416
|
+
isEnd: boolean,
|
|
417
|
+
unexpected: Token | undefined,
|
|
418
|
+
) => {
|
|
419
|
+
if (isTlapm && isEnd) {
|
|
420
|
+
const end = tokens[tokens.length - 1];
|
|
421
|
+
return { reason: "Unexpected EOF", ...getDefinitionEnd(getPreviousEnd(end)), isEnd };
|
|
422
|
+
}
|
|
423
|
+
if (isTlapm && unexpected) {
|
|
424
|
+
return {
|
|
425
|
+
reason: `Unexpected ${getTlapmName(unexpected)}`,
|
|
426
|
+
...getPreviousEnd(unexpected),
|
|
427
|
+
isEnd,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return { reason: message, line: token.line, column: token.column, isEnd };
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const createUnexpected = (message: string, token: Token = peekRaw()) =>
|
|
434
|
+
createError(message, token, token);
|
|
435
|
+
|
|
436
|
+
const isFieldName = (token: Token) =>
|
|
437
|
+
(token.kind === "Identifier" || token.kind === "Keyword") && !isTlapmKeyword(token);
|
|
225
438
|
|
|
226
439
|
const isAt = (text: string, offset = 0) => {
|
|
227
440
|
const token = peek(offset);
|
|
@@ -243,15 +456,16 @@ export const parseModule = (source: string): Module => {
|
|
|
243
456
|
|
|
244
457
|
const expect = (text: string) => {
|
|
245
458
|
if (!isAt(text))
|
|
246
|
-
throw
|
|
459
|
+
throw createUnexpected(`Expected ${JSON.stringify(text)} but found ${describe(peek())}`);
|
|
247
460
|
return advance();
|
|
248
461
|
};
|
|
249
462
|
|
|
250
463
|
const expectIdentifier = () => {
|
|
251
464
|
const token = peek();
|
|
252
465
|
if (token.kind !== "Identifier") {
|
|
253
|
-
throw
|
|
466
|
+
throw createUnexpected(`Expected an identifier but found ${describe(token)}`);
|
|
254
467
|
}
|
|
468
|
+
if (isTlapmKeyword(token)) throw createUnexpected(`Unexpected keyword ${token.text}`);
|
|
255
469
|
cursor.index++;
|
|
256
470
|
return token.text;
|
|
257
471
|
};
|
|
@@ -304,7 +518,7 @@ export const parseModule = (source: string): Module => {
|
|
|
304
518
|
return { kind: "Tuple", names };
|
|
305
519
|
};
|
|
306
520
|
|
|
307
|
-
const parseBounds = ()
|
|
521
|
+
const parseBounds = () =>
|
|
308
522
|
parseSeparated(() => {
|
|
309
523
|
const patterns = parseSeparated(parsePattern);
|
|
310
524
|
expect("\\in");
|
|
@@ -315,26 +529,81 @@ export const parseModule = (source: string): Module => {
|
|
|
315
529
|
const parseSubscript = (): Expression => {
|
|
316
530
|
const token = peek();
|
|
317
531
|
if (token.kind === "Identifier") {
|
|
318
|
-
cursor.index
|
|
319
|
-
|
|
532
|
+
const start = cursor.index;
|
|
533
|
+
const names = [advance().text];
|
|
534
|
+
while (isAt("!") && peek(1).kind === "Identifier") {
|
|
535
|
+
advance();
|
|
536
|
+
names.push(expectIdentifier());
|
|
537
|
+
}
|
|
538
|
+
return withRange(
|
|
539
|
+
{ kind: "Identifier", name: names.join("!"), location: getLocation(token) },
|
|
540
|
+
start,
|
|
541
|
+
);
|
|
320
542
|
}
|
|
321
543
|
if (isAt("<<") || isAt("(")) return parsePrimary();
|
|
322
|
-
throw
|
|
544
|
+
throw createUnexpected(`Expected a subscript but found ${describe(token)}`);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const isDefinitionAhead = (operator: Token) => {
|
|
548
|
+
const token = peek(1);
|
|
549
|
+
const next = peek(2);
|
|
550
|
+
if (token.kind === "Identifier" && next.text === "==") return true;
|
|
551
|
+
if (token.line === operator.line) return false;
|
|
552
|
+
if (token.text === "-.") return true;
|
|
553
|
+
return (
|
|
554
|
+
token.kind === "Identifier" &&
|
|
555
|
+
(next.text === "(" ||
|
|
556
|
+
next.text === "[" ||
|
|
557
|
+
(next.kind === "Operator" && isAt("==", POSTFIX_OPERATORS.has(next.text) ? 3 : 4)))
|
|
558
|
+
);
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const isStandalone = (operator: Token) => {
|
|
562
|
+
if (!EXPRESSION_PREFIXES.has(operator.text)) return true;
|
|
563
|
+
const token = peek(1);
|
|
564
|
+
return (
|
|
565
|
+
token.kind === "End" ||
|
|
566
|
+
token.kind === "ModuleEnd" ||
|
|
567
|
+
token.kind === "Separator" ||
|
|
568
|
+
isAt(",", 1) ||
|
|
569
|
+
isAt(")", 1) ||
|
|
570
|
+
(token.kind === "Keyword" && UNIT_KEYWORDS.has(token.text)) ||
|
|
571
|
+
isDefinitionAhead(operator)
|
|
572
|
+
);
|
|
323
573
|
};
|
|
324
574
|
|
|
325
575
|
const parseArgument = (): Expression => {
|
|
326
576
|
const token = peek();
|
|
327
|
-
const next = peek(1);
|
|
328
577
|
const isOperatorAlone =
|
|
329
578
|
token.kind === "Operator" &&
|
|
330
579
|
(INFIX_OPERATORS.has(token.text) || PREFIX_OPERATOR_REFERENCES.has(token.text)) &&
|
|
331
|
-
(
|
|
580
|
+
isStandalone(token);
|
|
581
|
+
// tlapm reads a prefix operator as the start of an expression, and (+), (-), (/) and (\X) as
|
|
582
|
+
// the operators \oplus, \ominus, \oslash and \otimes.
|
|
583
|
+
const isTlapmIncomplete =
|
|
584
|
+
isTlapm &&
|
|
585
|
+
token.kind !== "String" &&
|
|
586
|
+
(PREFIX_OPERATOR_REFERENCES.has(token.text) ||
|
|
587
|
+
(OPERATOR_ARGUMENTS.has(token.text) &&
|
|
588
|
+
token.text !== "'" &&
|
|
589
|
+
!POSTFIX_OPERATORS.has(token.text)) ||
|
|
590
|
+
(CIRCLED_OPERATORS.has(token.text) &&
|
|
591
|
+
peek(-1).text === "(" &&
|
|
592
|
+
peek(-1).line === token.line &&
|
|
593
|
+
peek(-1).endColumn + 1 === token.column &&
|
|
594
|
+
peek(1).text === ")" &&
|
|
595
|
+
peek(1).line === token.line &&
|
|
596
|
+
peek(1).column === token.endColumn + 1));
|
|
597
|
+
if (isTlapmIncomplete && isStandalone(token)) throw createError("incomplete expression", token);
|
|
332
598
|
if (isOperatorAlone) {
|
|
333
599
|
cursor.index++;
|
|
334
|
-
return
|
|
600
|
+
return withRange(
|
|
601
|
+
{ kind: "OperatorReference", operator: token.text, location: getLocation(token) },
|
|
602
|
+
cursor.index - 1,
|
|
603
|
+
);
|
|
335
604
|
}
|
|
336
605
|
const isPrefixAlone =
|
|
337
|
-
OPERATOR_ARGUMENTS.has(token.text) && (
|
|
606
|
+
token.kind !== "String" && OPERATOR_ARGUMENTS.has(token.text) && isStandalone(token);
|
|
338
607
|
if (isPrefixAlone) {
|
|
339
608
|
cursor.index++;
|
|
340
609
|
const location = getLocation(token);
|
|
@@ -350,10 +619,12 @@ export const parseModule = (source: string): Module => {
|
|
|
350
619
|
};
|
|
351
620
|
}
|
|
352
621
|
if (isAt("LAMBDA")) {
|
|
622
|
+
const start = cursor.index;
|
|
353
623
|
advance();
|
|
354
624
|
const params = parseSeparated(expectIdentifier);
|
|
355
625
|
expect(":");
|
|
356
|
-
|
|
626
|
+
const body = parseExpression();
|
|
627
|
+
return withRange({ kind: "Lambda", params, body, location: getLocation(token) }, start);
|
|
357
628
|
}
|
|
358
629
|
return parseExpression();
|
|
359
630
|
};
|
|
@@ -378,6 +649,7 @@ export const parseModule = (source: string): Module => {
|
|
|
378
649
|
return {
|
|
379
650
|
kind: bullet.text === "/\\" ? "And" : "Or",
|
|
380
651
|
operands,
|
|
652
|
+
isBulleted: true,
|
|
381
653
|
location: getLocation(bullet),
|
|
382
654
|
};
|
|
383
655
|
};
|
|
@@ -387,7 +659,7 @@ export const parseModule = (source: string): Module => {
|
|
|
387
659
|
const location = getLocation(start);
|
|
388
660
|
if (peek().kind === "Identifier" && (isAt("|->", 1) || isAt(":", 1))) {
|
|
389
661
|
const kind = isAt("|->", 1) ? "Record" : "RecordSet";
|
|
390
|
-
const fields = parseSeparated(()
|
|
662
|
+
const fields = parseSeparated(() => {
|
|
391
663
|
const name = expectIdentifier();
|
|
392
664
|
expect(kind === "Record" ? "|->" : ":");
|
|
393
665
|
return { name, value: parseExpression() };
|
|
@@ -409,13 +681,13 @@ export const parseModule = (source: string): Module => {
|
|
|
409
681
|
return { kind: "FunctionSet", domain: expression, range, location };
|
|
410
682
|
}
|
|
411
683
|
if (accept("EXCEPT")) {
|
|
412
|
-
const updates = parseSeparated(()
|
|
684
|
+
const updates = parseSeparated(() => {
|
|
413
685
|
expect("!");
|
|
414
686
|
const path: ExceptPathStep[] = [];
|
|
415
687
|
while (isAt(".") || isAt("[")) {
|
|
416
688
|
if (accept(".")) {
|
|
417
689
|
if (!isFieldName(peek()))
|
|
418
|
-
throw
|
|
690
|
+
throw createUnexpected(`Expected a field name but found ${describe(peek())}`);
|
|
419
691
|
path.push({ kind: "Field", name: advance().text });
|
|
420
692
|
continue;
|
|
421
693
|
}
|
|
@@ -468,10 +740,9 @@ export const parseModule = (source: string): Module => {
|
|
|
468
740
|
|
|
469
741
|
const parseTuple = (start: Token): Expression => {
|
|
470
742
|
const location = getLocation(start);
|
|
471
|
-
const elements = withoutOffside(() =>
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
});
|
|
743
|
+
const elements = withoutOffside(() =>
|
|
744
|
+
isAt(">>") || isAt(">>_") ? [] : parseSeparated(() => parseExpression()),
|
|
745
|
+
);
|
|
475
746
|
if (accept(">>_")) {
|
|
476
747
|
if (elements.length !== 1)
|
|
477
748
|
throw createError("Expected a single action inside <<A>>_v", start);
|
|
@@ -492,23 +763,28 @@ export const parseModule = (source: string): Module => {
|
|
|
492
763
|
}
|
|
493
764
|
const names = parseSeparated(expectIdentifier);
|
|
494
765
|
expect(":");
|
|
495
|
-
return {
|
|
766
|
+
return {
|
|
767
|
+
kind: "UnboundedQuantifier",
|
|
768
|
+
quantifier: start.text === "\\A" || start.text === "\\AA" ? "Forall" : "Exists",
|
|
769
|
+
names,
|
|
770
|
+
body: parseExpression(),
|
|
771
|
+
isTemporal,
|
|
772
|
+
location,
|
|
773
|
+
};
|
|
496
774
|
};
|
|
497
775
|
|
|
498
776
|
const parseCase = (start: Token): Expression => {
|
|
499
777
|
const arms: CaseArm[] = [];
|
|
500
|
-
const other = { value: undefined as Expression | undefined };
|
|
501
778
|
do {
|
|
502
779
|
if (accept("OTHER")) {
|
|
503
780
|
expect("->");
|
|
504
|
-
other
|
|
505
|
-
break;
|
|
781
|
+
return { kind: "Case", arms, other: parseExpression(), location: getLocation(start) };
|
|
506
782
|
}
|
|
507
783
|
const condition = parseExpression();
|
|
508
784
|
expect("->");
|
|
509
785
|
arms.push({ condition, body: parseExpression() });
|
|
510
786
|
} while (accept("[]"));
|
|
511
|
-
return { kind: "Case", arms, other:
|
|
787
|
+
return { kind: "Case", arms, other: undefined, location: getLocation(start) };
|
|
512
788
|
};
|
|
513
789
|
|
|
514
790
|
const isSelectorAhead = () => {
|
|
@@ -527,7 +803,7 @@ export const parseModule = (source: string): Module => {
|
|
|
527
803
|
|
|
528
804
|
const parseSelector = (): Selector => {
|
|
529
805
|
const token = advance();
|
|
530
|
-
if (token.kind === "Number") return { kind: "Operand", operand:
|
|
806
|
+
if (token.kind === "Number") return { kind: "Operand", operand: getNumeralValue(token.text) };
|
|
531
807
|
switch (token.text) {
|
|
532
808
|
case "<<":
|
|
533
809
|
case ">>":
|
|
@@ -545,25 +821,52 @@ export const parseModule = (source: string): Module => {
|
|
|
545
821
|
};
|
|
546
822
|
|
|
547
823
|
const parsePrimary = (): Expression => {
|
|
824
|
+
const isFactStart = cursor.index === factStart.index;
|
|
548
825
|
const token = advance();
|
|
549
826
|
const location = getLocation(token);
|
|
550
|
-
if (token.kind === "
|
|
827
|
+
if (isTlapm && token.kind === "Keyword" && token.text === "LAMBDA") {
|
|
828
|
+
const params = parseSeparated(expectIdentifier);
|
|
829
|
+
expect(":");
|
|
830
|
+
return { kind: "Lambda", params, body: parseExpression(), location };
|
|
831
|
+
}
|
|
832
|
+
if (token.kind === "Number") {
|
|
833
|
+
if (isTlapm && /^\\[BOH]/.test(token.text)) throw createError("incomplete expression", token);
|
|
834
|
+
return { kind: "Number", value: getNumeralValue(token.text), text: token.text, location };
|
|
835
|
+
}
|
|
836
|
+
if (token.kind === "Decimal") return { kind: "Decimal", text: token.text, location };
|
|
551
837
|
if (token.kind === "String") return { kind: "String", value: token.text, location };
|
|
552
|
-
|
|
838
|
+
const isStep = token.kind === "Step" && isStepReference(token);
|
|
839
|
+
if (isStep && getProofLevel() < 0 && proofLevels.length <= 1) {
|
|
840
|
+
throw createError("Step number used outside proof", token);
|
|
841
|
+
}
|
|
842
|
+
if (isStep && token.text[1] === "+") {
|
|
843
|
+
throw createError("<+> step number used in an expression", token);
|
|
844
|
+
}
|
|
845
|
+
if (token.kind === "Identifier" || isStep) {
|
|
846
|
+
const name = isStep ? (getStepName(token) ?? token.text) : token.text;
|
|
553
847
|
if (isLabelAhead()) {
|
|
554
848
|
const params = accept("(") ? parseSeparated(expectIdentifier) : [];
|
|
555
849
|
if (params.length > 0) expect(")");
|
|
556
850
|
expect("::");
|
|
557
|
-
return { kind: "Label", name
|
|
851
|
+
return { kind: "Label", name, params, body: parseExpression(), location };
|
|
558
852
|
}
|
|
559
853
|
const hasArguments = isAt("(");
|
|
560
854
|
const args = hasArguments ? parseArguments() : [];
|
|
561
855
|
if (!isSelectorAhead()) {
|
|
562
856
|
return hasArguments
|
|
563
|
-
? { kind: "Apply", operator:
|
|
564
|
-
: { kind: "Identifier", name
|
|
857
|
+
? { kind: "Apply", operator: name, args, location }
|
|
858
|
+
: { kind: "Identifier", name, location };
|
|
565
859
|
}
|
|
566
|
-
const
|
|
860
|
+
const instance = openModules
|
|
861
|
+
.flatMap((module) => module.instances)
|
|
862
|
+
.find((candidate) => candidate.name === name);
|
|
863
|
+
if (instance && !isFactStart && !isTlapm && instance.params.length !== args.length) {
|
|
864
|
+
throw createError(
|
|
865
|
+
`The operator ${name} requires ${instance.params.length} arguments`,
|
|
866
|
+
token,
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
const selectors: Selector[] = [{ kind: "Name", name, args }];
|
|
567
870
|
while (isSelectorAhead()) {
|
|
568
871
|
advance();
|
|
569
872
|
selectors.push(parseSelector());
|
|
@@ -576,13 +879,15 @@ export const parseModule = (source: string): Module => {
|
|
|
576
879
|
const qualifiedArgs = selectors.flatMap((selector) =>
|
|
577
880
|
"args" in selector ? selector.args : [],
|
|
578
881
|
);
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
882
|
+
if (qualifiedArgs.length === 0) return { kind: "Identifier", name: operator, location };
|
|
883
|
+
const argumentCounts = selectors.map((selector) =>
|
|
884
|
+
"args" in selector ? selector.args.length : 0,
|
|
885
|
+
);
|
|
886
|
+
return argumentCounts.slice(0, -1).some((count) => count > 0)
|
|
887
|
+
? { kind: "Apply", operator, args: qualifiedArgs, argumentCounts, location }
|
|
888
|
+
: { kind: "Apply", operator, args: qualifiedArgs, location };
|
|
582
889
|
}
|
|
583
|
-
|
|
584
|
-
INFIX_OPERATORS.has(token.text) || POSTFIX_OPERATORS.has(token.text) || token.text === "-.";
|
|
585
|
-
if (token.kind === "Operator" && isOperatorName && isAt("("))
|
|
890
|
+
if (token.kind === "Operator" && isOperatorName(token.text) && isAt("("))
|
|
586
891
|
return { kind: "Apply", operator: token.text, args: parseArguments(), location };
|
|
587
892
|
switch (token.text) {
|
|
588
893
|
case "(": {
|
|
@@ -615,15 +920,19 @@ export const parseModule = (source: string): Module => {
|
|
|
615
920
|
case "LET": {
|
|
616
921
|
const definitions: Definition[] = [];
|
|
617
922
|
const instances: Instance[] = [];
|
|
923
|
+
recursiveScopes.push(new Set());
|
|
618
924
|
while (!accept("IN")) {
|
|
925
|
+
if (isTlapm && isAt("RECURSIVE"))
|
|
926
|
+
throw createError("Unexpected keyword RECURSIVE", getDefinitionEnd(peekRaw()));
|
|
619
927
|
if (accept("RECURSIVE")) {
|
|
620
|
-
|
|
928
|
+
declareRecursive();
|
|
621
929
|
continue;
|
|
622
930
|
}
|
|
623
931
|
const unit = parseDefinition(false);
|
|
624
932
|
if (unit.kind === "Instance") instances.push(unit);
|
|
625
933
|
else definitions.push(unit);
|
|
626
934
|
}
|
|
935
|
+
recursiveScopes.pop();
|
|
627
936
|
const body = parseExpression();
|
|
628
937
|
return instances.length === 0
|
|
629
938
|
? { kind: "Let", definitions, body, location }
|
|
@@ -652,7 +961,7 @@ export const parseModule = (source: string): Module => {
|
|
|
652
961
|
};
|
|
653
962
|
}
|
|
654
963
|
}
|
|
655
|
-
throw
|
|
964
|
+
throw createUnexpected(`Unexpected ${describe(token)}`, token);
|
|
656
965
|
};
|
|
657
966
|
|
|
658
967
|
const parsePrefix = (): Expression => {
|
|
@@ -692,8 +1001,10 @@ export const parseModule = (source: string): Module => {
|
|
|
692
1001
|
};
|
|
693
1002
|
|
|
694
1003
|
const parseExpression = (minimumPrecedence = 0): Expression => {
|
|
1004
|
+
const start = cursor.index;
|
|
1005
|
+
const prefix = parsePrefix();
|
|
695
1006
|
const state = {
|
|
696
|
-
left:
|
|
1007
|
+
left: prefix.location.range ? prefix : withRange(prefix, start),
|
|
697
1008
|
previous: undefined as (InfixOperator & { text: string }) | undefined,
|
|
698
1009
|
};
|
|
699
1010
|
while (true) {
|
|
@@ -702,24 +1013,31 @@ export const parseModule = (source: string): Module => {
|
|
|
702
1013
|
const location = getLocation(token);
|
|
703
1014
|
if (token.text === "'" && minimumPrecedence <= 15) {
|
|
704
1015
|
advance();
|
|
705
|
-
state.left = { kind: "Prime", expression: state.left, location };
|
|
1016
|
+
state.left = withRange({ kind: "Prime", expression: state.left, location }, start);
|
|
706
1017
|
continue;
|
|
707
1018
|
}
|
|
708
1019
|
if (POSTFIX_OPERATORS.has(token.text) && minimumPrecedence <= 15) {
|
|
709
1020
|
advance();
|
|
710
|
-
state.left =
|
|
1021
|
+
state.left = withRange(
|
|
1022
|
+
{ kind: "Apply", operator: token.text, args: [state.left], location },
|
|
1023
|
+
start,
|
|
1024
|
+
);
|
|
711
1025
|
continue;
|
|
712
1026
|
}
|
|
713
1027
|
if (token.text === "[") {
|
|
714
1028
|
advance();
|
|
715
1029
|
const args = withoutOffside(() => parseSeparated(() => parseExpression()));
|
|
716
1030
|
withoutOffside(() => expect("]"));
|
|
717
|
-
state.left =
|
|
1031
|
+
state.left = withRange(
|
|
1032
|
+
{ kind: "FunctionApplication", function: state.left, args, location },
|
|
1033
|
+
start,
|
|
1034
|
+
);
|
|
718
1035
|
continue;
|
|
719
1036
|
}
|
|
720
1037
|
if (token.text === "." && isFieldName(peek(1))) {
|
|
721
1038
|
advance();
|
|
722
|
-
|
|
1039
|
+
const field = advance().text;
|
|
1040
|
+
state.left = withRange({ kind: "FieldAccess", record: state.left, field, location }, start);
|
|
723
1041
|
continue;
|
|
724
1042
|
}
|
|
725
1043
|
const infix = INFIX_OPERATORS.get(token.text);
|
|
@@ -752,44 +1070,59 @@ export const parseModule = (source: string): Module => {
|
|
|
752
1070
|
} else {
|
|
753
1071
|
state.left = { kind: "Apply", operator: token.text, args: [left, right], location };
|
|
754
1072
|
}
|
|
1073
|
+
state.left = withRange(state.left, start);
|
|
755
1074
|
state.previous = { ...infix, text: token.text };
|
|
756
1075
|
}
|
|
757
1076
|
return state.left;
|
|
758
1077
|
};
|
|
759
1078
|
|
|
760
|
-
const parseParams = ()
|
|
1079
|
+
const parseParams = () => {
|
|
761
1080
|
if (!accept("(")) return [];
|
|
762
1081
|
const params = parseDeclarations();
|
|
763
1082
|
expect(")");
|
|
764
1083
|
return params;
|
|
765
1084
|
};
|
|
766
1085
|
|
|
1086
|
+
const parseBody = () => {
|
|
1087
|
+
const start = getLocation(peek());
|
|
1088
|
+
const body = parseExpression();
|
|
1089
|
+
const last = tokens[cursor.index - 1];
|
|
1090
|
+
const end = { line: last.line, column: last.endColumn };
|
|
1091
|
+
return { body, bodyRange: { module: openModules[openModules.length - 1].name, start, end } };
|
|
1092
|
+
};
|
|
1093
|
+
|
|
767
1094
|
const parseDefinition = (isLocal: boolean): Definition | Instance => {
|
|
1095
|
+
const start = cursor.index;
|
|
1096
|
+
return withRange(parseDefinitionUnit(isLocal), start);
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
const parseDefinitionUnit = (isLocal: boolean): Definition | Instance => {
|
|
768
1100
|
const token = peek();
|
|
769
1101
|
const location = getLocation(token);
|
|
1102
|
+
const parseOperator = (name: string, params: OperatorParam[]): Definition => ({
|
|
1103
|
+
kind: "Operator",
|
|
1104
|
+
name,
|
|
1105
|
+
params,
|
|
1106
|
+
...parseBody(),
|
|
1107
|
+
isLocal,
|
|
1108
|
+
isRecursive: isRecursiveName(name),
|
|
1109
|
+
location,
|
|
1110
|
+
});
|
|
770
1111
|
if (
|
|
771
1112
|
token.kind === "Identifier" &&
|
|
772
1113
|
peek(1).kind === "Operator" &&
|
|
773
|
-
INFIX_OPERATORS.has(peek(1).text)
|
|
1114
|
+
INFIX_OPERATORS.has(peek(1).text) &&
|
|
1115
|
+
peek(2).kind === "Identifier" &&
|
|
1116
|
+
isAt("==", 3)
|
|
774
1117
|
) {
|
|
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
|
-
}
|
|
1118
|
+
const left = expectIdentifier();
|
|
1119
|
+
const operator = advance().text;
|
|
1120
|
+
const right = expectIdentifier();
|
|
1121
|
+
expect("==");
|
|
1122
|
+
return parseOperator(operator, [
|
|
1123
|
+
{ name: left, arity: 0 },
|
|
1124
|
+
{ name: right, arity: 0 },
|
|
1125
|
+
]);
|
|
793
1126
|
}
|
|
794
1127
|
const isPrefixDefinition = token.kind === "Operator" && token.text === "-.";
|
|
795
1128
|
const isPostfixDefinition =
|
|
@@ -799,15 +1132,7 @@ export const parseModule = (source: string): Module => {
|
|
|
799
1132
|
cursor.index += isPrefixDefinition ? 1 : 2;
|
|
800
1133
|
const param = isPrefixDefinition ? expectIdentifier() : token.text;
|
|
801
1134
|
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
|
-
};
|
|
1135
|
+
return parseOperator(operator, [{ name: param, arity: 0 }]);
|
|
811
1136
|
}
|
|
812
1137
|
const name = expectIdentifier();
|
|
813
1138
|
if (accept("[")) {
|
|
@@ -820,22 +1145,18 @@ export const parseModule = (source: string): Module => {
|
|
|
820
1145
|
bounds,
|
|
821
1146
|
body: parseExpression(),
|
|
822
1147
|
isLocal,
|
|
823
|
-
isRecursive:
|
|
1148
|
+
isRecursive: isRecursiveName(name),
|
|
824
1149
|
location,
|
|
825
1150
|
};
|
|
826
1151
|
}
|
|
1152
|
+
const definitionStart = cursor.index;
|
|
827
1153
|
const params = parseParams();
|
|
828
1154
|
expect("==");
|
|
829
|
-
if (isAt("INSTANCE"))
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
body: parseExpression(),
|
|
835
|
-
isLocal,
|
|
836
|
-
isRecursive: recursiveNames.has(name),
|
|
837
|
-
location,
|
|
838
|
-
};
|
|
1155
|
+
if (isAt("INSTANCE")) {
|
|
1156
|
+
const instance = parseInstance(name, params, isLocal, location);
|
|
1157
|
+
return { ...instance, definitionRange: getRange(definitionStart) };
|
|
1158
|
+
}
|
|
1159
|
+
return parseOperator(name, params);
|
|
839
1160
|
};
|
|
840
1161
|
|
|
841
1162
|
const parseInstance = (
|
|
@@ -844,145 +1165,577 @@ export const parseModule = (source: string): Module => {
|
|
|
844
1165
|
isLocal: boolean,
|
|
845
1166
|
location: SourceLocation,
|
|
846
1167
|
): Instance => {
|
|
1168
|
+
const start = cursor.index;
|
|
847
1169
|
expect("INSTANCE");
|
|
848
1170
|
const module = expectIdentifier();
|
|
849
1171
|
const substitutions = accept("WITH")
|
|
850
|
-
? parseSeparated(()
|
|
1172
|
+
? parseSeparated(() => {
|
|
851
1173
|
const target = advance().text;
|
|
852
1174
|
expect("<-");
|
|
853
1175
|
return { name: target, expression: parseArgument() };
|
|
854
1176
|
})
|
|
855
1177
|
: [];
|
|
856
|
-
return {
|
|
1178
|
+
return {
|
|
1179
|
+
kind: "Instance",
|
|
1180
|
+
name,
|
|
1181
|
+
params,
|
|
1182
|
+
module,
|
|
1183
|
+
substitutions,
|
|
1184
|
+
isLocal,
|
|
1185
|
+
location,
|
|
1186
|
+
instanceRange: getRange(start),
|
|
1187
|
+
};
|
|
857
1188
|
};
|
|
858
1189
|
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1190
|
+
const parseDeclaration = (): Declaration => {
|
|
1191
|
+
const location = getLocation(peek());
|
|
1192
|
+
if (isAt("_") && peek(1).kind === "Operator") {
|
|
1193
|
+
advance();
|
|
1194
|
+
const name = advance().text;
|
|
1195
|
+
return { name, arity: accept("_") ? 2 : 1, location };
|
|
1196
|
+
}
|
|
1197
|
+
if (peek().kind === "Operator" && isAt("_", 1)) {
|
|
1198
|
+
const operator = advance().text;
|
|
1199
|
+
advance();
|
|
1200
|
+
return { name: operator === "-" ? "-." : operator, arity: 1, location };
|
|
1201
|
+
}
|
|
1202
|
+
const name = expectIdentifier();
|
|
1203
|
+
if (!accept("(")) return { name, arity: 0, location };
|
|
1204
|
+
const placeholders = parseSeparated(() => expect("_"));
|
|
1205
|
+
expect(")");
|
|
1206
|
+
return { name, arity: placeholders.length, location };
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
const parseDeclarations = () => parseSeparated(parseDeclaration);
|
|
1210
|
+
|
|
1211
|
+
const declareRecursive = () => {
|
|
1212
|
+
const declarations = parseDeclarations();
|
|
1213
|
+
declarations.forEach((declaration) =>
|
|
1214
|
+
recursiveScopes[recursiveScopes.length - 1].add(declaration.name),
|
|
1215
|
+
);
|
|
1216
|
+
return declarations;
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
const getClosingOffset = (offset: number) => {
|
|
1220
|
+
const depth = { value: 0, offset };
|
|
1221
|
+
do {
|
|
1222
|
+
const token = peek(depth.offset);
|
|
1223
|
+
if (token.kind === "End") return depth.offset;
|
|
1224
|
+
if (token.kind === "Operator" && OPENING_BRACKETS.has(token.text)) depth.value++;
|
|
1225
|
+
if (token.kind === "Operator" && CLOSING_BRACKETS.has(token.text)) depth.value--;
|
|
1226
|
+
depth.offset++;
|
|
1227
|
+
} while (depth.value > 0);
|
|
1228
|
+
return depth.offset;
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
// SANY inserts a break before each definition, which ends the fact list or step before it.
|
|
1232
|
+
const isDefinitionStart = () => {
|
|
1233
|
+
const token = peek();
|
|
1234
|
+
const next = peek(1);
|
|
1235
|
+
if (token.kind !== "Identifier") {
|
|
1236
|
+
return isAt("-.") && next.kind === "Identifier" && isAt("==", 2);
|
|
1237
|
+
}
|
|
1238
|
+
if (isAt("(", 1) || isAt("[", 1)) return isAt("==", getClosingOffset(1));
|
|
1239
|
+
if (next.kind === "Operator" && POSTFIX_OPERATORS.has(next.text)) return isAt("==", 2);
|
|
1240
|
+
if (next.kind === "Operator" && INFIX_OPERATORS.has(next.text)) {
|
|
1241
|
+
return peek(2).kind === "Identifier" && isAt("==", 3);
|
|
1242
|
+
}
|
|
1243
|
+
return isAt("==", 1);
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
// SANY's proof levels, one for each open proof, which stay -1 until a step sets them.
|
|
1247
|
+
const proofLevels: number[] = [];
|
|
1248
|
+
|
|
1249
|
+
const getProofLevel = () => proofLevels[proofLevels.length - 1] ?? -1;
|
|
1250
|
+
|
|
1251
|
+
const getStepLevel = (step: Token) => {
|
|
1252
|
+
if (step.text[1] === "*") return -1;
|
|
1253
|
+
if (step.text[1] === "+") return -2;
|
|
1254
|
+
return Number(step.text.slice(1, step.text.indexOf(">")));
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
const getStepLabel = (step: Token) => {
|
|
1258
|
+
const label = step.text.slice(step.text.indexOf(">") + 1);
|
|
1259
|
+
const end = { value: label.length };
|
|
1260
|
+
while (label[end.value - 1] === ".") end.value--;
|
|
1261
|
+
return label.slice(0, end.value);
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
const isStepReference = (step: Token) =>
|
|
1265
|
+
!step.text.endsWith(".") && !UNNAMED_STEP_LABELS.has(getStepLabel(step));
|
|
1266
|
+
|
|
1267
|
+
const getStepName = (step: Token) => {
|
|
1268
|
+
const label = getStepLabel(step);
|
|
1269
|
+
if (UNNAMED_STEP_LABELS.has(label)) return undefined;
|
|
1270
|
+
const level = getStepLevel(step);
|
|
1271
|
+
if (level >= 0) return `<${level}>${label}`;
|
|
1272
|
+
const current = getProofLevel();
|
|
1273
|
+
const outer = proofLevels[proofLevels.length - 2];
|
|
1274
|
+
return `<${current < 0 && outer !== undefined ? outer : current}>${label}`;
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
const isProofStart = (token: Token) => {
|
|
1278
|
+
if (token.kind === "Keyword") return PROOF_STARTS.has(token.text);
|
|
1279
|
+
if (token.kind !== "Step") return false;
|
|
1280
|
+
if (token.text[1] === "*") return proofLevels.length === 0;
|
|
1281
|
+
if (token.text[1] === "+" || proofLevels.length === 0) return true;
|
|
1282
|
+
const level = getProofLevel();
|
|
1283
|
+
return level >= 0 && getStepLevel(token) > level;
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
const isCorrectLevel = (step: Token) => {
|
|
1287
|
+
const level = getStepLevel(step);
|
|
1288
|
+
const top = proofLevels.length - 1;
|
|
1289
|
+
const outer = proofLevels[top - 1] ?? -1;
|
|
1290
|
+
const isFirst = proofLevels[top] < 0;
|
|
1291
|
+
if (isFirst) proofLevels[top] = level < 0 ? outer + 1 : level;
|
|
1292
|
+
if (level === -1) return true;
|
|
1293
|
+
if (level === -2) return isFirst;
|
|
1294
|
+
return isFirst ? level > outer : proofLevels[top] === level;
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
const isDeclared = (name: string) =>
|
|
1298
|
+
!factNames.has(name) &&
|
|
1299
|
+
openModules.some(
|
|
1300
|
+
(module) =>
|
|
1301
|
+
module.constants.some((constant) => constant.name === name) ||
|
|
1302
|
+
module.variables.some((variable) => variable.name === name) ||
|
|
1303
|
+
module.definitions.some((definition) => definition.name === name),
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
const isHideFact = (fact: Fact) => {
|
|
1307
|
+
if (fact.kind === "ModuleFact" || fact.kind === "Subexpression") return true;
|
|
1308
|
+
if (fact.kind === "Apply") return fact.operator.includes("!");
|
|
1309
|
+
if (fact.kind !== "Identifier") return false;
|
|
1310
|
+
if (fact.name.startsWith("<") || fact.name.includes("!")) return true;
|
|
1311
|
+
return !BUILTIN_CONSTANTS.has(fact.name) && !isDeclared(fact.name);
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
const isFactAhead = () => {
|
|
1315
|
+
const token = peek();
|
|
1316
|
+
if (token.kind === "Step") return isStepReference(token);
|
|
1317
|
+
if (token.kind === "Keyword") return FACT_KEYWORDS.has(token.text);
|
|
1318
|
+
if (token.kind === "End" || token.kind === "ModuleEnd" || token.kind === "Separator") {
|
|
1319
|
+
return false;
|
|
1320
|
+
}
|
|
1321
|
+
return !isDefinitionStart();
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
// SANY reads an infix or postfix operator alone as its name, but a prefix operator such as - as the
|
|
1325
|
+
// start of an expression.
|
|
1326
|
+
const parseFact = (): Fact => {
|
|
1327
|
+
const start = cursor.index;
|
|
1328
|
+
factStart.index = start;
|
|
1329
|
+
const token = peek();
|
|
1330
|
+
if (
|
|
1331
|
+
token.kind === "Operator" &&
|
|
1332
|
+
!EXPRESSION_PREFIXES.has(token.text) &&
|
|
1333
|
+
isOperatorName(token.text)
|
|
1334
|
+
) {
|
|
1335
|
+
cursor.index++;
|
|
1336
|
+
return withRange(
|
|
1337
|
+
{ kind: "OperatorReference", operator: token.text, location: getLocation(token) },
|
|
1338
|
+
start,
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
if (!accept("MODULE")) return parseExpression();
|
|
1342
|
+
return withRange(
|
|
1343
|
+
{ kind: "ModuleFact", name: expectIdentifier(), location: getLocation(token) },
|
|
1344
|
+
start,
|
|
1345
|
+
);
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
const declareProofSymbol = (name: string) => proofScopes[proofScopes.length - 1]?.add(name);
|
|
1349
|
+
|
|
1350
|
+
const withProofScope = <Result>(parse: () => Result) => {
|
|
1351
|
+
proofScopes.push(new Set());
|
|
1352
|
+
const result = parse();
|
|
1353
|
+
proofScopes.pop();
|
|
1354
|
+
return result;
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
const isDeclaredSymbol = (name: string) =>
|
|
1358
|
+
proofScopes.some((scope) => scope.has(name)) ||
|
|
1359
|
+
openModules.some(
|
|
1360
|
+
(module) =>
|
|
1361
|
+
module.constants.some((constant) => constant.name === name) ||
|
|
1362
|
+
module.variables.some((variable) => variable.name === name),
|
|
1363
|
+
);
|
|
1364
|
+
|
|
1365
|
+
// tlapm's definable: names and operators joined by !.
|
|
1366
|
+
const parseDefinable = (): Fact => {
|
|
1367
|
+
const start = cursor.index;
|
|
1368
|
+
const token = peek();
|
|
1369
|
+
const parts = parseSeparated(() => {
|
|
1370
|
+
const part = peek();
|
|
1371
|
+
const isName = part.kind === "Identifier" && !isTlapmKeyword(part);
|
|
1372
|
+
if (!isName && part.kind !== "Operator") {
|
|
1373
|
+
throw createUnexpected(`Expected a definition name but found ${describe(part)}`, part);
|
|
870
1374
|
}
|
|
1375
|
+
cursor.index++;
|
|
1376
|
+
return part.text;
|
|
1377
|
+
}, "!");
|
|
1378
|
+
const location = getLocation(token);
|
|
1379
|
+
return withRange(
|
|
1380
|
+
parts.length === 1 && token.kind === "Operator"
|
|
1381
|
+
? { kind: "OperatorReference", operator: token.text, location }
|
|
1382
|
+
: { kind: "Identifier", name: parts.join("!"), location },
|
|
1383
|
+
start,
|
|
1384
|
+
);
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
const parseDefs = () => {
|
|
1388
|
+
if (isTlapm) return parseSeparated(parseDefinable);
|
|
1389
|
+
const defs = parseSeparated(parseFact);
|
|
1390
|
+
const invalid = defs.find((def) => def.kind === "Identifier" && isDeclaredSymbol(def.name));
|
|
1391
|
+
if (invalid) {
|
|
1392
|
+
throw createError("DEF clause entry should describe a defined operator", invalid.location);
|
|
1393
|
+
}
|
|
1394
|
+
return defs;
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
const parseFacts = () => ({
|
|
1398
|
+
facts: isFactAhead() ? parseSeparated(parseFact) : [],
|
|
1399
|
+
defs: accept("DEF") || accept("DEFS") ? parseDefs() : [],
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
const parseUseOrHide = (): UseOrHide => {
|
|
1403
|
+
const start = cursor.index;
|
|
1404
|
+
const keyword = advance();
|
|
1405
|
+
const isOnly = keyword.text === "USE" && accept("ONLY");
|
|
1406
|
+
const keywordRange = getRange(start);
|
|
1407
|
+
const use: UseOrHide = {
|
|
1408
|
+
kind: keyword.text === "USE" ? "Use" : "Hide",
|
|
1409
|
+
isOnly,
|
|
1410
|
+
...parseFacts(),
|
|
1411
|
+
keyword: keywordRange,
|
|
1412
|
+
location: getLocation(keyword),
|
|
1413
|
+
};
|
|
1414
|
+
const invalid =
|
|
1415
|
+
use.kind === "Hide" && !isTlapm ? use.facts.find((fact) => !isHideFact(fact)) : undefined;
|
|
1416
|
+
if (invalid) {
|
|
1417
|
+
throw createError(
|
|
1418
|
+
"The only expression allowed as a fact in a HIDE is the name of a theorem, assumption, or step",
|
|
1419
|
+
invalid.location,
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
return withRange(use, start);
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
const parseNewSymbol = (): NewSymbol => {
|
|
1426
|
+
const start = cursor.index;
|
|
1427
|
+
const location = getLocation(peek());
|
|
1428
|
+
const isNew = accept("NEW");
|
|
1429
|
+
const level = peek().kind === "Keyword" ? NEW_SYMBOL_LEVELS.get(peek().text) : undefined;
|
|
1430
|
+
if (!isNew && level === undefined) {
|
|
1431
|
+
throw createUnexpected(`Expected a declaration but found ${describe(peek())}`);
|
|
1432
|
+
}
|
|
1433
|
+
if (level !== undefined) advance();
|
|
1434
|
+
if (level === "Variable") {
|
|
871
1435
|
const name = expectIdentifier();
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1436
|
+
declareProofSymbol(name);
|
|
1437
|
+
return withRange(
|
|
1438
|
+
{ kind: "NewSymbol", name, arity: 0, level, set: undefined, location },
|
|
1439
|
+
start,
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
const declaration = parseDeclaration();
|
|
1443
|
+
declareProofSymbol(declaration.name);
|
|
1444
|
+
const isBounded = (level ?? "Constant") === "Constant" && isAt("\\in");
|
|
1445
|
+
if (isBounded && declaration.arity > 0) {
|
|
1446
|
+
throw createError("declared symbol with arguments before \\in");
|
|
1447
|
+
}
|
|
1448
|
+
if (isBounded) advance();
|
|
1449
|
+
return withRange(
|
|
1450
|
+
{
|
|
1451
|
+
kind: "NewSymbol",
|
|
1452
|
+
name: declaration.name,
|
|
1453
|
+
arity: declaration.arity,
|
|
1454
|
+
level: level ?? "Constant",
|
|
1455
|
+
set: isBounded ? parseExpression() : undefined,
|
|
1456
|
+
location,
|
|
1457
|
+
},
|
|
1458
|
+
start,
|
|
1459
|
+
);
|
|
1460
|
+
};
|
|
877
1461
|
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1462
|
+
const parseAssumeProve = (): AssumeProve => {
|
|
1463
|
+
const start = cursor.index;
|
|
1464
|
+
const location = getLocation(expect("ASSUME"));
|
|
1465
|
+
const assumptions = parseSeparated(() => {
|
|
1466
|
+
if (isAt("ASSUME")) return withProofScope(parseAssumeProve);
|
|
1467
|
+
if (peek().kind === "Keyword" && NEW_SYMBOL_STARTS.has(peek().text)) return parseNewSymbol();
|
|
1468
|
+
return parseExpression();
|
|
1469
|
+
});
|
|
1470
|
+
expect("PROVE");
|
|
1471
|
+
return withRange(
|
|
1472
|
+
{ kind: "AssumeProve", assumptions, conclusion: parseExpression(), location },
|
|
1473
|
+
start,
|
|
885
1474
|
);
|
|
886
1475
|
};
|
|
887
1476
|
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
cursor.index++;
|
|
1477
|
+
const isNameListAhead = (terminator: string) => {
|
|
1478
|
+
const offset = { value: 0 };
|
|
1479
|
+
while (peek(offset.value).kind === "Identifier" && isAt(",", offset.value + 1)) {
|
|
1480
|
+
offset.value += 2;
|
|
893
1481
|
}
|
|
1482
|
+
return peek(offset.value).kind === "Identifier" && isAt(terminator, offset.value + 1);
|
|
894
1483
|
};
|
|
895
1484
|
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
token.text === "<" && tokens[index + 1]?.kind === "Number" && tokens[index + 2]?.text === ">"
|
|
1485
|
+
const declareProofSymbols = (names: string[], bounds: Bound[]) => {
|
|
1486
|
+
names.forEach(declareProofSymbol);
|
|
1487
|
+
bounds.forEach((bound) =>
|
|
1488
|
+
(bound.pattern.kind === "Name" ? [bound.pattern.name] : bound.pattern.names).forEach(
|
|
1489
|
+
declareProofSymbol,
|
|
1490
|
+
),
|
|
903
1491
|
);
|
|
904
1492
|
};
|
|
905
1493
|
|
|
906
|
-
const
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
if (
|
|
910
|
-
|
|
911
|
-
return undefined;
|
|
1494
|
+
const parseStepBody = (): StepBody => {
|
|
1495
|
+
const token = peek();
|
|
1496
|
+
if (isAt("USE") || isAt("HIDE")) return parseUseOrHide();
|
|
1497
|
+
if (isAt("INSTANCE")) {
|
|
1498
|
+
return withRange(parseInstance(undefined, [], false, getLocation(token)), cursor.index);
|
|
912
1499
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
if (
|
|
1500
|
+
if (isAt("DEFINE") || isDefinitionStart()) {
|
|
1501
|
+
const start = cursor.index;
|
|
1502
|
+
accept("DEFINE");
|
|
1503
|
+
const definitions = [parseDefinition(false)];
|
|
1504
|
+
while (isDefinitionStart()) definitions.push(parseDefinition(false));
|
|
1505
|
+
const define: DefineStep = { kind: "Define", definitions, location: getLocation(token) };
|
|
1506
|
+
return withRange(define, start);
|
|
1507
|
+
}
|
|
1508
|
+
if (accept("HAVE")) return { kind: "Have", expression: parseExpression() };
|
|
1509
|
+
if (accept("TAKE")) {
|
|
1510
|
+
const isBounded = isBoundAhead();
|
|
1511
|
+
const names = isBounded ? [] : parseSeparated(expectIdentifier);
|
|
1512
|
+
const bounds = isBounded ? parseBounds() : [];
|
|
1513
|
+
declareProofSymbols(names, bounds);
|
|
1514
|
+
return { kind: "Take", names, bounds };
|
|
1515
|
+
}
|
|
1516
|
+
if (accept("WITNESS")) {
|
|
1517
|
+
return { kind: "Witness", expressions: parseSeparated(() => parseExpression()) };
|
|
1518
|
+
}
|
|
1519
|
+
if (accept("PICK")) {
|
|
1520
|
+
const isNames = isNameListAhead(":");
|
|
1521
|
+
const names = isNames ? parseSeparated(expectIdentifier) : [];
|
|
1522
|
+
const bounds = isNames ? [] : parseBounds();
|
|
1523
|
+
declareProofSymbols(names, bounds);
|
|
1524
|
+
expect(":");
|
|
1525
|
+
return { kind: "Pick", names, bounds, body: parseExpression() };
|
|
1526
|
+
}
|
|
1527
|
+
if (accept("CASE")) return { kind: "Case", expression: parseExpression() };
|
|
923
1528
|
return {
|
|
924
|
-
kind: "
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
body,
|
|
928
|
-
isLocal: false,
|
|
929
|
-
isRecursive: false,
|
|
930
|
-
location: getLocation(start),
|
|
1529
|
+
kind: "Assert",
|
|
1530
|
+
isSuffices: accept("SUFFICES"),
|
|
1531
|
+
statement: isAt("ASSUME") ? parseAssumeProve() : parseExpression(),
|
|
931
1532
|
};
|
|
932
1533
|
};
|
|
933
1534
|
|
|
1535
|
+
const parseStepStart = (isQed: boolean) => {
|
|
1536
|
+
const step = peek();
|
|
1537
|
+
if (step.kind !== "Step") {
|
|
1538
|
+
throw createUnexpected(`Expected a step number but found ${describe(step)}`, step);
|
|
1539
|
+
}
|
|
1540
|
+
if (!isCorrectLevel(step)) {
|
|
1541
|
+
throw createError(`${isQed ? "QED step" : "step"}'s number has bad level`, step);
|
|
1542
|
+
}
|
|
1543
|
+
if (getStepLevel(step) < 0 && getStepName(step) !== undefined) {
|
|
1544
|
+
throw createError("<*> and <+> cannot be used for a named step", step);
|
|
1545
|
+
}
|
|
1546
|
+
cursor.index++;
|
|
1547
|
+
return { name: getStepName(step), token: step.text, location: getLocation(step) };
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
// The names an assertion assumes stay in its own proof, and other steps declare theirs for the rest
|
|
1551
|
+
// of the enclosing proof.
|
|
1552
|
+
const parseStep = (): ProofStep => {
|
|
1553
|
+
const start = cursor.index;
|
|
1554
|
+
const step = parseStepStart(isAt("QED", 1));
|
|
1555
|
+
const scope = new Set<string>();
|
|
1556
|
+
proofScopes.push(scope);
|
|
1557
|
+
const body: StepBody = accept("QED") ? { kind: "Qed" } : parseStepBody();
|
|
1558
|
+
const head = getRange(start);
|
|
1559
|
+
const hasProof = isProofStart(peek());
|
|
1560
|
+
if (hasProof && !PROVABLE_STEPS.has(body.kind)) {
|
|
1561
|
+
throw createError("Proof of a step that does not take a proof");
|
|
1562
|
+
}
|
|
1563
|
+
const proof = hasProof ? parseProof() : undefined;
|
|
1564
|
+
proofScopes.pop();
|
|
1565
|
+
if (body.kind !== "Assert" || body.isSuffices) scope.forEach(declareProofSymbol);
|
|
1566
|
+
return withRange({ ...step, body, head, proof }, start);
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
const parseProofBody = (): Proof => {
|
|
1570
|
+
const location = getLocation(peek());
|
|
1571
|
+
if (isAt("BY") || (isAt("PROOF") && isAt("BY", 1))) {
|
|
1572
|
+
accept("PROOF");
|
|
1573
|
+
advance();
|
|
1574
|
+
const keyword = getRange(cursor.index - 1);
|
|
1575
|
+
return { kind: "By", isOnly: accept("ONLY"), ...parseFacts(), keyword, location };
|
|
1576
|
+
}
|
|
1577
|
+
const terminal = peek(isAt("PROOF") ? 1 : 0);
|
|
1578
|
+
if (
|
|
1579
|
+
terminal.kind === "Keyword" &&
|
|
1580
|
+
(terminal.text === "OBVIOUS" || terminal.text === "OMITTED")
|
|
1581
|
+
) {
|
|
1582
|
+
accept("PROOF");
|
|
1583
|
+
advance();
|
|
1584
|
+
const keyword = getRange(cursor.index - 1);
|
|
1585
|
+
return { kind: terminal.text === "OBVIOUS" ? "Obvious" : "Omitted", keyword, location };
|
|
1586
|
+
}
|
|
1587
|
+
accept("PROOF");
|
|
1588
|
+
const steps = [parseStep()];
|
|
1589
|
+
while (steps[steps.length - 1].body.kind !== "Qed") steps.push(parseStep());
|
|
1590
|
+
return { kind: "Steps", level: getProofLevel(), steps, location };
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
const parseProof = (): Proof => {
|
|
1594
|
+
const start = cursor.index;
|
|
1595
|
+
proofLevels.push(-1);
|
|
1596
|
+
const proof = withProofScope(parseProofBody);
|
|
1597
|
+
proofLevels.pop();
|
|
1598
|
+
return withRange(proof, start);
|
|
1599
|
+
};
|
|
1600
|
+
|
|
1601
|
+
const parseTheorem = (module: Module) => {
|
|
1602
|
+
const start = cursor.index;
|
|
1603
|
+
const keyword = advance();
|
|
1604
|
+
const isNamed = peek().kind === "Identifier" && isAt("==", 1);
|
|
1605
|
+
const name = isNamed ? expectIdentifier() : undefined;
|
|
1606
|
+
if (name !== undefined) {
|
|
1607
|
+
factNames.add(name);
|
|
1608
|
+
advance();
|
|
1609
|
+
}
|
|
1610
|
+
const parsed = withProofScope(() => {
|
|
1611
|
+
const statement = isAt("ASSUME") ? parseAssumeProve() : parseExpression();
|
|
1612
|
+
return { statement, proof: isProofStart(peek()) ? parseProof() : undefined };
|
|
1613
|
+
});
|
|
1614
|
+
const statement = parsed.statement;
|
|
1615
|
+
const theorem = withRange(
|
|
1616
|
+
{ name, statement, proof: parsed.proof, location: getLocation(keyword) },
|
|
1617
|
+
start,
|
|
1618
|
+
);
|
|
1619
|
+
module.theorems.push(theorem);
|
|
1620
|
+
module.units.push({ kind: "Theorem", theorem });
|
|
1621
|
+
if (name === undefined || statement.kind === "AssumeProve") return;
|
|
1622
|
+
module.definitions.push(
|
|
1623
|
+
withRange(
|
|
1624
|
+
{
|
|
1625
|
+
kind: "Operator",
|
|
1626
|
+
name,
|
|
1627
|
+
params: [],
|
|
1628
|
+
body: statement,
|
|
1629
|
+
isLocal: false,
|
|
1630
|
+
isRecursive: false,
|
|
1631
|
+
location: getLocation(keyword),
|
|
1632
|
+
},
|
|
1633
|
+
start,
|
|
1634
|
+
),
|
|
1635
|
+
);
|
|
1636
|
+
};
|
|
1637
|
+
|
|
934
1638
|
const parseAssumption = (): Assumption => {
|
|
1639
|
+
const start = cursor.index;
|
|
1640
|
+
return withRange(parseAssumptionUnit(), start);
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1643
|
+
const parseAssumptionUnit = (): Assumption => {
|
|
935
1644
|
const keyword = advance();
|
|
936
1645
|
const location = getLocation(keyword);
|
|
937
1646
|
const isAxiom = keyword.text === "AXIOM";
|
|
938
1647
|
if (peek().kind === "Identifier" && isAt("==", 1)) {
|
|
939
1648
|
const name = expectIdentifier();
|
|
1649
|
+
factNames.add(name);
|
|
940
1650
|
advance();
|
|
941
1651
|
return { name, expression: parseExpression(), isAxiom, location };
|
|
942
1652
|
}
|
|
943
1653
|
return { name: undefined, expression: parseExpression(), isAxiom, location };
|
|
944
1654
|
};
|
|
945
1655
|
|
|
946
|
-
const parseModuleBody = (
|
|
947
|
-
expect("----");
|
|
1656
|
+
const parseModuleBody = (): Module => {
|
|
1657
|
+
const location = getLocation(expect("----"));
|
|
948
1658
|
expect("MODULE");
|
|
949
1659
|
const name = expectIdentifier();
|
|
950
1660
|
expect("----");
|
|
951
1661
|
const module: Module = {
|
|
952
1662
|
name,
|
|
953
|
-
outer,
|
|
954
1663
|
modules: [],
|
|
955
|
-
extends:
|
|
1664
|
+
extends: [],
|
|
1665
|
+
extendsRanges: [],
|
|
956
1666
|
constants: [],
|
|
957
1667
|
variables: [],
|
|
958
1668
|
definitions: [],
|
|
959
1669
|
instances: [],
|
|
960
1670
|
assumptions: [],
|
|
1671
|
+
location,
|
|
1672
|
+
theorems: [],
|
|
1673
|
+
uses: [],
|
|
1674
|
+
units: [],
|
|
961
1675
|
};
|
|
1676
|
+
openModules.push(module);
|
|
1677
|
+
recursiveScopes.push(new Set());
|
|
1678
|
+
if (accept("EXTENDS")) {
|
|
1679
|
+
parseSeparated(() => {
|
|
1680
|
+
const start = cursor.index;
|
|
1681
|
+
module.extends.push(expectIdentifier());
|
|
1682
|
+
module.extendsRanges.push(getRange(start));
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
962
1685
|
while (true) {
|
|
1686
|
+
const tokenIndex = cursor.index;
|
|
1687
|
+
unitStart.index = tokenIndex;
|
|
963
1688
|
const token = peek();
|
|
964
|
-
if (token.kind === "ModuleEnd" || token.kind === "End")
|
|
1689
|
+
if (token.kind === "ModuleEnd" || token.kind === "End") {
|
|
1690
|
+
if (token.kind === "ModuleEnd") {
|
|
1691
|
+
location.range = {
|
|
1692
|
+
module: name,
|
|
1693
|
+
start: { line: location.line, column: location.column },
|
|
1694
|
+
end: { line: token.line, column: token.endColumn },
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
openModules.pop();
|
|
1698
|
+
recursiveScopes.pop();
|
|
1699
|
+
return module;
|
|
1700
|
+
}
|
|
965
1701
|
if (token.kind === "Separator") {
|
|
966
|
-
if (isAt("MODULE", 1))
|
|
967
|
-
|
|
1702
|
+
if (isAt("MODULE", 1)) {
|
|
1703
|
+
const submodule = parseModuleBody();
|
|
1704
|
+
module.modules.push(submodule);
|
|
1705
|
+
module.units.push({ kind: "Module", module: submodule });
|
|
1706
|
+
expect("====");
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
advance();
|
|
968
1710
|
continue;
|
|
969
1711
|
}
|
|
970
1712
|
switch (token.text) {
|
|
971
1713
|
case "CONSTANT":
|
|
972
|
-
case "CONSTANTS":
|
|
1714
|
+
case "CONSTANTS": {
|
|
973
1715
|
advance();
|
|
974
|
-
|
|
1716
|
+
const declarations = parseDeclarations();
|
|
1717
|
+
module.constants.push(...declarations);
|
|
1718
|
+
module.units.push({ kind: "Constants", declarations });
|
|
975
1719
|
continue;
|
|
1720
|
+
}
|
|
976
1721
|
case "VARIABLE":
|
|
977
|
-
case "VARIABLES":
|
|
1722
|
+
case "VARIABLES": {
|
|
978
1723
|
advance();
|
|
979
|
-
|
|
1724
|
+
const declarations = parseSeparated(() => {
|
|
1725
|
+
const start = cursor.index;
|
|
1726
|
+
const location = getLocation(peek());
|
|
1727
|
+
return withRange({ name: expectIdentifier(), arity: 0, location }, start);
|
|
1728
|
+
});
|
|
1729
|
+
module.variables.push(...declarations);
|
|
1730
|
+
module.units.push({ kind: "Variables", declarations });
|
|
980
1731
|
continue;
|
|
1732
|
+
}
|
|
981
1733
|
case "ASSUME":
|
|
982
1734
|
case "ASSUMPTION":
|
|
983
1735
|
case "AXIOM": {
|
|
984
1736
|
const assumption = parseAssumption();
|
|
985
1737
|
module.assumptions.push(assumption);
|
|
1738
|
+
module.units.push({ kind: "Assumption", assumption });
|
|
986
1739
|
if (assumption.name !== undefined) {
|
|
987
1740
|
module.definitions.push({
|
|
988
1741
|
kind: "Operator",
|
|
@@ -996,57 +1749,57 @@ export const parseModule = (source: string): Module => {
|
|
|
996
1749
|
}
|
|
997
1750
|
continue;
|
|
998
1751
|
}
|
|
1752
|
+
case "USE":
|
|
1753
|
+
case "HIDE": {
|
|
1754
|
+
const use = parseUseOrHide();
|
|
1755
|
+
module.uses.push(use);
|
|
1756
|
+
module.units.push({ kind: "Use", use });
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
999
1759
|
case "RECURSIVE":
|
|
1000
1760
|
advance();
|
|
1001
|
-
|
|
1761
|
+
module.units.push({ kind: "Recursive", declarations: declareRecursive() });
|
|
1002
1762
|
continue;
|
|
1003
1763
|
case "INSTANCE":
|
|
1004
|
-
|
|
1764
|
+
addDefinitionUnit(
|
|
1765
|
+
module,
|
|
1766
|
+
withRange(parseInstance(undefined, [], false, getLocation(token)), tokenIndex),
|
|
1767
|
+
);
|
|
1005
1768
|
continue;
|
|
1006
1769
|
case "LOCAL": {
|
|
1007
1770
|
advance();
|
|
1008
1771
|
if (isAt("INSTANCE")) {
|
|
1009
|
-
|
|
1772
|
+
addDefinitionUnit(
|
|
1773
|
+
module,
|
|
1774
|
+
withRange(parseInstance(undefined, [], true, getLocation(token)), tokenIndex),
|
|
1775
|
+
);
|
|
1010
1776
|
continue;
|
|
1011
1777
|
}
|
|
1012
|
-
|
|
1013
|
-
if (unit.kind === "Instance") module.instances.push(unit);
|
|
1014
|
-
else module.definitions.push(unit);
|
|
1778
|
+
addDefinitionUnit(module, parseDefinition(true));
|
|
1015
1779
|
continue;
|
|
1016
1780
|
}
|
|
1017
1781
|
}
|
|
1018
1782
|
if (token.kind === "Keyword" && THEOREM_KEYWORDS.has(token.text)) {
|
|
1019
|
-
|
|
1020
|
-
if (theorem) module.definitions.push(theorem);
|
|
1783
|
+
parseTheorem(module);
|
|
1021
1784
|
continue;
|
|
1022
1785
|
}
|
|
1023
|
-
|
|
1024
|
-
if (unit.kind === "Instance") module.instances.push(unit);
|
|
1025
|
-
else module.definitions.push(unit);
|
|
1786
|
+
addDefinitionUnit(module, parseDefinition(false));
|
|
1026
1787
|
}
|
|
1027
1788
|
};
|
|
1028
1789
|
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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;
|
|
1790
|
+
const addDefinitionUnit = (module: Module, unit: Definition | Instance) => {
|
|
1791
|
+
if (unit.kind === "Instance") {
|
|
1792
|
+
module.instances.push(unit);
|
|
1793
|
+
module.units.push({ kind: "Instance", instance: unit });
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
module.definitions.push(unit);
|
|
1797
|
+
module.units.push({ kind: "Definition", definition: unit });
|
|
1046
1798
|
};
|
|
1047
1799
|
|
|
1048
|
-
|
|
1049
|
-
accept("====");
|
|
1050
|
-
while (peekRaw().kind === "Separator") root.modules.push(parseNestedModule());
|
|
1051
|
-
return root;
|
|
1800
|
+
return parseModuleBody();
|
|
1052
1801
|
};
|
|
1802
|
+
|
|
1803
|
+
export const parseModule = (source: string) => parse(source, false);
|
|
1804
|
+
|
|
1805
|
+
export const parseTlapmModule = (source: string) => parse(source, true);
|