@plurnk/plurnk-grammar 0.1.0

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/src/index.ts ADDED
@@ -0,0 +1,142 @@
1
+ import {
2
+ BaseErrorListener,
3
+ CharStream,
4
+ CommonTokenStream,
5
+ type RecognitionException,
6
+ type Recognizer,
7
+ type Token,
8
+ } from "antlr4ng";
9
+ import { plurnkLexer } from "./generated/plurnkLexer.ts";
10
+ import { plurnkParser } from "./generated/plurnkParser.ts";
11
+ import { buildStatement, type PlurnkStatement, type Position } from "./ast.ts";
12
+ import { PlurnkParseError } from "./errors.ts";
13
+ import { PlurnkErrorStrategy, translateLexerMessage } from "./error-strategy.ts";
14
+
15
+ export { PlurnkParseError } from "./errors.ts";
16
+ export type { ErrorSource } from "./errors.ts";
17
+ export {
18
+ type PlurnkStatement,
19
+ type PlurnkOp,
20
+ type Position,
21
+ type LineMarker,
22
+ type FindStatement,
23
+ type ReadStatement,
24
+ type EditStatement,
25
+ type CopyStatement,
26
+ type MoveStatement,
27
+ type ShowStatement,
28
+ type HideStatement,
29
+ type SendStatement,
30
+ type ExecStatement,
31
+ } from "./ast.ts";
32
+
33
+ export type ParseItem =
34
+ | { kind: "statement"; statement: PlurnkStatement }
35
+ | { kind: "error"; error: PlurnkParseError }
36
+ | { kind: "text"; text: string; position: Position };
37
+
38
+ export type ParseResult = {
39
+ items: ParseItem[];
40
+ unparsedTail?: { from: Position; reason: string };
41
+ };
42
+
43
+ class RecordingListener extends BaseErrorListener {
44
+ readonly errors: PlurnkParseError[];
45
+ readonly source: "lexer" | "parser";
46
+
47
+ constructor(source: "lexer" | "parser", errors: PlurnkParseError[]) {
48
+ super();
49
+ this.source = source;
50
+ this.errors = errors;
51
+ }
52
+
53
+ override syntaxError(
54
+ recognizer: Recognizer<any>,
55
+ _offendingSymbol: Token | null,
56
+ line: number,
57
+ column: number,
58
+ msg: string,
59
+ _e: RecognitionException | null,
60
+ ): void {
61
+ const translated = this.source === "lexer"
62
+ ? translateLexerMessage(recognizer as plurnkLexer, msg)
63
+ : msg;
64
+ this.errors.push(new PlurnkParseError(line, column, this.source, translated));
65
+ }
66
+ }
67
+
68
+ const errorInRange = (
69
+ err: PlurnkParseError,
70
+ start: { line: number; column: number },
71
+ stop: { line: number; column: number },
72
+ ): boolean => {
73
+ if (err.line < start.line || err.line > stop.line) return false;
74
+ if (err.line === start.line && err.column < start.column) return false;
75
+ if (err.line === stop.line && err.column > stop.column) return false;
76
+ return true;
77
+ };
78
+
79
+ export const parse = (input: string): ParseResult => {
80
+ const lexer = new plurnkLexer(CharStream.fromString(input));
81
+ const errors: PlurnkParseError[] = [];
82
+ lexer.removeErrorListeners();
83
+ lexer.addErrorListener(new RecordingListener("lexer", errors));
84
+
85
+ const tokenStream = new CommonTokenStream(lexer);
86
+ const parser = new plurnkParser(tokenStream);
87
+ parser.removeErrorListeners();
88
+ parser.addErrorListener(new RecordingListener("parser", errors));
89
+ parser.errorHandler = new PlurnkErrorStrategy();
90
+
91
+ const tree = parser.document();
92
+
93
+ const items: ParseItem[] = [];
94
+ const consumedErrors = new Set<PlurnkParseError>();
95
+
96
+ for (const child of tree.children ?? []) {
97
+ const ctx = child as any;
98
+ const start = ctx.start ?? ctx.symbol;
99
+ const stop = ctx.stop ?? ctx.symbol;
100
+ if (!start) continue;
101
+
102
+ if (ctx.ruleIndex === plurnkParser.RULE_statement) {
103
+ const errForStatement = errors.find(
104
+ (e) => !consumedErrors.has(e) && errorInRange(e, start, stop ?? start),
105
+ );
106
+ if (errForStatement) {
107
+ consumedErrors.add(errForStatement);
108
+ items.push({ kind: "error", error: errForStatement });
109
+ } else {
110
+ try {
111
+ items.push({ kind: "statement", statement: buildStatement(ctx) });
112
+ } catch (e) {
113
+ if (e instanceof PlurnkParseError) {
114
+ items.push({ kind: "error", error: e });
115
+ } else {
116
+ throw e;
117
+ }
118
+ }
119
+ }
120
+ } else if (ctx.symbol?.type === plurnkLexer.TEXT) {
121
+ const position: Position = { line: start.line, column: start.column };
122
+ items.push({ kind: "text", text: ctx.symbol.text ?? "", position });
123
+ }
124
+ }
125
+
126
+ for (const err of errors) {
127
+ if (!consumedErrors.has(err)) {
128
+ items.push({ kind: "error", error: err });
129
+ }
130
+ }
131
+
132
+ let unparsedTail: ParseResult["unparsedTail"];
133
+ if (lexer.mode !== 0) {
134
+ const lastToken = tokenStream.get(tokenStream.size - 1);
135
+ unparsedTail = {
136
+ from: { line: lastToken?.line ?? 0, column: lastToken?.column ?? 0 },
137
+ reason: `lexer ended in non-default mode (${lexer.mode}); a statement was likely never closed`,
138
+ };
139
+ }
140
+
141
+ return { items, unparsedTail };
142
+ };