parser-lr 0.4.1 → 0.5.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 +35 -1
- package/bin/parser-lr.js +340 -5802
- 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 +2 -0
- package/dist/lib/grammar/index.d.ts.map +1 -1
- package/dist/lib/grammar/index.js +1 -0
- package/dist/lib/grammar/index.js.map +1 -1
- package/dist/lib/grammar/meta-grammar-table.d.ts.map +1 -1
- package/dist/lib/grammar/meta-grammar-table.js +4 -2
- 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 +211 -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 +19 -0
- package/dist/lib-cjs/grammar/index.js.map +1 -0
- package/dist/lib-cjs/grammar/meta-grammar-table.js +39 -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 +215 -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 +80 -0
- package/docs/parser-lr-enhancement-proposals.md +548 -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/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/grammar.grammar +428 -0
- package/grammars/lisp.grammar +95 -0
- package/package.json +9 -4
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
# parser-lr enhancement proposals (EduBASIC consumer)
|
|
2
|
+
|
|
3
|
+
This document is for agents working on [parser-lr](https://github.com/adamrmoss/parser-lr). It lists concrete improvements suggested by the EduBASIC migration (`parser-lr` **0.4.1**, grammar at [`src/lang/parsing/grammars/edu-basic.grammar`](../src/lang/parsing/grammars/edu-basic.grammar)).
|
|
4
|
+
|
|
5
|
+
**Goal:** let grammar authors write stable `ast` + `transform` rules for statements the same way expressions already work, so downstream bridges do not need `keyword-fallbacks.ts`-style workarounds.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Reference consumer
|
|
10
|
+
|
|
11
|
+
| Artifact | Path |
|
|
12
|
+
|----------|------|
|
|
13
|
+
| Grammar | [`src/lang/parsing/grammars/edu-basic.grammar`](../src/lang/parsing/grammars/edu-basic.grammar) |
|
|
14
|
+
| Generated table | [`src/lang/parsing/edu-basic.table.json`](../src/lang/parsing/edu-basic.table.json) |
|
|
15
|
+
| AST bridge | [`src/lang/parsing/ast-bridge/`](../src/lang/parsing/ast-bridge/) |
|
|
16
|
+
| Keyword fallbacks (debt) | [`src/lang/parsing/ast-bridge/statements/keyword-fallbacks.ts`](../src/lang/parsing/ast-bridge/statements/keyword-fallbacks.ts) |
|
|
17
|
+
| Dispatch table | [`src/lang/parsing/ast-bridge/statements/dispatch.ts`](../src/lang/parsing/ast-bridge/statements/dispatch.ts) |
|
|
18
|
+
|
|
19
|
+
Regenerate table after grammar changes:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run parser:table
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Parse smoke test:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node -e "
|
|
29
|
+
import { ParseContext } from 'parser-lr';
|
|
30
|
+
import table from './src/lang/parsing/edu-basic.table.json' with { type: 'json' };
|
|
31
|
+
const ctx = ParseContext.fromTableJson(JSON.stringify(table));
|
|
32
|
+
console.log(JSON.stringify(ctx.parseSource('CLS'), null, 2));
|
|
33
|
+
"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Current asymmetry
|
|
39
|
+
|
|
40
|
+
**Expressions** in EduBASIC have full `ast` + `transform` coverage (`comparison_expr.#binary`, `flatten(expr_list)`, etc.). Bridge code walks predictable node shapes.
|
|
41
|
+
|
|
42
|
+
**Statements** use `transform` only at the `statement` dispatcher:
|
|
43
|
+
|
|
44
|
+
```ebnf
|
|
45
|
+
statement ->
|
|
46
|
+
#cls pass(stmt)
|
|
47
|
+
| #print pass(stmt)
|
|
48
|
+
| … ;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Individual `*_stmt` productions have **no** `ast` entries and **no** `transform` rules. When `pass(stmt)` runs and the matched production has a single terminal child, the output often **collapses** to that terminal instead of preserving the production symbol.
|
|
52
|
+
|
|
53
|
+
That forces ~70 lines of `keyword-fallbacks.ts` plus duplicate handling in domain builders.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Priority 1: `pass()` must preserve production symbol
|
|
58
|
+
|
|
59
|
+
### Problem
|
|
60
|
+
|
|
61
|
+
Grammar author binds `[stmt]:cls_stmt` and expects consumers to key off `cls_stmt`. Today bare `CLS` yields `kw_cls`.
|
|
62
|
+
|
|
63
|
+
**Grammar fragment (reproducer):**
|
|
64
|
+
|
|
65
|
+
```ebnf
|
|
66
|
+
name "pass-collapse-repro" ;
|
|
67
|
+
|
|
68
|
+
tokens
|
|
69
|
+
kw_cls = /CLS/i ;
|
|
70
|
+
kw_with = /WITH/i ;
|
|
71
|
+
ident = /[a-z]+/ ;
|
|
72
|
+
|
|
73
|
+
start program_line ;
|
|
74
|
+
|
|
75
|
+
grammar
|
|
76
|
+
program_line =
|
|
77
|
+
#statement [stmt]:cls_stmt
|
|
78
|
+
;
|
|
79
|
+
|
|
80
|
+
cls_stmt =
|
|
81
|
+
kw_cls [ kw_with ident ]
|
|
82
|
+
;
|
|
83
|
+
|
|
84
|
+
transform
|
|
85
|
+
program_line ->
|
|
86
|
+
#statement pass(stmt) ;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Input:** `CLS`
|
|
90
|
+
|
|
91
|
+
**Actual output today:**
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"symbol": "kw_cls",
|
|
96
|
+
"children": [],
|
|
97
|
+
"text": "CLS",
|
|
98
|
+
"variant": null
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Required output:**
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"symbol": "cls_stmt",
|
|
107
|
+
"children": [
|
|
108
|
+
{ "symbol": "kw_cls", "text": "CLS", "children": [] }
|
|
109
|
+
],
|
|
110
|
+
"variant": null
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Input:** `CLS WITH foo`
|
|
115
|
+
|
|
116
|
+
**Required output (variant optional but symbol must stay `cls_stmt`):**
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"symbol": "cls_stmt",
|
|
121
|
+
"children": [
|
|
122
|
+
{ "symbol": "kw_cls", "text": "CLS", "children": [] },
|
|
123
|
+
{ "symbol": "kw_with", "text": "WITH", "children": [] },
|
|
124
|
+
{ "symbol": "ident", "text": "foo", "children": [] }
|
|
125
|
+
],
|
|
126
|
+
"variant": null
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Semantics proposal
|
|
131
|
+
|
|
132
|
+
`pass(slot)` where `slot` is bound to production `P`:
|
|
133
|
+
|
|
134
|
+
1. Always emit an AST node whose `symbol` is `P` (the production name).
|
|
135
|
+
2. Children are the transformed slots of the matched alternative (terminals and subtrees).
|
|
136
|
+
3. Set `variant` from the alternative label (`#program`, `#bare`, …) when present.
|
|
137
|
+
4. **Never** hoist a sole child terminal to replace the `P` node unless the transform explicitly says `pass(thatTerminal)` at the **same** rule (not a parent rule).
|
|
138
|
+
|
|
139
|
+
### EduBASIC statements affected (bare input → wrong symbol today)
|
|
140
|
+
|
|
141
|
+
| Input | Expected symbol | Actual symbol today |
|
|
142
|
+
|-------|-----------------|---------------------|
|
|
143
|
+
| `CLS` | `cls_stmt` | `kw_cls` |
|
|
144
|
+
| `PRINT` | `print_stmt` | `kw_print` |
|
|
145
|
+
| `END` | `end_stmt` (`variant: program`) | `kw_end` |
|
|
146
|
+
| `ELSE` | `else_stmt` | `kw_else` |
|
|
147
|
+
| `WEND` | `wend_stmt` | `kw_wend` |
|
|
148
|
+
| `UEND` | `uend_stmt` | `kw_uend` |
|
|
149
|
+
| `TRY` | `try_stmt` | `kw_try` |
|
|
150
|
+
| `CATCH` | `catch_stmt` | `kw_catch` |
|
|
151
|
+
| `FINALLY` | `finally_stmt` | `kw_finally` |
|
|
152
|
+
| `RETURN` | `return_stmt` | `kw_return` |
|
|
153
|
+
| `NEXT` | `next_stmt` | `kw_next` |
|
|
154
|
+
| `DO` | `do_stmt` (`variant: bare`) | `kw_do` |
|
|
155
|
+
| `LOOP` | `loop_stmt` (`variant: bare`) | `kw_loop` |
|
|
156
|
+
| `RANDOMIZE` | `randomize_stmt` | `kw_randomize` |
|
|
157
|
+
|
|
158
|
+
`END IF` correctly yields `end_stmt` with `variant: if` because multiple children prevent collapse.
|
|
159
|
+
|
|
160
|
+
### Consumer cleanup after fix
|
|
161
|
+
|
|
162
|
+
Delete [`keyword-fallbacks.ts`](../src/lang/parsing/ast-bridge/statements/keyword-fallbacks.ts) and remove the fallback call from [`dispatch.ts`](../src/lang/parsing/ast-bridge/statements/dispatch.ts). Remove duplicate `buildNullaryControlFlowStatement` paths that exist only for `*_stmt` vs `kw_*` pairs.
|
|
163
|
+
|
|
164
|
+
### Suggested parser-lr tests
|
|
165
|
+
|
|
166
|
+
Add fixtures under `grammars/fixtures/pass-preserves-production/`:
|
|
167
|
+
|
|
168
|
+
- `bare-terminal.grammar` (reproducer above)
|
|
169
|
+
- `labeled-variant.grammar` (`end_stmt` with `#program` / `#if`)
|
|
170
|
+
- `optional-present.grammar` (`CLS WITH x`)
|
|
171
|
+
- `optional-absent.grammar` (`CLS`)
|
|
172
|
+
|
|
173
|
+
Assert `node.symbol` after transform, not only parse success.
|
|
174
|
+
|
|
175
|
+
### Suggested CLI warning
|
|
176
|
+
|
|
177
|
+
At `table generate` time, warn when:
|
|
178
|
+
|
|
179
|
+
- Parent transform uses `pass(boundSlot)`
|
|
180
|
+
- Bound production has **no** `transform` rule
|
|
181
|
+
- Production can match with exactly one terminal child
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Priority 2: Statement-level `ast` + `transform` (parity with expressions)
|
|
186
|
+
|
|
187
|
+
### Problem
|
|
188
|
+
|
|
189
|
+
EduBASIC `ast` section today only describes expression shapes. Statements cannot use `build` / `flatten` / labeled variants.
|
|
190
|
+
|
|
191
|
+
### Target pattern for EduBASIC
|
|
192
|
+
|
|
193
|
+
After Priority 1, EduBASIC should be able to add rules like:
|
|
194
|
+
|
|
195
|
+
```ebnf
|
|
196
|
+
ast
|
|
197
|
+
cls_stmt =
|
|
198
|
+
#bare kw_cls
|
|
199
|
+
| #with kw_cls kw_with [bg]:expr
|
|
200
|
+
;
|
|
201
|
+
|
|
202
|
+
print_stmt =
|
|
203
|
+
#bare kw_print
|
|
204
|
+
| #items kw_print [list]:print_list
|
|
205
|
+
;
|
|
206
|
+
|
|
207
|
+
print_list =
|
|
208
|
+
#list [first]:print_segment { [rest]:print_segment }
|
|
209
|
+
;
|
|
210
|
+
|
|
211
|
+
print_segment =
|
|
212
|
+
#bare [value]:expr
|
|
213
|
+
| #trailingComma [value]:expr [comma]:comma
|
|
214
|
+
| #trailingSemicolon [value]:expr [semi]:semicolon
|
|
215
|
+
;
|
|
216
|
+
|
|
217
|
+
end_stmt =
|
|
218
|
+
#program kw_end
|
|
219
|
+
| #if kw_end kw_if
|
|
220
|
+
| #unless kw_end kw_unless
|
|
221
|
+
| #select kw_end kw_select
|
|
222
|
+
| #sub kw_end kw_sub
|
|
223
|
+
| #try kw_end kw_try
|
|
224
|
+
;
|
|
225
|
+
|
|
226
|
+
transform
|
|
227
|
+
cls_stmt ->
|
|
228
|
+
#bare cls_stmt.#bare(kw_cls)
|
|
229
|
+
| #with cls_stmt.#with(kw_cls, kw_with, bg) ;
|
|
230
|
+
|
|
231
|
+
print_stmt ->
|
|
232
|
+
#bare print_stmt.#bare(kw_print)
|
|
233
|
+
| #items print_stmt.#items(kw_print, list) ;
|
|
234
|
+
|
|
235
|
+
print_list ->
|
|
236
|
+
#list flatten(print_list.#list, first, rest) ;
|
|
237
|
+
|
|
238
|
+
print_segment ->
|
|
239
|
+
#bare print_segment.#bare(value)
|
|
240
|
+
| #trailingComma print_segment.#trailingComma(value, comma)
|
|
241
|
+
| #trailingSemicolon print_segment.#trailingSemicolon(value, semi) ;
|
|
242
|
+
|
|
243
|
+
end_stmt ->
|
|
244
|
+
#program end_stmt.#program(kw_end)
|
|
245
|
+
| #if end_stmt.#if(kw_end, kw_if)
|
|
246
|
+
| … ;
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Bridge code then becomes:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
export function buildClsStatement(node: AstNode): ClsStatement
|
|
253
|
+
{
|
|
254
|
+
if (node.variant === 'with')
|
|
255
|
+
{
|
|
256
|
+
return new ClsStatement(buildExpression(findChildBySymbol(node, 'bg')!));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return new ClsStatement();
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
No scanning for `kw_cls` at the root, no `findExpressionRoot` guessing.
|
|
264
|
+
|
|
265
|
+
### Enhancement requests for parser-lr
|
|
266
|
+
|
|
267
|
+
1. **Document** recommended statement authoring pattern (mirror expression docs).
|
|
268
|
+
2. **Optional shorthand:** if `ast` defines `cls_stmt` but `transform` omits it, apply identity build `cls_stmt.#alt(...)` from grammar bindings (opt-in flag `--infer-identity-transforms`).
|
|
269
|
+
3. **Table validation:** error if `transform` references `type.#variant` not declared in `ast`.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Priority 3: Stable shapes for nested statement helpers
|
|
274
|
+
|
|
275
|
+
### 3a. `help_topic` collapse
|
|
276
|
+
|
|
277
|
+
**Grammar:**
|
|
278
|
+
|
|
279
|
+
```ebnf
|
|
280
|
+
help_stmt = kw_help help_topic ;
|
|
281
|
+
help_topic = #keyword help_kw | #punct help_punct ;
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Input:** `HELP PRINT`
|
|
285
|
+
|
|
286
|
+
**Observed:** `help_stmt` with children `kw_help`, `kw_print` (no `help_topic` wrapper).
|
|
287
|
+
|
|
288
|
+
**Desired:** either preserve `help_topic` node:
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"symbol": "help_stmt",
|
|
293
|
+
"children": [
|
|
294
|
+
{ "symbol": "kw_help", "text": "HELP" },
|
|
295
|
+
{
|
|
296
|
+
"symbol": "help_topic",
|
|
297
|
+
"variant": "keyword",
|
|
298
|
+
"children": [{ "symbol": "kw_print", "text": "PRINT" }]
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
or require explicit transform:
|
|
305
|
+
|
|
306
|
+
```ebnf
|
|
307
|
+
help_topic ->
|
|
308
|
+
#keyword help_topic.#keyword(help_kw)
|
|
309
|
+
| #punct help_topic.#punct(help_punct) ;
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Consumer workaround today:** [`buildHelpStatement`](../src/lang/parsing/ast-bridge/statements/io.ts) scans for any child that is not `kw_help`.
|
|
313
|
+
|
|
314
|
+
### 3b. `case_selector` and `comparison_op`
|
|
315
|
+
|
|
316
|
+
**Grammar:**
|
|
317
|
+
|
|
318
|
+
```ebnf
|
|
319
|
+
case_selector =
|
|
320
|
+
#value expr
|
|
321
|
+
| #range expr kw_to expr
|
|
322
|
+
| #relational kw_is comparison_op expr
|
|
323
|
+
;
|
|
324
|
+
|
|
325
|
+
comparison_op =
|
|
326
|
+
#less less
|
|
327
|
+
| #greater_equal greater_equal
|
|
328
|
+
| … ;
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Input:** `CASE IS < 0`
|
|
332
|
+
|
|
333
|
+
**Observed `case_selector` (variant `relational`) children:** `kw_is`, `less`, `postfix_expr` — no `comparison_op` node.
|
|
334
|
+
|
|
335
|
+
**Desired:**
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"symbol": "case_selector",
|
|
340
|
+
"variant": "relational",
|
|
341
|
+
"children": [
|
|
342
|
+
{ "symbol": "kw_is", "text": "IS" },
|
|
343
|
+
{
|
|
344
|
+
"symbol": "comparison_op",
|
|
345
|
+
"variant": "less",
|
|
346
|
+
"children": [{ "symbol": "less", "text": "<" }]
|
|
347
|
+
},
|
|
348
|
+
{ "symbol": "postfix_expr", "…": "…" }
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
With transform:
|
|
354
|
+
|
|
355
|
+
```ebnf
|
|
356
|
+
case_selector ->
|
|
357
|
+
#value case_selector.#value(expr)
|
|
358
|
+
| #range case_selector.#range(expr, kw_to, expr)
|
|
359
|
+
| #relational case_selector.#relational(kw_is, comparison_op, expr) ;
|
|
360
|
+
|
|
361
|
+
comparison_op ->
|
|
362
|
+
#less comparison_op.#less(less)
|
|
363
|
+
| #greater_equal comparison_op.#greaterEqual(greater_equal)
|
|
364
|
+
| … ;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Consumer workaround today:** [`buildCaseSelector`](../src/lang/parsing/ast-bridge/statements/control-flow.ts) matches raw token names (`less_equal`) via `isComparisonOpSymbol`.
|
|
368
|
+
|
|
369
|
+
### 3c. `print_list` / repeat tails
|
|
370
|
+
|
|
371
|
+
**Grammar:**
|
|
372
|
+
|
|
373
|
+
```ebnf
|
|
374
|
+
print_stmt = kw_print [ print_list ] ;
|
|
375
|
+
print_list = print_segment { print_segment } ;
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Bridge must handle:
|
|
379
|
+
|
|
380
|
+
- `print_list` wrapper present or absent
|
|
381
|
+
- `print_list$repeat_0` flatten tails
|
|
382
|
+
- bare trailing `postfix_expr` siblings (when list flattening inlines items)
|
|
383
|
+
|
|
384
|
+
**Desired:** consistent `flatten(print_list.#list, first, rest)` output (same as `expr_list` for expressions).
|
|
385
|
+
|
|
386
|
+
**Consumer workaround today:** [`buildPrintSegmentsFromStatement`](../src/lang/parsing/ast-bridge/statements/io.ts) accepts `print_list`, `print_segment`, or raw expression children interchangeably.
|
|
387
|
+
|
|
388
|
+
### 3d. `let_target` / assignment paths
|
|
389
|
+
|
|
390
|
+
**Grammar:**
|
|
391
|
+
|
|
392
|
+
```ebnf
|
|
393
|
+
let_stmt = kw_let let_target compound_op expr ;
|
|
394
|
+
let_target = identifier assn_path_suffix ;
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Simple `LET x% = 1` often arrives as flat `identifier`, `equal`, `expr` without `let_target` wrapper.
|
|
398
|
+
|
|
399
|
+
**Desired:** always `let_target` node with `identifier` child and optional `assn_path_suffix` list (possibly empty).
|
|
400
|
+
|
|
401
|
+
**Consumer workaround today:** [`assignment-helpers.ts`](../src/lang/parsing/ast-bridge/assignment-helpers.ts) handles both `let_target` and bare `identifier` at the statement root.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Priority 4: `flatten()` repeat semantics
|
|
406
|
+
|
|
407
|
+
### Problem
|
|
408
|
+
|
|
409
|
+
EduBASIC relies on `flatten` for expression lists, array literals, struct members, and (target) print lists. Bridge code defends against:
|
|
410
|
+
|
|
411
|
+
- Empty repeat tail nodes (`postfix_expr` with `variant: primary` and zero children) — see [`isEmptyPostfixTail`](../src/lang/parsing/ast-bridge/ast-node-helpers.ts)
|
|
412
|
+
- Repeat segments named `foo$repeat_0` that drop or duplicate items
|
|
413
|
+
|
|
414
|
+
### Minimal reproducer grammar
|
|
415
|
+
|
|
416
|
+
```ebnf
|
|
417
|
+
name "flatten-repeat-repro" ;
|
|
418
|
+
|
|
419
|
+
tokens
|
|
420
|
+
ident = /[a-z]+/ ;
|
|
421
|
+
comma = /,/ ;
|
|
422
|
+
|
|
423
|
+
start list ;
|
|
424
|
+
|
|
425
|
+
grammar
|
|
426
|
+
list = item { comma item } ;
|
|
427
|
+
|
|
428
|
+
ast
|
|
429
|
+
list = #list [first]:item { [rest]:item } ;
|
|
430
|
+
|
|
431
|
+
transform
|
|
432
|
+
list -> flatten(list.#list, first, rest) ;
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Input:** `a,b,c`
|
|
436
|
+
|
|
437
|
+
**Required:** `list` node with three `item` payloads in order; no spurious empty tail nodes that confuse consumers.
|
|
438
|
+
|
|
439
|
+
### Migration note
|
|
440
|
+
|
|
441
|
+
EduBASIC previously carried a local patch to `applyFlatten` in `node_modules/parser-lr` for repeat-list correctness. That fix belongs upstream with fixture tests.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## Priority 5: `build()` optional slot handling
|
|
446
|
+
|
|
447
|
+
### Problem
|
|
448
|
+
|
|
449
|
+
When a production has optional slots (`[ kw_with expr ]`) and transform uses `build`, absent optionals should be omitted from the child list (or surfaced as explicit `null`), not shift subsequent bindings.
|
|
450
|
+
|
|
451
|
+
### Minimal reproducer
|
|
452
|
+
|
|
453
|
+
```ebnf
|
|
454
|
+
cls_stmt = kw_cls [ kw_with ident ] ;
|
|
455
|
+
|
|
456
|
+
ast
|
|
457
|
+
cls_stmt =
|
|
458
|
+
#main kw_cls [withKw]:kw_with [target]:ident
|
|
459
|
+
;
|
|
460
|
+
|
|
461
|
+
transform
|
|
462
|
+
cls_stmt -> cls_stmt.#main(kw_cls, withKw, target) ;
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Input:** `CLS` — `withKw` and `target` absent.
|
|
466
|
+
|
|
467
|
+
**Required:** `cls_stmt` node with only `kw_cls` child; optional named slots not required for downstream indexing.
|
|
468
|
+
|
|
469
|
+
### Migration note
|
|
470
|
+
|
|
471
|
+
EduBASIC previously patched `applyBuild` optional-slot skip in `node_modules/parser-lr`.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Priority 6: Labeled alternatives on unary productions
|
|
476
|
+
|
|
477
|
+
### Problem
|
|
478
|
+
|
|
479
|
+
Productions like `end_stmt` use labeled alternatives:
|
|
480
|
+
|
|
481
|
+
```ebnf
|
|
482
|
+
end_stmt =
|
|
483
|
+
#program kw_end
|
|
484
|
+
| #if kw_end kw_if
|
|
485
|
+
;
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
When Priority 1 is fixed, `variant` on the output `end_stmt` node must reflect `#program` vs `#if` even for bare `END` (single child `kw_end`).
|
|
489
|
+
|
|
490
|
+
**Input:** `END` → `{ symbol: "end_stmt", variant: "program", children: […] }`
|
|
491
|
+
|
|
492
|
+
**Input:** `END IF` → `{ symbol: "end_stmt", variant: "if", children: […] }`
|
|
493
|
+
|
|
494
|
+
Same for `do_stmt` / `loop_stmt` (`#bare`, `#while`, `#until`).
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Priority 7: Tooling and packaging
|
|
499
|
+
|
|
500
|
+
| Item | Rationale |
|
|
501
|
+
|------|-----------|
|
|
502
|
+
| `parser-lr table validate` | Check transform/`ast` consistency, pass-collapse warnings |
|
|
503
|
+
| Fixture corpus | Grammars + inputs + expected AST JSON (like EduBASIC cases above) |
|
|
504
|
+
| CommonJS `require` entry | Jest consumers (see `jest.config.js` `moduleNameMapper` for `parser-lr`) |
|
|
505
|
+
| Document `pass` vs `build` vs `flatten` | Include “do not collapse production symbol” rule |
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## What “elegant EduBASIC grammar” looks like after fixes
|
|
510
|
+
|
|
511
|
+
1. **Remove** duplicate `grammar` / `ast` / `transform` blocks (EduBASIC currently duplicates statement sections ~lines 611–1400 and ~1694–2123).
|
|
512
|
+
2. **Add** `ast` + `transform` for all `*_stmt` productions (or rely on Priority 1 + identity build).
|
|
513
|
+
3. **Delete** [`keyword-fallbacks.ts`](../src/lang/parsing/ast-bridge/statements/keyword-fallbacks.ts).
|
|
514
|
+
4. **Shrink** bridge helpers that only exist for flat/collapsed trees (`buildPrintSegmentsFromStatement` multi-path scan, `buildHelpStatement` flat child scan, `isComparisonOpSymbol` for `CASE IS`).
|
|
515
|
+
5. **Keep** [`dispatch.ts`](../src/lang/parsing/ast-bridge/statements/dispatch.ts) as a thin `symbol → builder` table keyed on stable `*_stmt` names.
|
|
516
|
+
|
|
517
|
+
Rough expected line reduction in `ast-bridge/`: on the order of **15–25%**, mostly from deleting fallback and defensive tree-walk code.
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Suggested implementation order for parser-lr agents
|
|
522
|
+
|
|
523
|
+
1. Fix `pass()` production-symbol preservation + tests (unblocks everything).
|
|
524
|
+
2. Land `flatten` and `build` optional-slot fixes with fixtures.
|
|
525
|
+
3. Add `table validate` warnings for missing statement transforms.
|
|
526
|
+
4. Document statement authoring guide with full mini-grammar (calc-style, but statements).
|
|
527
|
+
5. Work with EduBASIC to add statement `ast`/`transform` incrementally and delete fallbacks.
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## EduBASIC verification checklist
|
|
532
|
+
|
|
533
|
+
After each parser-lr release, run in this repo:
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
npm install parser-lr@<version>
|
|
537
|
+
npm run parser:table
|
|
538
|
+
npm test
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Key tests:
|
|
542
|
+
|
|
543
|
+
- [`specs/units/parsers/grammar-table.spec.ts`](../specs/units/parsers/grammar-table.spec.ts) — bare `CLS`, bare `PRINT`
|
|
544
|
+
- [`specs/units/parsers/ast-bridge.spec.ts`](../specs/units/parsers/ast-bridge.spec.ts) — dispatch and `kw_print` fallback (remove after Priority 1)
|
|
545
|
+
- [`specs/units/parsers/parser-service.spec.ts`](../specs/units/parsers/parser-service.spec.ts) — full statement coverage
|
|
546
|
+
- [`programs/SelectCaseTorture.bas`](../programs/SelectCaseTorture.BAS) — `CASE IS` relational selectors
|
|
547
|
+
|
|
548
|
+
When Priority 1 ships, add an assertion that `parseSource('CLS').symbol === 'cls_stmt'` and remove `keyword-fallbacks` from the codebase in the same PR that bumps the parser-lr version.
|