UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

445 lines (355 loc) 13.4 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 _codeUnit = require('./code-unit'); var _codeUnit2 = _interopRequireDefault(_codeUnit); var _specialSymbols = require('./special-symbols'); var _debug = require('./debug'); var _debug2 = _interopRequireDefault(_debug); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); 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"); } } /** * Base parser generator for LL and LR. */ var BaseParserGenerator = function () { /** * Instance constructor. */ function BaseParserGenerator(_ref) { var grammar = _ref.grammar, outputFile = _ref.outputFile, customTokenizer = _ref.customTokenizer, _ref$options = _ref.options, options = _ref$options === undefined ? {} : _ref$options; _classCallCheck(this, BaseParserGenerator); _debug2.default.time('Generating parser module'); this._grammar = grammar; this._outputFile = outputFile; this._customTokenizer = customTokenizer; this._encodeSymbols(); this._options = options; var templateTags = this.getTemplateTags(); this._openTag = templateTags.open; this._closeTag = templateTags.close; } /** * Returns template tags. */ _createClass(BaseParserGenerator, [{ key: 'getTemplateTags', value: function getTemplateTags() { // Default template tags, child classes may override. return { open: '{{{', close: '}}}' }; } /** * Returns options. */ }, { key: 'getOptions', value: function getOptions() { return this._options; } /** * Initializes result data from the template. */ }, { key: 'setTemplate', value: function setTemplate(template) { this._resultData = template; return this; } /** * Sets the parsing table. */ }, { key: 'setTable', value: function setTable(table) { this._table = table; return this; } /** * Returns the parsing table. */ }, { key: 'getTable', value: function getTable() { return this._table; } /** * Returns grammar. */ }, { key: 'getGrammar', value: function getGrammar() { return this._grammar; } /** * Generates code for semantic action. */ }, { key: 'buildSemanticAction', value: function buildSemanticAction(production) { var semanticActionCode = this.getSemanticActionCode(production); if (!semanticActionCode) { return null; } var semanticActionArgs = this.getSemanticActionParams(production).join(','); return '(' + semanticActionArgs + ') => { ' + semanticActionCode + ' }'; } /** * Creates handler prologue for locations. Use default implementation * from CodeUnit, plugins may implement custom logic. */ }, { key: 'createLocationPrologue', value: function createLocationPrologue(production) { return _codeUnit2.default.createLocationPrologue(production); } /** * Returns a list of semantic action parameters. Plugins * can transform it, e.g. adding type information. */ }, { key: 'getSemanticActionParams', value: function getSemanticActionParams(production) { return _codeUnit2.default.createProductionParamsArray({ production: production, captureLocations: this._grammar.shouldCaptureLocations() }); } /** * Returns transformed semantic action code. */ }, { key: 'getSemanticActionCode', value: function getSemanticActionCode(production) { var rawAction = production.getRawSemanticAction(); if (!rawAction) { return null; } var action = rawAction // Replace $1, $2, @1, ... $$ with _1, _2, _1loc, ... __, etc. .replace(/\$(\d+)/g, '_$1').replace(/@(\d+)/g, '_$1loc').replace(/\$\$/g, '__').replace(/@\$/g, '__loc'); if (this._grammar.shouldCaptureLocations()) { action = this.createLocationPrologue(production) + action; } return action || null; } /** * Generates parser code and writes it to disk as a reusable module. */ }, { key: 'generate', value: function generate() { this.generateParserData(); if (!this._outputFile) { return this._resultData; } _fs2.default.writeFileSync(this._outputFile, this._resultData, 'utf-8'); _debug2.default.timeEnd('Generating parser module'); try { return require(this._outputFile); } catch (e) { /* skip for other languages */ } } }, { key: 'getEncodedToken', value: function getEncodedToken(token) { if (!this._tokens.hasOwnProperty(token)) { return -1; } return this._tokens[token]; } }, { key: 'getEncodedNonTerminal', value: function getEncodedNonTerminal(nonTerminal) { if (!this._nonTerminals.hasOwnProperty(nonTerminal)) { return -1; } return this._nonTerminals[nonTerminal]; } }, { key: 'getEncodedSymbol', value: function getEncodedSymbol(symbol) { var nonTerminal = this.getEncodedNonTerminal(symbol); if (nonTerminal !== -1) { return nonTerminal; } return this.getEncodedToken(symbol); } /** * Writes data for a given template variable. */ }, { key: 'writeData', value: function writeData(templateVariable, data) { this._resultData = this._resultData.replace(this._openTag + templateVariable + this._closeTag, function () { return data; }); return this; } /** * Generates a wrapping namespace. */ }, { key: 'generateNamespace', value: function generateNamespace() {} /* no-op, plugins can override */ /** * Generates parser parts. */ }, { key: 'generateParserData', value: function generateParserData() { // Generate a wrapping namespace. this.generateNamespace(); // Arbitrary code included to the module. this.generateModuleInclude(); // Whether locations should be captured, and propagated. this.generateCaptureLocations(); // Lexical grammar. this.generateTokenizer(); // Syntactic grammar. this.generateProductions(); // Tables. this.generateTokensTable(); this.generateParseTable(); return this._resultData; } /** * Whether locations should be captured, and propagated. */ }, { key: 'generateCaptureLocations', value: function generateCaptureLocations() { this.writeData('CAPTURE_LOCATIONS', JSON.stringify(this._grammar.shouldCaptureLocations())); } /** * Encodes tokens, and non-terminals as indices (starting with * non-terminals in order, then tokens). */ }, { key: '_encodeSymbols', value: function _encodeSymbols() { var _this = this; var index = 0; this._nonTerminals = {}; this._grammar.getNonTerminals().forEach(function (symbol) { return _this._nonTerminals[symbol.getSymbol()] = '' + index++; }); this._tokens = {}; this._grammar.getTokens().concat(this._grammar.getTerminals()).forEach(function (symbol) { return _this._tokens[symbol.getSymbol()] = '' + index++; }); this._tokens[_specialSymbols.EOF] = '' + index; } /** * Generates code for a built-in or a custom tokenizer. */ }, { key: 'generateTokenizer', value: function generateTokenizer() { if (!this._customTokenizer) { // Built-in tokenizer. this.generateBuiltInTokenizer(); this.generateLexRules(); this.generateLexRulesByStartConditions(); } else { // Require custom tokenizer if was provided. this.writeData('TOKENIZER', 'tokenizer = require(\'' + this._customTokenizer + '\');'); } } /** * Injects the code passed in the module include directive. */ }, { key: 'generateModuleInclude', value: function generateModuleInclude() { this.writeData('MODULE_INCLUDE', this._grammar.getModuleInclude()); } /** * Generates built-in tokenizer instance. */ }, { key: 'generateBuiltInTokenizer', value: function generateBuiltInTokenizer() { var tokenizerCode = _fs2.default.readFileSync(__dirname + '/./templates/tokenizer.template.js', 'utf-8'); this.writeData('TOKENIZER', tokenizerCode); } /** * Generates rules for tokenizer. */ }, { key: 'generateLexRules', value: function generateLexRules() { var lexRules = this._grammar.getLexGrammar().getRules().map(function (lexRule) { return '[' + lexRule.getMatcher() + ', ' + ('function() { ' + lexRule.getRawHandler() + ' }]'); }); this.writeData('LEX_RULES', '[' + lexRules.join(',\n') + ']'); } /** * Generates lex rule table by start conditions. * conditionName: [<ruleIndex in the LEX_RULES table>, ...] */ }, { key: 'generateLexRulesByStartConditions', value: function generateLexRulesByStartConditions() { var lexGrammar = this._grammar.getLexGrammar(); var lexRulesByConditions = lexGrammar.getRulesByStartConditions(); var result = {}; for (var condition in lexRulesByConditions) { result[condition] = lexRulesByConditions[condition].map(function (lexRule) { return lexGrammar.getRuleIndex(lexRule); }); } this.writeData('LEX_RULES_BY_START_CONDITIONS', '' + JSON.stringify(result)); } }, { key: 'generateProductions', value: function generateProductions() { this.writeData('PRODUCTIONS', '[' + this.generateProductionsData().join(',\n') + ']'); } /** * Abstract method to implement in LL/LR. */ }, { key: 'generateProductionsData', value: function generateProductionsData() { throw new Error('Parser generator: `generateProductionsData` is not implemented.'); } }, { key: 'generateTokensTable', value: function generateTokensTable() { this.writeData('TOKENS', JSON.stringify(this._tokens)); } /** * Actual parsing table. */ }, { key: 'generateParseTable', value: function generateParseTable() { this.writeData('TABLE', JSON.stringify(this.generateParseTableData())); } /** * Abstract method to implement in LL/LR. */ }, { key: 'generateParseTableData', value: function generateParseTableData() { throw new Error('Parser generator: `generateParseTableData` is not implemented.'); } }]); return BaseParserGenerator; }(); exports.default = BaseParserGenerator;