parser-lr 0.2.3 → 0.3.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.
Files changed (58) hide show
  1. package/README.md +85 -3
  2. package/bin/parser-lr.js +336 -171
  3. package/bin/parser-lr.js.map +1 -1
  4. package/dist/lib/parse-table/analysis/first-follow.d.ts +6 -0
  5. package/dist/lib/parse-table/analysis/first-follow.d.ts.map +1 -1
  6. package/dist/lib/parse-table/analysis/first-follow.js +13 -0
  7. package/dist/lib/parse-table/analysis/first-follow.js.map +1 -1
  8. package/dist/lib/parse-table/bnf/desugar-ebnf.d.ts.map +1 -1
  9. package/dist/lib/parse-table/bnf/desugar-ebnf.js +2 -0
  10. package/dist/lib/parse-table/bnf/desugar-ebnf.js.map +1 -1
  11. package/dist/lib/parse-table/build-lr-table.d.ts +1 -0
  12. package/dist/lib/parse-table/build-lr-table.d.ts.map +1 -1
  13. package/dist/lib/parse-table/build-lr-table.js +11 -1
  14. package/dist/lib/parse-table/build-lr-table.js.map +1 -1
  15. package/dist/lib/parse-table/index.d.ts +1 -1
  16. package/dist/lib/parse-table/index.d.ts.map +1 -1
  17. package/dist/lib/parse-table/index.js +1 -1
  18. package/dist/lib/parse-table/index.js.map +1 -1
  19. package/dist/lib/parse-table/lr0/lr0-item-set.d.ts +26 -2
  20. package/dist/lib/parse-table/lr0/lr0-item-set.d.ts.map +1 -1
  21. package/dist/lib/parse-table/lr0/lr0-item-set.js +92 -54
  22. package/dist/lib/parse-table/lr0/lr0-item-set.js.map +1 -1
  23. package/dist/lib/parse-table/lr1/lr1-item-set.d.ts +31 -2
  24. package/dist/lib/parse-table/lr1/lr1-item-set.d.ts.map +1 -1
  25. package/dist/lib/parse-table/lr1/lr1-item-set.js +95 -60
  26. package/dist/lib/parse-table/lr1/lr1-item-set.js.map +1 -1
  27. package/dist/lib/parse-table/parse-table.d.ts +13 -3
  28. package/dist/lib/parse-table/parse-table.d.ts.map +1 -1
  29. package/dist/lib/parse-table/parse-table.js +19 -8
  30. package/dist/lib/parse-table/parse-table.js.map +1 -1
  31. package/dist/lib/parse-table/table/index.d.ts +1 -1
  32. package/dist/lib/parse-table/table/index.d.ts.map +1 -1
  33. package/dist/lib/parse-table/table/index.js +1 -1
  34. package/dist/lib/parse-table/table/index.js.map +1 -1
  35. package/dist/lib/parse-table/table/lr-parse-table.d.ts +14 -2
  36. package/dist/lib/parse-table/table/lr-parse-table.d.ts.map +1 -1
  37. package/dist/lib/parse-table/table/lr-parse-table.js +38 -3
  38. package/dist/lib/parse-table/table/lr-parse-table.js.map +1 -1
  39. package/dist/lib/parse-table/table/parse-action.d.ts +3 -1
  40. package/dist/lib/parse-table/table/parse-action.d.ts.map +1 -1
  41. package/dist/lib/parse-table/table/parse-action.js.map +1 -1
  42. package/dist/lib/parse-table/table/table-builder-base.d.ts +5 -4
  43. package/dist/lib/parse-table/table/table-builder-base.d.ts.map +1 -1
  44. package/dist/lib/parse-table/table/table-builder-base.js +28 -8
  45. package/dist/lib/parse-table/table/table-builder-base.js.map +1 -1
  46. package/dist/lib/shift-reduce/shift-reduce-engine.d.ts +10 -0
  47. package/dist/lib/shift-reduce/shift-reduce-engine.d.ts.map +1 -1
  48. package/dist/lib/shift-reduce/shift-reduce-engine.js +11 -0
  49. package/dist/lib/shift-reduce/shift-reduce-engine.js.map +1 -1
  50. package/dist/lib/transform/binding-map.d.ts +6 -0
  51. package/dist/lib/transform/binding-map.d.ts.map +1 -1
  52. package/dist/lib/transform/binding-map.js +17 -0
  53. package/dist/lib/transform/binding-map.js.map +1 -1
  54. package/dist/lib/transform/cst-transformer.d.ts +6 -0
  55. package/dist/lib/transform/cst-transformer.d.ts.map +1 -1
  56. package/dist/lib/transform/cst-transformer.js +24 -2
  57. package/dist/lib/transform/cst-transformer.js.map +1 -1
  58. package/package.json +12 -3
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shift-reduce parser for EBNF grammars. Describe your language with a `.grammar` file, build an LR parse table, then lex and parse source into a concrete syntax tree or AST, for execution or code generation.
4
4
 
5
- Grammar file syntax is documented in [`docs/grammar.md`](docs/grammar.md).
5
+ Grammar file syntax is documented in the [`.grammar` file syntax](https://github.com/adamrmoss/parser-lr/blob/main/docs/grammar.md) guide.
6
6
 
7
7
  ## Install
8
8
 
@@ -98,8 +98,90 @@ const source = await readFile('program.txt', 'utf8');
98
98
  const ast = context.parse(context.lex(source));
99
99
  ```
100
100
 
101
- `ParseContext` exposes `lex`, `parse`, and `createLexer` for finer control. See [`src/lib/README.md`](src/lib/README.md) for the main types.
101
+ `ParseContext` exposes `lex`, `parse`, and `createLexer` for finer control. See the [library API overview](https://github.com/adamrmoss/parser-lr/blob/main/src/lib/README.md).
102
102
 
103
103
  ## Example grammars
104
104
 
105
- Sample `.grammar` files ship in [`grammars/`](grammars/) (`calc.grammar`, `lisp.grammar`, `6502.grammar`, and the meta-grammar `grammar.grammar`). Use them as templates when writing your own language.
105
+ Sample `.grammar` files are in the [grammars](https://github.com/adamrmoss/parser-lr/tree/main/grammars) directory (`calc.grammar`, `lisp.grammar`, `6502.grammar`, and the meta-grammar `grammar.grammar`). Use them as templates when writing your own language.
106
+
107
+ ## LR table algorithms
108
+
109
+ All four algorithms desugar EBNF to plain BNF, build LR item sets, then fill ACTION and GOTO tables for the same shift-reduce parser. They differ in how item sets are formed, how reduce lookaheads are chosen, and how competing actions are handled.
110
+
111
+ | Algorithm | Item sets | Reduce lookaheads | Typical table size |
112
+ |-----------|-----------|-------------------|--------------------|
113
+ | `lr0` | LR(0) | Every terminal (plus `$eof` on accept) | Smallest |
114
+ | `slr` | LR(0) | FOLLOW of the production's left-hand side | Small |
115
+ | `lr1` | LR(1), one lookahead per item | The item's own lookahead terminal | Largest |
116
+ | `lalr` | LR(1) cores merged, lookaheads unioned | Same as LR(1) after merge | Between SLR and LR(1) |
117
+
118
+ The default is **`lr1`**. Use it unless you have a reason to prefer a smaller table or need to inspect a simpler construction.
119
+
120
+ ### LR(0)
121
+
122
+ LR(0) item sets contain dotted productions only; no lookahead symbols. When a production is complete in a state, reduce actions are emitted for **every terminal** in the grammar (not just plausible followers). That makes LR(0) the coarsest analysis and the most prone to spurious shift-reduce and reduce-reduce conflicts. It is mainly useful for teaching and debugging the item-set machinery.
123
+
124
+ ### SLR (Simple LR)
125
+
126
+ SLR reuses the same LR(0) item sets but tightens reduce lookaheads using precomputed **FOLLOW** sets. A reduce by `A → α` is offered only on terminals in FOLLOW(`A`). This eliminates many LR(0) false conflicts but still merges contexts that LR(1) would keep separate, so grammars like dangling `if`-`then`-`else` remain conflicted under SLR.
127
+
128
+ ### LR(1)
129
+
130
+ LR(1) item sets attach a **single lookahead terminal** to each item. Reduce actions fire only when the incoming token matches that lookahead, so distinct parse contexts become distinct states. This resolves grammars that SLR cannot, including expression precedence and the L=R (dangling reference) grammar from the Dragon book.
131
+
132
+ ### LALR (Look-Ahead LR)
133
+
134
+ LALR starts from the full LR(1) collection, then **merges states that share the same LR(0) core**, unioning the lookaheads of merged items. GOTO targets are recomputed from the LR(1) collection so merged states behave correctly. The result is often much smaller than LR(1) while accepting the same grammars in practice; when LALR introduces a conflict that LR(1) avoided, the merge was too aggressive for that grammar.
135
+
136
+ ### Conflicts and table building
137
+
138
+ When shift/reduce or reduce/reduce conflicts remain after table construction, the table is still built using default resolution:
139
+
140
+ - **Shift-reduce**: keep the **shift** action.
141
+ - **Reduce-reduce**: keep the **first reduce** action recorded for that slot.
142
+
143
+ Each resolved conflict is recorded on `ParseTable.conflicts`. Use `ParseTable.formatConflictWarnings()` for lines such as `state 12: shift/reduce conflict on token "else" resolved as shift`. The CLI `table generate` command writes those warnings to **stderr** after building the table JSON.
144
+
145
+ Conflict types:
146
+
147
+ - **Shift-reduce**: a state both shifts on a terminal and reduces by a production on that same terminal.
148
+ - **Reduce-reduce**: a state reduces by two different productions on the same terminal.
149
+
150
+ Precedence directives (`%left`, `%right`, `%prec`) are not supported in `.grammar` files today.
151
+
152
+ ### Examples from classic grammars
153
+
154
+ | Grammar | `lr0` / `slr` | `lr1` / `lalr` |
155
+ |---------|---------------|----------------|
156
+ | Expression precedence (`E → E + T \| T`, …) | Conflict-free | Conflict-free |
157
+ | Dangling else (`if E then S` vs `if E then S else S`) | Shift-reduce conflict on `else` (shift wins) | Shift-reduce conflict on `else` (shift wins) |
158
+ | L=R (`S → L = R \| R`, …) | SLR conflicted | Conflict-free |
159
+ | Ambiguous infix (`E → E + E \| E * E \| id`) | Conflicted (shift wins on operators) | Conflicted (shift wins on operators) |
160
+
161
+ ## Algorithm complexity
162
+
163
+ Let **n** be the input length in characters (lexing) or tokens (parsing). Let **G** denote grammar size: **P** productions, **N** non-terminals, **T** terminal/token names, and **R** lexer rules. Let **S** be the number of LR states after table construction, **I** the maximum item count in any item set, and **Σ** the grammar alphabet size (**T** + **N**).
164
+
165
+ | Phase | Operation | Time | Space |
166
+ |-------|-----------|------|-------|
167
+ | Grammar load | Meta-grammar lex + parse (`readGrammar`) | O(n) | O(n) |
168
+ | Desugar | EBNF → plain BNF (`desugarEbnf`) | O(P · d) | O(P) |
169
+ | Analysis | Nullable, FIRST, FOLLOW (`GrammarAnalysis`) | O(P · (N + T)) | O(N · T) |
170
+ | LR(0) sets | Closure + GOTO (`buildLr0ItemSets`) | O(S · Σ · I · P) | O(S · I) |
171
+ | LR(1) sets | Closure + GOTO (`buildLr1ItemSets`) | O(S · Σ · I · P) | O(S · I) |
172
+ | LALR merge | Core merge + GOTO targets (`mergeLalrItemSets`) | O(S_LR1 · I) | O(S · I) |
173
+ | Table fill | ACTION / GOTO (`TableBuilderBase`) | O(S · (T + N)) | O(S · T) |
174
+ | Lexer compile | Rule compilation (`compileLexerRules`) | O(R) | O(R) |
175
+ | Lexing | Longest-match scan (`Lexer`) | O(n · R) | O(n) tokens |
176
+ | Parsing | Shift-reduce (`ShiftReduceEngine`) | O(n) | O(n) |
177
+ | Transform | CST → AST (`CstTransformer`) | O(n) | O(n) |
178
+
179
+ **Notes:**
180
+
181
+ - **LR state count** can grow exponentially in |G| in the worst case (pathological grammars). Practical programming-language grammars typically yield polynomial-sized tables.
182
+ - **Closure** dominates table construction: each item set closure scans items and may add productions for every non-terminal after the dot.
183
+ - **Parsing** is linear in token count because each shift or reduce advances the input or shrinks the stack; table lookups are O(1) via hash maps.
184
+ - **Lexing** tries every active rule at each position (longest match, declaration-order tie-break). **R** is usually small and fixed for a given grammar.
185
+ - **FIRST / FOLLOW** use fixed-point iteration; each pass is O(P · w) where **w** is maximum production length, and the number of passes is bounded by **N**.
186
+
187
+ **End-to-end** for a grammar already loaded: lexing plus parsing is **O(n · R + n) = O(n · R)**; building a table from scratch adds the construction terms above (typically run once at compile or bootstrap time).