@scinorandex/sparse 0.0.8 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +41 -0
- package/AGENTS.md +60 -0
- package/README.md +286 -30
- package/dist/cli.js +113 -24
- package/dist/cli.js.map +1 -1
- package/dist/generator.d.ts +9 -2
- package/dist/generator.js +179 -50
- package/dist/generator.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +34 -1
- package/dist/index.js.map +1 -1
- package/dist/meta/common.d.ts +17 -0
- package/dist/meta/common.js +66 -1
- package/dist/meta/common.js.map +1 -1
- package/dist/meta/selfhosted.d.ts +4 -5
- package/dist/meta/selfhosted.js +51 -38
- package/dist/meta/selfhosted.js.map +1 -1
- package/dist/parser.d.ts +56 -33
- package/dist/parser.js +146 -18
- package/dist/parser.js.map +1 -1
- package/dist/table/selfhosted.d.ts +10 -1
- package/dist/table/selfhosted.js +32 -12
- package/dist/table/selfhosted.js.map +1 -1
- package/dist/table/validate.d.ts +17 -0
- package/dist/table/validate.js +84 -0
- package/dist/table/validate.js.map +1 -0
- package/dist/utils/Stack.d.ts +4 -0
- package/dist/utils/Stack.js +16 -1
- package/dist/utils/Stack.js.map +1 -1
- package/dist/utils/errorWindowBuilder.js +11 -11
- package/dist/utils/errorWindowBuilder.js.map +1 -1
- package/dist/utils/loadFiles.d.ts +8 -0
- package/dist/utils/loadFiles.js +47 -0
- package/dist/utils/loadFiles.js.map +1 -0
- package/dist/utils/reducers.d.ts +9 -0
- package/dist/utils/reducers.js +56 -0
- package/dist/utils/reducers.js.map +1 -0
- package/example/LoLang/example.ts +42 -18
- package/example/LoLang/tablelalr.txt +381 -0
- package/example/kleene-test/example.ts +35 -17
- package/example/math/example.ts +12 -7
- package/example/selfhosted/example.ts +30 -24
- package/opencode.json +18 -0
- package/package.json +9 -3
- package/src/cli.ts +132 -23
- package/src/generator.ts +237 -67
- package/src/index.ts +56 -3
- package/src/meta/common.ts +116 -0
- package/src/meta/selfhosted.ts +42 -11
- package/src/parser.ts +259 -47
- package/src/table/selfhosted.ts +48 -12
- package/src/table/validate.ts +143 -0
- package/src/utils/Stack.ts +22 -2
- package/src/utils/errorWindowBuilder.ts +15 -13
- package/src/utils/loadFiles.ts +54 -0
- package/src/utils/reducers.ts +93 -0
- package/test/cli.test.ts +144 -0
- package/test/codegen.test.ts +75 -0
- package/test/grammar.test.ts +243 -0
- package/test/helpers.ts +59 -0
- package/test/lalr.test.ts +114 -0
- package/test/parser.test.ts +392 -0
- package/test/table.test.ts +144 -0
- package/tsconfig.json +1 -1
- package/example/complicated/grammar.txt +0 -37
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadTable = exports.loadGrammar = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const common_1 = require("../meta/common");
|
|
9
|
+
const selfhosted_1 = require("../table/selfhosted");
|
|
10
|
+
const selfhosted_2 = require("../meta/selfhosted");
|
|
11
|
+
const loadGrammar = async (grammarPath, options = {}) => {
|
|
12
|
+
let source;
|
|
13
|
+
try {
|
|
14
|
+
source = await promises_1.default.readFile(grammarPath, "utf8");
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
reason: `Could not read the grammar file: ${err instanceof Error ? err.message : String(err)}`,
|
|
20
|
+
token: (0, common_1.syntheticGrammarToken)(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const result = (0, selfhosted_2.tryBuildProductions)(source, options);
|
|
24
|
+
if (result.success === false)
|
|
25
|
+
return Object.assign(Object.assign({}, result), { reason: `${result.reason} (in ${grammarPath})` });
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
exports.loadGrammar = loadGrammar;
|
|
29
|
+
const loadTable = async (tablePath, options = {}) => {
|
|
30
|
+
let source;
|
|
31
|
+
try {
|
|
32
|
+
source = await promises_1.default.readFile(tablePath, "utf8");
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
reason: `Could not read the parsing table file: ${err instanceof Error ? err.message : String(err)}`,
|
|
38
|
+
token: (0, common_1.syntheticGrammarToken)(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const result = (0, selfhosted_1.tryBuildStates)(source, Object.assign(Object.assign({}, options), { source: tablePath }));
|
|
42
|
+
if (result.success === false)
|
|
43
|
+
return Object.assign(Object.assign({}, result), { reason: `${result.reason} (in ${tablePath})` });
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
exports.loadTable = loadTable;
|
|
47
|
+
//# sourceMappingURL=loadFiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadFiles.js","sourceRoot":"","sources":["../../src/utils/loadFiles.ts"],"names":[],"mappings":";;;;;;AAAA,2DAA6B;AAC7B,2CAA+F;AAE/F,oDAAyE;AACzE,mDAAyD;AAMlD,MAAM,WAAW,GAAG,KAAK,EAC9B,WAAmB,EACnB,UAA8B,EAAE,EACD,EAAE;IACjC,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,oCAAoC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAC9F,KAAK,EAAE,IAAA,8BAAqB,GAAE;SAC/B,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,gCAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK;QAC1B,uCAAY,MAAM,KAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,QAAQ,WAAW,GAAG,IAAG;IACvE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAnBW,QAAA,WAAW,eAmBtB;AAKK,MAAM,SAAS,GAAG,KAAK,EAC5B,SAAiB,EACjB,UAA4B,EAAE,EACC,EAAE;IACjC,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,0CAA0C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YACpG,KAAK,EAAE,IAAA,8BAAqB,GAAE;SAC/B,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,2BAAc,EAAC,MAAM,kCAAO,OAAO,KAAE,MAAM,EAAE,SAAS,IAAG,CAAC;IACzE,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK;QAC1B,uCAAY,MAAM,KAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,QAAQ,SAAS,GAAG,IAAG;IACrE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAnBW,QAAA,SAAS,aAmBpB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Production } from "../meta/common";
|
|
2
|
+
export declare const KLEENE_REDUCER_NAME = "autogenerated-kleene";
|
|
3
|
+
export declare const namedProductions: (productions: Production[]) => string[];
|
|
4
|
+
export declare const missingReducerNames: (productions: Production[], reducers: Record<string, unknown>) => string[];
|
|
5
|
+
export declare const missingReducerMessage: (productions: Production[], reducers: Record<string, unknown>) => string;
|
|
6
|
+
export type ReducerMap<Node> = Record<string, (newInput: any, oldInput: any) => Node>;
|
|
7
|
+
export declare const defineReducers: <Reducers extends Record<string, (...args: never[]) => unknown>>(reducers: Reducers) => (newInput: any, oldInput: any) => any;
|
|
8
|
+
export declare const assertReducersCoverGrammar: (productions: Production[], reducers: ReducerMap<unknown>) => void;
|
|
9
|
+
export declare const enumToString: <TokenType = unknown>(tokenTypes: object) => (tokenType: TokenType) => string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enumToString = exports.assertReducersCoverGrammar = exports.defineReducers = exports.missingReducerMessage = exports.missingReducerNames = exports.namedProductions = exports.KLEENE_REDUCER_NAME = void 0;
|
|
4
|
+
exports.KLEENE_REDUCER_NAME = "autogenerated-kleene";
|
|
5
|
+
const namedProductions = (productions) => {
|
|
6
|
+
const names = new Set();
|
|
7
|
+
for (const production of productions)
|
|
8
|
+
if (production.name != null)
|
|
9
|
+
names.add(production.name);
|
|
10
|
+
return [...names].sort();
|
|
11
|
+
};
|
|
12
|
+
exports.namedProductions = namedProductions;
|
|
13
|
+
const missingReducerNames = (productions, reducers) => (0, exports.namedProductions)(productions).filter((name) => reducers[name] == null);
|
|
14
|
+
exports.missingReducerNames = missingReducerNames;
|
|
15
|
+
const describeKleene = (productions) => {
|
|
16
|
+
const needed = productions.filter((production) => production.name === exports.KLEENE_REDUCER_NAME ||
|
|
17
|
+
production.identifier.startsWith("<autogen-"));
|
|
18
|
+
if (needed.length === 0)
|
|
19
|
+
return "";
|
|
20
|
+
return `\nNote: this grammar uses "*" or "+", so it has ${needed.length} autogenerated list production(s) named "${exports.KLEENE_REDUCER_NAME}". That reducer has to flatten the list it is handed (see README, "Repetition with * and +").`;
|
|
21
|
+
};
|
|
22
|
+
const missingReducerMessage = (productions, reducers) => {
|
|
23
|
+
const missing = (0, exports.missingReducerNames)(productions, reducers);
|
|
24
|
+
if (missing.length === 0)
|
|
25
|
+
return "";
|
|
26
|
+
const has = (0, exports.namedProductions)(productions).filter((name) => reducers[name] != null);
|
|
27
|
+
const kleene = missing.includes(exports.KLEENE_REDUCER_NAME) ? describeKleene(productions) : "";
|
|
28
|
+
return (`No reducer for ${missing.length === 1 ? "production" : "productions"} ${missing
|
|
29
|
+
.map((name) => `"${name}"`)
|
|
30
|
+
.join(", ")}.` +
|
|
31
|
+
`\nReducers are keyed by the name given in the grammar, e.g. "<PROGRAM: program>" is reduced by reducers.program.` +
|
|
32
|
+
(has.length === 0 ? "\nYour reducer map is empty." : `\nYou have reducers for: ${has.join(", ")}.`) +
|
|
33
|
+
kleene);
|
|
34
|
+
};
|
|
35
|
+
exports.missingReducerMessage = missingReducerMessage;
|
|
36
|
+
const defineReducers = (reducers) => function sparseReducer(newInput, oldInput) {
|
|
37
|
+
const name = newInput === null || newInput === void 0 ? void 0 : newInput.name;
|
|
38
|
+
const lookup = reducers;
|
|
39
|
+
const reducer = name == null ? undefined : lookup[name];
|
|
40
|
+
if (reducer == null)
|
|
41
|
+
throw new Error(`No reducer for production "${name !== null && name !== void 0 ? name : ""}" (production ${oldInput === null || oldInput === void 0 ? void 0 : oldInput.index}). Reducers are keyed by the name given in the grammar.`);
|
|
42
|
+
return reducer(newInput, oldInput);
|
|
43
|
+
};
|
|
44
|
+
exports.defineReducers = defineReducers;
|
|
45
|
+
const assertReducersCoverGrammar = (productions, reducers) => {
|
|
46
|
+
const message = (0, exports.missingReducerMessage)(productions, reducers);
|
|
47
|
+
if (message !== "")
|
|
48
|
+
throw new Error(message);
|
|
49
|
+
};
|
|
50
|
+
exports.assertReducersCoverGrammar = assertReducersCoverGrammar;
|
|
51
|
+
const enumToString = (tokenTypes) => (tokenType) => {
|
|
52
|
+
const name = tokenTypes[tokenType];
|
|
53
|
+
return `${name !== null && name !== void 0 ? name : tokenType}`;
|
|
54
|
+
};
|
|
55
|
+
exports.enumToString = enumToString;
|
|
56
|
+
//# sourceMappingURL=reducers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reducers.js","sourceRoot":"","sources":["../../src/utils/reducers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { LR1StackSymbol, Sparse, buildProductions, buildStates } from "../../src/index";
|
|
3
3
|
import { ColumnAndRow, Slex, Token } from "@scinorandex/slex";
|
|
4
4
|
|
|
5
5
|
// prettier-ignore
|
|
6
|
-
enum LoLangTokenType {
|
|
6
|
+
export enum LoLangTokenType {
|
|
7
7
|
EOF,
|
|
8
8
|
|
|
9
9
|
IMPORT,
|
|
@@ -42,7 +42,7 @@ enum LoLangTokenType {
|
|
|
42
42
|
SINGLE_LINE_COMMENT, MULTI_LINE_COMMENT
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const lexerGenerator = new Slex<LoLangTokenType, {}>({
|
|
45
|
+
export const lexerGenerator = new Slex<LoLangTokenType, {}>({
|
|
46
46
|
EOF_TYPE: LoLangTokenType.EOF,
|
|
47
47
|
isHigherPrecedence: ({ current, next }) => current === LoLangTokenType.IDENTIFIER,
|
|
48
48
|
// Specifies that SINGLE_LINE_COMMENT and MULTI_LINE_COMMENT tokens should be ignored.
|
|
@@ -165,17 +165,18 @@ class Node {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
async function
|
|
168
|
+
async function parse(tablePath: string) {
|
|
169
169
|
const exampleSourceCode = await fs.readFile("./example/LoLang/Features_Array_Methods.lol", "utf8");
|
|
170
170
|
const lexer = lexerGenerator.generate(exampleSourceCode, () => ({}));
|
|
171
171
|
|
|
172
172
|
const toStringifiedTokenType = (type: LoLangTokenType) => LoLangTokenType[type];
|
|
173
173
|
const productions = buildProductions(await fs.readFile("./example/LoLang/grammar.txt", "utf8"));
|
|
174
|
-
const
|
|
174
|
+
const states = buildStates(await fs.readFile(tablePath, "utf8"));
|
|
175
|
+
const parserGenerator = new Sparse<LoLangTokenType, {}, Node>({ productions, toStringifiedTokenType, states });
|
|
175
176
|
|
|
176
177
|
const parser = parserGenerator.generate(lexer, {
|
|
177
178
|
reducer: (_, { input }) => new Node(input),
|
|
178
|
-
recover({ lexer, statesStack, symbolsStack, isSafe, addError, crash, finish, states }) {
|
|
179
|
+
recover({ lexer, statesStack, symbolsStack, isSafe, addError, crash, finish, insertToken, states }) {
|
|
179
180
|
let token = lexer.peekNextToken();
|
|
180
181
|
const currentState = statesStack.peek();
|
|
181
182
|
|
|
@@ -188,25 +189,26 @@ async function main() {
|
|
|
188
189
|
token = lexer.peekNextToken();
|
|
189
190
|
|
|
190
191
|
// check for lacking semicolons
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
const semicolonAction = states[currentState].getTerminalAction("SEMICOLON");
|
|
193
|
+
if (semicolonAction != null) {
|
|
194
|
+
// A semicolon the author forgot. It gets the same position as the token we choked on.
|
|
193
195
|
const newToken = new Token<LoLangTokenType, {}>(
|
|
194
196
|
LoLangTokenType.SEMICOLON,
|
|
195
197
|
";",
|
|
196
|
-
new ColumnAndRow(token.
|
|
198
|
+
new ColumnAndRow(token.line, token.column),
|
|
197
199
|
token.metadata,
|
|
198
200
|
);
|
|
199
201
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
// If the parser only wanted a semicolon to reduce, hand it back as the next token.
|
|
203
|
+
if (semicolonAction.type === "reduce") return finish({ newToken });
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
`Expected SEMICOLON but received ${LoLangTokenType[token.type]}. SEMICOLON was automatically inserted.`,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
205
|
+
// Otherwise shift the synthesized semicolon onto the stacks and carry on from there.
|
|
206
|
+
const inserted = insertToken(newToken);
|
|
207
|
+
if (inserted != null) return crash(inserted.reason);
|
|
209
208
|
|
|
209
|
+
addError(
|
|
210
|
+
`Expected SEMICOLON but received ${LoLangTokenType[token.type]}. SEMICOLON was automatically inserted.`,
|
|
211
|
+
);
|
|
210
212
|
if (isSafe()) return finish();
|
|
211
213
|
}
|
|
212
214
|
|
|
@@ -215,7 +217,29 @@ async function main() {
|
|
|
215
217
|
},
|
|
216
218
|
});
|
|
217
219
|
|
|
218
|
-
|
|
220
|
+
const parsingResult = parser.parse();
|
|
221
|
+
return JSON.stringify(parsingResult.result?.toObject());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function main() {
|
|
225
|
+
let nsTime = Date.now();
|
|
226
|
+
const lr1Result = await parse("./example/LoLang/table.txt");
|
|
227
|
+
const lr1Time = Date.now() - nsTime;
|
|
228
|
+
|
|
229
|
+
nsTime = Date.now();
|
|
230
|
+
const lalrResult = await parse("./example/LoLang/tablelalr.txt");
|
|
231
|
+
const lalrTime = Date.now() - nsTime;
|
|
232
|
+
|
|
233
|
+
console.log(`LR(1) time: ${lr1Time}ms`);
|
|
234
|
+
console.log(`LALR(1) time: ${lalrTime}ms`);
|
|
235
|
+
const delta = Math.abs(lalrTime - lr1Time);
|
|
236
|
+
console.log("LR(1) is " + (lr1Time < lalrTime ? "faster" : "slower") + " than LALR(1)" + ` (${delta}ms)`);
|
|
237
|
+
|
|
238
|
+
console.log("==============================");
|
|
239
|
+
console.log(`LR(1) result: ${lr1Result}`);
|
|
240
|
+
console.log(`LALR(1) result: ${lalrResult}`);
|
|
241
|
+
console.log("==============================");
|
|
242
|
+
console.log(lr1Result === lalrResult ? "Equal output" : "Not equal output");
|
|
219
243
|
}
|
|
220
244
|
|
|
221
245
|
main();
|