UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

577 lines (463 loc) 17.3 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 _state = require('./state'); var _state2 = _interopRequireDefault(_state); var _grammarMode = require('../grammar/grammar-mode'); var _specialSymbols = require('../special-symbols'); 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"); } } /** * An LRItem is built for a production at particular * dot position. The kernel item (for the augmented production) * builds the canonical collection, recursively applying * "closure" and "goto" operations. * * There are two types of LR items: LR(0) items, which are used by * LR(0) and SLR(1) parsers, and LR(1) items, which are used by * LALR(1) and CLR(1) parsers. An LR(1) is defined as: * * LR(1) = LR(0) + lookahead set. */ var LRItem = function () { function LRItem(production, dotPosition, grammar, canonicalCollection, setsGenerator) { var lookaheadSet = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null; _classCallCheck(this, LRItem); this._key = LRItem.keyForItem(production, dotPosition, lookaheadSet); // For LR(1) item, same as key, but withough lookaheads. this._lr0Key = LRItem.keyForItem(production, dotPosition, null); this._production = production; this._RHS = this._production.getRHS(); this._dotPosition = dotPosition; this._grammar = grammar; this._canonicalCollection = canonicalCollection; this._setsGenerator = setsGenerator; // The state this item belongs to. this._state = null; // The state this item goes to. this._outerState = null; this._reduceSet = null; // Next (advanced) item for this production. this._next = null; // LR(1) items maintain lookahead. this._lookaheadSet = lookaheadSet; } /** * Returns productions of this item. */ _createClass(LRItem, [{ key: 'getProduction', value: function getProduction() { return this._production; } /** * Returns dot position. */ }, { key: 'getDotPosition', value: function getDotPosition() { return this._dotPosition; } /** * Whether this item should be closured. */ }, { key: 'shouldClosure', value: function shouldClosure() { return !this._closured && !this.isFinal() && this._grammar.isNonTerminal(this.getCurrentSymbol()); } /** * Whether transition from this item does "shift" action. */ }, { key: 'isShift', value: function isShift() { return !this.isFinal() && this._grammar.isTokenSymbol(this.getCurrentSymbol()); } /** * Whether transition from this item does "reduce" action. */ }, { key: 'isReduce', value: function isReduce() { return this.isFinal() && !this.getProduction().isAugmented(); } /** * Whether this item is already connected to a closure. */ }, { key: 'isConnected', value: function isConnected() { return this._outerState !== null; } /** * Whether this item is a beginning item, i.e. has the dot * cursor at the very begin. */ }, { key: 'isBeginning', value: function isBeginning() { return this._dotPosition === 0; } /** * Returns next item of this item. */ }, { key: 'getNext', value: function getNext() { return this._next; } /** * Connects this item to the outer closure state. */ }, { key: 'connect', value: function connect(state) { this._outerState = state; // Update next item. var nextKey = LRItem.keyForItem(this._production, this._dotPosition + 1, this._lookaheadSet); var next = state.getItemByKey(nextKey); this._next = next; } /** * Returns the closure object for this item. */ }, { key: 'getState', value: function getState() { return this._state; } /** * Sets the state to which this item belongs to. */ }, { key: 'setState', value: function setState(state) { return this._state = state; } /** * Applies closure operation for this item. */ }, { key: 'closure', value: function closure() { var _this = this; if (!this.shouldClosure()) { return; } // Main kernel item for the augmented production, that creates a new state. if (!this._state) { this._state = new _state2.default( /* kernelItems */[this], /* grammar */this._grammar, /* canonicalCollection */this._canonicalCollection, /* setsGenerator */this._setsGenerator); } var productionsForSymbol = this._grammar.getProductionsForSymbol(this.getCurrentSymbol().getSymbol()); var addedItems = productionsForSymbol.map(function (production) { return new LRItem(production, /* dotPosition */0, _this._grammar, _this._canonicalCollection, _this._setsGenerator, /* lookaheadSet */_this._calculateLookaheadSet()); }); this._closured = true; this._state.addItems(addedItems); return this._state; } /** * Calculates a lookahead set for an added item * in the closure. The lookahead set is determined from * the previous item as First(followPart + previousLookahead). * * A -> a • B ß, a/b * B -> ∂, c/d * * where c/d is First(ß a b), and is the lookahead set. * If ß is ε, then a/b is lookahead set. */ }, { key: '_calculateLookaheadSet', value: function _calculateLookaheadSet() { if (!this._grammar.getMode().usesLookaheadSet()) { return null; } var lookaheadSet = void 0; var followPosition = this._dotPosition + 1; var RHS = this.getProduction().getRHS(); if (followPosition < RHS.length) { var lookaheadPart = RHS.slice(followPosition); lookaheadSet = this._setsGenerator.firstOfRHS(lookaheadPart); } var containsEpsilon = false; if (lookaheadSet) { containsEpsilon = lookaheadSet.hasOwnProperty(_specialSymbols.EPSILON); delete lookaheadSet[_specialSymbols.EPSILON]; } else { lookaheadSet = {}; } // If no follow part, or we got an empty set, use lookahead of // the previous item. The previous part should also be merged if // the set contains epsilon. if (Object.keys(lookaheadSet).length === 0 || containsEpsilon) { lookaheadSet = Object.assign({}, lookaheadSet, this.getLookaheadSet()); } return lookaheadSet; } /** * Goto operation from this item. */ }, { key: 'goto', value: function goto() { if (!this._outerState) { this._state.goto(); } return this._outerState; } /** * The symbol at the dot position. */ }, { key: 'getCurrentSymbol', value: function getCurrentSymbol() { return this._RHS[this._dotPosition]; } /** * Whether this reduce item conflicts with a shift symbol. */ }, { key: 'conflictsWithShiftSymbol', value: function conflictsWithShiftSymbol(symbol) { this._assertReduce(); var reduceSet = this.calculateReduceSet(); if (reduceSet === true) { return true; } return Object.keys(reduceSet).indexOf(symbol.getSymbol()) !== -1; } /** * Whether this reduce item conflicts with another reduce item * (this may happen if the lookaheads intersect). */ }, { key: 'conflictsWithReduceItem', value: function conflictsWithReduceItem(reduceItem) { this._assertReduce(); var thisReduceSet = Object.keys(this.calculateReduceSet()); if (thisReduceSet === true) { return true; } var thatReduceSet = Object.keys(reduceItem.calculateReduceSet()); var lookaheadsIntersection = thisReduceSet.filter(function (symbol) { return thatReduceSet.indexOf(symbol) !== -1; }); return lookaheadsIntersection.length !== 0; } }, { key: '_assertReduce', value: function _assertReduce() { if (!this.isReduce()) { throw new Error('Item ' + this.getKey() + ' is not a reduce item.'); } } /** * Whether we have seen the whole production. * * The item `S -> • ε` (or its short equivalent `S -> •`) is final as well. */ }, { key: 'isFinal', value: function isFinal() { return this._dotPosition === this._RHS.length || this.isEpsilonTransition(); } /** * Whether this item does transition on ε. */ }, { key: 'isEpsilonTransition', value: function isEpsilonTransition() { var currentSymbol = this.getCurrentSymbol(); return !!(currentSymbol && currentSymbol.isSymbol(_specialSymbols.EPSILON)); } /** * Returns a reduce set (usually a follow lookahead set) * for this item. LR0 mode reduces for every terminal, * SLR(1) uses Follow(LHS), and LALR(1)/CLR(1) lookaheads * set which is First(RHS) of the current symbol, including * all lookaheads from previous items. */ }, { key: 'getReduceSet', value: function getReduceSet() { if (!this._reduceSet) { this.setReduceSet(this.calculateReduceSet()); } return this._reduceSet; } /** * Sets explicit reduce set (usually in building LALR by SLR). */ }, { key: 'setReduceSet', value: function setReduceSet(reduceSet) { this._reduceSet = reduceSet; } }, { key: 'calculateReduceSet', value: function calculateReduceSet() { switch (this._grammar.getMode().getRaw()) { case _grammarMode.MODES.LR0: // LR0 reduces for all terminals, special `true` value. return true; case _grammarMode.MODES.LALR1: case _grammarMode.MODES.LALR1_BY_SLR1: // In "LALR(1) by SLR(1)" the reduce set is already calculated // in post-processing of the LR(0) automation. return this._reduceSet; case _grammarMode.MODES.SLR1: { // SLR(1) reduces in Follow(LHS). var LHS = this.getProduction().getLHS(); return this._setsGenerator.followOf(LHS); } case _grammarMode.MODES.CLR1: case _grammarMode.MODES.LALR1_BY_CLR1: // CLR(1) consider lookahead of the LR(1) item. return Object.assign({}, this._lookaheadSet); default: throw new Error('Unexpected grammar mode ' + this._grammar.getMode() + '.'); } } /** * Returns lookahead set (for LR(1) items). */ }, { key: 'getLookaheadSet', value: function getLookaheadSet() { return this._lookaheadSet; } /** * Sets a lookahead set to this item (can happen when we merge * states in LALR(1), and need to recalculate lookaheads. */ }, { key: 'setLookaheadSet', value: function setLookaheadSet(lookaheadSet) { // Extend the set. this._lookaheadSet = lookaheadSet; // Reset toStringKey (will be recalculated on next call to `toString`). this._toStringKey = null; // And rebuild the key since lookahead set is changed. this._key = LRItem.keyForItem(this._production, this._dotPosition, this._lookaheadSet); } /** * In LALR(1) mode we merge the states with the same kernel items, which * differ only in lookaheads set. The merging is done by extending the * lookaheads of the items. */ }, { key: 'mergeLookaheadSet', value: function mergeLookaheadSet(lookaheadSet) { // Extend the set. this.setLookaheadSet(Object.assign(this._lookaheadSet, lookaheadSet)); } /** * Returns serialized representation of an item. * E.g. `A -> • a A, c/d/e`. */ }, { key: 'getKey', value: function getKey() { return this._key; } /** * Same as `getKey`, but don't consider lookaheads of LR(1) item, * returning only its LR(0) representation. * * LR(1): `A -> • a A, c/d/e` * LR(0): `A -> • a A` */ }, { key: 'getLR0Key', value: function getLR0Key() { return this._lr0Key; } }, { key: 'toString', value: function toString() { if (!this._toStringKey) { this._toStringKey = this._getToStringKey(); } return this._toStringKey; } }, { key: '_getToStringKey', /** * Returns key to be displayed in toString. */ value: function _getToStringKey() { var RHS = this._RHS.map(function (symbol) { return symbol.getSymbol(); }); RHS.splice(this._dotPosition, 0, '•'); var lookaheads = ''; if (this._lookaheadSet) { lookaheads = ', #lookaheads= ' + JSON.stringify(Object.keys(this._lookaheadSet)); } return this._production.getLHS().getSymbol() + ' -> ' + ('' + RHS.join(' ') + lookaheads); } /** * Returns a key for a set of items. */ }, { key: 'advance', /** * Returns an a new item with an advanced dot position. */ value: function advance() { var next = new LRItem(this._production, /* dotPosition */this._dotPosition + 1, this._grammar, this._canonicalCollection, this._setsGenerator, // On goto transition lookaheads set doesn't change. /* lookaheadSet */this._grammar.getMode().usesLookaheadSet() ? Object.assign({}, this.getLookaheadSet()) : null); this._next = next; return next; } }], [{ key: 'keyForItem', value: function keyForItem(production, dotPosition) { var lookaheadSet = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; return production.getNumber() + '|' + dotPosition + (lookaheadSet ? '|' + Object.keys(lookaheadSet).join('|') : ''); } }, { key: 'keyForItems', value: function keyForItems(items) { if (!items.key) { items.key = items.map(function (item) { return item.getKey(); }).sort().join('|'); } return items.key; } /** * Returns an LR(0) key for a set of items (duplicates are removed). */ }, { key: 'lr0KeyForItems', value: function lr0KeyForItems(items) { if (!items.lr0Key) { var keysMap = {}; items.forEach(function (item) { return keysMap[item.getLR0Key()] = true; }); items.lr0Key = Object.keys(keysMap).sort().join('|'); } return items.lr0Key; } }]); return LRItem; }(); exports.default = LRItem;