Skip to content

Commit 7a628f4

Browse files
Merge pull request #152 from cmaglie/ll1-table-gen-fix
Bug fix in LL1 table generator
2 parents a4febfa + effc188 commit 7a628f4

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

src/ll/__tests__/grammar1.bnf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// https://github.com/DmitrySoshnikov/syntax/issues/151
2+
3+
%%
4+
5+
S
6+
: A
7+
;
8+
A
9+
: 'a'
10+
| /* empty */
11+
;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* The MIT License (MIT)
3+
* Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
4+
*/
5+
6+
import Grammar from '../../grammar/grammar';
7+
import {MODES as GRAMMAR_MODE} from '../../grammar/grammar-mode';
8+
import LLParsingTable from '../ll-parsing-table';
9+
import LLParser from '../ll-parser';
10+
11+
describe('ll-parsing-table', () => {
12+
it('ll1-grammar-1', () => {
13+
const grammarFile = __dirname + '/grammar1.bnf';
14+
const expectedTable = {
15+
S: {
16+
"'a'": '1',
17+
$: '1',
18+
},
19+
A: {
20+
"'a'": '2',
21+
$: '3',
22+
},
23+
};
24+
25+
const grammarBySLR = Grammar.fromGrammarFile(grammarFile, {
26+
mode: GRAMMAR_MODE.LL1,
27+
});
28+
expect(new LLParsingTable({grammar: grammarBySLR}).get()).toEqual(
29+
expectedTable
30+
);
31+
expect(new LLParser({grammar: grammarBySLR}).parse('a')).toEqual({
32+
status: 'accept',
33+
semanticValue: true,
34+
});
35+
expect(new LLParser({grammar: grammarBySLR}).parse('')).toEqual({
36+
status: 'accept',
37+
semanticValue: true,
38+
});
39+
});
40+
});

src/ll/ll-parsing-table.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import GrammarSymbol from '../grammar/grammar-symbol';
77
import SetsGenerator from '../sets-generator';
88
import TablePrinter from '../table-printer';
9-
import {EOF} from '../special-symbols';
9+
import {EOF, EPSILON} from '../special-symbols';
1010
import colors from 'colors';
1111
import debug from '../debug';
1212

@@ -175,20 +175,17 @@ export default class LLParsingTable {
175175
table[lhsSymbol] = {};
176176
}
177177

178-
// All productions goes under the terminal column, if
179-
// this terminal is not epsilon. Otherwise, an ε-production
180-
// goes under the columns from the Follow set of LHS.
181-
182-
let set = !production.isEpsilon()
183-
? this._setsGenerator.firstOfRHS(rhs)
184-
: this._setsGenerator.followOf(lhs);
185-
186-
const keys = Object.keys(set);
187-
188-
// Got derived epsilon through indirect production, fallback to
189-
// Follow(LHS). Example: A: B; B: ε
190-
if (keys.length === 1 && GrammarSymbol.isEpsilon(keys[0])) {
191-
set = this._setsGenerator.followOf(lhs);
178+
// All productions goes under the terminal column
179+
// of the First(RHS) terminals.
180+
let set = this._setsGenerator.firstOfRHS(rhs);
181+
182+
// If First(RHS) has an epsilon terminal it means
183+
// that we must check also under the Follow(LHS) column.
184+
// (because this production can be eliminated by epsilon).
185+
if (set.hasOwnProperty(EPSILON)) {
186+
// Compute (First(RHS) - {ε}) + Follow(LHS)
187+
delete set[EPSILON];
188+
set = Object.assign({}, set, this._setsGenerator.followOf(lhs));
192189
}
193190

194191
for (let terminal in set) {

0 commit comments

Comments
 (0)