@scinorandex/sparse 0.0.1 → 0.0.3
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 -4
- 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/package.json +4 -4
- 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
package/src/parser.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Slex, Token } from "@scinorandex/slex";
|
|
2
2
|
import { generateStates } from "./generator";
|
|
3
|
-
import { Production } from "./grammarParser";
|
|
4
3
|
import { Stack } from "./utils/Stack";
|
|
5
4
|
import { Result } from "./utils/Result";
|
|
5
|
+
import { Production } from "./meta/common";
|
|
6
6
|
|
|
7
7
|
export type TableAction = { type: "shift" | "reduce" | "goto"; value: number };
|
|
8
8
|
|
|
9
9
|
export class TableState {
|
|
10
10
|
public readonly actions: Map<string, TableAction>;
|
|
11
11
|
|
|
12
|
-
constructor() {
|
|
13
|
-
this.actions =
|
|
12
|
+
constructor(actions = new Map<string, TableAction>()) {
|
|
13
|
+
this.actions = actions;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
public getTerminalAction(terminal: string): TableAction | undefined {
|
|
@@ -20,8 +20,21 @@ export class TableState {
|
|
|
20
20
|
public getVariableAction(variable: string): TableAction | undefined {
|
|
21
21
|
return this.actions.get(variable);
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
toJSObject() {
|
|
25
|
+
return Object.fromEntries(this.actions);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static fromJSObject(json: ReturnType<TableState["toJSObject"]>) {
|
|
29
|
+
return new TableState(new Map(Object.entries(json)));
|
|
30
|
+
}
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
type ReducerType<TokenType, Metadata, Node> = (
|
|
34
|
+
newInput: { bag: any; name: string | null },
|
|
35
|
+
oldInput: { input: LR1StackSymbol<TokenType, Metadata, Node>[]; index: number },
|
|
36
|
+
) => Node;
|
|
37
|
+
|
|
25
38
|
export type LR1StackSymbol<TokenType, Metadata, Node> =
|
|
26
39
|
| { type: "token"; token: Token<TokenType, Metadata> }
|
|
27
40
|
| { type: "node"; node: Node };
|
|
@@ -32,7 +45,7 @@ export class Sparse<TokenType, Metadata, Node> {
|
|
|
32
45
|
productions: Production[];
|
|
33
46
|
states: TableState[];
|
|
34
47
|
toStringifiedTokenType: (tokenType: TokenType) => string;
|
|
35
|
-
}
|
|
48
|
+
},
|
|
36
49
|
) {}
|
|
37
50
|
|
|
38
51
|
public static tryFromProductions<TokenType, Metadata, Node>(options: {
|
|
@@ -65,9 +78,9 @@ export class Sparse<TokenType, Metadata, Node> {
|
|
|
65
78
|
public generate(
|
|
66
79
|
lexer: ReturnType<Slex<TokenType, Metadata>["generate"]>,
|
|
67
80
|
options: {
|
|
68
|
-
reducer:
|
|
81
|
+
reducer: ReducerType<TokenType, Metadata, Node>;
|
|
69
82
|
recover?: ParserRecoveryFunction<TokenType, Metadata, Node>;
|
|
70
|
-
}
|
|
83
|
+
},
|
|
71
84
|
) {
|
|
72
85
|
return new LR1Parser<TokenType, Metadata, Node>(this.options, options.reducer, options.recover ?? null, lexer);
|
|
73
86
|
}
|
|
@@ -104,9 +117,9 @@ class LR1Parser<TokenType, Metadata, Node> {
|
|
|
104
117
|
states: TableState[];
|
|
105
118
|
toStringifiedTokenType: (tokenType: TokenType) => string;
|
|
106
119
|
},
|
|
107
|
-
public readonly reducer:
|
|
120
|
+
public readonly reducer: ReducerType<TokenType, Metadata, Node>,
|
|
108
121
|
public readonly recover: ParserRecoveryFunction<TokenType, Metadata, Node> | null,
|
|
109
|
-
public readonly lexer: ReturnType<Slex<TokenType, Metadata>["generate"]
|
|
122
|
+
public readonly lexer: ReturnType<Slex<TokenType, Metadata>["generate"]>,
|
|
110
123
|
) {}
|
|
111
124
|
|
|
112
125
|
public parse(): ParserResult<TokenType, Metadata, Node> {
|
|
@@ -121,9 +134,9 @@ class LR1Parser<TokenType, Metadata, Node> {
|
|
|
121
134
|
if (this.recover === null)
|
|
122
135
|
throw new LR1ParserGraveError(
|
|
123
136
|
`Invalid syntax. Expected ${[...states[currentState].actions.keys()].join(
|
|
124
|
-
","
|
|
137
|
+
",",
|
|
125
138
|
)} but got ${toStringifiedTokenType(token.type)}`,
|
|
126
|
-
token
|
|
139
|
+
token,
|
|
127
140
|
);
|
|
128
141
|
|
|
129
142
|
const recoveryResult = this.recover({
|
|
@@ -166,29 +179,46 @@ class LR1Parser<TokenType, Metadata, Node> {
|
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
// try to perform the reduction
|
|
182
|
+
const productionIndex = production.originalProductionIndex;
|
|
183
|
+
|
|
169
184
|
try {
|
|
170
|
-
|
|
185
|
+
// prepare the object that you're going to send to the reducer function
|
|
186
|
+
const bag = {} as { [key: string]: Node | Token<TokenType, Metadata> };
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < production.rhs.length; i++) {
|
|
189
|
+
const rhsItem = production.rhs[i];
|
|
190
|
+
const poppedItem = popped[i];
|
|
191
|
+
if (rhsItem.name != null)
|
|
192
|
+
bag[rhsItem.name] = poppedItem.type === "token" ? poppedItem.token : poppedItem.node;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const newInput = { bag, name: production.name };
|
|
196
|
+
const oldInput = { input: popped, index: productionIndex };
|
|
197
|
+
|
|
198
|
+
const node = this.reducer(newInput, oldInput);
|
|
199
|
+
this.symbolsStack.push({ type: "node", node });
|
|
171
200
|
} catch (err) {
|
|
172
201
|
const input = JSON.stringify(popped, null, 2);
|
|
202
|
+
console.log(err);
|
|
173
203
|
throw new LR1ParserGraveError(
|
|
174
|
-
`Error while performing reduction for production: ${
|
|
175
|
-
token
|
|
204
|
+
`Error while performing reduction for production: ${productionIndex}. Reduction input: ${input}`,
|
|
205
|
+
token,
|
|
176
206
|
);
|
|
177
207
|
}
|
|
178
208
|
|
|
179
209
|
// get the top node and figure out what state to add to state stack
|
|
180
210
|
const topNode = this.statesStack.peek();
|
|
181
|
-
const gotoAction = states[topNode].getVariableAction(production.
|
|
211
|
+
const gotoAction = states[topNode].getVariableAction(production.identifier);
|
|
182
212
|
|
|
183
213
|
if (gotoAction == undefined)
|
|
184
214
|
throw new LR1ParserGraveError(
|
|
185
|
-
`No GOTO action for for state: ${topNode} and production: ${production.
|
|
186
|
-
token
|
|
215
|
+
`No GOTO action for for state: ${topNode} and production: ${production.identifier}. Actions: Reduce by production ${productionIndex}`,
|
|
216
|
+
token,
|
|
187
217
|
);
|
|
188
218
|
else if (gotoAction.type !== "goto")
|
|
189
219
|
throw new LR1ParserGraveError(
|
|
190
|
-
`Expected GOTO action for state: ${topNode} and production: ${production.
|
|
191
|
-
token
|
|
220
|
+
`Expected GOTO action for state: ${topNode} and production: ${production.identifier}. Actions: Reduce by production ${productionIndex}. Received: ${gotoAction.type}`,
|
|
221
|
+
token,
|
|
192
222
|
);
|
|
193
223
|
|
|
194
224
|
this.statesStack.push(gotoAction.value);
|
|
@@ -201,7 +231,11 @@ class LR1Parser<TokenType, Metadata, Node> {
|
|
|
201
231
|
}
|
|
202
232
|
|
|
203
233
|
export class LR1ParserGraveError<TokenType, Metadata> extends Error {
|
|
204
|
-
constructor(
|
|
234
|
+
constructor(
|
|
235
|
+
public readonly reason: string,
|
|
236
|
+
public readonly currentToken: Token<TokenType, Metadata>,
|
|
237
|
+
err?: Error,
|
|
238
|
+
) {
|
|
205
239
|
super(reason);
|
|
206
240
|
}
|
|
207
241
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Slex } from "@scinorandex/slex";
|
|
2
|
-
import { TableAction, TableState } from "
|
|
2
|
+
import { TableAction, TableState } from "../parser";
|
|
3
3
|
|
|
4
4
|
type TableTokenMetadata = {};
|
|
5
5
|
export enum TableTokenType {
|
|
@@ -20,11 +20,11 @@ export const tableLexerGenerator = new Slex<TableTokenType, TableTokenMetadata>(
|
|
|
20
20
|
|
|
21
21
|
tableLexerGenerator.addRule(
|
|
22
22
|
"lowercase",
|
|
23
|
-
"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"
|
|
23
|
+
"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",
|
|
24
24
|
);
|
|
25
25
|
tableLexerGenerator.addRule(
|
|
26
26
|
"uppercase",
|
|
27
|
-
"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"
|
|
27
|
+
"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",
|
|
28
28
|
);
|
|
29
29
|
tableLexerGenerator.addRule("letter", "${lowercase} | ${uppercase}");
|
|
30
30
|
tableLexerGenerator.addRule("digit", "0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9");
|
|
@@ -58,8 +58,8 @@ export const buildStates = (grammar: string) =>
|
|
|
58
58
|
token.type === TableTokenType.PRODUCTION_NAME
|
|
59
59
|
? { type: "goto", value: parseInt(value.lexeme) }
|
|
60
60
|
: value.type === TableTokenType.REDUCE_ACTION
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
? { type: "reduce", value: parseInt(value.lexeme.substring(1)) }
|
|
62
|
+
: { type: "shift", value: parseInt(value.lexeme.substring(1)) };
|
|
63
63
|
|
|
64
64
|
state.actions.set(token.lexeme, setting);
|
|
65
65
|
if (lexer.peekNextToken().type == TableTokenType.COMMA) lexer.getNextToken();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { GrammarToken } from "../meta/common";
|
|
2
|
+
import { BaseNode, getSelfHostedParserGenerator, grammarLexerGenerator, ListNode } from "../meta/selfhosted";
|
|
3
|
+
import { TableAction, TableState } from "../parser";
|
|
4
|
+
import { selfhosted } from "./states";
|
|
5
|
+
|
|
6
|
+
class StateNode extends BaseNode {
|
|
7
|
+
constructor(public readonly items: ListNode<ItemNode>) {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
toTableState(): TableState {
|
|
12
|
+
const state = new TableState();
|
|
13
|
+
for (const item of this.items.getItems()) {
|
|
14
|
+
const key = item.type === "terminal" ? `[${item.name.lexeme}]` : `<${item.name.lexeme}>`;
|
|
15
|
+
state.actions.set(key, item.action);
|
|
16
|
+
}
|
|
17
|
+
return state;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class ItemNode extends BaseNode {
|
|
22
|
+
constructor(
|
|
23
|
+
public readonly type: "terminal" | "variable",
|
|
24
|
+
public readonly name: GrammarToken,
|
|
25
|
+
public readonly action: TableAction,
|
|
26
|
+
) {
|
|
27
|
+
super();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Reducer = (bag: any) => BaseNode;
|
|
32
|
+
const reducers: { [key: string]: Reducer } = {
|
|
33
|
+
program: (bag: { states: ListNode<StateNode> }) => bag.states,
|
|
34
|
+
states: (bag: { state: ListNode<ItemNode>; rest?: ListNode<StateNode> }) => {
|
|
35
|
+
if (bag.rest == null) return new ListNode<StateNode>([new StateNode(bag.state)]);
|
|
36
|
+
else return bag.rest.add(new StateNode(bag.state));
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
items: (bag: { item: ItemNode; rest?: ListNode<ItemNode> }) => {
|
|
40
|
+
if (bag.rest == null) return new ListNode<ItemNode>([bag.item]);
|
|
41
|
+
else return bag.rest.add(bag.item);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
terminal: (bag: { name: GrammarToken; action: GrammarToken }) => {
|
|
45
|
+
const action: TableAction = {
|
|
46
|
+
type: bag.action.lexeme.startsWith("s") ? "shift" : "reduce",
|
|
47
|
+
value: parseInt(bag.action.lexeme.substring(1)),
|
|
48
|
+
};
|
|
49
|
+
return new ItemNode("terminal", bag.name, action);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
variable: (bag: { name: GrammarToken; state: GrammarToken }) => {
|
|
53
|
+
const action: TableAction = { type: "goto", value: parseInt(bag.state.lexeme) };
|
|
54
|
+
return new ItemNode("variable", bag.name, action);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const buildStates = (grammar: string): TableState[] => {
|
|
59
|
+
const lexer = grammarLexerGenerator.generate(grammar, () => ({}));
|
|
60
|
+
const parserGenerator = getSelfHostedParserGenerator(selfhosted as any);
|
|
61
|
+
const parser = parserGenerator.generate(lexer, {
|
|
62
|
+
reducer: ({ bag, name }) => reducers[name ?? ""](bag),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const parsingResult = (parser.parse().result as ListNode<StateNode>).getItemsReversed();
|
|
66
|
+
return parsingResult.map((state) => state.toTableState());
|
|
67
|
+
};
|