UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

572 lines (467 loc) 17.7 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> */ /** * An abstraction for an items set (kernel plus added), * known as a "closure". Recursively closes over * all added items, eventually forming an LR-parsing state. * * Usually there is one kernel item in a state, however there are * cases when kernel may contain several items. E.g. being in the state: * * S' -> • S * S -> • S "a" * | • "b" * * and having a transition on S, we get both first two items in the * kernel of the next state: * * S' -> S • * S -> S • "a" */ var _lrItem = require('./lr-item'); var _lrItem2 = _interopRequireDefault(_lrItem); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var State = function () { /** * A closure state may have several kernel items. An initial kernel * item can be passed in the constructor, other kernel items can * be added later via `add` method. */ function State(kernelItems, grammar, canonicalCollection) { _classCallCheck(this, State); this._kernelItems = kernelItems; this._items = []; this._grammar = grammar; this._canonicalCollection = canonicalCollection; this._number = null; // A map from transition symbol to the next state. this._transitionsForSymbol = null; // To avoid infinite recursion in case if an added item // is for a recursive production, S -> S "a". this._itemsMap = {}; // Also items map, but by LR(0) key. this._lr0ItemsMap = {}; // Add initial items, and closure them if needed. this.addItems(this._kernelItems); // And register the state in the collection. this._canonicalCollection.registerState(this); } /** * State number in the canonical collection. */ _createClass(State, [{ key: 'getNumber', value: function getNumber() { return this._number; } /** * Canonical collection can assign a specific * number to this state. */ }, { key: 'setNumber', value: function setNumber(number) { this._number = number; } /** * Kernel items for which the closure is built. */ }, { key: 'getKernelItems', value: function getKernelItems() { return this._kernelItems; } /** * All items in this closure (kernel plus all expanded). */ }, { key: 'getItems', value: function getItems() { return this._items; } /** * Whether this state is final. */ }, { key: 'isFinal', value: function isFinal() { return this._items.length === 1 && this._items[0].isFinal(); } /** * Whether the state is accepting. */ }, { key: 'isAccept', value: function isAccept() { return this.isFinal() && this._items[0].getProduction().isAugmented(); } /** * Returns all reduce items in this set. */ }, { key: 'getReduceItems', value: function getReduceItems() { if (!this._reduceItems) { this._reduceItems = this._items.filter(function (item) { return item.isReduce(); }); } return this._reduceItems; } /** * Calculates all the conflicts in the state, marking the * state as inadequate in case of "s/r" or "r/r" conflicts. * * Note: conflicts only possible in states that contain * reduce items, plus another reduce or shift item. */ }, { key: 'analyzeConflicts', value: function analyzeConflicts() { var _this = this; this._conflicts = { sr: [], rr: [] }; var reduceItems = this.getReduceItems(); // "reduce-reduce" conflicts. if (reduceItems.length > 1) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { reduceSet: for (var _iterator = reduceItems[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var reduceItem = _step.value; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = reduceItems[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var reduceItemToCheck = _step2.value; if (reduceItem !== reduceItemToCheck && reduceItem.conflictsWithReduceItem(reduceItemToCheck)) { this._conflicts.rr = reduceItems; break reduceSet; } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } // "shift-reduce" conflicts. this._items.forEach(function (item) { if (!item.isShift()) { return; } reduceItems.forEach(function (reduceItem) { if (reduceItem.conflictsWithShiftSymbol(item.getCurrentSymbol())) { _this._conflicts.sr.push([item, reduceItem]); } }); }); return this._conflicts; } /** * Gets conflicts of this set. */ }, { key: 'getConflicts', value: function getConflicts() { if (!this._conflicts) { this._conflicts = this.analyzeConflicts(); } return this._conflicts; } /** * Gets "reduce-reduce" conflicts. */ }, { key: 'getRRConflicts', value: function getRRConflicts() { return this.getConflicts().rr; } /** * Gets "shift-reduce" conflicts. */ }, { key: 'getSRConflicts', value: function getSRConflicts() { return this.getConflicts().sr; } /** * Whether the state is inadequate, i.e. has conflicts. */ }, { key: 'isInadequate', value: function isInadequate() { return this.getConflicts().sr.length > 0 || this.getConflicts().rr.length > 0; } }, { key: 'hasTransitionOnSymbol', value: function hasTransitionOnSymbol(symbol) { return this._transitionsForSymbol.hasOwnProperty(symbol); } }, { key: 'getTransitionOnSymbol', value: function getTransitionOnSymbol(symbol) { if (!this.hasTransitionOnSymbol(symbol)) { return null; } return this._transitionsForSymbol[symbol]; } }, { key: 'setSymbolTransition', value: function setSymbolTransition(item, state) { var symbol = item.getCurrentSymbol().getSymbol(); if (!this.hasTransitionOnSymbol(symbol)) { this._transitionsForSymbol[symbol] = { items: [], itemsMap: {}, state: state }; } var transitionsForSymbol = this._transitionsForSymbol[symbol]; if (!transitionsForSymbol.itemsMap.hasOwnProperty(item.getKey())) { transitionsForSymbol.itemsMap[item.getKey()] = item; transitionsForSymbol.items.push(item); } if (state) { transitionsForSymbol.state = state; item.connect(state); } } }, { key: 'getItemTransion', value: function getItemTransion(itemKey) { return this.getItemTransionInfo(itemKey).state; } }, { key: 'getItemTransionInfo', value: function getItemTransionInfo(itemKey) { if (!this._itemsMap.hasOwnProperty(itemKey)) { throw new Error('Item ' + itemKey + ' is not in the state ' + this._number + '.'); } var item = this._itemsMap[itemKey]; if (item.isFinal()) { throw new Error('Item ' + itemKey + ' is final.'); } return this.getTransitionOnSymbol(item.getCurrentSymbol().getSymbol()); } /** * `Goto` operation from the items set. If the state has several items with * the same transition symbol, they all go to the same outer state. If * a transition for this kernel items set was already calculated in some * previous state, then a new state is not created, instead the items are * connected to the calculated state. */ }, { key: 'goto', value: function goto() { if (this._visited) { return; } // Init the transition to null, it will be set // new state in the `goto` operation. if (!this._transitionsForSymbol) { this._transitionsForSymbol = {}; for (var i = 0; i < this._items.length; i++) { var item = this._items[i]; if (item.isFinal()) { continue; } this.setSymbolTransition(item, /* state */null); } } // Build the outer states if needed. for (var symbol in this._transitionsForSymbol) { var transitionsForSymbol = this.getTransitionOnSymbol(symbol); // Already calculated the outer state, exit. if (transitionsForSymbol.state) { continue; } var items = transitionsForSymbol.items; // See if we already calculated transition for this kernel set. var outerState = this._canonicalCollection.getTranstionForItems(items); // If not, create a new outer state with advanced kernel items. if (!outerState) { outerState = new State( /* kernelItems */items.map(function (item) { return item.advance(); }), /* grammar */this._grammar, /* canonicalCollection */this._canonicalCollection); this._canonicalCollection.registerTranstionForItems(items, outerState); } // And connect our items to it. for (var _i = 0; _i < items.length; _i++) { this.setSymbolTransition(items[_i], /* state */outerState); } } this._visited = true; // Recursively goto further in the graph. for (var _symbol in this._transitionsForSymbol) { this.getTransitionOnSymbol(_symbol).state.goto(); } } }, { key: 'isKernelItem', value: function isKernelItem(item) { return this._kernelItems.indexOf(item) !== -1; } /** * Adds items to this state, and closures them if needed. */ }, { key: 'addItems', value: function addItems(items) { var _this2 = this; items.map(function (item) { return _this2.addItem(item); }).forEach(function (item) { return item && item.closure(); }); } /** * Adds an item to this state. */ }, { key: 'addItem', value: function addItem(item) { if (this._itemsMap.hasOwnProperty(item.getKey())) { return; } this._items.push(item); this._itemsMap[item.getKey()] = item; if (!this._lr0ItemsMap[item.getLR0Key()]) { this._lr0ItemsMap[item.getLR0Key()] = []; } this._lr0ItemsMap[item.getLR0Key()].push(item); item.setState(this); return item; } /** * Returns an item by key. */ }, { key: 'getItemByKey', value: function getItemByKey(key) { return this._itemsMap[key]; } /** * Returns an item by LR(0) key. */ }, { key: 'getItemByLR0Key', value: function getItemByLR0Key(lr0Key) { var lr0ItemsCount = this._lr0ItemsMap[lr0Key].length; if (lr0ItemsCount !== 1) { throw new Error('Number of LR0 items for ' + lr0Key + ' is not 1 in ' + this.getNumber() + '. ' + 'Call mergeLR0Items before accessing this method.'); } return this._lr0ItemsMap[lr0Key][lr0ItemsCount - 1]; } /** * Merges items with the same LR(0) parts for LALR(1). */ }, { key: 'mergeLR0Items', value: function mergeLR0Items() { for (var lr0Key in this._lr0ItemsMap) { var items = this._lr0ItemsMap[lr0Key]; var rootItem = items[0]; // Merge the items, keeping only one. while (items.length > 1) { this.mergeTwoItems(rootItem, items.pop()); } } } }, { key: 'mergeTwoItems', value: function mergeTwoItems(first, second) { var transition = !first.isFinal() ? this.getItemTransionInfo(first.getKey()) : null; delete this._itemsMap[first.getKey()]; if (transition) { delete transition.itemsMap[first.getKey()]; } var secondKey = second.getKey(); first.mergeLookaheadSet(second.getLookaheadSet()); // And remove it from all collections. delete this._itemsMap[secondKey]; var itemIndex = this._items.indexOf(second); if (itemIndex !== -1) { this._items.splice(itemIndex, 1); } itemIndex = this._kernelItems.indexOf(second); if (itemIndex !== -1) { this._kernelItems.splice(itemIndex, 1); } if (transition) { delete transition.itemsMap[secondKey]; itemIndex = transition.items.indexOf(second); if (itemIndex !== -1) { transition.items.splice(itemIndex, 1); } } this._itemsMap[first.getKey()] = first; if (transition) { transition.itemsMap[first.getKey()] = first; } } /** * Merges the state with another one, that has the same * LR(0) items, but which differs only in lookaheads. * This is used in LALR(1) mode when is compressed from CLR(1). */ }, { key: 'mergeWithState', value: function mergeWithState(state) { var _this3 = this; if (this._items.length !== this._items.length) { throw new Error('LALR(1): State ' + state.getNumber() + ' is not compatible ' + ('with state ' + this.getNumber() + '.')); } if (_lrItem2.default.keyForItems(this.getKernelItems()) !== _lrItem2.default.keyForItems(state.getKernelItems())) { this._items.forEach(function (item) { var thatItem = state.getItemByLR0Key(item.getLR0Key()); if (!thatItem) { throw new Error('Item ' + item.toString() + ' presents in state ' + _this3.getNumber() + ', ' + ('but is absent in the ' + state.getNumber() + '.')); } _this3.mergeTwoItems(item, thatItem); }); } // After merging lookaheads, we should un-register this new // idential state, since it was registered in the constructor. this._canonicalCollection.unregisterState(state); return this; } }]); return State; }(); exports.default = State;