UNPKG

@jsonjoy.com/codegen

Version:

No-dependencies, low-level, high-performance JIT code generation package for JavaScript

238 lines 7.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Codegen = exports.CodegenStepExecJs = void 0; const compile_1 = require("./compile"); /** * Inline JavaScript statements that are executed in main function body. */ class CodegenStepExecJs { constructor(js) { this.js = js; } } exports.CodegenStepExecJs = CodegenStepExecJs; /** * A helper class which helps with building JavaScript code for a single * function. It keeps track of external dependencies, internally generated * constants, and execution steps, which at the end are all converted to * to an executable JavaScript function. * * The final output is a JavaScript function enclosed in a closure: * * ```js * (function(d1, d2, d3) { * var c1 = something; * var c2 = something; * var c3 = something; * return function(r0) { * var r1 = something; * var r2 = something; * var r3 = something; * return something; * } * }) * ``` * * Where `d*` are the external dependencies, `c*` are the internal constants, * and `r*` are the local immutable infinite registers. */ class Codegen { constructor(opts) { /** @ignore */ this.steps = []; /** @ignore */ this.dependencies = []; this.dependencyNames = []; this.linked = {}; /** @ignore */ this.constants = []; this.constantNames = []; this.options = { args: ['r0'], name: '', prologue: '', epilogue: '', processSteps: (steps) => steps.filter((step) => step instanceof CodegenStepExecJs), linkable: {}, ...opts, }; this.registerCounter = this.options.args.length; } /** * Add one or more JavaScript statements to the main function body. */ js(js) { this.steps.push(new CodegenStepExecJs(js)); } var(expression) { const r = this.getRegister(); if (expression) this.js('var ' + r + ' = ' + expression + ';'); else this.js('var ' + r + ';'); return r; } if(condition, then, otherwise) { this.js('if (' + condition + ') {'); then(); if (otherwise) { this.js('} else {'); otherwise(); } this.js('}'); } while(condition, block) { this.js('while (' + condition + ') {'); block(); this.js('}'); } doWhile(block, condition) { this.js('do {'); block(); this.js('} while (' + condition + ');'); } switch(expression, cases, def) { this.js('switch (' + expression + ') {'); for (const [match, block, noBreak] of cases) { this.js('case ' + match + ': {'); block(); if (!noBreak) this.js('break;'); this.js('}'); } if (def) { this.js('default: {'); def(); this.js('}'); } this.js('}'); } return(expression) { this.js('return ' + expression + ';'); } /** * Add any application specific execution step. Steps of `unknown` type * later need to converted to `CodegenStepExecJs` steps in the `.processStep` * callback. * * @param step A step in function execution logic. */ step(step) { this.steps.push(step); } /** * Codegen uses the idea of infinite registers. It starts with `0` and * increments it by one for each new register. Best practice is to use * a new register for each new variable and keep them immutable. * * Usage: * * ```js * const r = codegen.getRegister(); * codegen.js(`const ${r} = 1;`); * ``` * * @returns a unique identifier for a variable. */ getRegister() { return `r${this.registerCounter++}`; } r() { return this.getRegister(); } /** * Allows to wire up dependencies to the generated code. * * @param dep Any JavaScript dependency, could be a function, an object, * or anything else. * @param name Optional name of the dependency. If not provided, a unique * name will be generated, which starts with `d` and a counter * appended. * @returns Returns the dependency name, a code symbol which can be used as * variable name. */ linkDependency(dep, name = 'd' + this.dependencies.length) { this.dependencies.push(dep); this.dependencyNames.push(name); return name; } /** * Sames as {@link Codegen#linkDependency}, but allows to wire up multiple * dependencies at once. */ linkDependencies(deps) { return deps.map((dep) => this.linkDependency(dep)); } /** * Link a dependency from the pre-defined `options.linkable` object. This method * can be called many times with the same dependency name, the dependency will * be linked only once. * * @param name Linkable dependency name. */ link(name) { if (this.linked[name]) return; this.linked[name] = 1; this.linkDependency(this.options.linkable[name], name); } /** * Allows to encode any code or value in the closure of the generated * function. * * @param constant Any JavaScript value in string form. * @param name Optional name of the constant. If not provided, a unique * name will be generated, which starts with `c` and a counter * appended. * @returns Returns the constant name, a code symbol which can be used as * variable name. */ addConstant(constant, name = 'c' + this.constants.length) { this.constants.push(constant); this.constantNames.push(name); return name; } /** * Sames as {@link Codegen#addConstant}, but allows to create multiple * constants at once. */ addConstants(constants) { return constants.map((constant) => this.addConstant(constant)); } /** * Returns generated JavaScript code with the dependency list. * * ```js * const code = codegen.generate(); * const fn = eval(code.js)(...code.deps); * const result = fn(...args); * ``` */ generate(opts = {}) { const { name, args, prologue, epilogue } = { ...this.options, ...opts }; const steps = this.options.processSteps(this.steps); const js = `(function(${this.dependencyNames.join(', ')}) { ${this.constants.map((constant, index) => `var ${this.constantNames[index]} = (${constant});`).join('\n')} return ${name ? `function ${name}` : 'function'}(${args.join(',')}){ ${prologue} ${steps.map((step) => step.js).join('\n')} ${typeof epilogue === 'function' ? epilogue() : epilogue || ''} }})`; // console.log(js); return { deps: this.dependencies, js: js, }; } /** * Compiles the generated JavaScript code into a function. * * @returns JavaScript function ready for execution. */ compile(opts) { const closure = this.generate(opts); return (0, compile_1.compileClosure)(closure); } } exports.Codegen = Codegen; //# sourceMappingURL=Codegen.js.map