UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

375 lines (299 loc) 12.5 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; }; }(); 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;