parser-lr 0.2.4 → 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.
- package/README.md +82 -0
- package/bin/parser-lr.js +336 -171
- package/bin/parser-lr.js.map +1 -1
- 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/bin/parser-lr.js
CHANGED
|
@@ -3781,7 +3781,7 @@ const {
|
|
|
3781
3781
|
Help,
|
|
3782
3782
|
} = commander;
|
|
3783
3783
|
|
|
3784
|
-
var version$1 = "0.
|
|
3784
|
+
var version$1 = "0.3.0";
|
|
3785
3785
|
var packageDefinition = {
|
|
3786
3786
|
version: version$1};
|
|
3787
3787
|
|
|
@@ -5264,6 +5264,7 @@ async function lexChunkStreamAsync(grammar, chunks) {
|
|
|
5264
5264
|
* Returns the ACTION table key for a lexer token.
|
|
5265
5265
|
*
|
|
5266
5266
|
* @param token - Current input token.
|
|
5267
|
+
* @returns The ACTION table key for the token, or `$eof` for end-of-input.
|
|
5267
5268
|
*/
|
|
5268
5269
|
function tokenActionKey(token) {
|
|
5269
5270
|
if (isEofToken(token)) {
|
|
@@ -5275,6 +5276,7 @@ function tokenActionKey(token) {
|
|
|
5275
5276
|
* Builds a terminal CST node from a shifted token.
|
|
5276
5277
|
*
|
|
5277
5278
|
* @param token - Lexer token that was shifted.
|
|
5279
|
+
* @returns A terminal CST node carrying the token name, text, and location.
|
|
5278
5280
|
*/
|
|
5279
5281
|
function astNodeFromToken(token) {
|
|
5280
5282
|
return AstNode.terminal(token.name, token.text, token.location);
|
|
@@ -5283,10 +5285,12 @@ function astNodeFromToken(token) {
|
|
|
5283
5285
|
* Merges child spans into one source location.
|
|
5284
5286
|
*
|
|
5285
5287
|
* @param children - Reduced child subtrees.
|
|
5288
|
+
* @returns The smallest span covering all child locations, or null when none exist.
|
|
5286
5289
|
*/
|
|
5287
5290
|
function mergeChildLocations(children) {
|
|
5288
5291
|
let offset = null;
|
|
5289
5292
|
let end = null;
|
|
5293
|
+
// Walk children to find the earliest offset and latest end position.
|
|
5290
5294
|
for (const child of children) {
|
|
5291
5295
|
if (child.location === null) {
|
|
5292
5296
|
continue;
|
|
@@ -5346,6 +5350,7 @@ class ShiftReduceEngine {
|
|
|
5346
5350
|
const stateStack = [0];
|
|
5347
5351
|
const valueStack = [];
|
|
5348
5352
|
let tokenIndex = 0;
|
|
5353
|
+
// Drive the parser until accept, error, or an unrecoverable reduce failure.
|
|
5349
5354
|
while (true) {
|
|
5350
5355
|
const state = stateStack[stateStack.length - 1];
|
|
5351
5356
|
const currentToken = tokens[tokenIndex];
|
|
@@ -5402,6 +5407,7 @@ class ShiftReduceEngine {
|
|
|
5402
5407
|
* @param productionId - Production to reduce by.
|
|
5403
5408
|
* @param stateStack - Parser state stack.
|
|
5404
5409
|
* @param valueStack - Semantic value stack.
|
|
5410
|
+
* @returns The reduced subtree pushed onto the value stack, or null on stack underflow.
|
|
5405
5411
|
*/
|
|
5406
5412
|
static reduce(table, productionId, stateStack, valueStack) {
|
|
5407
5413
|
const production = table.production(productionId);
|
|
@@ -5410,6 +5416,7 @@ class ShiftReduceEngine {
|
|
|
5410
5416
|
}
|
|
5411
5417
|
const rhsLength = production.rhs.length;
|
|
5412
5418
|
const children = [];
|
|
5419
|
+
// Pop one state and value per right-hand side symbol.
|
|
5413
5420
|
for (let index = 0; index < rhsLength; index += 1) {
|
|
5414
5421
|
if (stateStack.length === 0 || valueStack.length === 0) {
|
|
5415
5422
|
return null;
|
|
@@ -5424,6 +5431,7 @@ class ShiftReduceEngine {
|
|
|
5424
5431
|
if (stateStack.length === 0) {
|
|
5425
5432
|
return null;
|
|
5426
5433
|
}
|
|
5434
|
+
// Follow GOTO for the production left-hand side and push the new subtree.
|
|
5427
5435
|
const gotoState = table.goto(stateStack[stateStack.length - 1], production.name);
|
|
5428
5436
|
if (gotoState === null) {
|
|
5429
5437
|
return null;
|
|
@@ -5438,6 +5446,7 @@ class ShiftReduceEngine {
|
|
|
5438
5446
|
*
|
|
5439
5447
|
* @param production - Reduced production metadata.
|
|
5440
5448
|
* @param children - Child subtrees matching the production right-hand side.
|
|
5449
|
+
* @returns A rule CST node spanning the reduced children.
|
|
5441
5450
|
*/
|
|
5442
5451
|
static nodeFromProduction(production, children) {
|
|
5443
5452
|
return AstNode.rule(production.name, children, mergeChildLocations(children), production.variant, production.id, production.origin);
|
|
@@ -5448,6 +5457,7 @@ class ShiftReduceEngine {
|
|
|
5448
5457
|
*
|
|
5449
5458
|
* @param table - Parse table with ACTION, GOTO, and production metadata.
|
|
5450
5459
|
* @param tokens - Token stream ending with `$eof`.
|
|
5460
|
+
* @returns The start symbol parse tree, or null on syntax error.
|
|
5451
5461
|
*/
|
|
5452
5462
|
function parseWithTable(table, tokens) {
|
|
5453
5463
|
return ShiftReduceEngine.parse(table, tokens);
|
|
@@ -5457,6 +5467,7 @@ function parseWithTable(table, tokens) {
|
|
|
5457
5467
|
*
|
|
5458
5468
|
* @param table - Parse table with ACTION, GOTO, and production metadata.
|
|
5459
5469
|
* @param tokens - Token stream ending with `$eof`.
|
|
5470
|
+
* @returns Parse tree on success, or error offset and message on failure.
|
|
5460
5471
|
*/
|
|
5461
5472
|
function parseWithTableResult(table, tokens) {
|
|
5462
5473
|
return ShiftReduceEngine.parseWithResult(table, tokens);
|
|
@@ -5683,6 +5694,7 @@ class EbnfDesugarer {
|
|
|
5683
5694
|
*/
|
|
5684
5695
|
desugarProduction(production) {
|
|
5685
5696
|
const alternatives = this.expandExpression(production.name, production.expression, null);
|
|
5697
|
+
// Record one BNF production per desugared alternative.
|
|
5686
5698
|
for (const alternative of alternatives) {
|
|
5687
5699
|
this.addProduction(production.name, alternative.symbols, alternative.variant, production.name);
|
|
5688
5700
|
}
|
|
@@ -5760,6 +5772,7 @@ class EbnfDesugarer {
|
|
|
5760
5772
|
*/
|
|
5761
5773
|
expandSequence(ownerName, elements, variant) {
|
|
5762
5774
|
let results = [{ symbols: [], variant }];
|
|
5775
|
+
// Cartesian product: combine each prefix alternative with each next-element alternative.
|
|
5763
5776
|
for (const element of elements) {
|
|
5764
5777
|
const elementAlternatives = this.expandExpression(ownerName, element, variant);
|
|
5765
5778
|
const combined = [];
|
|
@@ -5882,6 +5895,7 @@ class GrammarAnalysis {
|
|
|
5882
5895
|
* Returns whether a non-terminal can derive the empty string.
|
|
5883
5896
|
*
|
|
5884
5897
|
* @param name - Non-terminal name to test.
|
|
5898
|
+
* @returns True when the non-terminal can derive ε.
|
|
5885
5899
|
*/
|
|
5886
5900
|
isNullable(name) {
|
|
5887
5901
|
return this.nullable.has(name);
|
|
@@ -5906,10 +5920,12 @@ class GrammarAnalysis {
|
|
|
5906
5920
|
* Returns FIRST symbols for a symbol sequence.
|
|
5907
5921
|
*
|
|
5908
5922
|
* @param symbols - Right-hand side prefix or full sequence.
|
|
5923
|
+
* @returns Encoded terminal keys in FIRST of the sequence.
|
|
5909
5924
|
*/
|
|
5910
5925
|
firstOfSequence(symbols) {
|
|
5911
5926
|
const result = new Set();
|
|
5912
5927
|
let allNullable = true;
|
|
5928
|
+
// Accumulate FIRST of each prefix symbol until a non-nullable symbol stops propagation.
|
|
5913
5929
|
for (const symbol of symbols) {
|
|
5914
5930
|
const symbolFirst = this.firstOfSymbol(symbol);
|
|
5915
5931
|
GrammarAnalysis.addAll(result, symbolFirst);
|
|
@@ -5918,6 +5934,7 @@ class GrammarAnalysis {
|
|
|
5918
5934
|
break;
|
|
5919
5935
|
}
|
|
5920
5936
|
}
|
|
5937
|
+
// Every symbol is nullable, so end-of-input is in FIRST.
|
|
5921
5938
|
if (allNullable) {
|
|
5922
5939
|
result.add(EOF_TOKEN_NAME);
|
|
5923
5940
|
}
|
|
@@ -5949,10 +5966,12 @@ class GrammarAnalysis {
|
|
|
5949
5966
|
* Computes nullable non-terminals by fixed-point iteration.
|
|
5950
5967
|
*
|
|
5951
5968
|
* @param grammar - Plain BNF grammar to analyze.
|
|
5969
|
+
* @returns The set of non-terminal names that derive ε.
|
|
5952
5970
|
*/
|
|
5953
5971
|
static computeNullable(grammar) {
|
|
5954
5972
|
const nullable = new Set();
|
|
5955
5973
|
let changed = true;
|
|
5974
|
+
// Fixed-point: mark A nullable when every production A → α has nullable α.
|
|
5956
5975
|
while (changed) {
|
|
5957
5976
|
changed = false;
|
|
5958
5977
|
for (const production of grammar.productions) {
|
|
@@ -5976,6 +5995,7 @@ class GrammarAnalysis {
|
|
|
5976
5995
|
*
|
|
5977
5996
|
* @param grammar - Plain BNF grammar to analyze.
|
|
5978
5997
|
* @param nullable - Nullable non-terminal names.
|
|
5998
|
+
* @returns FIRST set for every non-terminal name.
|
|
5979
5999
|
*/
|
|
5980
6000
|
static computeFirst(grammar, nullable) {
|
|
5981
6001
|
const firstByNonTerminal = new Map();
|
|
@@ -5983,6 +6003,7 @@ class GrammarAnalysis {
|
|
|
5983
6003
|
firstByNonTerminal.set(name, new Set());
|
|
5984
6004
|
}
|
|
5985
6005
|
let changed = true;
|
|
6006
|
+
// Fixed-point: add FIRST(α) into FIRST(A) for each production A → α.
|
|
5986
6007
|
while (changed) {
|
|
5987
6008
|
changed = false;
|
|
5988
6009
|
for (const production of grammar.productions) {
|
|
@@ -6003,16 +6024,19 @@ class GrammarAnalysis {
|
|
|
6003
6024
|
* @param grammar - Plain BNF grammar to analyze.
|
|
6004
6025
|
* @param firstByNonTerminal - Precomputed FIRST sets.
|
|
6005
6026
|
* @param nullable - Nullable non-terminal names.
|
|
6027
|
+
* @returns FOLLOW set for every non-terminal name.
|
|
6006
6028
|
*/
|
|
6007
6029
|
static computeFollow(grammar, firstByNonTerminal, nullable) {
|
|
6008
6030
|
const followByNonTerminal = new Map();
|
|
6009
6031
|
for (const name of grammar.nonTerminalNames()) {
|
|
6010
6032
|
followByNonTerminal.set(name, new Set());
|
|
6011
6033
|
}
|
|
6034
|
+
// Seed FOLLOW of the start symbol with end-of-input.
|
|
6012
6035
|
const startFollow = followByNonTerminal.get(grammar.startSymbol) ?? new Set();
|
|
6013
6036
|
startFollow.add(EOF_TOKEN_NAME);
|
|
6014
6037
|
followByNonTerminal.set(grammar.startSymbol, startFollow);
|
|
6015
6038
|
let changed = true;
|
|
6039
|
+
// Fixed-point: propagate FOLLOW through every production A → α B β.
|
|
6016
6040
|
while (changed) {
|
|
6017
6041
|
changed = false;
|
|
6018
6042
|
for (const production of grammar.productions) {
|
|
@@ -6026,6 +6050,7 @@ class GrammarAnalysis {
|
|
|
6026
6050
|
const beta = production.rhs.slice(index + 1);
|
|
6027
6051
|
const betaFirst = GrammarAnalysis.firstOfSequenceStatic(beta, firstByNonTerminal, nullable);
|
|
6028
6052
|
GrammarAnalysis.addAll(follow, betaFirst);
|
|
6053
|
+
// When β is empty or nullable, inherit FOLLOW of the production lhs.
|
|
6029
6054
|
if (beta.length === 0 || GrammarAnalysis.isSequenceNullableStatic(beta, nullable)) {
|
|
6030
6055
|
const lhsFollow = followByNonTerminal.get(production.name) ?? new Set();
|
|
6031
6056
|
GrammarAnalysis.addAll(follow, lhsFollow);
|
|
@@ -6107,6 +6132,7 @@ class GrammarAnalysis {
|
|
|
6107
6132
|
* Computes nullable, FIRST, and FOLLOW sets for a BNF grammar.
|
|
6108
6133
|
*
|
|
6109
6134
|
* @param grammar - Plain BNF grammar to analyze.
|
|
6135
|
+
* @returns Nullable, FIRST, and FOLLOW analysis for the grammar.
|
|
6110
6136
|
*/
|
|
6111
6137
|
function analyzeGrammar(grammar) {
|
|
6112
6138
|
return new GrammarAnalysis(grammar);
|
|
@@ -6125,38 +6151,60 @@ function sortLr0Items(items) {
|
|
|
6125
6151
|
return left.dot - right.dot;
|
|
6126
6152
|
});
|
|
6127
6153
|
}
|
|
6154
|
+
/**
|
|
6155
|
+
* Returns encoded symbol keys that appear immediately after the dot in an item set.
|
|
6156
|
+
*
|
|
6157
|
+
* @param grammar - BNF grammar containing referenced productions.
|
|
6158
|
+
* @param items - Closed item set.
|
|
6159
|
+
* @returns Sorted terminal and non-terminal keys eligible for GOTO from this state.
|
|
6160
|
+
*/
|
|
6161
|
+
function symbolsAfterDot(grammar, items) {
|
|
6162
|
+
const keys = new Set();
|
|
6163
|
+
for (const item of items) {
|
|
6164
|
+
const production = grammar.production(item.productionId);
|
|
6165
|
+
if (production === null) {
|
|
6166
|
+
continue;
|
|
6167
|
+
}
|
|
6168
|
+
const nextSymbol = production.rhs[item.dot];
|
|
6169
|
+
if (nextSymbol === undefined) {
|
|
6170
|
+
continue;
|
|
6171
|
+
}
|
|
6172
|
+
keys.add(bnfParserSymbolKey(nextSymbol));
|
|
6173
|
+
}
|
|
6174
|
+
return [...keys].sort();
|
|
6175
|
+
}
|
|
6128
6176
|
/**
|
|
6129
6177
|
* Computes the LR(0) closure of an item set.
|
|
6130
6178
|
*
|
|
6131
6179
|
* @param grammar - Plain or augmented BNF grammar.
|
|
6132
6180
|
* @param items - Seed items.
|
|
6181
|
+
* @returns The seed items plus all items implied by dotted non-terminal prefixes.
|
|
6133
6182
|
*/
|
|
6134
6183
|
function lr0Closure(grammar, items) {
|
|
6135
6184
|
const result = sortLr0Items(items);
|
|
6136
6185
|
const itemKeys = new Set(result.map((item) => Lr0ItemSetBuilder.itemIdentity(item)));
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
}
|
|
6186
|
+
const pendingIndices = result.map((_, index) => index);
|
|
6187
|
+
// Expand closure from each newly discovered item only.
|
|
6188
|
+
while (pendingIndices.length > 0) {
|
|
6189
|
+
const item = result[pendingIndices.pop()];
|
|
6190
|
+
const production = grammar.production(item.productionId);
|
|
6191
|
+
if (production === null) {
|
|
6192
|
+
continue;
|
|
6193
|
+
}
|
|
6194
|
+
const nextSymbol = production.rhs[item.dot];
|
|
6195
|
+
if (nextSymbol === undefined || nextSymbol.kind !== 'nonTerminal') {
|
|
6196
|
+
continue;
|
|
6197
|
+
}
|
|
6198
|
+
for (const candidate of grammar.productionsFor(nextSymbol.name)) {
|
|
6199
|
+
const closureItem = {
|
|
6200
|
+
productionId: candidate.id,
|
|
6201
|
+
dot: 0,
|
|
6202
|
+
};
|
|
6203
|
+
const identity = Lr0ItemSetBuilder.itemIdentity(closureItem);
|
|
6204
|
+
if (!itemKeys.has(identity)) {
|
|
6205
|
+
itemKeys.add(identity);
|
|
6206
|
+
pendingIndices.push(result.length);
|
|
6207
|
+
result.push(closureItem);
|
|
6160
6208
|
}
|
|
6161
6209
|
}
|
|
6162
6210
|
}
|
|
@@ -6168,9 +6216,11 @@ function lr0Closure(grammar, items) {
|
|
|
6168
6216
|
* @param grammar - Plain or augmented BNF grammar.
|
|
6169
6217
|
* @param items - Closed item set.
|
|
6170
6218
|
* @param symbolKey - Encoded terminal or non-terminal symbol.
|
|
6219
|
+
* @returns The closed item set reached after advancing the dot past `symbolKey`.
|
|
6171
6220
|
*/
|
|
6172
6221
|
function lr0Goto(grammar, items, symbolKey) {
|
|
6173
6222
|
const moved = [];
|
|
6223
|
+
// Advance the dot on every item expecting `symbolKey`.
|
|
6174
6224
|
for (const item of items) {
|
|
6175
6225
|
const production = grammar.production(item.productionId);
|
|
6176
6226
|
if (production === null) {
|
|
@@ -6196,15 +6246,30 @@ function lr0Goto(grammar, items, symbolKey) {
|
|
|
6196
6246
|
class Lr0ItemSetCollection {
|
|
6197
6247
|
grammar;
|
|
6198
6248
|
itemSets;
|
|
6249
|
+
indexByIdentity;
|
|
6199
6250
|
/**
|
|
6200
6251
|
* Creates a collection from an ordered list of closed item sets.
|
|
6201
6252
|
*
|
|
6202
6253
|
* @param grammar - Grammar the item sets were built from.
|
|
6203
6254
|
* @param itemSets - Closed LR(0) item sets in discovery order.
|
|
6255
|
+
* @param indexByIdentity - Optional precomputed identity-to-state map.
|
|
6204
6256
|
*/
|
|
6205
|
-
constructor(
|
|
6257
|
+
constructor(
|
|
6258
|
+
/** Grammar analyzed when the item sets were constructed. */
|
|
6259
|
+
grammar,
|
|
6260
|
+
/** Closed LR(0) item sets indexed by parser state number. */
|
|
6261
|
+
itemSets, indexByIdentity) {
|
|
6206
6262
|
this.grammar = grammar;
|
|
6207
6263
|
this.itemSets = itemSets;
|
|
6264
|
+
if (indexByIdentity !== undefined) {
|
|
6265
|
+
this.indexByIdentity = indexByIdentity;
|
|
6266
|
+
return;
|
|
6267
|
+
}
|
|
6268
|
+
const builtIndex = new Map();
|
|
6269
|
+
for (let index = 0; index < itemSets.length; index += 1) {
|
|
6270
|
+
builtIndex.set(Lr0ItemSetBuilder.setIdentity(itemSets[index] ?? [], true), index);
|
|
6271
|
+
}
|
|
6272
|
+
this.indexByIdentity = builtIndex;
|
|
6208
6273
|
}
|
|
6209
6274
|
/**
|
|
6210
6275
|
* Returns the index of an item set matching `items`, or null when absent.
|
|
@@ -6212,13 +6277,8 @@ class Lr0ItemSetCollection {
|
|
|
6212
6277
|
* @param items - Closed item set to locate.
|
|
6213
6278
|
*/
|
|
6214
6279
|
indexOf(items) {
|
|
6215
|
-
const target = Lr0ItemSetBuilder.setIdentity(items);
|
|
6216
|
-
|
|
6217
|
-
if (Lr0ItemSetBuilder.setIdentity(this.itemSets[index]) === target) {
|
|
6218
|
-
return index;
|
|
6219
|
-
}
|
|
6220
|
-
}
|
|
6221
|
-
return null;
|
|
6280
|
+
const target = Lr0ItemSetBuilder.setIdentity(items, true);
|
|
6281
|
+
return this.indexByIdentity.get(target) ?? null;
|
|
6222
6282
|
}
|
|
6223
6283
|
}
|
|
6224
6284
|
/**
|
|
@@ -6229,42 +6289,42 @@ class Lr0ItemSetBuilder {
|
|
|
6229
6289
|
* Builds the canonical LR(0) collection for a grammar.
|
|
6230
6290
|
*
|
|
6231
6291
|
* @param grammar - Augmented or plain BNF grammar.
|
|
6292
|
+
* @returns The canonical LR(0) item set collection for the grammar.
|
|
6232
6293
|
*/
|
|
6233
6294
|
static build(grammar) {
|
|
6234
6295
|
const startProductions = grammar.productionsFor(grammar.startSymbol);
|
|
6235
6296
|
if (startProductions.length === 0) {
|
|
6236
6297
|
return new Lr0ItemSetCollection(grammar, []);
|
|
6237
6298
|
}
|
|
6299
|
+
// Seed state 0 from the start production at dot zero.
|
|
6238
6300
|
const initial = lr0Closure(grammar, [{
|
|
6239
6301
|
productionId: startProductions[0].id,
|
|
6240
6302
|
dot: 0,
|
|
6241
6303
|
}]);
|
|
6242
6304
|
const itemSets = [[...initial]];
|
|
6243
|
-
const
|
|
6244
|
-
[Lr0ItemSetBuilder.setIdentity(initial), 0],
|
|
6305
|
+
const indexByIdentity = new Map([
|
|
6306
|
+
[Lr0ItemSetBuilder.setIdentity(initial, true), 0],
|
|
6245
6307
|
]);
|
|
6246
|
-
const
|
|
6247
|
-
|
|
6248
|
-
while (
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
changed = true;
|
|
6263
|
-
}
|
|
6308
|
+
const pendingStates = [0];
|
|
6309
|
+
// Discover new states from each state exactly once.
|
|
6310
|
+
while (pendingStates.length > 0) {
|
|
6311
|
+
const stateIndex = pendingStates.pop();
|
|
6312
|
+
const itemSet = itemSets[stateIndex] ?? [];
|
|
6313
|
+
for (const symbolKey of symbolsAfterDot(grammar, itemSet)) {
|
|
6314
|
+
const gotoSet = lr0Goto(grammar, itemSet, symbolKey);
|
|
6315
|
+
if (gotoSet.length === 0) {
|
|
6316
|
+
continue;
|
|
6317
|
+
}
|
|
6318
|
+
const gotoKey = Lr0ItemSetBuilder.setIdentity(gotoSet, true);
|
|
6319
|
+
if (!indexByIdentity.has(gotoKey)) {
|
|
6320
|
+
const nextIndex = itemSets.length;
|
|
6321
|
+
indexByIdentity.set(gotoKey, nextIndex);
|
|
6322
|
+
itemSets.push([...gotoSet]);
|
|
6323
|
+
pendingStates.push(nextIndex);
|
|
6264
6324
|
}
|
|
6265
6325
|
}
|
|
6266
6326
|
}
|
|
6267
|
-
return new Lr0ItemSetCollection(grammar, itemSets);
|
|
6327
|
+
return new Lr0ItemSetCollection(grammar, itemSets, indexByIdentity);
|
|
6268
6328
|
}
|
|
6269
6329
|
/**
|
|
6270
6330
|
* Returns every terminal and non-terminal key that can appear in GOTO.
|
|
@@ -6290,9 +6350,11 @@ class Lr0ItemSetBuilder {
|
|
|
6290
6350
|
* Returns a stable identity string for an item set.
|
|
6291
6351
|
*
|
|
6292
6352
|
* @param items - Closed item set.
|
|
6353
|
+
* @param alreadySorted - When true, skips re-sorting because items are canonical.
|
|
6293
6354
|
*/
|
|
6294
|
-
static setIdentity(items) {
|
|
6295
|
-
|
|
6355
|
+
static setIdentity(items, alreadySorted = false) {
|
|
6356
|
+
const sorted = alreadySorted ? items : sortLr0Items(items);
|
|
6357
|
+
return sorted
|
|
6296
6358
|
.map((item) => Lr0ItemSetBuilder.itemIdentity(item))
|
|
6297
6359
|
.join('|');
|
|
6298
6360
|
}
|
|
@@ -6301,6 +6363,7 @@ class Lr0ItemSetBuilder {
|
|
|
6301
6363
|
* Builds the canonical LR(0) item set collection for a grammar.
|
|
6302
6364
|
*
|
|
6303
6365
|
* @param grammar - Augmented or plain BNF grammar.
|
|
6366
|
+
* @returns The canonical LR(0) item set collection for the grammar.
|
|
6304
6367
|
*/
|
|
6305
6368
|
function buildLr0ItemSets(grammar) {
|
|
6306
6369
|
return Lr0ItemSetBuilder.build(grammar);
|
|
@@ -6325,21 +6388,6 @@ function parseActionsEqual(left, right) {
|
|
|
6325
6388
|
return true;
|
|
6326
6389
|
}
|
|
6327
6390
|
}
|
|
6328
|
-
/**
|
|
6329
|
-
* Returns a readable parse action label for tests and diagnostics.
|
|
6330
|
-
*
|
|
6331
|
-
* @param action - Parse action to format.
|
|
6332
|
-
*/
|
|
6333
|
-
function formatParseAction(action) {
|
|
6334
|
-
switch (action.kind) {
|
|
6335
|
-
case 'shift':
|
|
6336
|
-
return `s${String(action.state)}`;
|
|
6337
|
-
case 'reduce':
|
|
6338
|
-
return `r${String(action.productionId)}`;
|
|
6339
|
-
case 'accept':
|
|
6340
|
-
return 'acc';
|
|
6341
|
-
}
|
|
6342
|
-
}
|
|
6343
6391
|
/**
|
|
6344
6392
|
* Classifies a conflict between two distinct parse actions.
|
|
6345
6393
|
*
|
|
@@ -6365,13 +6413,14 @@ class TableBuilderBase {
|
|
|
6365
6413
|
* @param grammar - Augmented BNF grammar.
|
|
6366
6414
|
* @param itemSets - Canonical item set collection.
|
|
6367
6415
|
* @param gotoItems - Computes the GOTO item set for one state and symbol.
|
|
6416
|
+
* @returns Map from `state:symbol` keys to target state indices.
|
|
6368
6417
|
*/
|
|
6369
6418
|
static buildGotoTargets(grammar, itemSets, indexOf, gotoItems) {
|
|
6370
6419
|
const targets = new Map();
|
|
6371
|
-
|
|
6420
|
+
// Precompute GOTO for every state and symbol reachable from its items.
|
|
6372
6421
|
for (let state = 0; state < itemSets.length; state += 1) {
|
|
6373
6422
|
const itemSet = itemSets[state] ?? [];
|
|
6374
|
-
for (const symbolKey of
|
|
6423
|
+
for (const symbolKey of symbolsAfterDot(grammar, itemSet)) {
|
|
6375
6424
|
const gotoSet = gotoItems(grammar, itemSet, symbolKey);
|
|
6376
6425
|
if (gotoSet.length === 0) {
|
|
6377
6426
|
continue;
|
|
@@ -6394,9 +6443,10 @@ class TableBuilderBase {
|
|
|
6394
6443
|
* @param gotoTargets - Precomputed GOTO target indices.
|
|
6395
6444
|
* @param stateActions - ACTION map for the state.
|
|
6396
6445
|
* @param stateGotos - GOTO map for the state.
|
|
6397
|
-
* @param conflicts -
|
|
6446
|
+
* @param conflicts - Resolved conflict list to append to when actions disagree.
|
|
6398
6447
|
*/
|
|
6399
6448
|
static fillShiftsAndGotos(grammar, state, itemSet, gotoTargets, stateActions, stateGotos, conflicts) {
|
|
6449
|
+
// Derive shift and GOTO entries from every incomplete item in the state.
|
|
6400
6450
|
for (const item of itemSet) {
|
|
6401
6451
|
const production = grammar.production(item.productionId);
|
|
6402
6452
|
if (production === null) {
|
|
@@ -6411,6 +6461,7 @@ class TableBuilderBase {
|
|
|
6411
6461
|
if (targetState === undefined) {
|
|
6412
6462
|
continue;
|
|
6413
6463
|
}
|
|
6464
|
+
// Non-terminal symbols populate GOTO; terminals populate ACTION shift entries.
|
|
6414
6465
|
if (nextSymbol.kind === 'nonTerminal') {
|
|
6415
6466
|
stateGotos.set(nextSymbol.name, targetState);
|
|
6416
6467
|
continue;
|
|
@@ -6424,7 +6475,7 @@ class TableBuilderBase {
|
|
|
6424
6475
|
* @param state - Parser state index.
|
|
6425
6476
|
* @param productionName - Left-hand side of the completed production.
|
|
6426
6477
|
* @param stateActions - ACTION map for the state.
|
|
6427
|
-
* @param conflicts -
|
|
6478
|
+
* @param conflicts - Resolved conflict list to append to when actions disagree.
|
|
6428
6479
|
*/
|
|
6429
6480
|
static setAcceptAction(state, productionName, stateActions, conflicts) {
|
|
6430
6481
|
if (productionName !== AUGMENTED_START_SYMBOL) {
|
|
@@ -6442,10 +6493,10 @@ class TableBuilderBase {
|
|
|
6442
6493
|
return `${String(state)}:${symbolKey}`;
|
|
6443
6494
|
}
|
|
6444
6495
|
/**
|
|
6445
|
-
* Inserts one ACTION entry,
|
|
6496
|
+
* Inserts one ACTION entry, resolving shift/reduce and reduce/reduce conflicts.
|
|
6446
6497
|
*
|
|
6447
6498
|
* @param stateActions - ACTION map for the current state.
|
|
6448
|
-
* @param conflicts -
|
|
6499
|
+
* @param conflicts - Resolved conflict list to append to when actions disagree.
|
|
6449
6500
|
* @param state - Parser state index.
|
|
6450
6501
|
* @param symbol - Encoded terminal or `$eof` key.
|
|
6451
6502
|
* @param incoming - Candidate parse action.
|
|
@@ -6456,15 +6507,32 @@ class TableBuilderBase {
|
|
|
6456
6507
|
stateActions.set(symbol, incoming);
|
|
6457
6508
|
return;
|
|
6458
6509
|
}
|
|
6510
|
+
// Identical re-insertions are ignored; disagreeing actions are resolved by conflict kind.
|
|
6459
6511
|
if (parseActionsEqual(existing, incoming)) {
|
|
6460
6512
|
return;
|
|
6461
6513
|
}
|
|
6514
|
+
const conflictKind = classifyParseConflict(existing, incoming);
|
|
6515
|
+
if (conflictKind === 'shift-reduce') {
|
|
6516
|
+
const shiftAction = existing.kind === 'shift' ? existing : incoming;
|
|
6517
|
+
stateActions.set(symbol, shiftAction);
|
|
6518
|
+
conflicts.push({
|
|
6519
|
+
kind: 'shift-reduce',
|
|
6520
|
+
state,
|
|
6521
|
+
symbol,
|
|
6522
|
+
existing,
|
|
6523
|
+
incoming,
|
|
6524
|
+
resolution: 'shift',
|
|
6525
|
+
});
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
// Reduce/reduce: keep the first reduce action already in the table.
|
|
6462
6529
|
conflicts.push({
|
|
6463
|
-
kind:
|
|
6530
|
+
kind: 'reduce-reduce',
|
|
6464
6531
|
state,
|
|
6465
6532
|
symbol,
|
|
6466
6533
|
existing,
|
|
6467
6534
|
incoming,
|
|
6535
|
+
resolution: 'reduce',
|
|
6468
6536
|
});
|
|
6469
6537
|
}
|
|
6470
6538
|
/**
|
|
@@ -6483,6 +6551,7 @@ class TableBuilderBase {
|
|
|
6483
6551
|
* Sorts LR(1) items for canonical set comparison.
|
|
6484
6552
|
*
|
|
6485
6553
|
* @param items - Items to sort.
|
|
6554
|
+
* @returns A new array sorted by production id, dot, then lookahead.
|
|
6486
6555
|
*/
|
|
6487
6556
|
function sortLr1Items(items) {
|
|
6488
6557
|
return [...items].sort((left, right) => {
|
|
@@ -6499,6 +6568,7 @@ function sortLr1Items(items) {
|
|
|
6499
6568
|
* Returns the LR(0) core key for one LR(1) item.
|
|
6500
6569
|
*
|
|
6501
6570
|
* @param item - LR(1) item to encode.
|
|
6571
|
+
* @returns A string `productionId:dot` shared by all lookaheads at the same dot position.
|
|
6502
6572
|
*/
|
|
6503
6573
|
function lr1CoreKey(item) {
|
|
6504
6574
|
return `${String(item.productionId)}:${String(item.dot)}`;
|
|
@@ -6507,6 +6577,7 @@ function lr1CoreKey(item) {
|
|
|
6507
6577
|
* Returns a stable identity string for one LR(1) item.
|
|
6508
6578
|
*
|
|
6509
6579
|
* @param item - Item to encode.
|
|
6580
|
+
* @returns A string `productionId:dot:lookahead` for set membership tests.
|
|
6510
6581
|
*/
|
|
6511
6582
|
function lr1ItemIdentity(item) {
|
|
6512
6583
|
return `${lr1CoreKey(item)}:${item.lookahead}`;
|
|
@@ -6515,9 +6586,12 @@ function lr1ItemIdentity(item) {
|
|
|
6515
6586
|
* Returns a stable identity string for an LR(1) item set.
|
|
6516
6587
|
*
|
|
6517
6588
|
* @param items - Closed item set.
|
|
6589
|
+
* @param alreadySorted - When true, skips re-sorting because items are canonical.
|
|
6590
|
+
* @returns A pipe-separated, sorted identity string for canonical set comparison.
|
|
6518
6591
|
*/
|
|
6519
|
-
function lr1SetIdentity(items) {
|
|
6520
|
-
|
|
6592
|
+
function lr1SetIdentity(items, alreadySorted = false) {
|
|
6593
|
+
const sorted = alreadySorted ? items : sortLr1Items(items);
|
|
6594
|
+
return sorted
|
|
6521
6595
|
.map((item) => lr1ItemIdentity(item))
|
|
6522
6596
|
.join('|');
|
|
6523
6597
|
}
|
|
@@ -6525,6 +6599,7 @@ function lr1SetIdentity(items) {
|
|
|
6525
6599
|
* Returns the LR(0) core identity for an LR(1) item set.
|
|
6526
6600
|
*
|
|
6527
6601
|
* @param items - LR(1) item set.
|
|
6602
|
+
* @returns A pipe-separated, sorted string of LR(0) cores with lookaheads removed.
|
|
6528
6603
|
*/
|
|
6529
6604
|
function lr1CoreSetIdentity(items) {
|
|
6530
6605
|
const cores = new Set(items.map((item) => lr1CoreKey(item)));
|
|
@@ -6536,9 +6611,11 @@ function lr1CoreSetIdentity(items) {
|
|
|
6536
6611
|
* @param beta - Remaining right-hand side symbols after a non-terminal.
|
|
6537
6612
|
* @param lookahead - Lookahead terminal following β.
|
|
6538
6613
|
* @param analysis - Nullable and FIRST analysis for the grammar.
|
|
6614
|
+
* @returns Terminal keys that may follow the dotted non-terminal in this item.
|
|
6539
6615
|
*/
|
|
6540
6616
|
function firstAfterSymbols(beta, lookahead, analysis) {
|
|
6541
6617
|
const result = new Set();
|
|
6618
|
+
// Accumulate FIRST of each prefix symbol until a non-nullable symbol stops propagation.
|
|
6542
6619
|
for (const symbol of beta) {
|
|
6543
6620
|
for (const terminal of analysis.firstOfSymbol(symbol)) {
|
|
6544
6621
|
result.add(terminal);
|
|
@@ -6547,6 +6624,7 @@ function firstAfterSymbols(beta, lookahead, analysis) {
|
|
|
6547
6624
|
return result;
|
|
6548
6625
|
}
|
|
6549
6626
|
}
|
|
6627
|
+
// Every symbol in β is nullable, so the item's own lookahead is reachable.
|
|
6550
6628
|
result.add(lookahead);
|
|
6551
6629
|
return result;
|
|
6552
6630
|
}
|
|
@@ -6556,36 +6634,37 @@ function firstAfterSymbols(beta, lookahead, analysis) {
|
|
|
6556
6634
|
* @param grammar - Augmented BNF grammar.
|
|
6557
6635
|
* @param analysis - Nullable and FIRST analysis for the grammar.
|
|
6558
6636
|
* @param items - Seed items.
|
|
6637
|
+
* @returns The seed items plus all items implied by dotted non-terminal prefixes.
|
|
6559
6638
|
*/
|
|
6560
6639
|
function lr1Closure(grammar, analysis, items) {
|
|
6561
6640
|
const result = sortLr1Items(items);
|
|
6562
6641
|
const itemKeys = new Set(result.map((item) => lr1ItemIdentity(item)));
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6642
|
+
const pendingIndices = result.map((_, index) => index);
|
|
6643
|
+
// Expand closure from each newly discovered item only.
|
|
6644
|
+
while (pendingIndices.length > 0) {
|
|
6645
|
+
const item = result[pendingIndices.pop()];
|
|
6646
|
+
const production = grammar.production(item.productionId);
|
|
6647
|
+
if (production === null) {
|
|
6648
|
+
continue;
|
|
6649
|
+
}
|
|
6650
|
+
const nextSymbol = production.rhs[item.dot];
|
|
6651
|
+
if (nextSymbol === undefined || nextSymbol.kind !== 'nonTerminal') {
|
|
6652
|
+
continue;
|
|
6653
|
+
}
|
|
6654
|
+
const beta = production.rhs.slice(item.dot + 1);
|
|
6655
|
+
// For each production of the non-terminal after the dot, propagate lookaheads.
|
|
6656
|
+
for (const candidate of grammar.productionsFor(nextSymbol.name)) {
|
|
6657
|
+
for (const lookahead of firstAfterSymbols(beta, item.lookahead, analysis)) {
|
|
6658
|
+
const closureItem = {
|
|
6659
|
+
productionId: candidate.id,
|
|
6660
|
+
dot: 0,
|
|
6661
|
+
lookahead,
|
|
6662
|
+
};
|
|
6663
|
+
const identity = lr1ItemIdentity(closureItem);
|
|
6664
|
+
if (!itemKeys.has(identity)) {
|
|
6665
|
+
itemKeys.add(identity);
|
|
6666
|
+
pendingIndices.push(result.length);
|
|
6667
|
+
result.push(closureItem);
|
|
6589
6668
|
}
|
|
6590
6669
|
}
|
|
6591
6670
|
}
|
|
@@ -6599,9 +6678,11 @@ function lr1Closure(grammar, analysis, items) {
|
|
|
6599
6678
|
* @param analysis - Nullable and FIRST analysis for the grammar.
|
|
6600
6679
|
* @param items - Closed item set.
|
|
6601
6680
|
* @param symbolKey - Encoded terminal or non-terminal symbol.
|
|
6681
|
+
* @returns The closed item set reached after advancing the dot past `symbolKey`.
|
|
6602
6682
|
*/
|
|
6603
6683
|
function lr1Goto(grammar, analysis, items, symbolKey) {
|
|
6604
6684
|
const moved = [];
|
|
6685
|
+
// Advance the dot on every item expecting `symbolKey`, preserving lookaheads.
|
|
6605
6686
|
for (const item of items) {
|
|
6606
6687
|
const production = grammar.production(item.productionId);
|
|
6607
6688
|
if (production === null) {
|
|
@@ -6628,34 +6709,45 @@ function lr1Goto(grammar, analysis, items, symbolKey) {
|
|
|
6628
6709
|
class Lr1ItemSetCollection {
|
|
6629
6710
|
grammar;
|
|
6630
6711
|
itemSets;
|
|
6712
|
+
indexByIdentity;
|
|
6631
6713
|
/**
|
|
6632
6714
|
* Creates a collection from an ordered list of closed item sets.
|
|
6633
6715
|
*
|
|
6634
6716
|
* @param grammar - Grammar the item sets were built from.
|
|
6635
6717
|
* @param itemSets - Closed LR(1) item sets in discovery order.
|
|
6718
|
+
* @param indexByIdentity - Optional precomputed identity-to-state map.
|
|
6636
6719
|
*/
|
|
6637
|
-
constructor(
|
|
6720
|
+
constructor(
|
|
6721
|
+
/** Grammar analyzed when the item sets were constructed. */
|
|
6722
|
+
grammar,
|
|
6723
|
+
/** Closed LR(1) item sets indexed by parser state number. */
|
|
6724
|
+
itemSets, indexByIdentity) {
|
|
6638
6725
|
this.grammar = grammar;
|
|
6639
6726
|
this.itemSets = itemSets;
|
|
6727
|
+
if (indexByIdentity !== undefined) {
|
|
6728
|
+
this.indexByIdentity = indexByIdentity;
|
|
6729
|
+
return;
|
|
6730
|
+
}
|
|
6731
|
+
const builtIndex = new Map();
|
|
6732
|
+
for (let index = 0; index < itemSets.length; index += 1) {
|
|
6733
|
+
builtIndex.set(lr1SetIdentity(itemSets[index] ?? [], true), index);
|
|
6734
|
+
}
|
|
6735
|
+
this.indexByIdentity = builtIndex;
|
|
6640
6736
|
}
|
|
6641
6737
|
/**
|
|
6642
6738
|
* Returns the index of an item set matching `items`, or null when absent.
|
|
6643
6739
|
*
|
|
6644
6740
|
* @param items - Closed item set to locate.
|
|
6741
|
+
* @returns State index, or null when no set has the same canonical identity.
|
|
6645
6742
|
*/
|
|
6646
6743
|
indexOf(items) {
|
|
6647
|
-
|
|
6648
|
-
for (let index = 0; index < this.itemSets.length; index += 1) {
|
|
6649
|
-
if (lr1SetIdentity(this.itemSets[index]) === target) {
|
|
6650
|
-
return index;
|
|
6651
|
-
}
|
|
6652
|
-
}
|
|
6653
|
-
return null;
|
|
6744
|
+
return this.indexByIdentity.get(lr1SetIdentity(items, true)) ?? null;
|
|
6654
6745
|
}
|
|
6655
6746
|
/**
|
|
6656
6747
|
* Returns LR(0) items derived from one LR(1) item set.
|
|
6657
6748
|
*
|
|
6658
6749
|
* @param items - LR(1) item set.
|
|
6750
|
+
* @returns Deduplicated LR(0) cores with lookaheads stripped.
|
|
6659
6751
|
*/
|
|
6660
6752
|
static lr0Core(items) {
|
|
6661
6753
|
const cores = new Map();
|
|
@@ -6678,43 +6770,43 @@ class Lr1ItemSetBuilder {
|
|
|
6678
6770
|
* @param grammar - Augmented BNF grammar.
|
|
6679
6771
|
* @param analysis - Nullable and FIRST analysis for the grammar.
|
|
6680
6772
|
* @param startLookahead - Lookahead terminal for the initial item.
|
|
6773
|
+
* @returns The canonical LR(1) item set collection for the grammar.
|
|
6681
6774
|
*/
|
|
6682
6775
|
static build(grammar, analysis, startLookahead) {
|
|
6683
6776
|
const startProductions = grammar.productionsFor(grammar.startSymbol);
|
|
6684
6777
|
if (startProductions.length === 0) {
|
|
6685
6778
|
return new Lr1ItemSetCollection(grammar, []);
|
|
6686
6779
|
}
|
|
6780
|
+
// Seed state 0 from the augmented start production and its initial lookahead.
|
|
6687
6781
|
const initial = lr1Closure(grammar, analysis, [{
|
|
6688
6782
|
productionId: startProductions[0].id,
|
|
6689
6783
|
dot: 0,
|
|
6690
6784
|
lookahead: startLookahead,
|
|
6691
6785
|
}]);
|
|
6692
6786
|
const itemSets = [[...initial]];
|
|
6693
|
-
const
|
|
6694
|
-
[lr1SetIdentity(initial), 0],
|
|
6787
|
+
const indexByIdentity = new Map([
|
|
6788
|
+
[lr1SetIdentity(initial, true), 0],
|
|
6695
6789
|
]);
|
|
6696
|
-
const
|
|
6697
|
-
|
|
6698
|
-
while (
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
changed = true;
|
|
6713
|
-
}
|
|
6790
|
+
const pendingStates = [0];
|
|
6791
|
+
// Discover new states from each state exactly once.
|
|
6792
|
+
while (pendingStates.length > 0) {
|
|
6793
|
+
const stateIndex = pendingStates.pop();
|
|
6794
|
+
const itemSet = itemSets[stateIndex] ?? [];
|
|
6795
|
+
for (const symbolKey of symbolsAfterDot(grammar, itemSet)) {
|
|
6796
|
+
const gotoSet = lr1Goto(grammar, analysis, itemSet, symbolKey);
|
|
6797
|
+
if (gotoSet.length === 0) {
|
|
6798
|
+
continue;
|
|
6799
|
+
}
|
|
6800
|
+
const gotoKey = lr1SetIdentity(gotoSet, true);
|
|
6801
|
+
if (!indexByIdentity.has(gotoKey)) {
|
|
6802
|
+
const nextIndex = itemSets.length;
|
|
6803
|
+
indexByIdentity.set(gotoKey, nextIndex);
|
|
6804
|
+
itemSets.push([...gotoSet]);
|
|
6805
|
+
pendingStates.push(nextIndex);
|
|
6714
6806
|
}
|
|
6715
6807
|
}
|
|
6716
6808
|
}
|
|
6717
|
-
return new Lr1ItemSetCollection(grammar, itemSets);
|
|
6809
|
+
return new Lr1ItemSetCollection(grammar, itemSets, indexByIdentity);
|
|
6718
6810
|
}
|
|
6719
6811
|
}
|
|
6720
6812
|
/**
|
|
@@ -6723,6 +6815,7 @@ class Lr1ItemSetBuilder {
|
|
|
6723
6815
|
* @param grammar - Augmented BNF grammar.
|
|
6724
6816
|
* @param analysis - Nullable and FIRST analysis for the grammar.
|
|
6725
6817
|
* @param startLookahead - Lookahead terminal for the initial item.
|
|
6818
|
+
* @returns The canonical LR(1) item set collection for the grammar.
|
|
6726
6819
|
*/
|
|
6727
6820
|
function buildLr1ItemSets(grammar, analysis, startLookahead) {
|
|
6728
6821
|
return Lr1ItemSetBuilder.build(grammar, analysis, startLookahead);
|
|
@@ -6731,11 +6824,13 @@ function buildLr1ItemSets(grammar, analysis, startLookahead) {
|
|
|
6731
6824
|
* Merges LR(1) item sets that share the same LR(0) core into LALR item sets.
|
|
6732
6825
|
*
|
|
6733
6826
|
* @param collection - Canonical LR(1) item set collection.
|
|
6827
|
+
* @returns A new collection with LR(1) states merged by LR(0) core.
|
|
6734
6828
|
*/
|
|
6735
6829
|
function mergeLalrItemSets(collection) {
|
|
6736
6830
|
const coreToLalrIndex = new Map();
|
|
6737
6831
|
const lr1ToLalr = new Map();
|
|
6738
6832
|
const mergedItemSets = [];
|
|
6833
|
+
// Assign each LR(1) state to an LALR state keyed by its LR(0) core identity.
|
|
6739
6834
|
for (let lr1Index = 0; lr1Index < collection.itemSets.length; lr1Index += 1) {
|
|
6740
6835
|
const itemSet = collection.itemSets[lr1Index] ?? [];
|
|
6741
6836
|
const core = lr1CoreSetIdentity(itemSet);
|
|
@@ -6759,17 +6854,18 @@ function mergeLalrItemSets(collection) {
|
|
|
6759
6854
|
* @param lr1Collection - Canonical LR(1) item set collection.
|
|
6760
6855
|
* @param lalrCollection - Merged LALR item set collection.
|
|
6761
6856
|
* @param lr1ToLalr - LR(1) state index to LALR state index map.
|
|
6857
|
+
* @returns Map from `state:symbol` keys to LALR GOTO target state indices.
|
|
6762
6858
|
*/
|
|
6763
6859
|
function buildLalrGotoTargets(grammar, analysis, lr1Collection, lr1ToLalr) {
|
|
6764
6860
|
const targets = new Map();
|
|
6765
|
-
|
|
6861
|
+
// Recompute LR(1) GOTOs and remap each target through the LR(1) → LALR index map.
|
|
6766
6862
|
for (let lr1State = 0; lr1State < lr1Collection.itemSets.length; lr1State += 1) {
|
|
6767
6863
|
const lalrState = lr1ToLalr.get(lr1State);
|
|
6768
6864
|
if (lalrState === undefined) {
|
|
6769
6865
|
continue;
|
|
6770
6866
|
}
|
|
6771
6867
|
const itemSet = lr1Collection.itemSets[lr1State] ?? [];
|
|
6772
|
-
for (const symbolKey of
|
|
6868
|
+
for (const symbolKey of symbolsAfterDot(grammar, itemSet)) {
|
|
6773
6869
|
const gotoSet = lr1Goto(grammar, analysis, itemSet, symbolKey);
|
|
6774
6870
|
if (gotoSet.length === 0) {
|
|
6775
6871
|
continue;
|
|
@@ -6796,9 +6892,11 @@ function buildLalrGotoTargets(grammar, analysis, lr1Collection, lr1ToLalr) {
|
|
|
6796
6892
|
*
|
|
6797
6893
|
* @param left - Existing merged item set.
|
|
6798
6894
|
* @param right - Additional LR(1) item set to merge in.
|
|
6895
|
+
* @returns Items with lookaheads unioned for each shared LR(0) core.
|
|
6799
6896
|
*/
|
|
6800
6897
|
function mergeItems(left, right) {
|
|
6801
6898
|
const lookaheadsByCore = new Map();
|
|
6899
|
+
// Collect every lookahead grouped by dotted-production core.
|
|
6802
6900
|
for (const item of [...left, ...right]) {
|
|
6803
6901
|
const core = lr1CoreKey(item);
|
|
6804
6902
|
const lookaheads = lookaheadsByCore.get(core) ?? new Set();
|
|
@@ -6806,6 +6904,7 @@ function buildLalrGotoTargets(grammar, analysis, lr1Collection, lr1ToLalr) {
|
|
|
6806
6904
|
lookaheadsByCore.set(core, lookaheads);
|
|
6807
6905
|
}
|
|
6808
6906
|
const merged = [];
|
|
6907
|
+
// Expand each core into one item per distinct lookahead.
|
|
6809
6908
|
for (const [core, lookaheads] of lookaheadsByCore.entries()) {
|
|
6810
6909
|
const [productionIdText, dotText] = core.split(':');
|
|
6811
6910
|
const productionId = Number(productionIdText);
|
|
@@ -6826,13 +6925,16 @@ function buildLalrGotoTargets(grammar, analysis, lr1Collection, lr1ToLalr) {
|
|
|
6826
6925
|
*
|
|
6827
6926
|
* @param lr1Collection - Canonical LR(1) item set collection.
|
|
6828
6927
|
* @param lalrCollection - Merged LALR item set collection.
|
|
6928
|
+
* @returns Map from LR(1) state index to merged LALR state index.
|
|
6829
6929
|
*/
|
|
6830
6930
|
function buildLr1ToLalrMap(lr1Collection, lalrCollection) {
|
|
6831
6931
|
const coreToLalrIndex = new Map();
|
|
6932
|
+
// Index LALR states by their LR(0) core identity.
|
|
6832
6933
|
for (let lalrIndex = 0; lalrIndex < lalrCollection.itemSets.length; lalrIndex += 1) {
|
|
6833
6934
|
coreToLalrIndex.set(lr1CoreSetIdentity(lalrCollection.itemSets[lalrIndex] ?? []), lalrIndex);
|
|
6834
6935
|
}
|
|
6835
6936
|
const lr1ToLalr = new Map();
|
|
6937
|
+
// Map each LR(1) state to the LALR state sharing its core.
|
|
6836
6938
|
for (let lr1Index = 0; lr1Index < lr1Collection.itemSets.length; lr1Index += 1) {
|
|
6837
6939
|
const core = lr1CoreSetIdentity(lr1Collection.itemSets[lr1Index] ?? []);
|
|
6838
6940
|
const lalrIndex = coreToLalrIndex.get(core);
|
|
@@ -6865,7 +6967,7 @@ class LrParseTable {
|
|
|
6865
6967
|
* @param stateCount - Number of parser states in the item set collection.
|
|
6866
6968
|
* @param actions - ACTION entries keyed by state and terminal symbol.
|
|
6867
6969
|
* @param gotos - GOTO entries keyed by state and non-terminal name.
|
|
6868
|
-
* @param conflicts - Shift/reduce and reduce/reduce conflicts
|
|
6970
|
+
* @param conflicts - Shift/reduce and reduce/reduce conflicts resolved during construction.
|
|
6869
6971
|
*/
|
|
6870
6972
|
constructor(algorithm, grammar, productions, stateCount, actions, gotos, conflicts) {
|
|
6871
6973
|
this.algorithm = algorithm;
|
|
@@ -6877,7 +6979,7 @@ class LrParseTable {
|
|
|
6877
6979
|
this.conflicts = conflicts;
|
|
6878
6980
|
}
|
|
6879
6981
|
/**
|
|
6880
|
-
* Whether the table
|
|
6982
|
+
* Whether the table was built without conflicts.
|
|
6881
6983
|
*/
|
|
6882
6984
|
get isConflictFree() {
|
|
6883
6985
|
return this.conflicts.length === 0;
|
|
@@ -6902,14 +7004,31 @@ class LrParseTable {
|
|
|
6902
7004
|
}
|
|
6903
7005
|
}
|
|
6904
7006
|
/**
|
|
6905
|
-
*
|
|
7007
|
+
* Formats one resolved conflict as a warning line.
|
|
6906
7008
|
*
|
|
6907
|
-
* @param
|
|
7009
|
+
* @param conflict - Resolved shift/reduce or reduce/reduce conflict.
|
|
6908
7010
|
*/
|
|
6909
|
-
function
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
7011
|
+
function formatParseConflictWarning(conflict) {
|
|
7012
|
+
const tokenLabel = formatConflictTokenLabel(conflict.symbol);
|
|
7013
|
+
if (conflict.kind === 'shift-reduce') {
|
|
7014
|
+
return `state ${String(conflict.state)}: shift/reduce conflict on ${tokenLabel} resolved as shift`;
|
|
7015
|
+
}
|
|
7016
|
+
const keptProduction = conflict.existing.kind === 'reduce'
|
|
7017
|
+
? conflict.existing.productionId
|
|
7018
|
+
: conflict.incoming.productionId;
|
|
7019
|
+
return `state ${String(conflict.state)}: reduce/reduce conflict on ${tokenLabel} `
|
|
7020
|
+
+ `resolved using rule ${String(keptProduction)}`;
|
|
7021
|
+
}
|
|
7022
|
+
/**
|
|
7023
|
+
* Returns a readable terminal label for conflict warning text.
|
|
7024
|
+
*
|
|
7025
|
+
* @param symbol - Encoded terminal or `$eof` key from the ACTION table.
|
|
7026
|
+
*/
|
|
7027
|
+
function formatConflictTokenLabel(symbol) {
|
|
7028
|
+
if (symbol === '$eof') {
|
|
7029
|
+
return 'token $eof';
|
|
7030
|
+
}
|
|
7031
|
+
return `token ${symbol}`;
|
|
6913
7032
|
}
|
|
6914
7033
|
/**
|
|
6915
7034
|
* Returns encoded right-hand side symbols for one production.
|
|
@@ -6925,6 +7044,7 @@ function encodeProductionRhs(production) {
|
|
|
6925
7044
|
*
|
|
6926
7045
|
* @param grammar - Plain BNF grammar to analyze.
|
|
6927
7046
|
* @param algorithm - LR table construction algorithm.
|
|
7047
|
+
* @returns A populated LR parse table, possibly recording conflicts.
|
|
6928
7048
|
*/
|
|
6929
7049
|
function buildLrTable(grammar, algorithm) {
|
|
6930
7050
|
const augmented = grammar.augment();
|
|
@@ -6948,7 +7068,7 @@ class LrTableBuilder {
|
|
|
6948
7068
|
* Builds an LR(0) parse table.
|
|
6949
7069
|
*
|
|
6950
7070
|
* @param grammar - Augmented BNF grammar.
|
|
6951
|
-
* @
|
|
7071
|
+
* @returns An LR(0) parse table using all terminals as reduce lookaheads.
|
|
6952
7072
|
*/
|
|
6953
7073
|
static buildLr0(grammar) {
|
|
6954
7074
|
const itemSets = buildLr0ItemSets(grammar);
|
|
@@ -6965,6 +7085,7 @@ class LrTableBuilder {
|
|
|
6965
7085
|
*
|
|
6966
7086
|
* @param grammar - Augmented BNF grammar.
|
|
6967
7087
|
* @param analysis - Nullable, FIRST, and FOLLOW analysis for the grammar.
|
|
7088
|
+
* @returns An SLR parse table using FOLLOW sets as reduce lookaheads.
|
|
6968
7089
|
*/
|
|
6969
7090
|
static buildSlr(grammar, analysis) {
|
|
6970
7091
|
const itemSets = buildLr0ItemSets(grammar);
|
|
@@ -6980,6 +7101,7 @@ class LrTableBuilder {
|
|
|
6980
7101
|
*
|
|
6981
7102
|
* @param grammar - Augmented BNF grammar.
|
|
6982
7103
|
* @param analysis - Nullable, FIRST, and FOLLOW analysis for the grammar.
|
|
7104
|
+
* @returns An LR(1) parse table with per-item lookaheads.
|
|
6983
7105
|
*/
|
|
6984
7106
|
static buildLr1(grammar, analysis) {
|
|
6985
7107
|
const itemSets = buildLr1ItemSets(grammar, analysis, EOF_TOKEN_NAME);
|
|
@@ -6990,6 +7112,7 @@ class LrTableBuilder {
|
|
|
6990
7112
|
*
|
|
6991
7113
|
* @param grammar - Augmented BNF grammar.
|
|
6992
7114
|
* @param analysis - Nullable, FIRST, and FOLLOW analysis for the grammar.
|
|
7115
|
+
* @returns An LALR parse table merged from the canonical LR(1) collection.
|
|
6993
7116
|
*/
|
|
6994
7117
|
static buildLalr(grammar, analysis) {
|
|
6995
7118
|
const lr1ItemSets = buildLr1ItemSets(grammar, analysis, EOF_TOKEN_NAME);
|
|
@@ -7005,12 +7128,14 @@ class LrTableBuilder {
|
|
|
7005
7128
|
* @param grammar - Augmented BNF grammar.
|
|
7006
7129
|
* @param itemSets - Canonical LR(0) item set collection.
|
|
7007
7130
|
* @param reduceLookaheads - Returns terminal keys for one completed production.
|
|
7131
|
+
* @returns A parse table with ACTION, GOTO, and any recorded conflicts.
|
|
7008
7132
|
*/
|
|
7009
7133
|
static buildFromLr0ItemSets(algorithm, grammar, itemSets, reduceLookaheads) {
|
|
7010
7134
|
const gotoTargets = TableBuilderBase.buildGotoTargets(grammar, itemSets.itemSets, (items) => itemSets.indexOf(items), TableBuilderBase.lr0GotoItems);
|
|
7011
7135
|
const actions = new Map();
|
|
7012
7136
|
const gotos = new Map();
|
|
7013
7137
|
const conflicts = [];
|
|
7138
|
+
// Fill ACTION and GOTO rows for every LR(0) state.
|
|
7014
7139
|
for (let state = 0; state < itemSets.itemSets.length; state += 1) {
|
|
7015
7140
|
const itemSet = itemSets.itemSets[state] ?? [];
|
|
7016
7141
|
const stateActions = new Map();
|
|
@@ -7018,6 +7143,7 @@ class LrTableBuilder {
|
|
|
7018
7143
|
actions.set(state, stateActions);
|
|
7019
7144
|
gotos.set(state, stateGotos);
|
|
7020
7145
|
TableBuilderBase.fillShiftsAndGotos(grammar, state, itemSet, gotoTargets, stateActions, stateGotos, conflicts);
|
|
7146
|
+
// Record reduce and accept actions for every completed item in the state.
|
|
7021
7147
|
for (const item of itemSet) {
|
|
7022
7148
|
const production = grammar.production(item.productionId);
|
|
7023
7149
|
if (production === null || item.dot !== production.rhs.length) {
|
|
@@ -7042,12 +7168,14 @@ class LrTableBuilder {
|
|
|
7042
7168
|
* @param itemSets - LR(1) or merged LALR item set collection used for ACTION/GOTO rows.
|
|
7043
7169
|
* @param lr1Collection - Canonical LR(1) collection used for LALR GOTO remapping.
|
|
7044
7170
|
* @param gotoTargetsOverride - Optional precomputed GOTO indices for LALR.
|
|
7171
|
+
* @returns A parse table with ACTION, GOTO, and any recorded conflicts.
|
|
7045
7172
|
*/
|
|
7046
7173
|
static buildFromLr1ItemSets(algorithm, grammar, analysis, itemSets, gotoTargetsOverride) {
|
|
7047
7174
|
const gotoTargets = gotoTargetsOverride ?? TableBuilderBase.buildGotoTargets(grammar, itemSets.itemSets, (items) => itemSets.indexOf(items), (grammarArg, items, symbolKey) => lr1Goto(grammarArg, analysis, items, symbolKey));
|
|
7048
7175
|
const actions = new Map();
|
|
7049
7176
|
const gotos = new Map();
|
|
7050
7177
|
const conflicts = [];
|
|
7178
|
+
// Fill ACTION and GOTO rows for every LR(1) or merged LALR state.
|
|
7051
7179
|
for (let state = 0; state < itemSets.itemSets.length; state += 1) {
|
|
7052
7180
|
const itemSet = itemSets.itemSets[state] ?? [];
|
|
7053
7181
|
const stateActions = new Map();
|
|
@@ -7055,6 +7183,7 @@ class LrTableBuilder {
|
|
|
7055
7183
|
actions.set(state, stateActions);
|
|
7056
7184
|
gotos.set(state, stateGotos);
|
|
7057
7185
|
TableBuilderBase.fillShiftsAndGotos(grammar, state, itemSet, gotoTargets, stateActions, stateGotos, conflicts);
|
|
7186
|
+
// Record reduce or accept for each completed item using its lookahead.
|
|
7058
7187
|
for (const item of itemSet) {
|
|
7059
7188
|
const production = grammar.production(item.productionId);
|
|
7060
7189
|
if (production === null || item.dot !== production.rhs.length) {
|
|
@@ -7071,25 +7200,6 @@ class LrTableBuilder {
|
|
|
7071
7200
|
}
|
|
7072
7201
|
}
|
|
7073
7202
|
|
|
7074
|
-
/**
|
|
7075
|
-
* Thrown when LR table construction encounters unresolved conflicts.
|
|
7076
|
-
*/
|
|
7077
|
-
class ParseTableBuildError extends ParserLrError {
|
|
7078
|
-
algorithm;
|
|
7079
|
-
conflicts;
|
|
7080
|
-
/**
|
|
7081
|
-
* Creates a parse table build error from detected conflicts.
|
|
7082
|
-
*
|
|
7083
|
-
* @param algorithm - LR algorithm that was attempted.
|
|
7084
|
-
* @param conflicts - Readable conflict descriptions.
|
|
7085
|
-
*/
|
|
7086
|
-
constructor(algorithm, conflicts) {
|
|
7087
|
-
super(`LR table build failed for ${algorithm}: ${conflicts.join('; ')}`);
|
|
7088
|
-
this.algorithm = algorithm;
|
|
7089
|
-
this.conflicts = conflicts;
|
|
7090
|
-
}
|
|
7091
|
-
}
|
|
7092
|
-
|
|
7093
7203
|
/**
|
|
7094
7204
|
* Thrown when serialized parse table JSON cannot be loaded.
|
|
7095
7205
|
*/
|
|
@@ -7129,6 +7239,7 @@ class ParseTable {
|
|
|
7129
7239
|
states;
|
|
7130
7240
|
algorithm;
|
|
7131
7241
|
parserStateCount;
|
|
7242
|
+
conflicts;
|
|
7132
7243
|
actionByState;
|
|
7133
7244
|
gotoByState;
|
|
7134
7245
|
productionById;
|
|
@@ -7146,8 +7257,9 @@ class ParseTable {
|
|
|
7146
7257
|
* @param productions - Flat BNF production metadata for reduce actions.
|
|
7147
7258
|
* @param actions - ACTION entries keyed by state and terminal symbol.
|
|
7148
7259
|
* @param gotos - GOTO entries keyed by state and non-terminal name.
|
|
7260
|
+
* @param conflicts - Shift/reduce and reduce/reduce conflicts resolved during construction.
|
|
7149
7261
|
*/
|
|
7150
|
-
constructor(grammarName, startSymbol, tokens, tokenRules, skipRules, states, algorithm, parserStateCount = 0, productions = [], actions = new Map(), gotos = new Map()) {
|
|
7262
|
+
constructor(grammarName, startSymbol, tokens, tokenRules, skipRules, states, algorithm, parserStateCount = 0, productions = [], actions = new Map(), gotos = new Map(), conflicts = []) {
|
|
7151
7263
|
this.grammarName = grammarName;
|
|
7152
7264
|
this.startSymbol = startSymbol;
|
|
7153
7265
|
this.tokens = tokens;
|
|
@@ -7156,6 +7268,7 @@ class ParseTable {
|
|
|
7156
7268
|
this.states = states;
|
|
7157
7269
|
this.algorithm = algorithm;
|
|
7158
7270
|
this.parserStateCount = parserStateCount;
|
|
7271
|
+
this.conflicts = conflicts;
|
|
7159
7272
|
this.actionByState = actions;
|
|
7160
7273
|
this.gotoByState = gotos;
|
|
7161
7274
|
const productionById = new Map();
|
|
@@ -7170,6 +7283,18 @@ class ParseTable {
|
|
|
7170
7283
|
get hasParserTable() {
|
|
7171
7284
|
return this.parserStateCount > 0 || this.actionByState.size > 0;
|
|
7172
7285
|
}
|
|
7286
|
+
/**
|
|
7287
|
+
* Whether the table was built without conflicts.
|
|
7288
|
+
*/
|
|
7289
|
+
get isConflictFree() {
|
|
7290
|
+
return this.conflicts.length === 0;
|
|
7291
|
+
}
|
|
7292
|
+
/**
|
|
7293
|
+
* Returns conflict warning lines for this table.
|
|
7294
|
+
*/
|
|
7295
|
+
formatConflictWarnings() {
|
|
7296
|
+
return this.conflicts.map((conflict) => formatParseConflictWarning(conflict));
|
|
7297
|
+
}
|
|
7173
7298
|
/**
|
|
7174
7299
|
* Builds a parse table from a parsed grammar.
|
|
7175
7300
|
*
|
|
@@ -7180,9 +7305,6 @@ class ParseTable {
|
|
|
7180
7305
|
static fromGrammar(grammar, algorithm = 'lr1') {
|
|
7181
7306
|
const bnf = desugarEbnf(grammar);
|
|
7182
7307
|
const lrTable = buildLrTable(bnf, algorithm);
|
|
7183
|
-
if (!lrTable.isConflictFree) {
|
|
7184
|
-
throw new ParseTableBuildError(algorithm, formatLrConflicts(lrTable));
|
|
7185
|
-
}
|
|
7186
7308
|
return ParseTable.fromLrParseTable(grammar, lrTable);
|
|
7187
7309
|
}
|
|
7188
7310
|
/**
|
|
@@ -7298,7 +7420,7 @@ class ParseTable {
|
|
|
7298
7420
|
* Creates a {@link ParseTable} from a built LR table and source grammar.
|
|
7299
7421
|
*
|
|
7300
7422
|
* @param grammar - Parsed `.grammar` file model.
|
|
7301
|
-
* @param lrTable -
|
|
7423
|
+
* @param lrTable - Built LR parse table, including any resolved conflict warnings.
|
|
7302
7424
|
*/
|
|
7303
7425
|
static fromLrParseTable(grammar, lrTable) {
|
|
7304
7426
|
const productions = lrTable.productions.map((production) => ({
|
|
@@ -7308,7 +7430,7 @@ class ParseTable {
|
|
|
7308
7430
|
variant: production.variant,
|
|
7309
7431
|
origin: production.origin,
|
|
7310
7432
|
}));
|
|
7311
|
-
return new ParseTable(grammar.name, grammar.startSymbol, tokenInventory(grammar), [...grammar.tokenRules], [...grammar.skipRules], [...grammar.states], lrTable.algorithm, lrTable.stateCount, productions, lrTable.actions, lrTable.gotos);
|
|
7433
|
+
return new ParseTable(grammar.name, grammar.startSymbol, tokenInventory(grammar), [...grammar.tokenRules], [...grammar.skipRules], [...grammar.states], lrTable.algorithm, lrTable.stateCount, productions, lrTable.actions, lrTable.gotos, [...lrTable.conflicts]);
|
|
7312
7434
|
}
|
|
7313
7435
|
/**
|
|
7314
7436
|
* Converts ACTION maps into JSON rows.
|
|
@@ -15535,6 +15657,15 @@ function productionSlots(production) {
|
|
|
15535
15657
|
};
|
|
15536
15658
|
});
|
|
15537
15659
|
}
|
|
15660
|
+
/**
|
|
15661
|
+
* Returns the shared `$repeat` prefix for a numbered synthetic repeat symbol or reference.
|
|
15662
|
+
*
|
|
15663
|
+
* @param name - Repeat non-terminal or transform reference name.
|
|
15664
|
+
*/
|
|
15665
|
+
function repeatSymbolPrefix(name) {
|
|
15666
|
+
const match = /^(.+\$repeat)_\d+$/.exec(name);
|
|
15667
|
+
return match?.[1] ?? null;
|
|
15668
|
+
}
|
|
15538
15669
|
/**
|
|
15539
15670
|
* Finds the child index for a transform reference name.
|
|
15540
15671
|
*
|
|
@@ -15552,6 +15683,14 @@ function referenceSlotIndex(production, reference) {
|
|
|
15552
15683
|
return slot.index;
|
|
15553
15684
|
}
|
|
15554
15685
|
}
|
|
15686
|
+
const repeatPrefix = repeatSymbolPrefix(reference);
|
|
15687
|
+
if (repeatPrefix !== null) {
|
|
15688
|
+
for (const slot of productionSlots(production)) {
|
|
15689
|
+
if (slot.binding === null && repeatSymbolPrefix(slot.symbol) === repeatPrefix) {
|
|
15690
|
+
return slot.index;
|
|
15691
|
+
}
|
|
15692
|
+
}
|
|
15693
|
+
}
|
|
15555
15694
|
return null;
|
|
15556
15695
|
}
|
|
15557
15696
|
|
|
@@ -15610,7 +15749,7 @@ class CstTransformer {
|
|
|
15610
15749
|
? this.transformNode(node.children[0])
|
|
15611
15750
|
: null;
|
|
15612
15751
|
}
|
|
15613
|
-
const rule = this.
|
|
15752
|
+
const rule = this.resolveTransformRule(node);
|
|
15614
15753
|
if (rule === null) {
|
|
15615
15754
|
return this.defaultTransform(node);
|
|
15616
15755
|
}
|
|
@@ -15857,6 +15996,28 @@ class CstTransformer {
|
|
|
15857
15996
|
}
|
|
15858
15997
|
return null;
|
|
15859
15998
|
}
|
|
15999
|
+
/**
|
|
16000
|
+
* Resolves a transform rule for a CST node, aliasing numbered repeat symbols to `$repeat_0`.
|
|
16001
|
+
*
|
|
16002
|
+
* @param node - CST node being transformed.
|
|
16003
|
+
*/
|
|
16004
|
+
resolveTransformRule(node) {
|
|
16005
|
+
const candidates = [node.symbol];
|
|
16006
|
+
if (node.origin !== null && node.origin !== node.symbol) {
|
|
16007
|
+
candidates.push(node.origin);
|
|
16008
|
+
}
|
|
16009
|
+
const repeatPrefix = repeatSymbolPrefix(node.symbol);
|
|
16010
|
+
if (repeatPrefix !== null) {
|
|
16011
|
+
candidates.push(`${repeatPrefix}_0`);
|
|
16012
|
+
}
|
|
16013
|
+
for (const candidate of candidates) {
|
|
16014
|
+
const rule = this.schema.rule(candidate);
|
|
16015
|
+
if (rule !== null) {
|
|
16016
|
+
return rule;
|
|
16017
|
+
}
|
|
16018
|
+
}
|
|
16019
|
+
return null;
|
|
16020
|
+
}
|
|
15860
16021
|
/**
|
|
15861
16022
|
* Returns whether a CST node represents an epsilon production.
|
|
15862
16023
|
*
|
|
@@ -16305,6 +16466,10 @@ function registerTableCommands(program) {
|
|
|
16305
16466
|
algorithm: options.algorithm,
|
|
16306
16467
|
});
|
|
16307
16468
|
const json = context.table.toJsonString();
|
|
16469
|
+
// Write conflict warnings to stderr after table generation.
|
|
16470
|
+
for (const warning of context.table.formatConflictWarnings()) {
|
|
16471
|
+
process.stderr.write(`${warning}\n`);
|
|
16472
|
+
}
|
|
16308
16473
|
// Write JSON to disk or stdout.
|
|
16309
16474
|
if (options.output !== undefined) {
|
|
16310
16475
|
await writeTextFile(options.output, json);
|