syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
572 lines (467 loc) • 17.7 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>
*/
/**
* 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;