syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
308 lines (244 loc) • 8.61 kB
JavaScript
;
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* C# tokenizer template.
*/
var CSHARP_TOKENIZER_TEMPLATE = _fs2.default.readFileSync(__dirname + '/templates/tokenizer.template.cs', 'utf-8');
/**
* The trait is used by parser generators (LL/LR) for C#.
*/
/**
* The MIT License (MIT)
* Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
var CSharpParserGeneratorTrait = {
/**
* Generates parser class name.
*/
generateParserClassName: function generateParserClassName(className) {
this.writeData('PARSER_CLASS_NAME', className);
},
/**
* Generates parsing table in C# Dictionary format.
*/
generateParseTable: function generateParseTable() {
this.writeData('TABLE', this._buildTable(this.generateParseTableData()));
},
/**
* Converts JS object into C# Dictionary.
*
* In C# we represent a table as an array, where index is a state number,
* and a value is a record of LR entries (shift/reduce/etc).
*
* Example:
* {
* new Dictionary<int, string>()
* {
* {0, "1"},
* {3, "s8"},
* {4, "s2"},
* },
* }
*/
_buildTable: function _buildTable(table) {
var _this = this;
var entries = [];
Object.keys(table).forEach(function (state) {
entries.push('new Dictionary<int, string>() ' + _this._toCSharpDictionary(table[state], 'number', 'string'));
});
return '{ ' + entries.join(',\n\n') + ' }';
},
/**
* Generates tokens table in C# Dictionary format.
*/
generateTokensTable: function generateTokensTable() {
this.writeData('TOKENS', this._toCSharpDictionary(this._tokens, 'string', 'number'));
},
/**
* Production handlers are implemented as methods on the `yyparse` class.
*/
buildSemanticAction: function buildSemanticAction(production) {
var action = this.getSemanticActionCode(production);
if (!action) {
return null;
}
action = this._actionFromHandler(action);
var args = this.getSemanticActionParams(production)
// Append type information for C#.
.map(function (arg) {
return 'dynamic ' + arg;
}).join(',');
// Save the action, they are injected later.
this._productionHandlers.push({ args: args, action: action });
return '"_handler' + this._productionHandlers.length + '"';
},
/**
* Default format in the [ ] array notation.
*/
generateProductionsData: function generateProductionsData() {
return this.generateRawProductionsData().map(function (data) {
return 'new object[] { ' + data + ' }';
});
},
/**
* Generates built-in tokenizer instance.
*/
generateBuiltInTokenizer: function generateBuiltInTokenizer() {
this.writeData('TOKENIZER', CSHARP_TOKENIZER_TEMPLATE);
},
/**
* Creates an action from raw handler.
*/
_actionFromHandler: function _actionFromHandler(handler) {
var action = (this._scopeVars(handler) || '').trim();
if (!action) {
return 'return null;';
}
if (!/;\s*$/.test(action)) {
action += ';';
}
return action;
},
/**
* Generates rules for tokenizer.
*/
generateLexRules: function generateLexRules() {
var _this2 = this;
var lexRules = this._grammar.getLexGrammar().getRules().map(function (lexRule) {
var action = _this2._actionFromHandler(lexRule.getRawHandler());
_this2._lexHandlers.push({ args: '', action: action });
var flags = [];
if (lexRule.isCaseInsensitive()) {
flags.push('i');
}
if (flags.length > 0) {
flags = '(?' + flags.join('') + ')';
} else {
flags = '';
}
// Example: new string[] {@"^\s+", "_lexRule1"},
return 'new string[] { @"' + flags + lexRule.getRawMatcher() + '", ' + ('"_lexRule' + _this2._lexHandlers.length + '" }');
});
this.writeData('LEX_RULES', '{ ' + lexRules.join(',\n') + ' }');
},
generateLexRulesByStartConditions: 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', '' + this._toCSharpDictionary(result, 'string'));
},
/**
* Replaces global vars like `yytext`, `$$`, etc. to be
* referred from `yyparse`.
*/
_scopeVars: function _scopeVars(code) {
return code.replace(/yytext/g, 'yyparse.yytext').replace(/yyleng/g, 'yyparse.yyleng').replace(/yyloc/g, 'YyLoc.yyloc');
},
/**
* Type-converts a key of a C# dictionary, e.g. string or number, etc.
*/
_dictKey: function _dictKey(key, keyType) {
switch (keyType) {
case 'string':
return '"' + key + '"';
case 'number':
return Number(key);
default:
throw new Error('_dictKey: Incorrect type ' + keyType);
}
},
/**
* Type-converts a value of a C# dictionary, e.g. string or number, etc.
*/
_dictValue: function _dictValue(value, valueType) {
if (Array.isArray(value)) {
// Support only int arrays here for simplicity.
return 'new int[] { ' + value.join(', ') + ' }';
}
switch (valueType) {
case 'string':
return '"' + value + '"';
case 'number':
return Number(value);
default:
throw new Error('_dictValue: Incorrect value ' + valueType);
}
},
/**
* Converts JS object to C#'s Dictionary representation.
*
* {x: 10, y: 20} -> {{"x", 10}, {"y", 20}}
*
* The `keyTransform`, and `valueTransform` are used to put
* the data in needed types, e.g. strings, numbers, arrays, etc.
*/
_toCSharpDictionary: function _toCSharpDictionary(object, keyType, valueType) {
var result = [];
for (var k in object) {
var value = object[k];
var key = k.replace(/"/g, '\\"');
result.push('{ ' + this._dictKey(key, keyType) + ', ' + (this._dictValue(value, valueType) + ' }'));
}
return '{ ' + result.join(', ') + ' }';
},
/**
* C#-specific lex rules handler declarations.
*/
generateLexHandlers: function generateLexHandlers() {
var handlers = this._generateHandlers(this._lexHandlers, '_lexRule', 'object');
this.writeData('LEX_RULE_HANDLERS', handlers.join('\n\n'));
},
/**
* C#-specific handler declarations.
*/
generateProductionHandlers: function generateProductionHandlers() {
var handlers = this._generateHandlers(this._productionHandlers, '_handler', 'void');
this.writeData('PRODUCTION_HANDLERS', handlers.join('\n\n'));
},
/**
* Productions array in C# format.
*/
generateProductions: function generateProductions() {
this.writeData('PRODUCTIONS', '{ ' + this.generateProductionsData().join(',\n') + ' }');
},
/**
* Injects the code passed in the module include directive.
* Default is class Init { public void run() { ... }}
* which is used to setup parsing hooks, etc.
*/
generateModuleInclude: function generateModuleInclude() {
var moduleInclude = this._grammar.getModuleInclude();
var defaultModuleInclude = '\n namespace SyntaxParser\n {\n public class Init\n {\n public static void run()\n {\n // Put init code here.\n // E.g. yyparse.onParseBegin = (string code) => { ... };\n }\n }\n }\n ';
if (!moduleInclude) {
moduleInclude = defaultModuleInclude;
} else if (!/class Init\s+{/.test(moduleInclude)) {
moduleInclude += defaultModuleInclude;
}
this.writeData('MODULE_INCLUDE', moduleInclude);
},
/**
* Generates C#'s void function declarations for handlers.
*
* Example:
*
* public void _handler1(dynamic _1, dynamic _2)
* {
* __ = _1 + _2;
* }
*/
_generateHandlers: function _generateHandlers(handlers, name, returnType) {
return handlers.map(function (_ref, index) {
var args = _ref.args,
action = _ref.action;
return 'public ' + returnType + ' ' + name + (index + 1) + ('(' + args + ') {\n' + action + '\n}');
});
}
};
module.exports = CSharpParserGeneratorTrait;