@scinorandex/sparse 0.0.2 → 0.0.4
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 +2 -2
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/generator.d.ts +4 -1
- package/dist/generator.js +47 -32
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/meta/common.d.ts +57 -0
- package/dist/meta/common.js +62 -0
- package/dist/meta/common.js.map +1 -0
- package/dist/meta/handwritten.d.ts +12 -0
- package/dist/{grammarParser.js → meta/handwritten.js} +24 -24
- package/dist/meta/handwritten.js.map +1 -0
- package/dist/meta/selfhosted.d.ts +23 -0
- package/dist/meta/selfhosted.js +206 -0
- package/dist/meta/selfhosted.js.map +1 -0
- package/dist/meta/states.d.ts +1580 -0
- package/dist/meta/states.js +1493 -0
- package/dist/meta/states.js.map +1 -0
- package/dist/parser.d.ts +16 -5
- package/dist/parser.js +25 -7
- package/dist/parser.js.map +1 -1
- package/dist/{tableParser.d.ts → table/handwritten.d.ts} +1 -1
- package/dist/{tableParser.js → table/handwritten.js} +2 -2
- package/dist/table/handwritten.js.map +1 -0
- package/dist/table/selfhosted.d.ts +2 -0
- package/dist/table/selfhosted.js +65 -0
- package/dist/table/selfhosted.js.map +1 -0
- package/dist/table/states.d.ts +592 -0
- package/dist/table/states.js +593 -0
- package/dist/table/states.js.map +1 -0
- package/dist/utils/Result.d.ts +1 -1
- package/dist/utils/errorWindowBuilder.d.ts +1 -1
- package/example/LoLang/example.ts +14 -7
- package/example/complicated/grammar.txt +37 -0
- package/example/math/example.ts +1 -1
- package/example/selfhosted/01-meta-grammar.txt +10 -0
- package/example/selfhosted/02-table-grammar.txt +6 -0
- package/example/selfhosted/codegen-meta.ts +1489 -0
- package/example/selfhosted/codegen-table.ts +589 -0
- package/example/selfhosted/example.ts +39 -0
- package/example/sql/grammar.txt +50 -0
- package/example/sql/table.txt +238 -0
- package/package.json +3 -3
- package/src/cli.ts +1 -1
- package/src/generator.ts +52 -34
- package/src/index.ts +3 -2
- package/src/meta/common.ts +74 -0
- package/src/{grammarParser.ts → meta/handwritten.ts} +17 -21
- package/src/meta/selfhosted.ts +272 -0
- package/src/meta/states.ts +1489 -0
- package/src/parser.ts +53 -19
- package/src/{tableParser.ts → table/handwritten.ts} +5 -5
- package/src/table/selfhosted.ts +67 -0
- package/src/table/states.ts +589 -0
- package/src/utils/Result.ts +1 -1
- package/src/utils/errorWindowBuilder.ts +1 -1
- package/tsconfig.build.json +30 -0
- package/tsconfig.json +6 -6
- package/dist/grammarParser.d.ts +0 -27
- package/dist/grammarParser.js.map +0 -1
- package/dist/tableParser.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ColumnAndRow, Token } from "@scinorandex/slex";
|
|
2
|
+
|
|
3
|
+
export type Production = {
|
|
4
|
+
lhs: GrammarToken;
|
|
5
|
+
identifier: string;
|
|
6
|
+
originalProductionIndex: number;
|
|
7
|
+
name: string | null;
|
|
8
|
+
rhs: { type: "terminal" | "variable"; token: GrammarToken; identifier: string; name: string | null }[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const dehydateGrammarToken = ({ column, lexeme, line, type }: GrammarToken) => {
|
|
12
|
+
return { column, lexeme, line, type };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const hydrateGrammarToken = (
|
|
16
|
+
token: ReturnType<typeof dehydateGrammarToken>,
|
|
17
|
+
): Token<GrammarTokenType, GrammarTokenMetadata> => {
|
|
18
|
+
return new Token(token.type, token.lexeme, new ColumnAndRow(token.line, token.column), {});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const dehydateProduction = (opts: Production) => {
|
|
22
|
+
const { lhs, identifier, rhs, originalProductionIndex, name } = opts;
|
|
23
|
+
return {
|
|
24
|
+
lhs: dehydateGrammarToken(lhs),
|
|
25
|
+
identifier,
|
|
26
|
+
name,
|
|
27
|
+
originalProductionIndex,
|
|
28
|
+
rhs: rhs.map(({ type, token, identifier, name }) => ({
|
|
29
|
+
type,
|
|
30
|
+
token: dehydateGrammarToken(token),
|
|
31
|
+
identifier,
|
|
32
|
+
name,
|
|
33
|
+
})),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const hydrateProduction = (production: ReturnType<typeof dehydateProduction>): Production => {
|
|
38
|
+
return {
|
|
39
|
+
lhs: hydrateGrammarToken(production.lhs),
|
|
40
|
+
identifier: production.identifier,
|
|
41
|
+
originalProductionIndex: production.originalProductionIndex,
|
|
42
|
+
name: production.name,
|
|
43
|
+
rhs: production.rhs.map(({ type, token, name }) => ({
|
|
44
|
+
type,
|
|
45
|
+
token: hydrateGrammarToken(token),
|
|
46
|
+
identifier: token.lexeme,
|
|
47
|
+
name,
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type GrammarTokenMetadata = {};
|
|
53
|
+
export enum GrammarTokenType {
|
|
54
|
+
IDENTIFIER,
|
|
55
|
+
L_ANGLE,
|
|
56
|
+
R_ANGLE,
|
|
57
|
+
L_BRACKET,
|
|
58
|
+
R_BRACKET,
|
|
59
|
+
L_PAREN,
|
|
60
|
+
R_PAREN,
|
|
61
|
+
PIPE,
|
|
62
|
+
QUESTION_MARK,
|
|
63
|
+
COLON,
|
|
64
|
+
SEMICOLON,
|
|
65
|
+
NUMBER,
|
|
66
|
+
EQUALS,
|
|
67
|
+
COMMA,
|
|
68
|
+
EOF,
|
|
69
|
+
|
|
70
|
+
PRODUCTION_NAME,
|
|
71
|
+
TOKEN_NAME,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type GrammarToken = Token<GrammarTokenType, GrammarTokenMetadata>;
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
import { Slex
|
|
2
|
-
import { Result } from "
|
|
3
|
-
|
|
4
|
-
export type Production = { lhs: GrammarToken; rhs: { type: "terminal" | "variable"; token: GrammarToken }[] };
|
|
5
|
-
|
|
6
|
-
export type GrammarTokenMetadata = {};
|
|
7
|
-
export enum GrammarTokenType {
|
|
8
|
-
PRODUCTION_NAME,
|
|
9
|
-
TOKEN_NAME,
|
|
10
|
-
COLON,
|
|
11
|
-
SEMICOLON,
|
|
12
|
-
EOF,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type GrammarToken = Token<GrammarTokenType, GrammarTokenMetadata>;
|
|
1
|
+
import { Slex } from "@scinorandex/slex";
|
|
2
|
+
import { Result } from "../utils/Result";
|
|
3
|
+
import { GrammarToken, GrammarTokenMetadata, GrammarTokenType, Production } from "./common";
|
|
16
4
|
|
|
17
5
|
export const grammarLexerGenerator = new Slex<GrammarTokenType, GrammarTokenMetadata>({
|
|
18
6
|
EOF_TYPE: GrammarTokenType.EOF,
|
|
@@ -21,11 +9,11 @@ export const grammarLexerGenerator = new Slex<GrammarTokenType, GrammarTokenMeta
|
|
|
21
9
|
|
|
22
10
|
grammarLexerGenerator.addRule(
|
|
23
11
|
"lowercase",
|
|
24
|
-
"a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z"
|
|
12
|
+
"a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z",
|
|
25
13
|
);
|
|
26
14
|
grammarLexerGenerator.addRule(
|
|
27
15
|
"uppercase",
|
|
28
|
-
"A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z"
|
|
16
|
+
"A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z",
|
|
29
17
|
);
|
|
30
18
|
grammarLexerGenerator.addRule("letter", "${lowercase} | ${uppercase}");
|
|
31
19
|
grammarLexerGenerator.addRule("digit", "0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9");
|
|
@@ -53,19 +41,27 @@ export const tryBuildProductions = (lexer: LexerInterface | string): Result<Prod
|
|
|
53
41
|
lexer.getNextToken();
|
|
54
42
|
};
|
|
55
43
|
|
|
44
|
+
let productionIndex = 0;
|
|
45
|
+
|
|
56
46
|
const buildProduction = (): Result<Production> => {
|
|
57
47
|
const productionLHS = lexer.getNextToken();
|
|
58
48
|
expect(GrammarTokenType.COLON);
|
|
59
49
|
|
|
60
|
-
const production: Production = {
|
|
50
|
+
const production: Production = {
|
|
51
|
+
lhs: productionLHS,
|
|
52
|
+
identifier: productionLHS.lexeme,
|
|
53
|
+
rhs: [],
|
|
54
|
+
name: null,
|
|
55
|
+
originalProductionIndex: productionIndex++,
|
|
56
|
+
};
|
|
61
57
|
|
|
62
58
|
while (lexer.peekNextToken().type != GrammarTokenType.SEMICOLON) {
|
|
63
59
|
const productionRHS = lexer.getNextToken();
|
|
64
60
|
|
|
65
61
|
if (productionRHS.type == GrammarTokenType.PRODUCTION_NAME)
|
|
66
|
-
production.rhs.push({ type: "variable", token: productionRHS });
|
|
62
|
+
production.rhs.push({ type: "variable", token: productionRHS, identifier: productionRHS.lexeme, name: null });
|
|
67
63
|
else if (productionRHS.type == GrammarTokenType.TOKEN_NAME)
|
|
68
|
-
production.rhs.push({ type: "terminal", token: productionRHS });
|
|
64
|
+
production.rhs.push({ type: "terminal", token: productionRHS, identifier: productionRHS.lexeme, name: null });
|
|
69
65
|
else
|
|
70
66
|
return {
|
|
71
67
|
success: false,
|
|
@@ -91,7 +87,7 @@ export const buildProductions = (lexer: LexerInterface | string): Production[] =
|
|
|
91
87
|
const result = tryBuildProductions(lexer);
|
|
92
88
|
if (result.success === false)
|
|
93
89
|
throw new Error(
|
|
94
|
-
`Encountered error "${result.reason}" while parsing grammar at ${result.token.line}:${result.token.column}
|
|
90
|
+
`Encountered error "${result.reason}" while parsing grammar at ${result.token.line}:${result.token.column}`,
|
|
95
91
|
);
|
|
96
92
|
return result.value;
|
|
97
93
|
};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { RegexEngine, Slex } from "@scinorandex/slex";
|
|
2
|
+
import { Result, Sparse } from "../index";
|
|
3
|
+
import { selfhosted } from "./states";
|
|
4
|
+
import { TableState } from "../parser";
|
|
5
|
+
import { GrammarToken, GrammarTokenMetadata, GrammarTokenType, hydrateProduction, Production } from "./common";
|
|
6
|
+
|
|
7
|
+
export const grammarLexerGenerator = new Slex<GrammarTokenType, GrammarTokenMetadata>({
|
|
8
|
+
EOF_TYPE: GrammarTokenType.EOF,
|
|
9
|
+
isHigherPrecedence: () => false,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
grammarLexerGenerator.addRule(
|
|
13
|
+
"lowercase",
|
|
14
|
+
"a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z",
|
|
15
|
+
);
|
|
16
|
+
grammarLexerGenerator.addRule(
|
|
17
|
+
"uppercase",
|
|
18
|
+
"A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z",
|
|
19
|
+
);
|
|
20
|
+
grammarLexerGenerator.addRule("letter", "${lowercase} | ${uppercase}");
|
|
21
|
+
grammarLexerGenerator.addRule("digit", "0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9");
|
|
22
|
+
grammarLexerGenerator.addRule("alphanumeric", "${letter} | ${digit}");
|
|
23
|
+
grammarLexerGenerator.addRule(
|
|
24
|
+
"identifier",
|
|
25
|
+
"(${letter} | $_) (${letter} | ${digit} | $_)*",
|
|
26
|
+
GrammarTokenType.IDENTIFIER,
|
|
27
|
+
);
|
|
28
|
+
grammarLexerGenerator.addRule("colon", "$:", GrammarTokenType.COLON);
|
|
29
|
+
grammarLexerGenerator.addRule("semicolon", "$;", GrammarTokenType.SEMICOLON);
|
|
30
|
+
grammarLexerGenerator.addRule("langle", "$<", GrammarTokenType.L_ANGLE);
|
|
31
|
+
grammarLexerGenerator.addRule("rangle", "$>", GrammarTokenType.R_ANGLE);
|
|
32
|
+
grammarLexerGenerator.addRule("lbracket", "$[", GrammarTokenType.L_BRACKET);
|
|
33
|
+
grammarLexerGenerator.addRule("rbracket", "$]", GrammarTokenType.R_BRACKET);
|
|
34
|
+
grammarLexerGenerator.addRule("lparen", "$(", GrammarTokenType.L_PAREN);
|
|
35
|
+
grammarLexerGenerator.addRule("rparen", "$)", GrammarTokenType.R_PAREN);
|
|
36
|
+
grammarLexerGenerator.addRule("pipe", "$|", GrammarTokenType.PIPE);
|
|
37
|
+
grammarLexerGenerator.addRule("equals", "$=", GrammarTokenType.EQUALS);
|
|
38
|
+
grammarLexerGenerator.addRule("comma", "$,", GrammarTokenType.COMMA);
|
|
39
|
+
grammarLexerGenerator.addRule("question_mark", "$?", GrammarTokenType.QUESTION_MARK);
|
|
40
|
+
grammarLexerGenerator.addRule("number", "(${digit})+", GrammarTokenType.NUMBER);
|
|
41
|
+
|
|
42
|
+
export abstract class BaseNode {}
|
|
43
|
+
|
|
44
|
+
export class ListNode<T> extends BaseNode {
|
|
45
|
+
private items: T[];
|
|
46
|
+
|
|
47
|
+
constructor(items: T | T[]) {
|
|
48
|
+
super();
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(items)) this.items = items;
|
|
51
|
+
else this.items = [items];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
add(item: T) {
|
|
55
|
+
this.items.push(item);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getItems() {
|
|
60
|
+
return this.items;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getItemsReversed() {
|
|
64
|
+
return this.items.toReversed();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class ProductionNode extends BaseNode {
|
|
69
|
+
constructor(
|
|
70
|
+
public readonly lhs: GrammarToken,
|
|
71
|
+
public readonly rhs: ListNode<TokenNode | GroupedTokenNode>,
|
|
72
|
+
public originalProductionIndex: number,
|
|
73
|
+
public readonly name?: string | undefined,
|
|
74
|
+
) {
|
|
75
|
+
super();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
toStruct(): Production {
|
|
79
|
+
if (this.originalProductionIndex === -1) throw new Error("ProductionNode has no originalProductionIndex");
|
|
80
|
+
|
|
81
|
+
const hasGrouped = this.rhs.getItemsReversed().some((node) => node instanceof GroupedTokenNode);
|
|
82
|
+
if (hasGrouped) throw new Error("Need to unroll before serializing");
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
lhs: this.lhs,
|
|
86
|
+
identifier: `<${this.lhs.lexeme}>`,
|
|
87
|
+
name: this.name ?? null,
|
|
88
|
+
originalProductionIndex: this.originalProductionIndex,
|
|
89
|
+
rhs: this.rhs.getItemsReversed().map((node) => (node as TokenNode).toStruct()),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setOriginalProductionIndex(index: number) {
|
|
94
|
+
this.originalProductionIndex = index;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function expandProduction(production: ProductionNode): ProductionNode[] {
|
|
99
|
+
const rhs = production.rhs.getItemsReversed();
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < rhs.length; i++) {
|
|
102
|
+
const item = rhs[i];
|
|
103
|
+
|
|
104
|
+
const beforeItems = rhs.slice(0, i);
|
|
105
|
+
const afterItems = rhs.slice(i + 1);
|
|
106
|
+
|
|
107
|
+
if (item instanceof GroupedTokenNode) {
|
|
108
|
+
// need to create two new productions, one optional and the other not
|
|
109
|
+
// then apply unrolling on both
|
|
110
|
+
|
|
111
|
+
const withoutCurrentItem = new ProductionNode(
|
|
112
|
+
production.lhs,
|
|
113
|
+
new ListNode<TokenNode | GroupedTokenNode>([...beforeItems, ...afterItems].toReversed()),
|
|
114
|
+
production.originalProductionIndex,
|
|
115
|
+
production.name,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const withCurrentItem = new ProductionNode(
|
|
119
|
+
production.lhs,
|
|
120
|
+
new ListNode<TokenNode | GroupedTokenNode>(
|
|
121
|
+
[...beforeItems, ...item.inside.getItemsReversed(), ...afterItems].toReversed(),
|
|
122
|
+
),
|
|
123
|
+
production.originalProductionIndex,
|
|
124
|
+
production.name,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const newProductions: ProductionNode[] = [withoutCurrentItem, withCurrentItem];
|
|
128
|
+
return newProductions.flatMap((prod) => expandProduction(prod));
|
|
129
|
+
} else if (item instanceof TokenNode) {
|
|
130
|
+
if (item.variables.getItems().length > 1) {
|
|
131
|
+
// this token node has many variants and we should unroll it
|
|
132
|
+
const variables = item.variables.getItemsReversed();
|
|
133
|
+
|
|
134
|
+
const newProductions = variables.map((variable) => {
|
|
135
|
+
return new ProductionNode(
|
|
136
|
+
production.lhs,
|
|
137
|
+
new ListNode<TokenNode | GroupedTokenNode>([
|
|
138
|
+
...beforeItems,
|
|
139
|
+
new TokenNode(item.type, new ListNode<GrammarToken>([variable]), item.name),
|
|
140
|
+
]),
|
|
141
|
+
production.originalProductionIndex,
|
|
142
|
+
production.name,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return newProductions.flatMap((prod) => expandProduction(prod));
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error("shoudn't reach here, ProductionNode RHS is neither GroupedTokenNode nor TokenNode");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// if it reached here then its normal
|
|
154
|
+
return [production];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class TokenNode extends BaseNode {
|
|
158
|
+
constructor(
|
|
159
|
+
public readonly type: "terminal" | "variable",
|
|
160
|
+
public readonly variables: ListNode<GrammarToken>,
|
|
161
|
+
public readonly name: string | undefined,
|
|
162
|
+
) {
|
|
163
|
+
super();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
toStruct() {
|
|
167
|
+
const items = this.variables.getItems();
|
|
168
|
+
if (items.length > 1) throw new Error("Not yet unrolled");
|
|
169
|
+
|
|
170
|
+
const lexeme = items[0].lexeme;
|
|
171
|
+
const identifier = this.type === "variable" ? `<${lexeme}>` : `[${lexeme}]`;
|
|
172
|
+
const name = this.name ?? null;
|
|
173
|
+
|
|
174
|
+
return { type: this.type, token: items[0], identifier, name };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
class GroupedTokenNode extends BaseNode {
|
|
179
|
+
constructor(public readonly inside: ListNode<TokenNode>) {
|
|
180
|
+
super();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class ProgramNode extends BaseNode {
|
|
185
|
+
constructor(public readonly productions: ListNode<ProductionNode>) {
|
|
186
|
+
super();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
unrollProductions() {
|
|
190
|
+
const productions = this.productions.getItemsReversed();
|
|
191
|
+
for (let i = 0; i < productions.length; i++) productions[i].setOriginalProductionIndex(i);
|
|
192
|
+
return productions.flatMap((node) => expandProduction(node));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getProductions() {
|
|
196
|
+
return this.unrollProductions().map((node) => node.toStruct());
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function getSelfHostedParserGenerator(e: typeof selfhosted = selfhosted) {
|
|
201
|
+
return new Sparse<GrammarTokenType, GrammarTokenMetadata, BaseNode>({
|
|
202
|
+
productions: e.productions.map((production) => hydrateProduction(production as any)),
|
|
203
|
+
states: e.states.map((state) => TableState.fromJSObject(state as any)),
|
|
204
|
+
toStringifiedTokenType: (type: GrammarTokenType) => GrammarTokenType[type],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type Reducer = (bag: any) => BaseNode;
|
|
209
|
+
const reducers: { [key: string]: Reducer } = {
|
|
210
|
+
program: (bag: { productions?: ListNode<ProductionNode> }) =>
|
|
211
|
+
new ProgramNode(bag.productions ?? new ListNode<ProductionNode>([])),
|
|
212
|
+
|
|
213
|
+
productions: (bag: { production: ProductionNode; rest?: ListNode<ProductionNode> }) => {
|
|
214
|
+
if (bag.rest == null) return new ListNode<ProductionNode>([bag.production]);
|
|
215
|
+
else return bag.rest.add(bag.production);
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
production: (bag: {
|
|
219
|
+
production_name: GrammarToken;
|
|
220
|
+
tokens: ListNode<TokenNode | GroupedTokenNode>;
|
|
221
|
+
uuid?: GrammarToken;
|
|
222
|
+
}) => new ProductionNode(bag.production_name, bag.tokens, -1, bag.uuid?.lexeme),
|
|
223
|
+
|
|
224
|
+
tokens: (bag: { token: TokenNode | GroupedTokenNode; rest?: ListNode<TokenNode | GroupedTokenNode> }) => {
|
|
225
|
+
if (bag.rest == null) return new ListNode<TokenNode | GroupedTokenNode>([bag.token]);
|
|
226
|
+
else return bag.rest.add(bag.token);
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
token: (bag: { token: TokenNode }) => bag.token,
|
|
230
|
+
grouped_token: (bag: { tokens: ListNode<TokenNode> }) => new GroupedTokenNode(bag.tokens),
|
|
231
|
+
|
|
232
|
+
variable: (bag: { inside: ListNode<GrammarToken>; token_name?: GrammarToken }) =>
|
|
233
|
+
new TokenNode("variable", bag.inside, bag.token_name?.lexeme),
|
|
234
|
+
|
|
235
|
+
terminal: (bag: { inside: ListNode<GrammarToken>; token_name?: GrammarToken }) =>
|
|
236
|
+
new TokenNode("terminal", bag.inside, bag.token_name?.lexeme),
|
|
237
|
+
|
|
238
|
+
inside: (bag: { identifier: GrammarToken; rest?: ListNode<GrammarToken> }) => {
|
|
239
|
+
if (bag.rest == null) return new ListNode<GrammarToken>([bag.identifier]);
|
|
240
|
+
else return bag.rest.add(bag.identifier);
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const tryBuildProductions = (lexer: LexerInterface | string): Result<Production[]> => {
|
|
245
|
+
if (typeof lexer === "string") lexer = grammarLexerGenerator.generate(lexer, () => ({}));
|
|
246
|
+
|
|
247
|
+
const parserGenerator = getSelfHostedParserGenerator();
|
|
248
|
+
const parser = parserGenerator.generate(lexer as RegexEngine<GrammarTokenType, GrammarTokenMetadata>, {
|
|
249
|
+
reducer: ({ bag, name }) => {
|
|
250
|
+
return reducers[name ?? ""](bag);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const parsingResult = parser.parse().result as ProgramNode;
|
|
255
|
+
const productions = parsingResult.getProductions();
|
|
256
|
+
return { success: true, value: productions };
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
interface LexerInterface {
|
|
260
|
+
peekNextToken(): GrammarToken;
|
|
261
|
+
getNextToken(): GrammarToken;
|
|
262
|
+
hasNextToken(): boolean;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const buildProductions = (lexer: LexerInterface | string): Production[] => {
|
|
266
|
+
const result = tryBuildProductions(lexer);
|
|
267
|
+
if (result.success === false)
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Encountered error "${result.reason}" while parsing grammar at ${result.token.line}:${result.token.column}`,
|
|
270
|
+
);
|
|
271
|
+
return result.value;
|
|
272
|
+
};
|