syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
293 lines (235 loc) • 11.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
* The MIT License (MIT)
* Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
var _grammarSymbol = require('../grammar/grammar-symbol');
var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);
var _setsGenerator = require('../sets-generator');
var _setsGenerator2 = _interopRequireDefault(_setsGenerator);
var _tablePrinter = require('../table-printer');
var _tablePrinter2 = _interopRequireDefault(_tablePrinter);
var _specialSymbols = require('../special-symbols');
var _colors = require('colors');
var _colors2 = _interopRequireDefault(_colors);
var _debug = require('../debug');
var _debug2 = _interopRequireDefault(_debug);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* LL parsing table.
*
* Example for a left-factored calculator grammar:
*
* 1. E -> T E'
*
* 2. E' -> "+" T E'
* 3. | ε
*
* 4. T -> F T'
*
* 5. T' -> "*" F T'
* 6. | ε
*
* 7. F -> "id"
* 8. | "(" E ")"
*
* LL(1) parsing table:
*
* ┌────┬─────┬─────┬──────┬─────┬─────┬───┐
* │ │ "+" │ "*" │ "id" │ "(" │ ")" │ $ │
* ├────┼─────┼─────┼──────┼─────┼─────┼───┤
* │ E │ - │ - │ 1 │ 1 │ - │ - │
* ├────┼─────┼─────┼──────┼─────┼─────┼───┤
* │ E' │ 2 │ - │ - │ - │ 3 │ 3 │
* ├────┼─────┼─────┼──────┼─────┼─────┼───┤
* │ T │ - │ - │ 4 │ 4 │ - │ - │
* ├────┼─────┼─────┼──────┼─────┼─────┼───┤
* │ T' │ 6 │ 5 │ - │ - │ 6 │ 6 │
* ├────┼─────┼─────┼──────┼─────┼─────┼───┤
* │ F │ - │ - │ 7 │ 8 │ - │ - │
* └────┴─────┴─────┴──────┴─────┴─────┴───┘
*
* Notes:
*
* - Row headers are grammar non-terminals
*
* - Columns are the grammar tokens
*
* - The entries are the next production number to apply
* for derivation (replacing a non-terminal on the stack with
* its right-hand side).
*
* - The entries are build from "predict-sets" (combination of the
* "first", and "follow" sets).
*/
var LLParsingTable = function () {
/**
* Builds an LL parsing table for a given grammar.
*/
function LLParsingTable(_ref) {
var grammar = _ref.grammar;
_classCallCheck(this, LLParsingTable);
this._grammar = grammar;
this._setsGenerator = new _setsGenerator2.default({ grammar: grammar });
_debug2.default.time('Building LL parsing table');
this._tableTokens = grammar.getTerminals().concat(grammar.getTokens(), _grammarSymbol2.default.get(_specialSymbols.EOF));
this._table = this._build();
_debug2.default.timeEnd('Building LL parsing table');
}
_createClass(LLParsingTable, [{
key: 'get',
value: function get() {
return this._table;
}
}, {
key: 'print',
value: function print() {
this._grammar.print();
console.info('\n' + this._grammar.getMode().toString() + ' parsing table:\n');
var tokenSymbols = this._tableTokens.map(function (token) {
return token.getSymbol();
});
var printer = new _tablePrinter2.default({
head: [''].concat(tokenSymbols)
});
for (var nonTerminal in this._table) {
var stateLabel = _colors2.default.blue(nonTerminal);
var row = _defineProperty({}, stateLabel, []);
for (var k = 0; k < tokenSymbols.length; k++) {
var entry = this._table[nonTerminal][tokenSymbols[k]] || '';
if (this.entryHasConflict(entry)) {
entry = _colors2.default.red(entry);
}
row[stateLabel].push(entry);
}
printer.push(row);
}
console.info(printer.toString());
console.info('');
}
/**
* Whether the table/grammar has conflicts.
*/
}, {
key: 'hasConflicts',
value: function hasConflicts() {
return this._hasConflicts;
}
/**
* Returns table/grammar conflicts.
*/
}, {
key: 'getConflicts',
value: function getConflicts() {
if (!this._conflicts) {
this._conflicts = this._analyzeConfilcts();
this._hasConflicts = Object.keys(this._conflicts).length !== 0;
}
return this._conflicts;
}
}, {
key: '_analyzeConfilcts',
value: function _analyzeConfilcts() {
var conflicts = Object.create(null);
for (var nonTerminal in this._table) {
var row = this._table[nonTerminal];
for (var token in row) {
var entry = row[token];
if (!this.entryHasConflict(entry)) {
continue;
}
if (!conflicts[nonTerminal]) {
conflicts[nonTerminal] = {};
}
conflicts[nonTerminal][token] = entry;
}
}
return conflicts;
}
/**
* Builds the LL parsing table from First and Follow sets.
*
* To build an LL(1) parsing table we need the Predict set,
* however the Predict set is just a combination of the
* First set of the production, plus the Follow set if the
* production derives epsilon. So in building the table
* we use First and Follow sets directly delegating to needed
* parts during the table construction.
*/
}, {
key: '_build',
value: function _build() {
var table = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this._grammar.getProductions()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var production = _step.value;
var lhs = production.getLHS();
var rhs = production.getRHS();
var lhsSymbol = lhs.getSymbol();
// Initialize columns for this non-terminal.
if (!table[lhsSymbol]) {
table[lhsSymbol] = {};
}
// All productions goes under the terminal column, if
// this terminal is not epsilon. Otherwise, an ε-production
// goes under the columns from the Follow set of LHS.
var set = !production.isEpsilon() ? this._setsGenerator.firstOfRHS(rhs) : this._setsGenerator.followOf(lhs);
for (var terminal in set) {
this._putProductionNumber(table[lhsSymbol], terminal, production.getNumber());
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return table;
}
}, {
key: 'entryHasConflict',
value: function entryHasConflict(entry) {
return entry.includes('/');
}
/**
* If we can any conflict ("FIRST/FIRST", "FIRST/FOLLOW", "FOLLOW/FOLLOW"),
* the table entry records via `/`, e.g. "2/5" - conflict, ambiguous choice
* of the next grammar rule.
*/
}, {
key: '_putProductionNumber',
value: function _putProductionNumber(row, column, entry) {
var previousEntry = row[column];
if (previousEntry === entry) {
return;
}
// Exclude duplicates for possibly the same conflict entry.
if (previousEntry) {
previousEntry = previousEntry.split('/');
if (!previousEntry.includes(entry)) {
previousEntry.push(entry);
}
entry = previousEntry.join('/');
}
row[column] = entry.toString();
}
}]);
return LLParsingTable;
}();
exports.default = LLParsingTable;