parser-lr 0.2.4 → 0.3.1
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 +82 -0
- package/bin/parser-lr.js +3385 -3220
- package/bin/parser-lr.js.map +1 -1
- package/dist/lib/grammar/grammar.json +3123 -3123
- package/dist/lib/parse-table/analysis/first-follow.d.ts +6 -0
- package/dist/lib/parse-table/analysis/first-follow.d.ts.map +1 -1
- package/dist/lib/parse-table/analysis/first-follow.js +13 -0
- package/dist/lib/parse-table/analysis/first-follow.js.map +1 -1
- package/dist/lib/parse-table/bnf/desugar-ebnf.d.ts.map +1 -1
- package/dist/lib/parse-table/bnf/desugar-ebnf.js +2 -0
- package/dist/lib/parse-table/bnf/desugar-ebnf.js.map +1 -1
- package/dist/lib/parse-table/build-lr-table.d.ts +1 -0
- package/dist/lib/parse-table/build-lr-table.d.ts.map +1 -1
- package/dist/lib/parse-table/build-lr-table.js +11 -1
- package/dist/lib/parse-table/build-lr-table.js.map +1 -1
- package/dist/lib/parse-table/index.d.ts +1 -1
- package/dist/lib/parse-table/index.d.ts.map +1 -1
- package/dist/lib/parse-table/index.js +1 -1
- package/dist/lib/parse-table/index.js.map +1 -1
- package/dist/lib/parse-table/lr0/lr0-item-set.d.ts +26 -2
- package/dist/lib/parse-table/lr0/lr0-item-set.d.ts.map +1 -1
- package/dist/lib/parse-table/lr0/lr0-item-set.js +92 -54
- package/dist/lib/parse-table/lr0/lr0-item-set.js.map +1 -1
- package/dist/lib/parse-table/lr1/lr1-item-set.d.ts +31 -2
- package/dist/lib/parse-table/lr1/lr1-item-set.d.ts.map +1 -1
- package/dist/lib/parse-table/lr1/lr1-item-set.js +95 -60
- package/dist/lib/parse-table/lr1/lr1-item-set.js.map +1 -1
- package/dist/lib/parse-table/parse-table.d.ts +13 -3
- package/dist/lib/parse-table/parse-table.d.ts.map +1 -1
- package/dist/lib/parse-table/parse-table.js +19 -8
- package/dist/lib/parse-table/parse-table.js.map +1 -1
- package/dist/lib/parse-table/table/index.d.ts +1 -1
- package/dist/lib/parse-table/table/index.d.ts.map +1 -1
- package/dist/lib/parse-table/table/index.js +1 -1
- package/dist/lib/parse-table/table/index.js.map +1 -1
- package/dist/lib/parse-table/table/lr-parse-table.d.ts +14 -2
- package/dist/lib/parse-table/table/lr-parse-table.d.ts.map +1 -1
- package/dist/lib/parse-table/table/lr-parse-table.js +38 -3
- package/dist/lib/parse-table/table/lr-parse-table.js.map +1 -1
- package/dist/lib/parse-table/table/parse-action.d.ts +3 -1
- package/dist/lib/parse-table/table/parse-action.d.ts.map +1 -1
- package/dist/lib/parse-table/table/parse-action.js.map +1 -1
- package/dist/lib/parse-table/table/table-builder-base.d.ts +5 -4
- package/dist/lib/parse-table/table/table-builder-base.d.ts.map +1 -1
- package/dist/lib/parse-table/table/table-builder-base.js +28 -8
- package/dist/lib/parse-table/table/table-builder-base.js.map +1 -1
- package/dist/lib/shift-reduce/shift-reduce-engine.d.ts +10 -0
- package/dist/lib/shift-reduce/shift-reduce-engine.d.ts.map +1 -1
- package/dist/lib/shift-reduce/shift-reduce-engine.js +11 -0
- package/dist/lib/shift-reduce/shift-reduce-engine.js.map +1 -1
- package/dist/lib/transform/binding-map.d.ts +6 -0
- package/dist/lib/transform/binding-map.d.ts.map +1 -1
- package/dist/lib/transform/binding-map.js +17 -0
- package/dist/lib/transform/binding-map.js.map +1 -1
- package/dist/lib/transform/cst-transformer.d.ts +6 -0
- package/dist/lib/transform/cst-transformer.d.ts.map +1 -1
- package/dist/lib/transform/cst-transformer.js +24 -2
- package/dist/lib/transform/cst-transformer.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -103,3 +103,85 @@ const ast = context.parse(context.lex(source));
|
|
|
103
103
|
## Example grammars
|
|
104
104
|
|
|
105
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).
|