syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
530 lines (429 loc) • 19.6 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 _grammar = require('../grammar/grammar');
var _grammar2 = _interopRequireDefault(_grammar);
var _grammarMode = require('../grammar/grammar-mode');
var _lrItem = require('./lr-item');
var _lrItem2 = _interopRequireDefault(_lrItem);
var _setsGenerator = require('../sets-generator');
var _setsGenerator2 = _interopRequireDefault(_setsGenerator);
var _specialSymbols = require('../special-symbols');
var _debug = require('../debug');
var _debug2 = _interopRequireDefault(_debug);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
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"); } }
/**
* Canonical collection of LR items.
*
* LR(0) and SLR(1) use collection of LR(0)-items,
* LALR(1) and CLR(1) use collection of LR(1)-items.
*
* The collection is built for a grammar starting from the root
* item (for the augmented production), which recursively
* applies "closure" and "goto" operations.
*/
var CanonicalCollection = function () {
function CanonicalCollection(_ref) {
var grammar = _ref.grammar;
_classCallCheck(this, CanonicalCollection);
this._grammar = grammar;
// Stores transition from a kernel set to an outer state.
// This is to reuse the same outer state, if the transition was
// already calculated. In this case we just connect to the outer state.
this._kernelSetsTransitions = {};
// Stores states by their kernel items LR(0) key. This is to merge
// similar states in case of LALR(1) mode.
this._lr0ItemSets = {};
// All the states that form this collection.
this._states = new Set();
_debug2.default.time('Building canonical collection');
// Root item for the augmented production, "closure" and "goto"
// operations applied on this item build the entire collection.
this._rootItem = new _lrItem2.default(
/* production */this._grammar.getAugmentedProduction(),
/* dotPosition */0,
/* grammar */this._grammar,
/* canonicalCollection */this,
/* setsGenerator */new _setsGenerator2.default({ grammar: grammar }),
/*lookaheadSet */this._grammar.getMode().usesLookaheadSet() ? _defineProperty({}, _specialSymbols.EOF, true) : null);
// Build the entire graph.
this._rootItem.closure().goto();
this._remap();
_debug2.default.timeEnd('Building canonical collection');
_debug2.default.log('Number of states in the collection: ' + this._states.size);
// LALR(1) by converting to SLR(1), default fast LALR(1) mode.
if (this._grammar.getMode().isLALR1()) {
this._buildLALRBySLR();
}
// LALR(1) by compressing CLR(1): mostly for educational purposes, slow.
else if (this._grammar.getMode().isLALR1ByCLR1()) {
_debug2.default.time('Compressing CLR to LALR');
this._compressCLRToLALR();
_debug2.default.timeEnd('Compressing CLR to LALR');
_debug2.default.log('Number of states after compression: ' + this._states.size);
}
}
/**
* Basic LALR(1) implementation compressing from CLR(1).
*
* This can be slow on complex grammars, and one can better
* use LALR(1) by SLR(1) method.
*/
_createClass(CanonicalCollection, [{
key: '_compressCLRToLALR',
value: function _compressCLRToLALR() {
var _this = this;
for (var lr0StateKey in this._lr0ItemSets) {
var states = this._lr0ItemSets[lr0StateKey];
var rootState = states[0];
rootState.mergeLR0Items();
while (states.length > 1) {
var state = states.pop();
state.mergeLR0Items();
rootState.mergeWithState(state);
}
rootState.getItems().forEach(function (item) {
// If the item was already connected, we should recalculate its
// connection to the first state in the LR(0) states collection,
// since only this state will be kept after states are merged.
if (item.isConnected()) {
var outerStates = _this.getLR0ItemsSet(item.goto());
var outerState = outerStates[0];
item.connect(outerState);
}
});
}
// After compression reassign new numbers to states.
this._remap();
}
/**
* Builds LALR(1) by SLR(1) grammar, and post-processes LR-items
* by calculating needed lookahead sets.
*
* See good concise explanation of the algorithm here:
* https://web.cs.dal.ca/~sjackson/lalr1.html
*/
}, {
key: '_buildLALRBySLR',
value: function _buildLALRBySLR() {
_debug2.default.time('Building LALR-by-SLR');
this._buildExtendedLALR1Grammar();
this._extendedFollowSets = new _setsGenerator2.default({
grammar: this._extendedLALRGrammar
}).getFollowSets();
// Mutate the set with extended symbols to reflect the
// symbols from the original grammar.
for (var nonTerminal in this._extendedFollowSets) {
var set = this._extendedFollowSets[nonTerminal];
for (var symbol in set) {
if (this._setsAliasMap.hasOwnProperty(symbol)) {
set[this._setsAliasMap[symbol]] = true;
delete set[symbol];
}
}
}
this._groupExtendedLALRByFinalSets();
this._updateLALRItemReduceSet();
_debug2.default.timeEnd('Building LALR-by-SLR');
}
/**
* Groups extended LALR(1) by final sets.
*
* We merge extended rules, if they are from the same original
* rule, and go to the same final set (has the same state number
* in the very last symbol of RHS).
*/
}, {
key: '_groupExtendedLALRByFinalSets',
value: function _groupExtendedLALRByFinalSets() {
var _this2 = this;
_debug2.default.time('LALR-by-SLR: Group extended productions by final sets');
this._groupedFinalSets = {};
this._extendedLALRGrammar.getProductions().forEach(function (production) {
var LHS = production.getLHS();
var RHS = production.getRHS();
var lastSymbol = RHS[RHS.length - 1];
var originalLHS = LHS.getOrignialSymbol();
var finalSet = lastSymbol.getEndContext();
if (!_this2._groupedFinalSets.hasOwnProperty(finalSet)) {
_this2._groupedFinalSets[finalSet] = {};
}
if (!_this2._groupedFinalSets[finalSet].hasOwnProperty(originalLHS)) {
_this2._groupedFinalSets[finalSet][originalLHS] = {};
}
// Merge follow sets.
Object.assign(_this2._groupedFinalSets[finalSet][originalLHS], _this2._extendedFollowSets[LHS.getSymbol()]);
});
_debug2.default.timeEnd('LALR-by-SLR: Group extended productions by final sets');
}
/**
* Updates the reduce sets for items in the LALR by SLR algorithm.
*/
}, {
key: '_updateLALRItemReduceSet',
value: function _updateLALRItemReduceSet() {
var _this3 = this;
_debug2.default.time('LALR-by-SLR: Updating item reduce sets');
var states = [].concat(_toConsumableArray(this._states));
var _loop = function _loop(state) {
states[state].getReduceItems().forEach(function (reduceItem) {
var LHS = reduceItem.getProduction().getLHS().getSymbol();
reduceItem.setReduceSet(_this3._groupedFinalSets[state][LHS]);
});
};
for (var state in this._groupedFinalSets) {
_loop(state);
}
_debug2.default.timeEnd('LALR-by-SLR: Updating item reduce sets');
}
/**
* We use LALR(1) by SLR(1) algorithm here. Once we have built LR(0)
* automation, we build the extended grammar, considering the context.
* This context further results to needed lookahead set for LALR(1) which
* is obtain as Follow(LHS), i.e. the same as in SLR(1).
*/
}, {
key: '_buildExtendedLALR1Grammar',
value: function _buildExtendedLALR1Grammar() {
_debug2.default.time('LALR-by-SLR: Building extended grammar for LALR');
var extendedBnf = {};
this._setsAliasMap = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this._states[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var state = _step.value;
var items = state.getItems();
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = items[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var item = _step2.value;
// Extended items are built only for beginning items.
if (!item.isBeginning()) {
continue;
}
// We traverse the full path of the item, in order to
// to identify components with contexts.
var current = item;
var visited = new Set();
var LHS = item.getProduction().getLHS().getSymbol();
var lhsTransit = state.getTransitionOnSymbol(LHS);
var lhsToState = lhsTransit ? lhsTransit.state.getNumber() : _specialSymbols.EOF;
var extendedLHSSymbol = state.getNumber() + '|' + LHS + '|' + lhsToState;
// Init the rules for the new LHS.
if (!extendedBnf.hasOwnProperty(extendedLHSSymbol)) {
extendedBnf[extendedLHSSymbol] = [];
}
var extendedRHS = [];
while (current !== null && !visited.has(current)) {
visited.add(visited);
var transitionSymbol = current.getCurrentSymbol();
if (transitionSymbol) {
var rawSymbol = transitionSymbol.getSymbol();
var fromState = current.getState().getNumber();
var toState = void 0;
// Epsilon reduces in this state.
if (transitionSymbol.isEpsilon()) {
toState = fromState;
} else if (current.getNext()) {
toState = current.getNext().getState().getNumber();
}
if (toState != null) {
var extendedRHSSymbol = fromState + '|' + rawSymbol + '|' + toState;
extendedRHS.push(extendedRHSSymbol);
// Collect extended token/terminal symbols as aliases of the
// original terminal symbols: this is needed to compute
// First/Follow sets as original symbols.
if (this._grammar.isTokenSymbol(rawSymbol)) {
this._setsAliasMap[extendedRHSSymbol] = rawSymbol;
}
}
}
current = current.getNext();
}
// Append the new RHS alternative.
extendedBnf[extendedLHSSymbol].push(extendedRHS.join(' '));
}
} 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;
}
}
}
this._extendedLALRGrammar = new _grammar2.default({
bnf: extendedBnf,
mode: _grammarMode.MODES.LALR1_EXTENDED
});
_debug2.default.timeEnd('LALR-by-SLR: Building extended grammar for LALR');
}
}, {
key: 'registerState',
value: function registerState(state) {
this._states.add(state);
// Collect states by LR(0) items, to reuse and merge the same
// states in case or LALR(1) mode.
var lr0KeyForItems = _lrItem2.default.lr0KeyForItems(state.getKernelItems());
if (!this._lr0ItemSets.hasOwnProperty(lr0KeyForItems)) {
this._lr0ItemSets[lr0KeyForItems] = [];
}
this._lr0ItemSets[lr0KeyForItems].push(state);
}
}, {
key: 'unregisterState',
value: function unregisterState(state) {
this._states.delete(state);
var keyForItems = _lrItem2.default.keyForItems(state.getKernelItems());
delete this._kernelSetsTransitions[keyForItems];
var lr0KeyForItems = _lrItem2.default.lr0KeyForItems(state.getKernelItems());
var lr0States = this._lr0ItemSets[lr0KeyForItems];
var stateIndex = lr0States.indexOf(state);
if (stateIndex !== -1) {
lr0States.splice(stateIndex, 1);
}
}
}, {
key: 'getStates',
value: function getStates() {
return this._states;
}
}, {
key: 'hasTranstionOnItems',
value: function hasTranstionOnItems(items) {
return !!this.getTranstionForItems(items);
}
}, {
key: 'getTranstionForItems',
value: function getTranstionForItems(items) {
return this._kernelSetsTransitions[_lrItem2.default.keyForItems(items)];
}
}, {
key: 'registerTranstionForItems',
value: function registerTranstionForItems(items, outerState) {
this._kernelSetsTransitions[_lrItem2.default.keyForItems(items)] = outerState;
}
/**
* In LALR(1) there could be several states with the same
* LR(0) items, but which differ only in lookahead symbols.
* In this case we merge such states extending their lookaheads.
*/
}, {
key: 'getLR0ItemsSet',
value: function getLR0ItemsSet(state) {
return this._lr0ItemSets[_lrItem2.default.lr0KeyForItems(state.getKernelItems())];
}
}, {
key: 'print',
value: function print() {
var _this4 = this;
console.info('\nCanonical collection of LR items:');
this._grammar.print();
this._states.forEach(function (state) {
var stateTags = [];
if (state.isFinal()) {
stateTags.push('final');
if (state.isAccept()) {
stateTags.push('accept');
}
}
console.info('\nState ' + state.getNumber() + ':' + (stateTags.length > 0 ? ' (' + stateTags.join(', ') + ')' : ''));
state.getItems().forEach(function (item) {
return _this4._printItem(item, state);
});
});
}
}, {
key: '_printItem',
value: function _printItem(item, state) {
var itemTags = [];
if (state.isKernelItem(item)) {
itemTags.push('kernel');
}
if (item.isShift()) {
itemTags.push('shift');
}
if (item.isReduce()) {
itemTags.push('reduce by production ' + item.getProduction().getNumber());
}
if (item.isFinal() && !item.isReduce()) {
itemTags.push('accept');
}
if (item.goto()) {
itemTags.push('goes to state ' + item.goto().getNumber());
}
console.info(' - ' + item.toString() + (itemTags.length > 0 ? ' (' + itemTags.join(', ') + ')' : ''));
}
}, {
key: 'getRoot',
value: function getRoot() {
return this._rootItem;
}
}, {
key: 'getStartingState',
value: function getStartingState() {
return this.getRoot().getState();
}
}, {
key: '_remap',
value: function _remap() {
var number = 0;
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = this._states[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var state = _step3.value;
state.setNumber(number++);
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
}
}]);
return CanonicalCollection;
}();
exports.default = CanonicalCollection;