UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

293 lines (235 loc) 11.1 kB
'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;