function fixSuperclass(def) { var ctorVarName = def.getCtorVarName(); var target = def.returnStatements.length ? def.returnStatements[def.returnStatements.length - 1] : null; // console.log('def.superclass: ', ctorVarName, 'TARGET: ', target); if (ctorVarName && target) { var inheritStatement = { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "MemberExpression", "computed": false, "object": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "require" }, "arguments": [ { "type": "Literal", "value": "raptor-util", "raw": "'raptor-util'" } ] }, "property": { "type": "Identifier", "name": "inherit" } }, "arguments": [ { "type": "Identifier", "name": ctorVarName } ] } }; if (def.superclass.type === 'Literal') { inheritStatement.expression.arguments.push({ "type": "CallExpression", "callee": { "type": "Identifier", "name": "require" }, "arguments": [ def.superclass ] }); } else { inheritStatement.expression.arguments.push(def.superclass); } insertBefore(target.node, target.parent, [inheritStatement]); // Walk the tree again to fix all references to the superclass estraverse.replace(target.parent, { enter: function(node, parent) { }, leave: function(node, parent) { var finalParts = []; var modified = false; if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression') { var callee = node.expression.callee; if (callee.type === 'MemberExpression' && callee.object.type === 'MemberExpression') { var idParts = idNodeToArray(callee); // console.log("*****", idParts); if (idParts && idParts.length) { if (idParts[0] === ctorVarName) { var i=0; while (i<idParts.length) { var cur = idParts[i]; var next = i+1<idParts.length ? idParts[i+1] : undefined; if (cur === 'superclass' && next) { if (next === 'constructor') { i++; // Skip past 'superclass' i++; // Skip past 'constructor' finalParts.push('$super'); modified = true; continue; } else if (next !== 'call' && next !== 'apply') { i++; // Skip past 'superclass' i++; // Skip past 'constructor' finalParts.push('$super'); finalParts.push('prototype'); finalParts.push(next); modified = true; continue; } } else { finalParts.push(cur); } i++; continue; } } } } if (modified) { node.expression.callee = idArrayToNode(finalParts); } } } }); } }
function transformAST(ast) { var defs = []; var defStack = []; var def; function checkNode(node, parent) { var curDef; if ((curDef = parseDefineNode(node))) { def = curDef; def.node = node; def.parent = parent; defs.push(def); defStack.push(def); } else if (def && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') && node !== def.factoryFunctionNode) { def.curLevel++; } checkModuleLogger(def, node, parent); if (def) { if (node.type === 'FunctionDeclaration') { def.scope[node.id.name] = node; } else if (node.type === 'VariableDeclarator') { def.scope[node.id.name] = node.init; } } } ast = estraverse.replace(ast, { enter: function(node, parent) { checkNode(node, parent); }, leave: function(node, parent) { if (def && def.node === node) { defStack.pop(); def = defStack.length ? defStack[defStack.length-1] : null; } else if (def && node.type === 'ReturnStatement') { if (def.curLevel === 0) { if (node.argument && node.argument.type === 'Identifier') { def.exportsVarName = node.argument.name; } def.returnStatements.push({ node: node, parent: parent }); } } else if (def && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration')) { def.curLevel--; } } }); // Transform found define nodes defs.forEach(transformDef); // Fix top-level "use strict" (if found) fixUseStrict(ast); return ast; }
module.exports = function (code) { let ast = esprima.parse(code); let attrs = Object.create(null); let identifiers = []; estraverse.traverse(ast, { enter: node => { if (node.type == estraverse.Syntax.Identifier) { if (identifiers.indexOf(node.name) == -1) { identifiers.push(node.name); } } if (node.type !== estraverse.Syntax.MemberExpression || node.computed) { return; } let name = node.property.name; let existing = attrs[name]; if (!existing) { attrs[name] = []; } attrs[name].push(node); } }); // filter out the attributes that are beneficial for compression let vars = []; Object.keys(attrs).forEach(key => { let occurrences = attrs[key]; let count = occurrences.length; // We only get smaller code if length * (count - 1) > (2*count + 5) : // .attr -> [x] // -c*(l-2) = 2c-lc // (function (,x) {}(,"attr") // +5+l // 2c-lc + 5+l < 0 <=> l(c-1) > 2c+5 if (key.length * (count - 1) > (2 * count + 5)) { let varName = util.getFreeLetter(identifiers); occurrences.forEach(node => { node.computed = true; node.property.name = varName; }); identifiers.push(varName); vars.push({ name: varName, value: `'${key}'` }); } }); // move first variable statement to attributes (this only works with string values ATM) let target; let varDecl = []; estraverse.traverse(ast, { enter: node => { if (target) { return; } if (node.type === estraverse.Syntax.VariableDeclarator) { let value; if (node.init) { value = node.init.name || node.init.raw; } varDecl.push({name: node.id.name, value: value}); } }, leave: node => { if (node.type === estraverse.Syntax.VariableDeclaration) { target = node; } } }); estraverse.replace(ast, { enter: node => { if (node === target) { return estraverse.VisitorOption.Remove; } } }); vars = vars.concat(varDecl); vars.sort((a, b) => { if (typeof a.value == 'undefined') { return typeof b.value == 'undefined' ? 0 : 1; } if (typeof b.value == 'undefined') { return -1; } return a.value === b.value ? 0 : a.value < b.value ? -1 : 1; }); let f = escodegen.generate(ast, { format: { compact: true, quotes: 'double', semicolons: false } }); let names = vars.map(v => v.name); let strings = vars.map(x => x.value).filter(x => x); return `(function (${names.join(', ')}) {${f}})(${strings.join(', ')});`; };
function buildFunction(type, functionName, gen, scope) { var code = gen.toString(functionName) .replace(/((?!\.)types\[\d+])(\.values)/g, "$1"); // enums: use types[N] instead of reflected types[N].values var ast = espree.parse(code); /* eslint-disable no-extra-parens */ estraverse.replace(ast, { enter: function(node, parent) { // rename vars if ( node.type === "Identifier" && renameVars[node.name] && ( (parent.type === "MemberExpression" && parent.object === node) || (parent.type === "BinaryExpression" && parent.right === node) ) ) return { "type": "Identifier", "name": renameVars[node.name] }; // replace this.ctor with the actual ctor if ( node.type === "MemberExpression" && node.object.type === "ThisExpression" && node.property.type === "Identifier" && node.property.name === "ctor" ) return { "type": "Identifier", "name": "$root" + type.fullName }; // replace types[N] with the field's actual type if ( node.type === "MemberExpression" && node.object.type === "Identifier" && node.object.name === "types" && node.property.type === "Literal" ) return { "type": "Identifier", "name": "$root" + type.fieldsArray[node.property.value].resolvedType.fullName }; return undefined; } }); /* eslint-enable no-extra-parens */ code = escodegen.generate(ast, { format: { newline: "\n", quotes: "double" } }); if (config.beautify) code = beautifyCode(code); code = code.replace(/ {4}/g, "\t"); var hasScope = scope && Object.keys(scope).length, isCtor = functionName === type.name; if (hasScope) // remove unused scope vars Object.keys(scope).forEach(function(key) { if (!new RegExp("\\b(" + key + ")\\b", "g").test(code)) delete scope[key]; }); var lines = code.split(/\n/g); if (isCtor) // constructor push(lines[0]); else if (hasScope) // enclose in an iife push(escapeName(type.name) + "." + escapeName(functionName) + " = (function(" + Object.keys(scope).map(escapeName).join(", ") + ") { return " + lines[0]); else push(escapeName(type.name) + "." + escapeName(functionName) + " = " + lines[0]); lines.slice(1, lines.length - 1).forEach(function(line) { var prev = indent; var i = 0; while (line.charAt(i++) === "\t") ++indent; push(line.trim()); indent = prev; }); if (isCtor) push("}"); else if (hasScope) push("};})(" + Object.keys(scope).map(function(key) { return scope[key]; }).join(", ") + ");"); else push("};"); }
function code(packge, moduleName, ast) { const esprima = require('esprima') const escodegen = require('escodegen') const estraverse = require('estraverse') const factory = require('./asmfactory') const meta = require('../dish.json') /** get the phase I scanned meta data */ const symtbl = meta[packge][moduleName].symtbl const fields = meta[packge][moduleName].data const mem = { /** memory parameters */ byte: {size: 0, width: 1, heap: 'HEAPU8'}, char: {size: 1, width: 2, heap: 'HEAPI16'}, bool: {size: 2, width: 4, heap: 'HEAPI32'}, int: {size: 2, width: 4, heap: 'HEAPI32'}, uint: {size: 2, width: 4, heap: 'HEAPU32'}, object: {size: 2, width: 4, heap: 'HEAPI32'}, float: {size: 2, width: 4, heap: 'HEAPF32'}, double: {size: 3, width: 8, heap: 'HEAPF64'} } /** * Transform Code */ const body = ast.body const lines = [] /** * Do the MemberExpression thunks * * 0 - ExpressionStatement 'use asm' * 1 - VariableDeclaration - imports * i - FunctionDeclaration - functions * n - ReturnStatement - exports */ for (let each in body) { if (body[each].type === 'FunctionDeclaration') { const func = body[each] for (let k = func.params.length; k<func.body.body.length; k++) { estraverse.replace(func.body.body[k], { enter: function(node, parent) { if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && !node.callee.computed) { /** * self.hasComponent(...) * => * Klass_hasComponent(...) */ const object = symtbl[func.id.name][node.callee.object.name] const name = object.type === moduleName ? node.callee.property.name : object.type+'_'+node.callee.property.name node.arguments.unshift({ type: 'Identifier', name: object.name }) return factory.ObjectMethod(name, node.arguments) } if (node.type === 'MemberExpression' && !node.computed && parent.type !== 'MemberExpression') { /** * self.x * => * HEAP[self+x>>size] */ const object = symtbl[func.id.name][node.object.name] const member = getField(node.property.name, meta[packge][object.type].data) if (member) { const heap = mem[member.type].heap const size = mem[member.type].size const offset = member.offset return factory.ObjectMember(node.object.name, offset, heap, size) } } if (node.type === 'MemberExpression' && node.computed) { if (node.object.type === 'MemberExpression' ) { /** * self.components[index] * => * HEAP[self+components+(index<<size)>>size] */ const object = symtbl[func.id.name][node.object.object.name] const member = getField(node.object.property.name, meta[packge][object.type].data) if (member) { const heap = mem[member.type].heap const size = mem[member.type].size const offset = member.offset return factory.ObjectArray(node.object.object.name, node.object.property.name, offset, heap, size) } } else { /** * indices[index] * => * HEAP[indices+(index<<size)>>size] */ const object = symtbl[func.id.name][node.object.name] const index = symtbl[func.id.name][node.property.name] const heap = mem[object.type].heap const size = mem[object.type].size return factory.LocalArray(object.name, index.name, heap, size) } } } }) } /** * Ensure the RHS is coerced */ for (let k = func.params.length; k<func.body.body.length; k++) { estraverse.replace(func.body.body[k], { enter: function(node, parent) { // if (node.type === 'AssignmentExpression') { //BinaryExpression //ReturnStatement console.log(JSON.stringify(node, null, 2)) console.log('-------------------------------------') // right hand side should be coerced // } } }) } } } //const out = str.substr(0, startPos)+escodegen.generate(ast) const out = escodegen.generate(ast) return out function getField(name, fields) { for (let f in fields) { if (fields[f].name === name) { return fields[f] } } } }
function main(fileName) { var code = require('fs').readFileSync(fileName).toString(); var ast = esprima.parse(code); var strings = {}; var scopeDepth = 0; // initial: global // pass 1: extract all strings estraverse.traverse(ast, { enter: function(node) { if (shouldSwitchScope(node)) { scopeDepth++; } if (scopeDepth == 0 && node.type === esprima.Syntax.VariableDeclarator && node.init && node.init.type === esprima.Syntax.ArrayExpression && node.init.elements.every(function(e) {return e.type === esprima.Syntax.Literal})) { strings[node.id.name] = node.init.elements.map(function(e) { return e.value; }); this.skip(); } }, leave: function(node) { if (shouldSwitchScope(node)) { scopeDepth--; } } }); // pass 2: restore code ast = estraverse.replace(ast, { enter: function(node) { }, leave: function(node) { // restore strings if (node.type === esprima.Syntax.MemberExpression && node.computed && strings.hasOwnProperty(node.object.name) && node.property.type === esprima.Syntax.Literal ) { var val = strings[node.object.name][node.property.value]; return { type: esprima.Syntax.Literal, value: val, raw: val } } if (node.type === esprima.Syntax.MemberExpression && node.property.type === esprima.Syntax.Literal && typeof node.property.value === 'string' ) { return { type: esprima.Syntax.MemberExpression, computed: false, object: node.object, property: { type: esprima.Syntax.Identifier, name: node.property.value } } } } }); console.log(escodegen.generate(ast)); }
Compiler.prototype.translate = function translate(ast) { if (this.options.ibem !== false) { // Lazily parse i-bem if (!ibemAst) { ibemAst = esprima.parse('function ibem() {' + bemhtml.ibem + '\n}'); ibemAst = JSON.stringify(ibemAst.body[0].body.body); } var ibem = JSON.parse(ibemAst); // Add i-bem block to the AST assert.equal(ast.type, 'Program'); ast = { type: 'Program', body: ibem.concat(ast.body) }; } // Ok, I admit it. Translation process is a bit f****d. var self = this; var allowed = { Program: true, ExpressionStatement: true, CallExpression: true, MemberExpression: true }; ast = estraverse.replace(ast, { enter: function(ast, parent, notify) { // Do not get too deep if (!allowed[ast.type]) { this.skip(); } }, leave: function(ast) { // 1. mark all match calls ast = self.markMatches(ast); // 2. replace all custom matches with match() calls ast = self.replaceCustom(ast); // 3. Merge match(cond).match(cond) into match(cond, cond) and // match(cond)(match(cond)) into match()(match(cond, cond) ast = self.mergeMatches(ast); return ast; } }); // 4. Flatten every statement and replace local/apply/applyNext/applyCtx for (var i = 0; i < ast.body.length; i++) { var stmt = ast.body[i]; if (stmt.type !== 'ExpressionStatement' || !stmt.expression.bemMarked) { continue; } var exprs = this.flatten(stmt.expression); ast.body.splice.apply(ast.body, [ i, 1 ].concat(exprs.map(function(expr) { return { type: 'ExpressionStatement', expression: expr } }))); i += exprs.length - 1; } return xjst.translate(ast, this.options); };