syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
577 lines (463 loc) • 17.3 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 _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;