parser-lr 0.4.1 → 0.6.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/README.md +39 -1
- package/bin/parser-lr.js +8373 -7960
- package/bin/parser-lr.js.map +1 -1
- package/dist/lib/grammar/grammar-json-path.d.ts +2 -0
- package/dist/lib/grammar/grammar-json-path.d.ts.map +1 -0
- package/dist/lib/grammar/grammar-json-path.esm.d.ts +5 -0
- package/dist/lib/grammar/grammar-json-path.esm.d.ts.map +1 -0
- package/dist/lib/grammar/grammar-json-path.esm.js +9 -0
- package/dist/lib/grammar/grammar-json-path.esm.js.map +1 -0
- package/dist/lib/grammar/grammar-json-path.js +2 -0
- package/dist/lib/grammar/grammar-json-path.js.map +1 -0
- package/dist/lib/grammar/index.d.ts +3 -0
- package/dist/lib/grammar/index.d.ts.map +1 -1
- package/dist/lib/grammar/index.js +2 -0
- package/dist/lib/grammar/index.js.map +1 -1
- package/dist/lib/grammar/meta-grammar-table.d.ts +12 -0
- package/dist/lib/grammar/meta-grammar-table.d.ts.map +1 -1
- package/dist/lib/grammar/meta-grammar-table.js +33 -3
- package/dist/lib/grammar/meta-grammar-table.js.map +1 -1
- package/dist/lib/grammar/table-validator.d.ts +26 -0
- package/dist/lib/grammar/table-validator.d.ts.map +1 -0
- package/dist/lib/grammar/table-validator.js +258 -0
- package/dist/lib/grammar/table-validator.js.map +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/transform/cst-transformer.d.ts +14 -0
- package/dist/lib/transform/cst-transformer.d.ts.map +1 -1
- package/dist/lib/transform/cst-transformer.js +82 -11
- package/dist/lib/transform/cst-transformer.js.map +1 -1
- package/dist/lib/transform/grammar-fixture.d.ts +56 -0
- package/dist/lib/transform/grammar-fixture.d.ts.map +1 -0
- package/dist/lib/transform/grammar-fixture.js +101 -0
- package/dist/lib/transform/grammar-fixture.js.map +1 -0
- package/dist/lib-cjs/ast/ast-node.js +70 -0
- package/dist/lib-cjs/ast/ast-node.js.map +1 -0
- package/dist/lib-cjs/errors/index.js +9 -0
- package/dist/lib-cjs/errors/index.js.map +1 -0
- package/dist/lib-cjs/errors/parser-lr-error.js +52 -0
- package/dist/lib-cjs/errors/parser-lr-error.js.map +1 -0
- package/dist/lib-cjs/grammar/ast-schema.js +43 -0
- package/dist/lib-cjs/grammar/ast-schema.js.map +1 -0
- package/dist/lib-cjs/grammar/ast-type.js +3 -0
- package/dist/lib-cjs/grammar/ast-type.js.map +1 -0
- package/dist/lib-cjs/grammar/expression.js +3 -0
- package/dist/lib-cjs/grammar/expression.js.map +1 -0
- package/dist/lib-cjs/grammar/grammar-from-cst.js +641 -0
- package/dist/lib-cjs/grammar/grammar-from-cst.js.map +1 -0
- package/dist/lib-cjs/grammar/grammar-json-path.js +13 -0
- package/dist/lib-cjs/grammar/grammar-json-path.js.map +1 -0
- package/dist/lib-cjs/grammar/grammar-literals.js +71 -0
- package/dist/lib-cjs/grammar/grammar-literals.js.map +1 -0
- package/dist/lib-cjs/grammar/grammar.js +64 -0
- package/dist/lib-cjs/grammar/grammar.js.map +1 -0
- package/dist/lib-cjs/grammar/grammar.json +5760 -0
- package/dist/lib-cjs/grammar/index.js +21 -0
- package/dist/lib-cjs/grammar/index.js.map +1 -0
- package/dist/lib-cjs/grammar/meta-grammar-table.js +68 -0
- package/dist/lib-cjs/grammar/meta-grammar-table.js.map +1 -0
- package/dist/lib-cjs/grammar/production.js +3 -0
- package/dist/lib-cjs/grammar/production.js.map +1 -0
- package/dist/lib-cjs/grammar/read-grammar-error.js +22 -0
- package/dist/lib-cjs/grammar/read-grammar-error.js.map +1 -0
- package/dist/lib-cjs/grammar/read-grammar.js +24 -0
- package/dist/lib-cjs/grammar/read-grammar.js.map +1 -0
- package/dist/lib-cjs/grammar/table-validator.js +262 -0
- package/dist/lib-cjs/grammar/table-validator.js.map +1 -0
- package/dist/lib-cjs/grammar/token-rule.js +3 -0
- package/dist/lib-cjs/grammar/token-rule.js.map +1 -0
- package/dist/lib-cjs/grammar/transform-expression.js +3 -0
- package/dist/lib-cjs/grammar/transform-expression.js.map +1 -0
- package/dist/lib-cjs/grammar/transform-rule.js +3 -0
- package/dist/lib-cjs/grammar/transform-rule.js.map +1 -0
- package/dist/lib-cjs/grammar/transform-schema.js +43 -0
- package/dist/lib-cjs/grammar/transform-schema.js.map +1 -0
- package/dist/lib-cjs/index.js +66 -0
- package/dist/lib-cjs/index.js.map +1 -0
- package/dist/lib-cjs/lexer/index.js +32 -0
- package/dist/lib-cjs/lexer/index.js.map +1 -0
- package/dist/lib-cjs/lexer/lexer-compile-error.js +22 -0
- package/dist/lib-cjs/lexer/lexer-compile-error.js.map +1 -0
- package/dist/lib-cjs/lexer/lexer-compile.js +201 -0
- package/dist/lib-cjs/lexer/lexer-compile.js.map +1 -0
- package/dist/lib-cjs/lexer/lexer-error.js +22 -0
- package/dist/lib-cjs/lexer/lexer-error.js.map +1 -0
- package/dist/lib-cjs/lexer/lexer-input-error.js +17 -0
- package/dist/lib-cjs/lexer/lexer-input-error.js.map +1 -0
- package/dist/lib-cjs/lexer/lexer-state-error.js +19 -0
- package/dist/lib-cjs/lexer/lexer-state-error.js.map +1 -0
- package/dist/lib-cjs/lexer/lexer.js +278 -0
- package/dist/lib-cjs/lexer/lexer.js.map +1 -0
- package/dist/lib-cjs/lexer/token.js +44 -0
- package/dist/lib-cjs/lexer/token.js.map +1 -0
- package/dist/lib-cjs/package.json +3 -0
- package/dist/lib-cjs/parse-context-error.js +19 -0
- package/dist/lib-cjs/parse-context-error.js.map +1 -0
- package/dist/lib-cjs/parse-context.js +137 -0
- package/dist/lib-cjs/parse-context.js.map +1 -0
- package/dist/lib-cjs/parse-output-error.js +19 -0
- package/dist/lib-cjs/parse-output-error.js.map +1 -0
- package/dist/lib-cjs/parse-output.js +18 -0
- package/dist/lib-cjs/parse-output.js.map +1 -0
- package/dist/lib-cjs/parse-table/analysis/first-follow.js +294 -0
- package/dist/lib-cjs/parse-table/analysis/first-follow.js.map +1 -0
- package/dist/lib-cjs/parse-table/analysis/index.js +9 -0
- package/dist/lib-cjs/parse-table/analysis/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/bnf/bnf-grammar.js +140 -0
- package/dist/lib-cjs/parse-table/bnf/bnf-grammar.js.map +1 -0
- package/dist/lib-cjs/parse-table/bnf/bnf-production.js +3 -0
- package/dist/lib-cjs/parse-table/bnf/bnf-production.js.map +1 -0
- package/dist/lib-cjs/parse-table/bnf/bnf-symbol.js +39 -0
- package/dist/lib-cjs/parse-table/bnf/bnf-symbol.js.map +1 -0
- package/dist/lib-cjs/parse-table/bnf/desugar-ebnf.js +224 -0
- package/dist/lib-cjs/parse-table/bnf/desugar-ebnf.js.map +1 -0
- package/dist/lib-cjs/parse-table/bnf/index.js +13 -0
- package/dist/lib-cjs/parse-table/bnf/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/build-lr-table.js +171 -0
- package/dist/lib-cjs/parse-table/build-lr-table.js.map +1 -0
- package/dist/lib-cjs/parse-table/index.js +49 -0
- package/dist/lib-cjs/parse-table/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/lr-algorithm-error.js +19 -0
- package/dist/lib-cjs/parse-table/lr-algorithm-error.js.map +1 -0
- package/dist/lib-cjs/parse-table/lr-algorithm.js +31 -0
- package/dist/lib-cjs/parse-table/lr-algorithm.js.map +1 -0
- package/dist/lib-cjs/parse-table/lr0/index.js +14 -0
- package/dist/lib-cjs/parse-table/lr0/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/lr0/lr0-item-set.js +286 -0
- package/dist/lib-cjs/parse-table/lr0/lr0-item-set.js.map +1 -0
- package/dist/lib-cjs/parse-table/lr1/index.js +18 -0
- package/dist/lib-cjs/parse-table/lr1/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/lr1/lr1-item-set.js +432 -0
- package/dist/lib-cjs/parse-table/lr1/lr1-item-set.js.map +1 -0
- package/dist/lib-cjs/parse-table/parse-table-build-error.js +24 -0
- package/dist/lib-cjs/parse-table/parse-table-build-error.js.map +1 -0
- package/dist/lib-cjs/parse-table/parse-table-error.js +19 -0
- package/dist/lib-cjs/parse-table/parse-table-error.js.map +1 -0
- package/dist/lib-cjs/parse-table/parse-table-json.js +12 -0
- package/dist/lib-cjs/parse-table/parse-table-json.js.map +1 -0
- package/dist/lib-cjs/parse-table/parse-table.js +345 -0
- package/dist/lib-cjs/parse-table/parse-table.js.map +1 -0
- package/dist/lib-cjs/parse-table/slr/index.js +13 -0
- package/dist/lib-cjs/parse-table/slr/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/slr/parse-action.js +8 -0
- package/dist/lib-cjs/parse-table/slr/parse-action.js.map +1 -0
- package/dist/lib-cjs/parse-table/slr/slr-table.js +18 -0
- package/dist/lib-cjs/parse-table/slr/slr-table.js.map +1 -0
- package/dist/lib-cjs/parse-table/table/index.js +21 -0
- package/dist/lib-cjs/parse-table/table/index.js.map +1 -0
- package/dist/lib-cjs/parse-table/table/lr-parse-table.js +192 -0
- package/dist/lib-cjs/parse-table/table/lr-parse-table.js.map +1 -0
- package/dist/lib-cjs/parse-table/table/parse-action.js +54 -0
- package/dist/lib-cjs/parse-table/table/parse-action.js.map +1 -0
- package/dist/lib-cjs/parse-table/table/table-builder-base.js +153 -0
- package/dist/lib-cjs/parse-table/table/table-builder-base.js.map +1 -0
- package/dist/lib-cjs/parse-table/token-inventory.js +18 -0
- package/dist/lib-cjs/parse-table/token-inventory.js.map +1 -0
- package/dist/lib-cjs/parser-lr.js +143 -0
- package/dist/lib-cjs/parser-lr.js.map +1 -0
- package/dist/lib-cjs/shift-reduce/index.js +11 -0
- package/dist/lib-cjs/shift-reduce/index.js.map +1 -0
- package/dist/lib-cjs/shift-reduce/shift-reduce-engine.js +224 -0
- package/dist/lib-cjs/shift-reduce/shift-reduce-engine.js.map +1 -0
- package/dist/lib-cjs/transform/binding-map.js +82 -0
- package/dist/lib-cjs/transform/binding-map.js.map +1 -0
- package/dist/lib-cjs/transform/cst-transformer.js +480 -0
- package/dist/lib-cjs/transform/cst-transformer.js.map +1 -0
- package/dist/lib-cjs/transform/grammar-fixture.js +109 -0
- package/dist/lib-cjs/transform/grammar-fixture.js.map +1 -0
- package/dist/lib-cjs/transform/index.js +12 -0
- package/dist/lib-cjs/transform/index.js.map +1 -0
- package/docs/grammar.md +113 -0
- package/docs/parser-lr-enhancement-proposals.md +337 -0
- package/grammars/6502.grammar +233 -0
- package/grammars/calc.grammar +30 -0
- package/grammars/ferrite.grammar +991 -0
- package/grammars/fixtures/build-optional/build-optional.grammar +32 -0
- package/grammars/fixtures/build-optional/input.txt +1 -0
- package/grammars/fixtures/comparison-op/comparison-op.grammar +28 -0
- package/grammars/fixtures/comparison-op/input.txt +1 -0
- package/grammars/fixtures/flatten-repeat/flatten-repeat.grammar +28 -0
- package/grammars/fixtures/flatten-repeat/input.txt +1 -0
- package/grammars/fixtures/help-topic/help-topic.grammar +28 -0
- package/grammars/fixtures/help-topic/input.txt +1 -0
- package/grammars/fixtures/let-target/input.txt +1 -0
- package/grammars/fixtures/let-target/let-target.grammar +33 -0
- package/grammars/fixtures/nested-production-repro/input.txt +1 -0
- package/grammars/fixtures/nested-production-repro/nested-production-repro.grammar +26 -0
- package/grammars/fixtures/pass-preserves-production/bare-terminal/bare-terminal.grammar +24 -0
- package/grammars/fixtures/pass-preserves-production/bare-terminal/input.txt +1 -0
- package/grammars/fixtures/pass-preserves-production/labeled-variant/input-end-if.txt +1 -0
- package/grammars/fixtures/pass-preserves-production/labeled-variant/input-end.txt +1 -0
- package/grammars/fixtures/pass-preserves-production/labeled-variant/labeled-variant.grammar +24 -0
- package/grammars/fixtures/pass-preserves-production/optional-absent/input.txt +1 -0
- package/grammars/fixtures/pass-preserves-production/optional-absent/optional-absent.grammar +24 -0
- package/grammars/fixtures/pass-preserves-production/optional-present/input.txt +1 -0
- package/grammars/fixtures/pass-preserves-production/optional-present/optional-present.grammar +24 -0
- package/grammars/fixtures/table-only-import/calc.table.json +190 -0
- package/grammars/fixtures/table-only-import/table-only-smoke.cjs +38 -0
- package/grammars/fixtures/table-only-import/table-only-smoke.mjs +18 -0
- package/grammars/grammar.grammar +428 -0
- package/grammars/lisp.grammar +95 -0
- package/package.json +10 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fixtureGrammarPath = fixtureGrammarPath;
|
|
4
|
+
exports.findChildrenBySymbol = findChildrenBySymbol;
|
|
5
|
+
exports.findChildBySymbol = findChildBySymbol;
|
|
6
|
+
exports.parseFixture = parseFixture;
|
|
7
|
+
exports.discoverFixtures = discoverFixtures;
|
|
8
|
+
exports.runFixtureCase = runFixtureCase;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const parse_context_js_1 = require("../parse-context.js");
|
|
12
|
+
/**
|
|
13
|
+
* Returns the `.grammar` file for one fixture directory.
|
|
14
|
+
*
|
|
15
|
+
* Fixture grammars are named after their directory (for example
|
|
16
|
+
* `bare-terminal/bare-terminal.grammar`). The meta-grammar keeps the reserved
|
|
17
|
+
* name `grammar.grammar` at the repo root only.
|
|
18
|
+
*
|
|
19
|
+
* @param fixtureDir - Directory containing a fixture grammar and input files.
|
|
20
|
+
*/
|
|
21
|
+
function fixtureGrammarPath(fixtureDir) {
|
|
22
|
+
const grammarPath = (0, node_path_1.join)(fixtureDir, `${(0, node_path_1.basename)(fixtureDir)}.grammar`);
|
|
23
|
+
return (0, node_fs_1.existsSync)(grammarPath) ? grammarPath : null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns child AST nodes matching a symbol name.
|
|
27
|
+
*
|
|
28
|
+
* @param node - Parent AST node.
|
|
29
|
+
* @param symbol - Child symbol to find.
|
|
30
|
+
*/
|
|
31
|
+
function findChildrenBySymbol(node, symbol) {
|
|
32
|
+
return node.children.filter((child) => child.symbol === symbol);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns the first child AST node matching a symbol name.
|
|
36
|
+
*
|
|
37
|
+
* @param node - Parent AST node.
|
|
38
|
+
* @param symbol - Child symbol to find.
|
|
39
|
+
*/
|
|
40
|
+
function findChildBySymbol(node, symbol) {
|
|
41
|
+
return node.children.find((child) => child.symbol === symbol) ?? null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parses one fixture grammar and input file into an AST.
|
|
45
|
+
*
|
|
46
|
+
* @param fixtureDir - Directory containing a named fixture grammar and an input file.
|
|
47
|
+
* @param inputFileName - Input file name within the fixture directory.
|
|
48
|
+
*/
|
|
49
|
+
function parseFixture(fixtureDir, inputFileName = 'input.txt') {
|
|
50
|
+
const grammarPath = fixtureGrammarPath(fixtureDir);
|
|
51
|
+
if (grammarPath === null) {
|
|
52
|
+
throw new Error(`fixture grammar not found in ${fixtureDir}`);
|
|
53
|
+
}
|
|
54
|
+
const grammarSource = (0, node_fs_1.readFileSync)(grammarPath, 'utf8');
|
|
55
|
+
const input = (0, node_fs_1.readFileSync)((0, node_path_1.join)(fixtureDir, inputFileName), 'utf8').trimEnd();
|
|
56
|
+
const context = parse_context_js_1.ParseContext.fromGrammar(grammarSource, 'lr1');
|
|
57
|
+
return context.parseSource(input);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Discovers fixture directories containing a named `.grammar` file under a root path.
|
|
61
|
+
*
|
|
62
|
+
* @param rootDir - Root directory to scan recursively.
|
|
63
|
+
*/
|
|
64
|
+
function discoverFixtures(rootDir) {
|
|
65
|
+
const fixtures = [];
|
|
66
|
+
const visit = (directory) => {
|
|
67
|
+
const grammarPath = fixtureGrammarPath(directory);
|
|
68
|
+
if (grammarPath !== null) {
|
|
69
|
+
const inputFiles = (0, node_fs_1.readdirSync)(directory)
|
|
70
|
+
.filter((name) => name.startsWith('input') && name.endsWith('.txt'))
|
|
71
|
+
.sort();
|
|
72
|
+
for (const inputFile of inputFiles) {
|
|
73
|
+
const baseName = inputFile.replace(/^input/, '').replace(/\.txt$/, '');
|
|
74
|
+
const expectedName = baseName === '' ? 'expected.json' : `expected${baseName}.json`;
|
|
75
|
+
const expectedPath = (0, node_path_1.join)(directory, expectedName);
|
|
76
|
+
fixtures.push({
|
|
77
|
+
name: `${directory.split('/').slice(-2).join('/')}/${inputFile}`,
|
|
78
|
+
grammarPath,
|
|
79
|
+
inputPath: (0, node_path_1.join)(directory, inputFile),
|
|
80
|
+
expectedPath: (0, node_fs_1.existsSync)(expectedPath) ? expectedPath : null,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })) {
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
visit((0, node_path_1.join)(directory, entry.name));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
visit(rootDir);
|
|
91
|
+
return fixtures;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parses a fixture case and compares to expected JSON when present.
|
|
95
|
+
*
|
|
96
|
+
* @param fixture - Fixture metadata.
|
|
97
|
+
*/
|
|
98
|
+
function runFixtureCase(fixture, expectFn) {
|
|
99
|
+
const grammarSource = (0, node_fs_1.readFileSync)(fixture.grammarPath, 'utf8');
|
|
100
|
+
const input = (0, node_fs_1.readFileSync)(fixture.inputPath, 'utf8').trimEnd();
|
|
101
|
+
const context = parse_context_js_1.ParseContext.fromGrammar(grammarSource, 'lr1');
|
|
102
|
+
const ast = context.parseSource(input);
|
|
103
|
+
if (fixture.expectedPath !== null) {
|
|
104
|
+
const expected = JSON.parse((0, node_fs_1.readFileSync)(fixture.expectedPath, 'utf8'));
|
|
105
|
+
expectFn(ast).toEqual(expected);
|
|
106
|
+
}
|
|
107
|
+
return ast;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=grammar-fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grammar-fixture.js","sourceRoot":"","sources":["../../../src/lib/transform/grammar-fixture.ts"],"names":[],"mappings":";;AA0BA,gDAKC;AAQD,oDAGC;AAQD,8CAGC;AAQD,oCAiBC;AAOD,4CAyCC;AAOD,wCAkBC;AAvJD,qCAAgE;AAChE,yCAA2C;AAG3C,0DAAmD;AAanD;;;;;;;;GAQG;AACH,SAAgB,kBAAkB,CAAC,UAAkB;IAEjD,MAAM,WAAW,GAAG,IAAA,gBAAI,EAAC,UAAU,EAAE,GAAG,IAAA,oBAAQ,EAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAExE,OAAO,IAAA,oBAAU,EAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,oBAAoB,CAAC,IAAa,EAAE,MAAc;IAE9D,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,IAAa,EAAE,MAAc;IAE3D,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,SAAgB,YAAY,CACxB,UAAkB,EAClB,aAAa,GAAG,WAAW;IAG3B,MAAM,WAAW,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAEnD,IAAI,WAAW,KAAK,IAAI,EACxB,CAAC;QACG,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,aAAa,GAAG,IAAA,sBAAY,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAA,sBAAY,EAAC,IAAA,gBAAI,EAAC,UAAU,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IAC9E,MAAM,OAAO,GAAG,+BAAY,CAAC,WAAW,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAE/D,OAAO,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,OAAe;IAE5C,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAE1C,MAAM,KAAK,GAAG,CAAC,SAAiB,EAAQ,EAAE;QAEtC,MAAM,WAAW,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAElD,IAAI,WAAW,KAAK,IAAI,EACxB,CAAC;YACG,MAAM,UAAU,GAAG,IAAA,qBAAW,EAAC,SAAS,CAAC;iBACpC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;iBACnE,IAAI,EAAE,CAAC;YAEZ,KAAK,MAAM,SAAS,IAAI,UAAU,EAClC,CAAC;gBACG,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;gBACvE,MAAM,YAAY,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,QAAQ,OAAO,CAAC;gBACpF,MAAM,YAAY,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,YAAY,CAAC,CAAC;gBAEnD,QAAQ,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,EAAE;oBAChE,WAAW;oBACX,SAAS,EAAE,IAAA,gBAAI,EAAC,SAAS,EAAE,SAAS,CAAC;oBACrC,YAAY,EAAE,IAAA,oBAAU,EAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI;iBAC/D,CAAC,CAAC;YACP,CAAC;QACL,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,IAAA,qBAAW,EAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EACnE,CAAC;YACG,IAAI,KAAK,CAAC,WAAW,EAAE,EACvB,CAAC;gBACG,KAAK,CAAC,IAAA,gBAAI,EAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACvC,CAAC;QACL,CAAC;IACL,CAAC,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,CAAC;IAEf,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAC1B,OAA2B,EAC3B,QAAqE;IAGrE,MAAM,aAAa,GAAG,IAAA,sBAAY,EAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,IAAA,sBAAY,EAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IAChE,MAAM,OAAO,GAAG,+BAAY,CAAC,WAAW,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAEvC,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI,EACjC,CAAC;QACG,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAExE,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,GAAG,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformCst = exports.describeProductionSlots = exports.CstTransformer = exports.referenceSlotIndex = exports.productionSlots = exports.parseRhsSymbolKey = void 0;
|
|
4
|
+
var binding_map_js_1 = require("./binding-map.js");
|
|
5
|
+
Object.defineProperty(exports, "parseRhsSymbolKey", { enumerable: true, get: function () { return binding_map_js_1.parseRhsSymbolKey; } });
|
|
6
|
+
Object.defineProperty(exports, "productionSlots", { enumerable: true, get: function () { return binding_map_js_1.productionSlots; } });
|
|
7
|
+
Object.defineProperty(exports, "referenceSlotIndex", { enumerable: true, get: function () { return binding_map_js_1.referenceSlotIndex; } });
|
|
8
|
+
var cst_transformer_js_1 = require("./cst-transformer.js");
|
|
9
|
+
Object.defineProperty(exports, "CstTransformer", { enumerable: true, get: function () { return cst_transformer_js_1.CstTransformer; } });
|
|
10
|
+
Object.defineProperty(exports, "describeProductionSlots", { enumerable: true, get: function () { return cst_transformer_js_1.describeProductionSlots; } });
|
|
11
|
+
Object.defineProperty(exports, "transformCst", { enumerable: true, get: function () { return cst_transformer_js_1.transformCst; } });
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/transform/index.ts"],"names":[],"mappings":";;;AAAA,mDAA0F;AAAjF,mHAAA,iBAAiB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAAE,oHAAA,kBAAkB,OAAA;AAE/D,2DAA6F;AAApF,oHAAA,cAAc,OAAA;AAAE,6HAAA,uBAAuB,OAAA;AAAE,kHAAA,YAAY,OAAA"}
|
package/docs/grammar.md
CHANGED
|
@@ -155,6 +155,118 @@ transform
|
|
|
155
155
|
;
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
### Choosing `pass`, `build`, and `flatten`
|
|
159
|
+
|
|
160
|
+
| Transform | Use when |
|
|
161
|
+
|-----------|----------|
|
|
162
|
+
| `pass(slot)` | Lift one bound child unchanged. `pass(boundSlot)` preserves the bound production symbol and its `#` variant. `pass(terminalName)` at the same rule may collapse to that terminal. |
|
|
163
|
+
| `build(type.#variant, …)` | Construct an AST node with a fixed shape. Omit absent optional bindings from the argument list. |
|
|
164
|
+
| `flatten(type.#variant, head, tail)` | Turn a `{ repeat }` list into one AST node with ordered children. Use the repeat non-terminal (for example `list$repeat_0`) as `tail` on the head production. |
|
|
165
|
+
|
|
166
|
+
**Production symbols are preserved.** When a CST node has parse-table metadata, transforms keep its production name even if it has only one terminal child. Bare `CLS` through `pass(stmt)` where `[stmt]:cls_stmt` yields an `cls_stmt` node, not `kw_cls`.
|
|
167
|
+
|
|
168
|
+
### Transform contract
|
|
169
|
+
|
|
170
|
+
The transformer applies these rules to every CST node:
|
|
171
|
+
|
|
172
|
+
| Situation | Behavior |
|
|
173
|
+
|-----------|----------|
|
|
174
|
+
| `pass(slot)` | Preserve the bound production's symbol and `#` variant; never collapse to its sole terminal child. |
|
|
175
|
+
| No transform rule for production `P` | Emit an identity node `{ symbol: P, variant, children }`. A named non-terminal is kept even when it has a single child; the child is not hoisted in its place. |
|
|
176
|
+
| `build(type.#variant, …)` | Absent optional bindings are omitted from `children`, not shifted into other slots. |
|
|
177
|
+
| `flatten(type.#list, head, tail)` | Repeat tails produce no empty placeholder nodes and drop no items. |
|
|
178
|
+
|
|
179
|
+
**Nested non-terminals stay wrapped.** A production referenced inside another production keeps its own node, so consumers match the grammar non-terminal (for example `comparison_op`) rather than a lexer token name (`less`). To retain a token *inside* that wrapper, bind it: write `comparison_op = #less [tok]:less` and reference `tok` from the transform. Without the binding, `build(comparison_op.#less, tok)` has no slot to read and the child is dropped.
|
|
180
|
+
|
|
181
|
+
Before / after for `CASE IS < 0` with `case_selector = #relational kw_is comparison_op integer`:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"symbol": "case_selector",
|
|
186
|
+
"variant": "relational",
|
|
187
|
+
"children": [
|
|
188
|
+
{ "symbol": "kw_is" },
|
|
189
|
+
{ "symbol": "comparison_op", "variant": "less", "children": [ { "symbol": "less" } ] },
|
|
190
|
+
{ "symbol": "integer" }
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Run `parser-lr table validate -g mylang.grammar` to check `ast` / `transform` consistency. The validator:
|
|
196
|
+
|
|
197
|
+
- warns when `pass(boundSlot)` targets a production that can match a single terminal and has no transform rule;
|
|
198
|
+
- errors when a `build` / `fold` / `flatten` references a `type.#variant` not declared in `ast`;
|
|
199
|
+
- warns when a production, `ast` type, or `transform` rule is declared more than once (the later definition silently overrides the earlier one).
|
|
200
|
+
|
|
201
|
+
Add `--strict` to fail on warnings in CI.
|
|
202
|
+
|
|
203
|
+
## Statement authoring
|
|
204
|
+
|
|
205
|
+
Statements follow the same `grammar` / `ast` / `transform` pattern as expressions. Dispatch on stable `*_stmt` production names instead of keyword tokens.
|
|
206
|
+
|
|
207
|
+
```ebnf
|
|
208
|
+
grammar
|
|
209
|
+
statement =
|
|
210
|
+
#cls pass(stmt)
|
|
211
|
+
| #print pass(stmt)
|
|
212
|
+
| #end pass(stmt)
|
|
213
|
+
;
|
|
214
|
+
|
|
215
|
+
cls_stmt =
|
|
216
|
+
#bare kw_cls
|
|
217
|
+
| #with kw_cls kw_with [bg]:expr
|
|
218
|
+
;
|
|
219
|
+
|
|
220
|
+
print_stmt =
|
|
221
|
+
#bare kw_print
|
|
222
|
+
| #items kw_print [list]:print_list
|
|
223
|
+
;
|
|
224
|
+
|
|
225
|
+
end_stmt =
|
|
226
|
+
#program kw_end
|
|
227
|
+
| #if kw_end kw_if
|
|
228
|
+
;
|
|
229
|
+
|
|
230
|
+
ast
|
|
231
|
+
cls_stmt =
|
|
232
|
+
#bare kw_cls
|
|
233
|
+
| #with kw_cls kw_with [bg]:expr
|
|
234
|
+
;
|
|
235
|
+
|
|
236
|
+
print_stmt =
|
|
237
|
+
#bare kw_print
|
|
238
|
+
| #items kw_print [list]:print_list
|
|
239
|
+
;
|
|
240
|
+
|
|
241
|
+
end_stmt =
|
|
242
|
+
#program kw_end
|
|
243
|
+
| #if kw_end kw_if
|
|
244
|
+
;
|
|
245
|
+
|
|
246
|
+
transform
|
|
247
|
+
statement ->
|
|
248
|
+
#cls pass(stmt)
|
|
249
|
+
| #print pass(stmt)
|
|
250
|
+
| #end pass(stmt)
|
|
251
|
+
;
|
|
252
|
+
|
|
253
|
+
cls_stmt ->
|
|
254
|
+
#bare cls_stmt.#bare(kw_cls)
|
|
255
|
+
| #with cls_stmt.#with(kw_cls, kw_with, bg) ;
|
|
256
|
+
|
|
257
|
+
print_stmt ->
|
|
258
|
+
#bare print_stmt.#bare(kw_print)
|
|
259
|
+
| #items print_stmt.#items(kw_print, list) ;
|
|
260
|
+
|
|
261
|
+
end_stmt ->
|
|
262
|
+
#program end_stmt.#program(kw_end)
|
|
263
|
+
| #if end_stmt.#if(kw_end, kw_if) ;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
After `pass(stmt)`, consumers see `cls_stmt`, `print_stmt`, or `end_stmt` at the statement root. Add `ast` + `transform` rules per statement production rather than scanning for keyword terminals in bridge code.
|
|
267
|
+
|
|
268
|
+
See [`docs/parser-lr-enhancement-proposals.md`](parser-lr-enhancement-proposals.md) for the EduBASIC migration that motivated these conventions.
|
|
269
|
+
|
|
158
270
|
## Complete example
|
|
159
271
|
|
|
160
272
|
See [`grammars/calc.grammar`](../grammars/calc.grammar) for a small working grammar with `grammar`, `ast`, and `transform` sections. [`grammars/lisp.grammar`](../grammars/lisp.grammar) and [`grammars/6502.grammar`](../grammars/6502.grammar) show larger languages.
|
|
@@ -163,6 +275,7 @@ See [`grammars/calc.grammar`](../grammars/calc.grammar) for a small working gram
|
|
|
163
275
|
|
|
164
276
|
```bash
|
|
165
277
|
parser-lr table generate -g mylang.grammar -o mylang.json
|
|
278
|
+
parser-lr table validate -g mylang.grammar
|
|
166
279
|
parser-lr parse -i source.txt -g mylang.grammar
|
|
167
280
|
parser-lr parse -i source.txt -t mylang.json
|
|
168
281
|
```
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# parser-lr open requests (EduBASIC)
|
|
2
|
+
|
|
3
|
+
Audience: agents working on [parser-lr](https://github.com/adamrmoss/parser-lr).
|
|
4
|
+
|
|
5
|
+
Consumer: **edu-basic** (`parser-lr` **0.5.0** in [`package.json`](../package.json)). Reference grammar: [`src/lang/parsing/grammars/edu-basic.grammar`](../src/lang/parsing/grammars/edu-basic.grammar).
|
|
6
|
+
|
|
7
|
+
This document lists **only what parser-lr still needs to change**. Items resolved in 0.5.0 are noted so they are not re-opened.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Resolved in 0.5.0 (no further work)
|
|
12
|
+
|
|
13
|
+
| Item | Notes |
|
|
14
|
+
|------|--------|
|
|
15
|
+
| **`pass()` preserves production symbol** | Bare `CLS` → `cls_stmt`, not `kw_cls`. Same for `PRINT`, `END`, `ELSE`, `WEND`, `DO`, `LOOP`, etc. |
|
|
16
|
+
| **`table validate` command** | Documented in README; implementation exists but is blocked by packaging bug below. |
|
|
17
|
+
| **CJS `require` export** | `package.json` `exports.require` → `dist/lib-cjs/index.js`. |
|
|
18
|
+
|
|
19
|
+
After upgrading to a build that includes the pass fix, EduBASIC can delete [`keyword-fallbacks.ts`](../src/lang/parsing/ast-bridge/statements/keyword-fallbacks.ts) (kept temporarily for older tables / edge cases).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. CLI cannot find `grammar.json` (blocker)
|
|
24
|
+
|
|
25
|
+
### Symptom
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run parser:table
|
|
29
|
+
# ENOENT: .../node_modules/parser-lr/bin/grammar.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Same failure for:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx parser-lr table validate -g src/lang/parsing/grammars/edu-basic.grammar
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Actual layout in published 0.5.0
|
|
39
|
+
|
|
40
|
+
| Path | Present? |
|
|
41
|
+
|------|----------|
|
|
42
|
+
| `dist/lib/grammar/grammar.json` | Yes |
|
|
43
|
+
| `dist/lib-cjs/grammar/grammar.json` | Yes |
|
|
44
|
+
| `bin/grammar.json` | **No** |
|
|
45
|
+
|
|
46
|
+
CLI bundle resolves meta-grammar relative to `bin/`, not `dist/lib/grammar/`.
|
|
47
|
+
|
|
48
|
+
### Required fix
|
|
49
|
+
|
|
50
|
+
Bundle CLI against the same `grammarJsonDirectory()` helper the library uses, or copy `grammar.json` beside `bin/parser-lr.js` in `prepack`.
|
|
51
|
+
|
|
52
|
+
### Acceptance test (parser-lr repo)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm pack
|
|
56
|
+
tmpdir=$(mktemp -d)
|
|
57
|
+
tar -xzf parser-lr-*.tgz -C "$tmpdir"
|
|
58
|
+
cd "$tmpdir/package"
|
|
59
|
+
node bin/parser-lr.js table validate -g grammars/calc.grammar
|
|
60
|
+
node bin/parser-lr.js table generate -g grammars/calc.grammar -o /tmp/calc.json -a lalr
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Both commands must exit 0 on a clean install from the tarball.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 2. Table-only import must not load meta-grammar (blocker for Jest)
|
|
68
|
+
|
|
69
|
+
### Symptom
|
|
70
|
+
|
|
71
|
+
EduBASIC tests import `ParseContext` / `AstNode` from `parser-lr` only to parse a pre-built table. Jest fails:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
SyntaxError: Cannot use 'import.meta' outside a module
|
|
75
|
+
at grammar-json-path.esm.js
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Import chain today:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
dist/lib/index.js
|
|
82
|
+
→ grammar/index.js
|
|
83
|
+
→ read-grammar.js
|
|
84
|
+
→ meta-grammar-table.js (readFileSync grammar.json at module load)
|
|
85
|
+
→ grammar-json-path.esm.js (uses import.meta.url)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`readGrammar()` is not called; meta-grammar still loads because it sits on the public barrel.
|
|
89
|
+
|
|
90
|
+
### Required fix (pick one)
|
|
91
|
+
|
|
92
|
+
**Option A (preferred):** Lazy-load meta-grammar inside `readGrammar()` / `metaGrammarTable()` only. Do not import `meta-grammar-table.js` from `grammar/index.js` at top level. Split exports:
|
|
93
|
+
|
|
94
|
+
- `parser-lr` — parse runtime (`ParseContext`, `AstNode`, lexer, shift-reduce, transform)
|
|
95
|
+
- `parser-lr/grammar` or `parser-lr/read-grammar` — grammar file parsing (`readGrammar`, `validateGrammarTable`)
|
|
96
|
+
|
|
97
|
+
**Option B:** Ensure `dist/lib-cjs/` is safe under Jest without `import.meta`, and document that table-only consumers must `require('parser-lr')` (CJS). EduBASIC can point Jest at CJS, but the ESM entry should still not pull meta-grammar for consumers that only call `ParseContext.fromTableJson`.
|
|
98
|
+
|
|
99
|
+
### Acceptance test (parser-lr repo)
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// table-only-smoke.cjs — must not throw under plain node
|
|
103
|
+
const { ParseContext } = require('parser-lr');
|
|
104
|
+
const table = require('./grammars/calc.table.json'); // or generate inline
|
|
105
|
+
const ctx = ParseContext.fromTableJson(JSON.stringify(table));
|
|
106
|
+
console.log(ctx.parseSource('1 + 2')?.symbol);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// table-only-smoke.mjs — same for ESM default entry
|
|
111
|
+
import { ParseContext } from 'parser-lr';
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Neither import may read `grammar.json` or evaluate `import.meta`.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 3. Preserve intermediate production nodes (high)
|
|
119
|
+
|
|
120
|
+
### Problem
|
|
121
|
+
|
|
122
|
+
`pass()` fix covers **bound slots** on statement dispatch (`[stmt]:cls_stmt`). Nested productions used inside statements still **flatten** when they have no `transform` rule, even when the grammar names them explicitly.
|
|
123
|
+
|
|
124
|
+
EduBASIC bridge code must scan for raw tokens or alternate shapes.
|
|
125
|
+
|
|
126
|
+
### 3a. `comparison_op` inside `case_selector`
|
|
127
|
+
|
|
128
|
+
**Grammar (already in edu-basic):**
|
|
129
|
+
|
|
130
|
+
```ebnf
|
|
131
|
+
case_selector =
|
|
132
|
+
#relational kw_is comparison_op expr
|
|
133
|
+
;
|
|
134
|
+
|
|
135
|
+
comparison_op =
|
|
136
|
+
#less less
|
|
137
|
+
| #greater_equal greater_equal
|
|
138
|
+
| … ;
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`comparison_op` **has** expression-level transforms:
|
|
142
|
+
|
|
143
|
+
```ebnf
|
|
144
|
+
comparison_op ->
|
|
145
|
+
#less pass(tok)
|
|
146
|
+
| #greaterEqual pass(tok)
|
|
147
|
+
| … ;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Input:** `CASE IS < 0`
|
|
151
|
+
|
|
152
|
+
**Actual `case_selector` children (variant `relational`):** `kw_is`, `less`, `expr`
|
|
153
|
+
|
|
154
|
+
**Required:** `kw_is`, `comparison_op` (variant `less`, child `less`), `expr`
|
|
155
|
+
|
|
156
|
+
Without this, consumers match lexer token names (`less`, `greater_equal`) instead of the grammar non-terminal `comparison_op`.
|
|
157
|
+
|
|
158
|
+
### 3b. `let_target` inside `let_stmt`
|
|
159
|
+
|
|
160
|
+
**Grammar:**
|
|
161
|
+
|
|
162
|
+
```ebnf
|
|
163
|
+
let_stmt = kw_let let_target compound_op expr ;
|
|
164
|
+
let_target = identifier assn_path_suffix ;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Input:** `LET x% = 1`
|
|
168
|
+
|
|
169
|
+
**Actual:** flat `identifier`, `equal`, `expr` under `let_stmt` (no `let_target` wrapper).
|
|
170
|
+
|
|
171
|
+
**Required:** `let_stmt` children include `let_target` wrapping `identifier` (and optional `assn_path_suffix`).
|
|
172
|
+
|
|
173
|
+
EduBASIC workaround: [`assignment-helpers.ts`](../src/lang/parsing/ast-bridge/assignment-helpers.ts) `buildLetAssignmentTarget` accepts bare `identifier` at statement root.
|
|
174
|
+
|
|
175
|
+
### 3c. `print_list` / `print_segment`
|
|
176
|
+
|
|
177
|
+
**Grammar:**
|
|
178
|
+
|
|
179
|
+
```ebnf
|
|
180
|
+
print_stmt = kw_print [ print_list ] ;
|
|
181
|
+
print_list = print_segment { print_segment } ;
|
|
182
|
+
print_segment = #bare expr | #trailingComma expr comma | … ;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Bridge must accept: `print_list` wrapper, `print_list$repeat_*` tails, bare `print_segment`, or trailing `postfix_expr` siblings interchangeably ([`io.ts`](../src/lang/parsing/ast-bridge/statements/io.ts) `buildPrintSegmentsFromStatement`).
|
|
186
|
+
|
|
187
|
+
**Required:** stable list shape matching expression `flatten` output (same as `expr_list`).
|
|
188
|
+
|
|
189
|
+
### Proposed semantics
|
|
190
|
+
|
|
191
|
+
When a CST node is a **non-terminal production** `P` and `P` has **no** `transform` rule:
|
|
192
|
+
|
|
193
|
+
1. Emit AST node `{ symbol: P, variant: <alt label>, children: [ transformed children ] }`.
|
|
194
|
+
2. Do **not** hoist single-child terminals to replace `P`.
|
|
195
|
+
3. Apply existing `transform` rules when present (as with `comparison_op` in expression context).
|
|
196
|
+
|
|
197
|
+
This generalizes the 0.5.0 `pass()` bound-slot fix to **all referenced non-terminals**.
|
|
198
|
+
|
|
199
|
+
### Minimal reproducer grammar (add to parser-lr fixtures)
|
|
200
|
+
|
|
201
|
+
```ebnf
|
|
202
|
+
name "nested-production-repro" ;
|
|
203
|
+
|
|
204
|
+
tokens
|
|
205
|
+
kw_is = /IS/i ;
|
|
206
|
+
kw_case = /CASE/i ;
|
|
207
|
+
less = /</ ;
|
|
208
|
+
ident = /[a-z]+/ ;
|
|
209
|
+
integer = /[0-9]+/ ;
|
|
210
|
+
|
|
211
|
+
start line ;
|
|
212
|
+
|
|
213
|
+
grammar
|
|
214
|
+
line = kw_case case_selector ;
|
|
215
|
+
case_selector = #relational kw_is comparison_op integer ;
|
|
216
|
+
comparison_op = #less less ;
|
|
217
|
+
|
|
218
|
+
ast
|
|
219
|
+
comparison_op =
|
|
220
|
+
#less [tok]:less
|
|
221
|
+
;
|
|
222
|
+
|
|
223
|
+
transform
|
|
224
|
+
comparison_op ->
|
|
225
|
+
#less comparison_op.#less(tok) ;
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Input:** `CASE IS < 0`
|
|
229
|
+
|
|
230
|
+
**Assert:** `case_selector` child at index 1 has `symbol === "comparison_op"`, not `less`.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 4. `table validate` must work on edu-basic grammar (medium)
|
|
235
|
+
|
|
236
|
+
Depends on **§1** (CLI packaging).
|
|
237
|
+
|
|
238
|
+
Once the CLI runs, validate this consumer grammar:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npx parser-lr table validate -g src/lang/parsing/grammars/edu-basic.grammar
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Useful warnings (already described in README) should include:
|
|
245
|
+
|
|
246
|
+
- `pass(boundSlot)` where bound production has no `transform` and can match a single terminal (legacy warning; should disappear after §3).
|
|
247
|
+
- `transform` references `type.#variant` not declared in `ast`.
|
|
248
|
+
- Duplicate `grammar` / `ast` / `transform` sections (edu-basic currently duplicates statement blocks; validator should flag).
|
|
249
|
+
|
|
250
|
+
`--strict` should be usable in CI.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 5. Document transform contract (medium)
|
|
255
|
+
|
|
256
|
+
Update [`docs/grammar.md`](https://github.com/adamrmoss/parser-lr/blob/main/docs/grammar.md) with explicit rules:
|
|
257
|
+
|
|
258
|
+
| Transform | Behavior |
|
|
259
|
+
|-----------|----------|
|
|
260
|
+
| `pass(slot)` | Preserve bound production symbol; never collapse to sole terminal child. |
|
|
261
|
+
| No rule for production `P` | Identity node `{ symbol: P, children }` (proposed §3). |
|
|
262
|
+
| `build(type.#variant, …)` | Absent optional slots omitted from children, not shifted. |
|
|
263
|
+
| `flatten(type.#list, head, tail)` | Repeat tails: no empty placeholder nodes; no dropped items. |
|
|
264
|
+
|
|
265
|
+
Include before/after JSON for `CLS`, `CASE IS < 0`, and `LET x = 1`.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 6. Regression fixtures to add in parser-lr (medium)
|
|
270
|
+
|
|
271
|
+
| Fixture | Covers |
|
|
272
|
+
|---------|--------|
|
|
273
|
+
| `pass-preserves-production/` | Bare optional production (`cls_stmt = kw_cls [ … ]`) |
|
|
274
|
+
| `nested-production-repro/` | §3 `comparison_op` inside parent without parent transform |
|
|
275
|
+
| `let-target-repro/` | §3b `let_target` wrapper |
|
|
276
|
+
| `flatten-repeat/` | `a,b,c` list with no spurious empty tails |
|
|
277
|
+
| `build-optional-slots/` | Optional grammar slots absent in `build()` output |
|
|
278
|
+
| `table-only-import/` | §2 smoke scripts |
|
|
279
|
+
|
|
280
|
+
Each fixture: `.grammar`, input line(s), expected AST JSON golden file.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## EduBASIC verification (run after parser-lr release)
|
|
285
|
+
|
|
286
|
+
Run these locally after bumping `parser-lr` and regenerating the table:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
npm install parser-lr@<version>
|
|
290
|
+
npm run parser:table
|
|
291
|
+
npm run parser:check
|
|
292
|
+
npm test
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Spot checks:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
node -e "
|
|
299
|
+
import { ParseContext } from 'parser-lr';
|
|
300
|
+
import table from './src/lang/parsing/edu-basic.table.json' with { type: 'json' };
|
|
301
|
+
const ctx = ParseContext.fromTableJson(JSON.stringify(table));
|
|
302
|
+
for (const line of ['CLS', 'PRINT', 'CASE IS < 0', 'LET x% = 1', 'HELP PRINT']) {
|
|
303
|
+
const ast = ctx.parseSource(line);
|
|
304
|
+
console.log(line, '=>', ast?.symbol, JSON.stringify(ast?.children?.map(c => c.symbol)));
|
|
305
|
+
}
|
|
306
|
+
"
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Expected after all items above:
|
|
310
|
+
|
|
311
|
+
| Line | Root symbol | Notable children |
|
|
312
|
+
|------|-------------|------------------|
|
|
313
|
+
| `CLS` | `cls_stmt` | `kw_cls` |
|
|
314
|
+
| `PRINT` | `print_stmt` | `kw_print` |
|
|
315
|
+
| `CASE IS < 0` | `case_stmt` | selector child includes `comparison_op` |
|
|
316
|
+
| `LET x% = 1` | `let_stmt` | includes `let_target` |
|
|
317
|
+
| `HELP PRINT` | `help_stmt` | `kw_help`, `help_topic` |
|
|
318
|
+
|
|
319
|
+
Then remove from edu-basic:
|
|
320
|
+
|
|
321
|
+
- [`keyword-fallbacks.ts`](../src/lang/parsing/ast-bridge/statements/keyword-fallbacks.ts)
|
|
322
|
+
- Flat-tree fallback branches in [`io.ts`](../src/lang/parsing/ast-bridge/statements/io.ts) (`buildHelpStatement`, `buildPrintSegmentsFromStatement`)
|
|
323
|
+
- `isComparisonOpSymbol` token-name matching in [`control-flow.ts`](../src/lang/parsing/ast-bridge/statements/control-flow.ts)
|
|
324
|
+
- Bare `identifier` path in [`assignment-helpers.ts`](../src/lang/parsing/ast-bridge/assignment-helpers.ts)
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Priority summary
|
|
329
|
+
|
|
330
|
+
| Priority | Item | Blocks edu-basic |
|
|
331
|
+
|----------|------|------------------|
|
|
332
|
+
| P0 | §1 CLI `grammar.json` path | `npm run parser:table`, `table validate` |
|
|
333
|
+
| P0 | §2 Lazy meta-grammar / split entry | Jest and any `import { ParseContext } from 'parser-lr'` |
|
|
334
|
+
| P1 | §3 Intermediate production nodes | Bridge simplification; `CASE IS`, `LET`, `PRINT` |
|
|
335
|
+
| P2 | §4–§6 Validate, docs, fixtures | CI confidence, upstream maintenance |
|
|
336
|
+
|
|
337
|
+
Target: **parser-lr 0.5.1** (or 0.6.0) with tarball acceptance tests for §1 and §2.
|