syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
375 lines (299 loc) • 12.5 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; }; }();
var _grammarSymbol = require('./grammar/grammar-symbol');
var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);
var _tablePrinter = require('./table-printer');
var _tablePrinter2 = _interopRequireDefault(_tablePrinter);
var _specialSymbols = require('./special-symbols');
var _debug = require('./debug');
var _debug2 = _interopRequireDefault(_debug);
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"); } }
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; } /**
* The MIT License (MIT)
* Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
// Default exclude set on merging.
var EXCLUDE_EPSILON = _defineProperty({}, _specialSymbols.EPSILON, true);
/**
* Pasrsing sets generator.
*/
var SetsGenerator = function () {
/**
* Constructs First, Follow, and Predict sets
* for a given grammar.
*/
function SetsGenerator(_ref) {
var grammar = _ref.grammar;
_classCallCheck(this, SetsGenerator);
this._grammar = grammar;
this._firstSets = {};
this._followSets = {};
this._predictSets = {};
}
/**
* Rules for First Sets
*
* - If X is a terminal then First(X) is just X!
* - If there is a Production X → ε then add ε to first(X)
* - If there is a Production X → Y1Y2..Yk then add first(Y1Y2..Yk) to First(X)
* - First(Y1Y2..Yk) is either
* - First(Y1) (if First(Y1) doesn't contain ε)
* - OR (if First(Y1) does contain ε) then First(Y1Y2..Yk) is everything
* in First(Y1) <except for ε > as well as everything in First(Y2..Yk)
* - If First(Y1) First(Y2)..First(Yk) all contain ε then add ε
* to First(Y1Y2..Yk) as well.
*/
_createClass(SetsGenerator, [{
key: 'getFirstSets',
value: function getFirstSets() {
_debug2.default.time('Building First sets');
this._buildSet(this.firstOf);
_debug2.default.timeEnd('Building First sets');
return this._firstSets;
}
/**
* Returns First set for a particular symbol.
*
* If we have alternative productions for `S` as:
*
* 1. S -> "a" B
* 2. S -> Y X
* 3. Y -> "b"
*
* then the firstOf(S) is {"a": true, "b": true}, where "a" is gotten
* directly, and "b" by following Y non-terminal.
*/
}, {
key: 'firstOf',
value: function firstOf(symbol) {
var _this = this;
if (symbol instanceof _grammarSymbol2.default) {
symbol = symbol.getSymbol();
}
// A set may already be built from some previous analysis
// of a RHS, so check whether it's already there and don't rebuild.
if (this._firstSets[symbol]) {
return this._firstSets[symbol];
}
var firstSet = this._firstSets[symbol] = {};
// If it's a terminal, its First set contains just itself.
if (this._grammar.isTokenSymbol(symbol) || _grammarSymbol2.default.isEpsilon(symbol) || _grammarSymbol2.default.isEOF(symbol)) {
firstSet[symbol] = true;
return this._firstSets[symbol];
}
var productionsForSymbol = this._grammar.getProductionsForSymbol(symbol);
productionsForSymbol.forEach(function (production) {
var RHS = production.getRHS();
_this._mergeSets(firstSet, _this.firstOfRHS(RHS));
});
return firstSet;
}
/**
* Returns First set of the whole RHS, excluding derived epsilons.
*/
}, {
key: 'firstOfRHS',
value: function firstOfRHS(RHS) {
var firstSet = {};
for (var i = 0; i < RHS.length; i++) {
var productionSymbol = RHS[i];
// Direct epsilon goes to the First set.
if (productionSymbol.isEpsilon()) {
firstSet[_specialSymbols.EPSILON] = true;
break;
}
// Calculate First of current symbol on RHS.
var firstOfCurrent = this.firstOf(productionSymbol);
// Put the First set of this non-terminal in our set,
// excluding the EPSILON.
this._mergeSets(firstSet, firstOfCurrent, EXCLUDE_EPSILON);
// And if there was no EPSILON, we're done (otherwise, we
// don't break the loop, and proceed to the next symbol of the RHS.
if (!firstOfCurrent.hasOwnProperty(_specialSymbols.EPSILON)) {
break;
}
// If all symbols on RHS are eliminated, or the last
// symbol contains EPSILON, add it to the set.
else if (i === RHS.length - 1) {
firstSet[_specialSymbols.EPSILON] = true;
}
}
return firstSet;
}
/**
* Rules for Follow Sets
*
* - First put $ (the end of input marker) in Follow(S) (S is the start symbol)
* - If there is a production A → aBb, (where a can be a whole string)
* then everything in FIRST(b) except for ε is placed in FOLLOW(B).
* - If there is a production A → aB, then everything in
* FOLLOW(A) is in FOLLOW(B)
* - If there is a production A → aBb, where FIRST(b) contains ε,
* then everything in FOLLOW(A) is in FOLLOW(B)
*/
}, {
key: 'getFollowSets',
value: function getFollowSets() {
_debug2.default.time('Building Follow sets');
this._buildSet(this.followOf);
_debug2.default.timeEnd('Building Follow sets');
return this._followSets;
}
/**
* Returns Follow set for a particular symbol.
*/
}, {
key: 'followOf',
value: function followOf(symbol) {
var _this2 = this;
if (symbol instanceof _grammarSymbol2.default) {
symbol = symbol.getSymbol();
}
// If was already calculated from some previous run.
if (this._followSets[symbol]) {
return this._followSets[symbol];
}
// Else init and calculate.
var followSet = this._followSets[symbol] = {};
// Start symbol always contain `$` in its follow set.
if (symbol === this._grammar.getStartSymbol()) {
followSet[_specialSymbols.EOF] = true;
}
// We need to analyze all productions where our
// symbol is used (i.e. where it appears on RHS).
var productionsWithSymbol = this._grammar.getProductionsWithSymbol(symbol);
productionsWithSymbol.forEach(function (production) {
var RHS = production.getRHSSymbols();
var symbolIndex = void 0;
// Get the follow symbol of our symbol. A symbol can appear
// several times on the RHS, e.g. S -> AaAb, so Follow set should
// be {a, b}. Also a symbol may appear as the last symbol, in
// which case it should be Follow(LHS).
while ((symbolIndex = RHS.indexOf(symbol)) !== -1) {
var followPart = RHS.slice(symbolIndex + 1);
// followOf(symbol) is firstOf(followSymbol),
// excluding epsilon.
if (followPart.length > 0) {
while (followPart.length > 0) {
var productionSymbol = followPart[0];
var firstOfFollow = _this2.firstOf(productionSymbol);
_this2._mergeSets(followSet, firstOfFollow, EXCLUDE_EPSILON);
// If no epsilon in the First of follow, we're done.
if (!firstOfFollow.hasOwnProperty(_specialSymbols.EPSILON)) {
break;
}
// Else, move to the next symbol, eliminating this one.
followPart.shift();
}
}
// If nothing following our symbol, or all following symbols
// were eliminated (i.e. they all contained only epsilons)
// we should merge followOf(LHS) to the Follow set of our symbol.
if (followPart.length === 0) {
var LHS = production.getLHS();
if (!LHS.isSymbol(symbol)) {
// To avoid cases like: B -> aB
_this2._mergeSets(followSet, _this2.followOf(LHS));
}
}
// Search in the next part.
RHS = followPart;
}
});
return followSet;
}
/**
* The Predict set for a production is the First set of this production,
* plus, if the First set contains ε, the Follow set.
*
* Predict(A -> α) = First(α) ∪ (if α =>* ε) then Follow(A) else ∅
*/
}, {
key: 'getPredictSets',
value: function getPredictSets() {
var _this3 = this;
this._predictSets = {};
_debug2.default.time('Building Predict sets');
this._grammar.getProductions().forEach(function (production) {
var LHS = production.getLHS();
var RHS = production.getRHS();
if (RHS.length == 0 || RHS[0].isEpsilon()) {
return;
}
var setKey = production.getNumber() + '. ' + production.toString();
// Predict set for this production.
var predictSet = _this3._predictSets[setKey] = {};
// Consists of the First set.
var firstSet = _this3.firstOfRHS(RHS);
_this3._mergeSets(predictSet, firstSet);
// Plus, the Follow set if First set contains epsilon.
if (firstSet.hasOwnProperty(_specialSymbols.EPSILON)) {
_this3._mergeSets(predictSet, _this3.followOf(LHS));
}
});
_debug2.default.timeEnd('Building Predict sets');
return this._predictSets;
}
/**
* Outputs a set with the label in readable format.
*/
}, {
key: 'printSet',
value: function printSet(set) {
var lhsHeader = void 0;
var rhsHeader = void 0;
switch (set) {
case this._firstSets:
lhsHeader = 'Symbol';
rhsHeader = 'First set';
break;
case this._followSets:
lhsHeader = 'Symbol';
rhsHeader = 'Follow set';
break;
case this._predictSets:
lhsHeader = 'Production';
rhsHeader = 'Predict set';
break;
default:
throw new Error('Unknow set');
}
console.info('\n' + rhsHeader + ':\n');
var printer = new _tablePrinter2.default({
head: [lhsHeader, rhsHeader]
});
for (var symbol in set) {
printer.push([symbol, Object.keys(set[symbol]).join(', ')]);
}
console.info(printer.toString());
console.info('');
}
/**
* Builds a set based on the `builder` function.
*/
}, {
key: '_buildSet',
value: function _buildSet(builder) {
var _this4 = this;
this._grammar.getProductions().forEach(function (production) {
builder.call(_this4, production.getLHS());
});
}
}, {
key: '_mergeSets',
value: function _mergeSets(to, from, exclude) {
exclude || (exclude = []);
for (var k in from) {
if (from.hasOwnProperty(k) && !exclude.hasOwnProperty(k)) {
to[k] = from[k];
}
}
}
}]);
return SetsGenerator;
}();
exports.default = SetsGenerator;