UNPKG

onelang

Version:

OneLang transpiler framework core

471 lines 24.9 kB
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "../One/Ast", "../One/Transforms/CaseConverter", "../One/Transforms/IncludesCollector", "./OneTemplate/TemplateGenerator", "./ExprLang/ExprLangVM", "../Utils/StringHelpers", "../Utils/ArrayHelpers"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Ast_1 = require("../One/Ast"); const CaseConverter_1 = require("../One/Transforms/CaseConverter"); const IncludesCollector_1 = require("../One/Transforms/IncludesCollector"); const TemplateGenerator_1 = require("./OneTemplate/TemplateGenerator"); const ExprLangVM_1 = require("./ExprLang/ExprLangVM"); const StringHelpers_1 = require("../Utils/StringHelpers"); const ArrayHelpers_1 = require("../Utils/ArrayHelpers"); class TempVariable { constructor(name, code) { this.name = name; this.code = code; } } class TempVarHandler { constructor() { this.prefix = "tmp"; this.variables = []; this.stack = []; this.nextIndex = 0; } get empty() { return this.variables.length === 0; } get current() { return this.stack[this.stack.length - 1]; } create() { const name = `${this.prefix}${this.nextIndex++}`; this.stack.push(name); return name; } finish(code) { const name = this.stack.pop(); this.variables.push(new TempVariable(name, code)); return name; } reset() { const result = this.variables; this.stack = []; this.variables = []; return result; } } class CodeGeneratorModel { constructor(generator) { this.generator = generator; this.tempVarHandler = new TempVarHandler(); this.includes = []; this.classes = []; this.interfaces = []; this.enums = []; this.config = { genMeta: false }; } // temporary variable's name get result() { return this.tempVarHandler.current; } log(data) { console.log(`[CodeGeneratorModel] ${data}`); } typeName(type) { const cls = this.generator.lang.classes[type.className]; const result = cls ? this.generator.call(cls.generator, [type.typeArguments.map(x => this.typeName(x)), type.typeArguments]) : this.generator.getTypeName(type); return result; } isIfBlock(block) { return block.statements && block.statements.length === 1 && block.statements[0].stmtType === Ast_1.OneAst.StatementType.If; } getOverlayCallCode(callExpr, extraArgs) { const methodRef = callExpr.method; // TODO: I should either use metaPath or methodRef/classRef everywhere, but not hacks like this let metaPath = methodRef.methodRef.metaPath; if (!metaPath) { if (methodRef.methodRef.classRef) metaPath = `${methodRef.methodRef.classRef.name}/${methodRef.methodRef.name}`; else { this.log("Meta path is missing!"); return null; } } const metaPathParts = metaPath.split("/"); const className = metaPathParts[0]; const methodName = metaPathParts[1]; const generatorName = `${className}.${methodName}`; const cls = this.generator.lang.classes[className]; const method = cls && cls.methods[methodName]; // if extraArgs was used then we only accept a method with extra args and vice versa if (!method || ((method.extraArgs.length === 0) !== !extraArgs)) return null; const extraArgValues = method.extraArgs.map(extraArgName => { if (!extraArgs.hasOwnProperty(extraArgName)) throw new Error(`Extra argument '${extraArgName}' is missing!`); return extraArgs[extraArgName]; }); const stdMethod = this.generator.stdlib.classes[className].methods[methodName]; const methodArgs = stdMethod.parameters.map(x => x.outName); const exprCallArgs = callExpr.arguments; if (methodArgs.length !== exprCallArgs.length) throw new Error(`Invalid argument count for '${generatorName}': expected: ${methodArgs.length}, actual: ${callExpr.arguments.length}.`); // TODO: move this to AST visitor for (let i = 0; i < callExpr.arguments.length; i++) callExpr.arguments[i].paramName = methodArgs[i]; const thisArg = methodRef.thisExpr ? this.gen(methodRef.thisExpr) : null; const overlayFunc = this.generator.lang.classes[className].methods[methodName]; const typeArgs = methodRef.thisExpr && methodRef.thisExpr.valueType.typeArguments.map(x => this.typeName(x)); const code = this.generator.call(overlayFunc.generator, [thisArg, typeArgs, ...exprCallArgs, ...extraArgValues]); return code; } escapeQuotes(obj) { if (typeof obj === "string") { return obj.replace(/"/g, '\\"'); } else { for (const node of obj) node.text = node.text.replace(/"/g, '\\"'); return obj; } } gen(obj, ...genArgs) { const objExpr = obj; const type = obj.stmtType || objExpr.exprKind; const isStatement = !!obj.stmtType; if (type === Ast_1.OneAst.ExpressionKind.Call) { const callExpr = obj; const overlayCallCode = this.getOverlayCallCode(callExpr); if (overlayCallCode) return overlayCallCode; const methodRef = callExpr.method; const methodArgs = methodRef.methodRef.parameters; if (!methodArgs) throw new Error(`Method implementation is not found: ${methodRef.methodRef.metaPath} for ${this.generator.lang.extension}`); if (methodArgs.length !== callExpr.arguments.length) throw new Error(`Invalid argument count for '${methodRef.methodRef.metaPath}': expected: ${methodArgs.length}, actual: ${callExpr.arguments.length}.`); // TODO: move this to AST visitor for (let i = 0; i < methodArgs.length; i++) callExpr.arguments[i].paramName = methodArgs[i].outName; } else if (type === Ast_1.OneAst.ExpressionKind.New) { const callExpr = obj; const cls = callExpr.cls; const methodRef = cls.classRef.constructor; const methodArgs = methodRef ? methodRef.parameters : []; if (!methodArgs) throw new Error(`Method implementation is not found: ${methodRef.metaPath} for ${this.generator.lang.extension}`); if (methodArgs.length !== callExpr.arguments.length) throw new Error(`Invalid argument count for '${methodRef.metaPath}': expected: ${methodArgs.length}, actual: ${callExpr.arguments.length}.`); // TODO: move this to AST visitor for (let i = 0; i < methodArgs.length; i++) callExpr.arguments[i].paramName = methodArgs[i].outName; } else if (type === Ast_1.OneAst.ExpressionKind.VariableReference) { const varRef = obj; if (varRef.varType === Ast_1.OneAst.VariableRefType.InstanceField) { const className = varRef.thisExpr.valueType.className; const fieldName = varRef.varRef.name; const cls = this.generator.lang.classes[className]; if (cls) { const func = cls.fields && cls.fields[fieldName]; const thisArg = varRef.thisExpr ? this.gen(varRef.thisExpr) : null; const gen = cls.fields[fieldName]; //this.log(varPath); if (gen) { const code = this.generator.call(gen.generator, [thisArg]); return code; } } } } let genName = StringHelpers_1.lcFirst(type.toString()); if (type === Ast_1.OneAst.ExpressionKind.Literal) { const literalExpr = obj; if (literalExpr.literalType === "boolean") { genName = `${literalExpr.value ? "true" : "false"}Literal`; } else { genName = `${literalExpr.literalType}Literal`; if (literalExpr.literalType === "string" || literalExpr.literalType === "character") { const escapedJson = JSON.stringify(literalExpr.value); literalExpr.escapedText = escapedJson.substr(1, escapedJson.length - 2); literalExpr.escapedTextSingle = literalExpr.escapedText.replace(/'/g, "\\'"); } } } else if (type === Ast_1.OneAst.ExpressionKind.VariableReference) { const varRef = obj; genName = StringHelpers_1.lcFirst(varRef.varType); } else if (type === Ast_1.OneAst.ExpressionKind.MethodReference) { const methodRef = obj; genName = methodRef.thisExpr ? "instanceMethod" : "staticMethod"; } else if (type === Ast_1.OneAst.ExpressionKind.Unary) { const unaryExpr = obj; const unaryName = unaryExpr.unaryType; const fullName = `${unaryName}${unaryExpr.operator}`; genName = this.generator.lang.expressions[fullName] ? fullName : unaryName; } else if (type === Ast_1.OneAst.ExpressionKind.Binary) { const binaryExpr = obj; const leftType = binaryExpr.left.valueType.repr(); const rightType = binaryExpr.right.valueType.repr(); const ops = this.generator.lang.operators; const opMatch = Object.keys(ops).find(opName => { const [left, op, right] = opName.split(' '); return binaryExpr.operator === op && (left === "any" || left === leftType) && (right === "any" || right === rightType); }); if (opMatch) return this.generator.call(ops[opMatch].generator, [binaryExpr.left, binaryExpr.right]); const fullName = `binary${binaryExpr.operator}`; if (this.generator.lang.expressions[fullName]) genName = fullName; } else if (type === Ast_1.OneAst.StatementType.VariableDeclaration) { const varDecl = obj; const initType = varDecl.initializer.exprKind; if (initType === Ast_1.OneAst.ExpressionKind.MapLiteral && this.generator.lang.expressions["mapLiteralDeclaration"]) genName = "mapLiteralDeclaration"; else if (initType === Ast_1.OneAst.ExpressionKind.Call) { const overlayCall = this.getOverlayCallCode(varDecl.initializer, { result: varDecl.outName }); if (overlayCall) return overlayCall; } } if (objExpr.valueType && objExpr.valueType.typeArguments) { objExpr.typeArgs = objExpr.valueType.typeArguments.map(x => this.generator.getTypeName(x)); } const exprTmpl = this.generator.lang.expressions[genName]; if (!exprTmpl) throw new Error(`Expression template not found: ${genName}!`); // TODO (hack): using global "result" and "resultType" variables const usingResult = exprTmpl.template.includes("{{result}}"); if (usingResult) this.tempVarHandler.create(); let genResult = this.generator.call(exprTmpl.generator, [obj, ...genArgs]); if (usingResult) genResult = [new TemplateGenerator_1.GeneratedNode(this.tempVarHandler.finish(genResult))]; if (!usingResult && isStatement && !this.tempVarHandler.empty) { const prefix = TemplateGenerator_1.TemplateGenerator.joinLines(this.tempVarHandler.reset().map(v => v.code), "\n"); genResult = [...prefix, new TemplateGenerator_1.GeneratedNode("\n"), ...genResult]; } for (const item of genResult) if (!item.astNode) item.astNode = obj; return genResult; } clsName(obj) { const cls = this.generator.lang.classes[obj.name]; return cls && cls.template ? cls.template : obj.outName; } } class CodeGenerator { // templates: { [name: string]: TemplateMethod } = {}; // operatorGenerators: { [name: string]: TemplateMethod } = {}; // expressionGenerators: { [name: string]: TemplateMethod } = {}; // classGenerators: { // [className: string]: { // typeGenerator: TemplateMethod, // methods: { [methodName: string]: TemplateMethod }, // fields: { [fieldName: string]: TemplateMethod } // } // } = {}; constructor(schema, stdlib, lang) { this.schema = schema; this.stdlib = stdlib; this.lang = lang; this.model = new CodeGeneratorModel(this); this.templateVars = new ExprLangVM_1.VariableSource("Templates"); this.caseConverter = new CaseConverter_1.SchemaCaseConverter(lang.casing); this.setupTemplateGenerator(); this.setupEnums(); this.setupClasses(); this.setupIncludes(); } setupTemplateGenerator() { const codeGenVars = new ExprLangVM_1.VariableSource("CodeGeneratorModel"); codeGenVars.addCallback("includes", () => this.model.includes); codeGenVars.addCallback("classes", () => this.model.classes); codeGenVars.addCallback("config", () => this.model.config); // TODO: hack, see https://github.com/koczkatamas/onelang/issues/17 codeGenVars.addCallback("reflectedClasses", () => this.model.classes.filter(x => x.attributes["reflect"])); codeGenVars.addCallback("interfaces", () => this.model.interfaces); codeGenVars.addCallback("enums", () => this.model.enums); codeGenVars.addCallback("mainBlock", () => this.schema.mainBlock); codeGenVars.addCallback("result", () => this.model.result); for (const name of ["gen", "isIfBlock", "typeName", "escapeQuotes", "clsName"]) codeGenVars.setVariable(name, (...args) => this.model[name].apply(this.model, args)); for (const name of Object.keys(this.lang.templates)) this.templateVars.setVariable(name, this.lang.templates[name].generator); const varContext = new ExprLangVM_1.VariableContext([codeGenVars, this.templateVars]); this.templateGenerator = new TemplateGenerator_1.TemplateGenerator(varContext); this.templateGenerator.objectHook = obj => this.model.gen(obj); } call(method, args) { const callStackItem = this.templateGenerator.callStack[this.templateGenerator.callStack.length - 1]; const varContext = callStackItem ? callStackItem.vars : this.templateGenerator.rootVars; return this.templateGenerator.methodCall(method, args, this.model, varContext); } getTypeName(type) { if (!type) return "???"; if (type.isClassOrInterface) { const cls = this.model.generator.lang.classes[type.className]; if (cls) { return this.call(cls.generator, [type.typeArguments.map(x => this.getTypeName(x)), type.typeArguments]) .map(x => x.text).join(""); } else { let result = this.caseConverter.getName(type.className, "class"); if (type.typeArguments && type.typeArguments.length > 0) { // TODO: make this templatable result += `<${type.typeArguments.map(x => this.getTypeName(x)).join(", ")}>`; } return result; } } else if (type.isEnum) { return this.caseConverter.getName(type.enumName, "enum"); } else if (type.isGenerics) { return this.lang.genericsOverride ? this.lang.genericsOverride : type.genericsName; } else { return this.lang.primitiveTypes ? this.lang.primitiveTypes[type.typeKind] : type.typeKind.toString(); } } convertIdentifier(name, vars, mode) { const isLocalVar = vars.includes(name); const knownKeyword = ["true", "false"].includes(name); return `${isLocalVar || mode === "declaration" || mode === "field" || knownKeyword ? "" : "this."}${name}`; } getMethodPath(method) { let parts = []; let currExpr = method; while (true) { if (currExpr.exprKind === Ast_1.OneAst.ExpressionKind.PropertyAccess) { const propAcc = currExpr; parts.push(propAcc.propertyName); currExpr = propAcc.object; } else if (currExpr.exprKind === Ast_1.OneAst.ExpressionKind.Identifier) { parts.push(currExpr.text); break; } else return null; } const funcName = parts.reverse().map(x => x.toLowerCase()).join("."); return funcName; } genParameters(method) { return method.parameters.map((param, idx) => ({ idx, name: param.outName, type: this.getTypeName(param.type), typeInfo: param.type })); } setupIncludes() { const includesCollector = new IncludesCollector_1.IncludesCollector(this.lang); includesCollector.process(this.schema); const includes = Array.from(includesCollector.includes).map(name => ({ name, source: this.lang.includeSources[name] || name })); this.model.includes = ArrayHelpers_1.sortBy(includes, x => x.name); } setupEnums() { this.model.enums = Object.values(this.schema.enums).map(enum_ => { return { name: enum_.outName, values: enum_.values.map((x, i) => ({ name: x.outName, intValue: i, origName: x.name })) }; }); } convertMethod(method) { return { name: method.outName, returnType: this.getTypeName(method.returns), returnTypeInfo: method.returns, body: method.body, parameters: this.genParameters(method), visibility: method.visibility || "public", static: method.static || false, throws: method.throws || false, attributes: method.attributes, }; } setupClasses() { this.model.interfaces = Object.values(this.schema.interfaces).map(intf => { const methods = Object.values(intf.methods).map(method => this.convertMethod(method)); return { name: intf.outName, methods: methods, typeArguments: intf.typeArguments && intf.typeArguments.length > 0 ? intf.typeArguments : null, baseInterfaces: intf.baseInterfaces, baseClasses: intf.baseInterfaces, attributes: intf.attributes, }; }); this.model.classes = Object.values(this.schema.classes).map(cls => { const methods = Object.values(cls.methods).map(method => this.convertMethod(method)); const constructor = cls.constructor ? { body: cls.constructor.body, parameters: this.genParameters(cls.constructor), throws: cls.constructor.throws || false } : null; const fields = Object.values(cls.fields).map(field => { return { name: this.caseConverter.getName(field.outName, "field"), type: this.getTypeName(field.type), typeInfo: field.type, visibility: field.visibility || "public", public: field.visibility ? field.visibility === "public" : true, initializer: field.initializer, static: field.static || false }; }); return { name: cls.outName, methods: methods, constructor, typeArguments: cls.typeArguments && cls.typeArguments.length > 0 ? cls.typeArguments : null, baseClass: cls.baseClass, baseInterfaces: cls.baseInterfaces, baseClasses: (cls.baseClass ? [cls.baseClass] : []).concat(cls.baseInterfaces), attributes: cls.attributes, // TODO: hack, see https://github.com/koczkatamas/onelang/issues/17 needsConstructor: constructor !== null || fields.some(x => x.visibility === "public" && !x.static && !!x.initializer), virtualMethods: methods.filter(x => x.attributes["virtual"]), publicMethods: methods.filter(x => x.visibility === "public"), protectedMethods: methods.filter(x => x.visibility === "protected"), privateMethods: methods.filter(x => x.visibility === "private"), fields: fields, instanceFields: fields.filter(x => !x.static), staticFields: fields.filter(x => x.static), publicFields: fields.filter(x => x.visibility === "public"), protectedFields: fields.filter(x => x.visibility === "protected"), privateFields: fields.filter(x => x.visibility === "private"), }; }); } generate(callTestMethod) { const generatedNodes = this.call(this.lang.templates["main"].generator, []); this.generatedCode = ""; for (const tmplNode of generatedNodes || []) { if (tmplNode.astNode && tmplNode.astNode.nodeData) { const nodeData = tmplNode.astNode.nodeData; let dstRange = nodeData.destRanges[this.lang.name]; if (!dstRange) dstRange = nodeData.destRanges[this.lang.name] = { start: this.generatedCode.length, end: -1 }; dstRange.end = this.generatedCode.length + tmplNode.text.length; } this.generatedCode += tmplNode.text; } if (callTestMethod) { const testClassName = this.caseConverter.getName("test_class", "class"); const testMethodName = this.caseConverter.getName("test_method", "method"); const testClass = this.model.classes.find(x => x.name === testClassName); if (testClass) { const testMethod = testClass.methods.find(x => x.name === testMethodName); this.generatedCode += "\n\n" + this.call(this.lang.templates["testGenerator"].generator, [testClassName, testMethodName, testMethod]).map(x => x.text).join(""); } } return this.generatedCode; } } exports.CodeGenerator = CodeGenerator; }); //# sourceMappingURL=CodeGenerator.js.map