UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

768 lines (618 loc) 23.2 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; }; }(); /** * The MIT License (MIT) * Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com> */ var _bnfParserGen = require('../generated/bnf-parser.gen.js'); var _bnfParserGen2 = _interopRequireDefault(_bnfParserGen); var _grammarMode = require('./grammar-mode'); var _grammarMode2 = _interopRequireDefault(_grammarMode); var _grammarSymbol = require('./grammar-symbol'); var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol); var _lexGrammar = require('./lex-grammar'); var _lexGrammar2 = _interopRequireDefault(_lexGrammar); var _lexRule = require('./lex-rule'); var _lexRule2 = _interopRequireDefault(_lexRule); var _lexParserGen = require('../generated/lex-parser.gen.js'); var _lexParserGen2 = _interopRequireDefault(_lexParserGen); var _production = require('./production'); var _production2 = _interopRequireDefault(_production); var _colors = require('colors'); var _colors2 = _interopRequireDefault(_colors); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _vm = require('vm'); var _vm2 = _interopRequireDefault(_vm); 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 _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Class encapsulates operations with a grammar. */ var Grammar = function () { /** * A grammar may be passed as an object with `lex` and `bnf` properties. * The `lex` part is a set of rules for the lexer, and `bnf` is actual * context-free grammar. If `start` property is passed, it's used as the * start symbol, otherwise it's inferred from the first production. * * const grammar = { * * // Lexical grammar * // format: <regexp rule>: token * // The token can either be a raw string value, like "foo", * // or a variable (written in ALL_CAPITALIZED notation), which * // can be used further in the `bnf` grammar. * * "lex": { * "macros": { * "digit": "[0-9]", * }, * * "rules": [ * ["a", "return 'a';"], * ["\\(", "return '(';"], * ["\\)", "return ')';"], * ["\\+", "return '+';"], * ["{digit}+(\\.{digit}+)?\\b", "return 'NUMBER';"], * * // A rule with start conditions. Such rules are matched only * // when a scanner enters these states. * [["string", "code"], '[^"]', "return 'STRING';"], * ], * * "startConditions": { https://gist.github.com/DmitrySoshnikov/f5e2583b37e8f758c789cea9dcdf238a * "string": 1, // inclusive condition %s * "code": 0, // exclusive consition %x * }, * }, * * // Arbitrary code to be included in the generated parser. * * "moduleInclude": "const AST = require('./ast');" * * "tokens": "a ( ) + NUMBER", * * "start": "S", * * // BNF grammar * * bnf: { * "S": [ "F", * "( S + F )" ], * "F": [ "a", * "NUMBER" ], * } * }; * * Note: the "bnf" can also be passed as a string: * * bnf: ` * S -> F * | ( S + F ) * * F -> a * | NUMBER * ` * * Note: if no `lex` is provided, the lexical grammar is inferred * from the list of all terminals in the `bnf` grammar. */ function Grammar(_ref) { var lex = _ref.lex, tokens = _ref.tokens, bnf = _ref.bnf, operators = _ref.operators, start = _ref.start, mode = _ref.mode, _ref$moduleInclude = _ref.moduleInclude, moduleInclude = _ref$moduleInclude === undefined ? '' : _ref$moduleInclude, _ref$captureLocations = _ref.captureLocations, captureLocations = _ref$captureLocations === undefined ? false : _ref$captureLocations; _classCallCheck(this, Grammar); this._mode = new _grammarMode2.default(mode); this._startSymbol = start; this._captureLocations = captureLocations; // Operators and precedence. this._operators = this._processOperators(operators); // Actual BNF grammar. this._originalBnf = bnf; this._bnf = this._processBnf(this._originalBnf); // Injecting user code, including handlers for `yyparse.onParseBegin`, // and `yyparse.onParseEnd`. this._moduleInclude = moduleInclude; this._nonTerminals = this.getNonTerminals(); this._terminals = this.getTerminals(); // Process tokens list, or extract from BNF. this._tokens = this._processTokens(tokens); // Lexical grammar. this._lexGrammar = this._createLexGrammar(lex); // Caching maps. this._productionsForSymbol = {}; this._productionsWithSymbol = {}; } /** * Loads a grammar object from a grammar file, * for the specific options. */ _createClass(Grammar, [{ key: 'getLexGrammar', /** * Returns associated lexical grammar. */ value: function getLexGrammar() { return this._lexGrammar; } /** * Returns Start symbol of this grammar (it's initialized * during normalization process). */ }, { key: 'getStartSymbol', value: function getStartSymbol() { return this._startSymbol; } /** * Returns grammar mode. */ }, { key: 'getMode', value: function getMode() { return this._mode; } /** * Returns module include code. */ }, { key: 'getModuleInclude', value: function getModuleInclude() { return this._moduleInclude; } /** * Whther should capture locations. */ }, { key: 'shouldCaptureLocations', value: function shouldCaptureLocations() { return this._captureLocations; } /** * Returns precedence and associativity of operators. */ }, { key: 'getOperators', value: function getOperators() { return this._operators; } /** * Returns list of terminals in this grammar. */ }, { key: 'getTerminals', value: function getTerminals() { var _this = this; if (!this._terminals) { this._terminals = []; this._terminalsMap = {}; this._bnf.forEach(function (production) { production.getRHS().forEach(function (symbol) { if (symbol.isTerminal() && !_this._terminalsMap.hasOwnProperty(symbol.getSymbol())) { _this._terminalsMap[symbol.getSymbol()] = true; _this._terminals.push(symbol); } }); }); } return this._terminals; } /** * Returns terminal symbols. */ }, { key: 'getTerminalSymbols', value: function getTerminalSymbols() { if (!this._terminalSymbols) { this._terminalSymbols = this.getTerminals().map(function (symbol) { return symbol.getSymbol(); }); } return this._terminalSymbols; } /** * Returns list of non-terminals in this grammar. */ }, { key: 'getNonTerminals', value: function getNonTerminals() { var _this2 = this; if (!this._nonTerminals) { this._nonTerminals = []; this._nonTerminalsMap = {}; this._bnf.forEach(function (production) { if (production.isAugmented()) { return; } var nonTerminal = production.getLHS(); if (!_this2._nonTerminalsMap.hasOwnProperty(nonTerminal.getSymbol())) { _this2._nonTerminalsMap[nonTerminal.getSymbol()] = true; _this2._nonTerminals.push(nonTerminal); } }); } return this._nonTerminals; } /** * Returns list of non-terminal symbols. */ }, { key: 'getNonTerminalSymbols', value: function getNonTerminalSymbols() { if (!this._nonTerminalSymbols) { this._nonTerminalSymbols = this.getNonTerminals().map(function (symbol) { return symbol.getSymbol(); }); } return this._nonTerminalSymbols; } /** * Returns tokens. Infer tokens from the grammar if * they were not passed explicitly. */ }, { key: 'getTokens', value: function getTokens() { var _this3 = this; if (!this._tokens) { this._tokens = []; this._tokensMap = {}; this._bnf.forEach(function (production) { if (production.isAugmented() || production.isEpsilon()) { return; } production.getRHS().forEach(function (symbol) { var rawSymbol = symbol.getSymbol(); if (!symbol.isTerminal() && !_this3._nonTerminalsMap.hasOwnProperty(rawSymbol) && !_this3._tokensMap.hasOwnProperty(rawSymbol)) { _this3._tokensMap[rawSymbol] = true; _this3._tokens.push(symbol); } }); }); } return this._tokens; } /** * Returns token symbols. */ }, { key: 'getTokenSymbols', value: function getTokenSymbols() { if (!this._tokenSymbols) { this._tokenSymbols = this.getTokens().map(function (symbol) { return symbol.getSymbol(); }); } return this._tokenSymbols; } /** * Returns grammar productions. */ }, { key: 'getProductions', value: function getProductions() { return this._bnf; } /** * Returns productions for a specific non-terminal. */ }, { key: 'getProductionsForSymbol', value: function getProductionsForSymbol(symbol) { if (!this._productionsForSymbol.hasOwnProperty(symbol)) { this._productionsForSymbol[symbol] = this._bnf.filter(function (production) { return production.getLHS().isSymbol(symbol); }); } return this._productionsForSymbol[symbol]; } /** * Returns productions where a non-terminal is used (appears on RHS). */ }, { key: 'getProductionsWithSymbol', value: function getProductionsWithSymbol(symbol) { if (!this._productionsWithSymbol.hasOwnProperty(symbol)) { this._productionsWithSymbol[symbol] = this._bnf.filter(function (production) { return production.getRHSSymbolsMap().hasOwnProperty(symbol); }); } return this._productionsWithSymbol[symbol]; } /** * Gets a production by number. */ }, { key: 'getProduction', value: function getProduction(number) { // LL grammars do not have augmented 0-production. return this._bnf[this._mode.isLL() ? number - 1 : number]; } /** * Returns an augmented production (used in LR parsers), * which is built during normalization process. The augmented * production is always the first one. */ }, { key: 'getAugmentedProduction', value: function getAugmentedProduction() { if (!this._mode.isLR()) { throw new TypeError('Augmented production is built only for LR grammars'); } return this._bnf[0]; } /** * Tokens are either raw text values like "foo", or * one of the variables from the lexical grammar. */ }, { key: 'isTokenSymbol', value: function isTokenSymbol(symbol) { if (symbol instanceof _grammarSymbol2.default) { symbol = symbol.getSymbol(); } return this._terminalsMap.hasOwnProperty(symbol) || this._tokensMap.hasOwnProperty(symbol); } /** * Whether a symbol is a non-terminal in this grammar. */ }, { key: 'isNonTerminal', value: function isNonTerminal(symbol) { if (symbol instanceof _grammarSymbol2.default) { symbol = symbol.getSymbol(); } return this._nonTerminalsMap.hasOwnProperty(symbol); } /** * Pretty prints the grammar. */ }, { key: 'print', value: function print() { var _this4 = this; console.info('\nGrammar:\n'); var pad = ' '; var productions = this.getProductions(); var numberPad = productions.length.toString().length; productions.forEach(function (production) { var productionOutput = '' + pad + _this4._padLeft(production.getNumber(), numberPad) + '. ' + production.toString(); console.info(productionOutput); if (_this4._mode.isLR() && production.isAugmented()) { var splitter = Array(productionOutput.length - 2).join('-'); console.info('' + pad + splitter); } }); } }, { key: '_padLeft', value: function _padLeft(value, times) { value = value.toString(); var spaces = Array(times - value.length + 1).join(' '); return spaces + value; } }, { key: '_processOperators', value: function _processOperators(operators) { var processedOperators = {}; if (operators) { operators.forEach(function (opData, i) { opData.slice(1).forEach(function (op) { processedOperators[op] = { precedence: i + 1, assoc: opData[0] }; }); }); } return processedOperators; } /** * Generates data arrays for lex rules inferred from terminals. */ }, { key: '_generateLexRulesDataForTerminals', value: function _generateLexRulesDataForTerminals() { return this.getTerminals().map(function (terminal) { return [_lexRule2.default.matcherFromTerminal(terminal.getSymbol()), // matcher 'return ' + terminal.quotedTerminal() + ';']; } // token handler ); } /** * Creates lex grammar instance. */ }, { key: '_createLexGrammar', value: function _createLexGrammar(lex) { var _lex$rules; if (!lex) { lex = { rules: [] }; } // Infer automatic lex-rules from raw terminals // (symbols in quotes) in BNF productions RHS. (_lex$rules = lex.rules).push.apply(_lex$rules, _toConsumableArray(this._generateLexRulesDataForTerminals())); return new _lexGrammar2.default(lex); } /** * Processes tokens. */ }, { key: '_processTokens', value: function _processTokens(tokens) { var _this5 = this; if (typeof tokens === 'string') { tokens = tokens.split(/\s+/); } this._tokensMap = {}; return Array.isArray(tokens) ? tokens.map(function (token) { _this5._tokensMap[token] = true; return _grammarSymbol2.default.get(token); }) : this.getTokens(); } }, { key: '_processBnf', value: function _processBnf(originalBnf) { var /* grammar */_this6 = this; var processedBnf = []; var nonTerminals = Object.keys(originalBnf); var isDefaultLR = this._mode.isLR() && !this._mode.isLALR1Extended(); // LR grammar uses augmented 0-production. var number = isDefaultLR ? 0 : 1; if (!this._startSymbol) { this._startSymbol = nonTerminals[0]; } // Augmented rule, $accept -> S. The LALR(1) extended // grammar already have this rule. if (isDefaultLR) { var augmentedProduction = new _production2.default( /* LHS */'$accept', /* RHS */this._startSymbol, /* number */number++, /* semanticAction */null, /* isShort */false, /* grammar */this); processedBnf[0] = augmentedProduction; } nonTerminals.forEach(function (LHS) { originalBnf[LHS].forEach(function (RHS, k) { var semanticAction = null; var precedence = null; if (Array.isArray(RHS)) { var precedenceTag = null; if (typeof RHS[1] === 'string') { semanticAction = RHS[1]; if (RHS[2] !== null && typeof RHS[2] === 'object') { precedenceTag = RHS[2].prec; } } else if (RHS[1] !== null && typeof RHS[1] === 'object') { precedenceTag = RHS[1].prec; } RHS = RHS[0]; if (precedenceTag && _this6._operators) { precedence = _this6._operators[precedenceTag].precedence; } } processedBnf.push(new _production2.default(LHS, RHS, /* number */number++, semanticAction, /* isShort */k > 0, _this6, precedence)); }); }); return processedBnf; } }], [{ key: 'fromGrammarFile', value: function fromGrammarFile(grammarFile) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var grammarType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'bnf'; var grammarData = Grammar.dataFromGrammarFile(grammarFile, grammarType); return Grammar.fromData(grammarData, options); } /** * Reads grammar file data. Supports reading `bnf`, * and `lex` grammars based on mode. */ }, { key: 'dataFromGrammarFile', value: function dataFromGrammarFile(grammarFile) { var grammarType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'bnf'; return Grammar.dataFromString(_fs2.default.readFileSync(grammarFile, 'utf-8'), grammarType); } /** * Creates Grammar instance from grammar data for * a particular parsing options. */ }, { key: 'fromData', value: function fromData(grammarData) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return new Grammar(Object.assign({}, grammarData, options)); } /** * Creates Grammar instance from grammar string. */ }, { key: 'fromString', value: function fromString(string) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var grammarType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'bnf'; var grammarData = Grammar.dataFromString(string, grammarType); return Grammar.fromData(grammarData, options); } /** * Generates data from grammar string. */ }, { key: 'dataFromString', value: function dataFromString(grammarString) { var grammarType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'bnf'; var grammarData = null; _debug2.default.time('Grammar loaded in'); try { // Pure JSON representation. grammarData = JSON.parse(grammarString); _debug2.default.log('Grammar (' + grammarType + ') is in JSON format'); } catch (e) { // JS code. try { grammarData = _vm2.default.runInNewContext('\n (function() { return (' + grammarString + ');})()\n '); _debug2.default.log('Grammar (' + grammarType + ') is in JS format'); } catch (jsEx) { var jsError = jsEx.stack; // A grammar as a string, for BNF, and lex. switch (grammarType) { case 'bnf': grammarData = Grammar.parseFromType(_bnfParserGen2.default, grammarType, grammarString, jsError); // BNF grammar may define lexical grammar inline. if (typeof grammarData.lex === 'string') { grammarData.lex = Grammar.parseFromType(_lexParserGen2.default, 'lex', grammarData.lex, jsError); } break; case 'lex': grammarData = Grammar.parseFromType(_lexParserGen2.default, grammarType, grammarString, jsError); break; default: throw new Error('Unknown grammar type: ' + grammarType); } } } _debug2.default.timeEnd('Grammar loaded in'); return grammarData; } /** * Parses grammar (lex or bnf). */ }, { key: 'parseFromType', value: function parseFromType(ParserType, grammarType, grammarString, jsError) { var grammarData = null; try { grammarData = ParserType.parse(grammarString); _debug2.default.log('Grammar (' + grammarType + ') is in Yacc/Bison format'); } catch (ex) { console.error(_colors2.default.red('\nParsing grammar in JS-format failed:\n\n') + jsError + '\n'); console.error(_colors2.default.red('\nParsing grammar in Yacc/Bison format failed:\n\n')); throw ex; } return grammarData; } }]); return Grammar; }(); exports.default = Grammar;