UNPKG

tyson

Version:

A tool that generates a TypeScript file from your Jison grammar's semantic actions so the TypeScript compiler will check those semantic actions for errors.

231 lines (230 loc) 9.93 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var fs_1 = __importDefault(require("fs")); var path_1 = __importDefault(require("path")); var prettier_1 = __importDefault(require("prettier")); var bnf_parser_1 = __importDefault(require("./lib/bnf-parser")); var DEFAULTS = { typeDictInterfaceName: "TysonTypeDictionary", emitUnusedLocations: false, emitUnusedSemanticValueParams: false, }; function generateTypeScriptFile(config) { var completeConfig = applyDefaultsAsNeeded(config); var pathToBnfGrammarFile = completeConfig.pathToBnfGrammarFile, pathToOutputFile = completeConfig.pathToOutputFile; var grammarSource = fs_1.default.readFileSync(pathToBnfGrammarFile, "utf8"); var parsedGrammar = bnf_parser_1.default.parse(grammarSource); var generatedCode = generateCode(completeConfig, parsedGrammar); var prettierConfig = prettier_1.default.resolveConfig.sync(completeConfig.pathToOutputFile) || {}; fs_1.default.writeFileSync(pathToOutputFile, prettier_1.default.format(generatedCode, __assign(__assign({}, prettierConfig), { parser: "typescript" }))); } exports.generateTypeScriptFile = generateTypeScriptFile; function applyDefaultsAsNeeded(config) { var typeDictInterfaceName = config.typeDictInterfaceName !== undefined ? config.typeDictInterfaceName : DEFAULTS.typeDictInterfaceName; var emitUnusedLocations = config.emitUnusedLocations !== undefined ? config.emitUnusedLocations : DEFAULTS.emitUnusedLocations; var emitUnusedSemanticValueParams = config.emitUnusedSemanticValueParams !== undefined ? config.emitUnusedSemanticValueParams : DEFAULTS.emitUnusedSemanticValueParams; return __assign(__assign({}, config), { typeDictInterfaceName: typeDictInterfaceName, emitUnusedLocations: emitUnusedLocations, emitUnusedSemanticValueParams: emitUnusedSemanticValueParams }); } function generateCode(config, grammar) { var start = grammar.start; if (start === undefined) { throw new Error("The grammar defined in " + config.pathToBnfGrammarFile + " does not have a start symbol declared. Please declare one using `%start <symbolName>`."); } var dependencies = getActionDependencies(grammar); var importStatement = generateImportStatementCode(config, dependencies); var yyDeclarations = generateYyDeclarations(dependencies); var locationInterfaceDeclaration = generateTokenLocationInterfaceDeclarationIfNeeded(dependencies); var semanticActions = generateSemanticActionCode(__assign(__assign({}, grammar), { start: start }), config); return (importStatement + "\n\n" + yyDeclarations + "\n\n" + (grammar.moduleInclude || '') + "\n\n" + locationInterfaceDeclaration + "\n\n" + semanticActions); } function generateImportStatementCode(config, dependencies) { var relativePath = path_1.default.relative(path_1.default.dirname(config.pathToOutputFile), config.pathToTypeDictFile); var withoutExtension = relativePath.replace(/(?:\.d)?\.ts/, ""); var withLeadingDotSlash = withoutExtension.charAt(0) === "." ? withoutExtension : "." + path_1.default.sep + withoutExtension; return ("import { " + config.typeDictInterfaceName + (dependencies.yy ? ", yy" : "") + " } from " + JSON.stringify(withLeadingDotSlash) + ";"); } function generateYyDeclarations(dependencies) { var yytext = dependencies.yytext ? "declare var yytext: string;" : ""; var yyleng = dependencies.yyleng ? "declare var yyleng: string;" : ""; var yylineno = dependencies.yylineno ? "declare var yylineno: string;" : ""; return yytext + yyleng + yylineno; } function getActionDependencies(grammar) { var tokenLocation = false; var yy = false; var yytext = false; var yyleng = false; var yylineno = false; Object.values(grammar.bnf).forEach(function (ruleActionPairs) { return ruleActionPairs.map(normalizeRuleActionPair).forEach(function (_a) { var action = _a.action; if (/@(?:\$|-?\d+\b)/.test(action)) { tokenLocation = true; } if (/\byy\b/.test(action)) { yy = true; } if (/\byytext\b/.test(action)) { yytext = true; } if (/\byyleng\b/.test(action)) { yyleng = true; } if (/\byylineno\b/.test(action)) { yylineno = true; } }); }); return { tokenLocation: tokenLocation, yy: yy, yytext: yytext, yyleng: yyleng, yylineno: yylineno }; } function generateTokenLocationInterfaceDeclarationIfNeeded(dependencies) { return dependencies.tokenLocation ? "interface TokenLocation { first_line: number; last_line: number; first_column: number; last_column: number; range: [number, number]; }" : ""; } function generateSemanticActionCode(grammar, config) { var startSymbol = grammar.start, bnf = grammar.bnf; var code = "const semanticActions = {"; Object.entries(bnf).forEach(function (_a) { var _b = __read(_a, 2), symbolName = _b[0], ruleActionPairs = _b[1]; ruleActionPairs.forEach(function (ruleActionPair) { var _a = normalizeRuleActionPair(ruleActionPair), rule = _a.rule, action = _a.action; var methodDeclaration = JSON.stringify(symbolName + " -> " + rule) + generateParenthesizedParamDefs(rule, action, config) + generateReturnAnnotation(symbolName, config.typeDictInterfaceName) + "{" + generate$$Declaration(symbolName, config.typeDictInterfaceName) + action.replace(/@(\$|-?\d+\b)/g, 'yylstack["@$1"]') + (symbolName === startSymbol ? "}," : "\nreturn $$;},"); code += methodDeclaration + "\n\n"; }); }); code += "};"; return code; } function normalizeRuleActionPair(ruleActionPair) { if (typeof ruleActionPair === "string") { return { rule: ruleActionPair, action: "$$ = $1;" }; } else if ("object" === typeof ruleActionPair[1]) { return { rule: ruleActionPair[0], action: "$$ = $1;" }; } else { return { rule: ruleActionPair[0], action: ruleActionPair[1] }; } } function generateParenthesizedParamDefs(rhs, action, config) { var possibleYylstackParamDef = generateYylstackParamDefIfNeeded(rhs, action, config); var possibleYylstackArgDefWithCommaAndNewlinesIfNeeded = possibleYylstackParamDef === "" ? "" : possibleYylstackParamDef + ",\n\n"; var semanticValueParamDefs = generateSemanticValueParamDefs(rhs, action, config); return ("(" + possibleYylstackArgDefWithCommaAndNewlinesIfNeeded + semanticValueParamDefs + ")"); } function generateYylstackParamDefIfNeeded(rhs, action, config) { var referencedLocations = action.match(/@(\$|-?\d+\b)/g); if (referencedLocations === null) { return ""; } else { return ("yylstack: " + generateYylstackType(rhs, referencedLocations, config)); } } function generateYylstackType(rule, referencedLocations, config) { var allLocations = ["@$"].concat(rule .split(/\s+/g) .filter(function (symbol) { return symbol != ""; }) .map(function (_arg, i) { return "@" + (i + 1); })); var emittedLocations = config.emitUnusedLocations ? allLocations : allLocations.filter(function (location) { return referencedLocations.includes(location); }); return ("{" + emittedLocations.map(function (key) { return '"' + key + '": TokenLocation'; }).join(", ") + "}"); } function generateSemanticValueParamDefs(rhs, action, config) { var namesOfReferencedSemanticValueParams = action.match(/\$\d+\b/g) || []; var allParams = rhs .split(/\s+/g) .filter(function (symbol) { return symbol != ""; }) .map(function (symbol, i) { return { name: "$" + (i + 1), type: config.typeDictInterfaceName + "[" + JSON.stringify(symbol) + "]", }; }); var emittedParams = config.emitUnusedSemanticValueParams ? allParams : allParams.filter(function (param) { return namesOfReferencedSemanticValueParams.includes(param.name); }); return emittedParams .map(function (param) { return param.name + ": " + param.type; }) .join(", "); } function generateReturnAnnotation(symbolName, typeDictInterfaceName) { return ": " + typeDictInterfaceName + "[" + JSON.stringify(symbolName) + "]"; } function generate$$Declaration(lefthandSymbol, typeDictInterfaceName) { return ("let $$: " + typeDictInterfaceName + "[" + JSON.stringify(lefthandSymbol) + "];"); }