syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
768 lines (618 loc) • 23.2 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 _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;