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