UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

499 lines (419 loc) 18.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 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 _canonicalCollection = require('./canonical-collection'); var _canonicalCollection2 = _interopRequireDefault(_canonicalCollection); var _grammarSymbol = require('../grammar/grammar-symbol'); var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol); 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"); } } /** * The LR parsing table is built by traversing the graph of the * canonical collection of LR items. Rows of the table correspond to * the state (closure) numbers, and columns are terminals (for "action" * part) and non-terminals (for "goto" part). * * For the example grammar (where S' -> S is the augmented production): * * +------------+ * | S' -> S | * +------------+ * | S -> A A | * | A -> "a" A | * | | "b" | * +------------+ * * The LR(0) parsing table, which is used by LR(0) and SLR(1) parsers, * looks like this: * * State Action Goto * +---+-----+-----+-----++-----+-----+ * | │ "a" │ "b" │ $ ││ S │ A │ * +---+-----+-----+-----++-----+-----+ * | 0 │ s2 │ s4 │ ││ 3 │ 1 │ * +---+-----+-----+-----++-----+-----+ * | 1 │ s2 │ s4 │ ││ │ 5 │ * +---+-----+-----+-----++-----+-----+ * | 2 │ s2 │ s4 │ ││ │ 6 │ * +---+-----+-----+-----++-----+-----+ * | 3 │ │ │ acc ││ │ │ * +---+-----+-----+-----++-----+-----+ * | 4 │ r3 │ r3 │ r3 ││ │ │ * +---+-----+-----+-----++-----+-----+ * | 5 │ r1 │ r1 │ r1 ││ │ │ * +---+-----+-----+-----++-----+-----+ * | 6 │ r2 │ r2 │ r2 ││ │ │ * +---+-----+-----+-----++-----+-----+ * * Note: for LR(1) items used by LALR(1) and CLR(1) number of reduce steps * may decrease. Also number of states may increase in case of CLR(1). * * - State: number of a state (closure) in the graph * * Action: * * - s<n> - Shift<n>: "shift" action (move a symbol onto the stack) and * transit to the state n next * * - r<k> - Reduce<k>: "reduce" action (replace RHS of a production <k> * which is on top of the stack, with its LHS. * * - acc - "accept" action, successful parse. * * Goto: * * - <n> - goto to state <n> in the graph. * * Examples: * * - 0:A -> 3: if we're in the state 0, and see non-terminal A, go to * the state 3. * * - 1:"a" -> s2: if we're in the state 1, and see the "a" terminal, * shift it from buffer onto the stack, and go to the state 2. * * - 5:"b" -> r2: if we're in the state 5, and see the "b" terminal, * reduce the RHS of the production 2 on top of the stack to its * LHS non-terminal. */ /** * Type of an entry in the parsing table. */ var EntryType = { ERROR: 0, GOTO: 1, SHIFT: 2, REDUCE: 3, ACCEPT: 4, SR_CONFLICT: 5, RR_CONFLICT: 6 }; /** * LR parsing table class. */ var LRParsingTable = function () { /** * The table is built from the canonical collection, * which was built for the specific grammar. */ function LRParsingTable(_ref) { var _ref$canonicalCollect = _ref.canonicalCollection, canonicalCollection = _ref$canonicalCollect === undefined ? null : _ref$canonicalCollect, grammar = _ref.grammar, _ref$resolveConflicts = _ref.resolveConflicts, resolveConflicts = _ref$resolveConflicts === undefined ? false : _ref$resolveConflicts; _classCallCheck(this, LRParsingTable); this._grammar = grammar; this._canonicalCollection = canonicalCollection; this._shouldResolveConflicts = resolveConflicts; if (!this._canonicalCollection) { this._canonicalCollection = new _canonicalCollection2.default({ grammar: this._grammar }); } // Stores conflicts data. this._conflictsData = {}; _debug2.default.time('Building LR parsing table'); this._action = grammar.getTerminals().concat(grammar.getTokens(), _grammarSymbol2.default.get(_specialSymbols.EOF)); this._goto = grammar.getNonTerminals(); this._table = {}; this._build(); _debug2.default.timeEnd('Building LR parsing table'); } _createClass(LRParsingTable, [{ key: 'get', value: function get() { return this._table; } /** * Returns conflicts data. */ }, { key: 'getConflictsData', value: function getConflictsData() { return this._conflictsData; } }, { key: 'print', value: function print() { var _this = this; this._grammar.print(); console.info('\n' + this._grammar.getMode().toString() + ' parsing table:\n'); var actionSymbols = this._action.map(function (actionSymbol) { return actionSymbol.getSymbol(); }); var nonTerminals = this._grammar.getNonTerminals().map(function (nonTerminal) { return nonTerminal.getSymbol(); }); var printer = new _tablePrinter2.default({ head: [''].concat(actionSymbols, nonTerminals) }); var _loop = function _loop(stateNumber) { var tableRow = _this._table[stateNumber]; var stateLabel = _colors2.default.blue(stateNumber); var row = _defineProperty({}, stateLabel, []); // Action part. actionSymbols.forEach(function (actionSymbol) { var entry = tableRow[actionSymbol] || ''; if (_this._hasConflict(entry)) { entry = _colors2.default.red(entry); } else if (entry === 'acc') { entry = _colors2.default.green(entry); } row[stateLabel].push(entry); }); // Goto part. nonTerminals.forEach(function (nonTerminal) { row[stateLabel].push(tableRow[nonTerminal] || ''); }); printer.push(row); }; for (var stateNumber in this._table) { _loop(stateNumber); } console.info(printer.toString()); console.info(''); } }, { key: '_hasConflict', value: function _hasConflict(entry) { var entryType = LRParsingTable.getEntryType(entry); return entryType === EntryType.RR_CONFLICT || entryType === EntryType.SR_CONFLICT; } }, { key: '_build', value: function _build() { var _this2 = this; this._canonicalCollection.getStates().forEach(function (state) { // Fill actions and goto for this state (row). var row = _this2._table[state.getNumber()] = {}; state.getItems().forEach(function (item) { // For final item we should "reduce". In LR(0) type we // reduce unconditionally for every terminal, in other types // e.g. SLR(1) consider lookahead (follow) sets. if (item.isFinal()) { var production = item.getProduction(); // For the final item of the augmented production, // the action is "acc" (accept). if (production.isAugmented()) { row[_specialSymbols.EOF] = 'acc'; } else { // Otherwise, reduce. _this2._action.forEach(function (terminal) { if (_this2._shouldReduce(item, terminal)) { _this2._putActionEntry(row, terminal.getSymbol(), 'r' + production.getNumber()); } }); } } else { var transitionSymbol = item.getCurrentSymbol(); var nextState = item.goto().getNumber(); // Other terminals do "shift" action and go to the next state, // and non-terminals just go to the next if (_this2._grammar.isTokenSymbol(transitionSymbol)) { _this2._putActionEntry(row, transitionSymbol.getSymbol(), 's' + nextState); } else { row[transitionSymbol.getSymbol()] = nextState; } } }); // Resolve conflicts, if any. _this2._resolveConflicts(state, row); }); } }, { key: '_getStateConflictData', value: function _getStateConflictData(state) { var stateNumber = state.getNumber(); if (!this._conflictsData.hasOwnProperty(stateNumber)) { this._conflictsData[stateNumber] = {}; } return this._conflictsData[stateNumber]; } }, { key: '_initSymbolConflictData', value: function _initSymbolConflictData(state, symbol, conflict) { this._getStateConflictData(state)[symbol] = { conflict: conflict, resolved: false }; } }, { key: '_resolveConflicts', value: function _resolveConflicts(state, row) { for (var symbol in row) { var entry = row[symbol]; var entryType = LRParsingTable.getEntryType(entry); // Shift-reduce. if (entryType === EntryType.SR_CONFLICT) { this._initSymbolConflictData(state, symbol, entry); this._resolveSRConflict(state, row, symbol); } // Reduce-reduce. else if (entryType === EntryType.RR_CONFLICT) { this._initSymbolConflictData(state, symbol, entry); this._resolveRRConflict(state, row, symbol); } } } }, { key: '_resolveSRConflict', value: function _resolveSRConflict(state, row, symbol) { var entry = row[symbol]; var operators = this._grammar.getOperators(); var _splitSRParts = this.splitSRParts(entry), _splitSRParts2 = _slicedToArray(_splitSRParts, 2), reducePart = _splitSRParts2[0], shiftPart = _splitSRParts2[1]; // Default resolution is to shift if no precedence is specified. if (!operators.hasOwnProperty(symbol)) { if (this._shouldResolveConflicts) { row[symbol] = shiftPart; this._getStateConflictData(state)[symbol].resolved = 'no precedence, shift by default'; } return; } // Else, working with operators precedence. var _operators$symbol = operators[symbol], symbolPrecedence = _operators$symbol.precedence, symbolAssoc = _operators$symbol.assoc; var productionPrecedence = this._grammar.getProduction(reducePart.slice(1)).getPrecedence(); var symbolConflictData = this._getStateConflictData(state)[symbol]; // 1. If production's precedence is higher, the choice is to reduce: // // R: E -> E * E • (reduce since `*` > `+`) // S: E -> E • + E // if (productionPrecedence > symbolPrecedence) { row[symbol] = reducePart; symbolConflictData.resolved = 'reduce (higher production precedence)'; } // 2. If the symbol's precedence is higher, the choice is to shift: // // E -> E + E • // E -> E • * E (shift since `*` > `+`) // else if (symbolPrecedence > productionPrecedence) { row[symbol] = shiftPart; symbolConflictData.resolved = 'shift (higher symbol precedence)'; } // 3. If they have equal precedence, the choice is made based on the // associativity of that precedence level: // // E -> E * E • (choose to reduce since `*` is left-associative) // E -> E • * E // // This case we want `id * id * id` to be left-associative, i.e. // `(id * id) * id`, but not right-associative, that would be // `id * (id * id)`. // else if (productionPrecedence === symbolPrecedence && productionPrecedence !== 0 && symbolPrecedence !== 0) { // Left-assoc. if (symbolAssoc === 'left') { row[symbol] = reducePart; symbolConflictData.resolved = 'reduce (same precedence, left-assoc)'; } else if (symbolAssoc === 'right') { row[symbol] = shiftPart; symbolConflictData.resolved = 'shift (same precedence, right-assoc)'; } else if (symbolAssoc === 'nonassoc') { // No action on `nonassoc`. delete row[symbol]; symbolConflictData.resolved = 'removed conflict for nonassoc'; } } } }, { key: '_resolveRRConflict', value: function _resolveRRConflict(state, row, symbol) { if (!this._shouldResolveConflicts) { return; } var entry = row[symbol]; var _entry$split = entry.split('/'), _entry$split2 = _slicedToArray(_entry$split, 2), r1 = _entry$split2[0], r2 = _entry$split2[1]; var symbolConflictData = this._getStateConflictData(state)[symbol]; // R/R conflicts are resolved by choosing a production that // goes first in the grammar (i.e. its number is smaller). row[symbol] = Number(r1.slice(1)) < Number(r2.slice(1)) ? r1 : r2; symbolConflictData.resolved = 'first in order production is chosen ' + row[symbol].slice(1); } }, { key: 'splitSRParts', value: function splitSRParts(entry) { var srConflict = entry.split('/'); return LRParsingTable.getEntryType(srConflict[0]) === EntryType.REDUCE ? [srConflict[0], srConflict[1]] : [srConflict[1], srConflict[0]]; } }, { key: '_shouldReduce', value: function _shouldReduce(item, terminal) { var reduceSet = item.getReduceSet(); // LR(0) reduces for all terminals. if (reduceSet === true) { return true; } // SLR(1) considers Follow(LHS), LALR(1) and CLR(1) // considers lookahead sets. return reduceSet.hasOwnProperty(terminal.getSymbol()); } }, { key: '_putActionEntry', value: function _putActionEntry(row, column, entry) { var previousEntry = row[column]; // In case we have a transition on the same // symbol, and an action was already registered. if (previousEntry === entry) { return; } // Register an entry handling possible "shift-reduce" (s/r) // or "reduce-reduce" (r/r) conflicts. // Exclude duplicates for possibly the same conflict entry. if (previousEntry) { previousEntry = previousEntry.split('/'); if (previousEntry.indexOf(entry) === -1) { previousEntry.push(entry); } entry = previousEntry.join('/'); } row[column] = entry; } }], [{ key: 'getEntryType', value: function getEntryType(entry) { if (typeof entry === 'number') { return EntryType.GOTO; } else if (entry.indexOf('/') !== -1) { return entry.indexOf('s') !== -1 ? EntryType.SR_CONFLICT : EntryType.RR_CONFLICT; } else if (entry[0] === 's') { return EntryType.SHIFT; } else if (entry[0] === 'r') { return EntryType.REDUCE; } else if (entry === 'acc') { return EntryType.ACCEPT; } return EntryType.ERROR; } }, { key: 'EntryType', get: function get() { return EntryType; } }]); return LRParsingTable; }(); exports.default = LRParsingTable;