UNPKG

cwise-parser

Version:
195 lines (174 loc) 5.12 kB
"use strict" var esprima = require("esprima") var uniq = require("uniq") var PREFIX_COUNTER = 0 function CompiledArgument(name, lvalue, rvalue) { this.name = name this.lvalue = lvalue this.rvalue = rvalue this.count = 0 } function CompiledRoutine(body, args, thisVars, localVars) { this.body = body this.args = args this.thisVars = thisVars this.localVars = localVars } function isGlobal(identifier) { if(identifier === "eval") { throw new Error("cwise-parser: eval() not allowed") } if(typeof window !== "undefined") { return identifier in window } else if(typeof global !== "undefined") { return identifier in global } else if(typeof self !== "undefined") { return identifier in self } else { return false } } function getArgNames(ast) { var params = ast.body[0].expression.callee.params var names = new Array(params.length) for(var i=0; i<params.length; ++i) { names[i] = params[i].name } return names } function preprocess(func) { var src = ["(", func, ")()"].join("") var ast = esprima.parse(src, { range: true }) //Compute new prefix var prefix = "_inline_" + (PREFIX_COUNTER++) + "_" //Parse out arguments var argNames = getArgNames(ast) var compiledArgs = new Array(argNames.length) for(var i=0; i<argNames.length; ++i) { compiledArgs[i] = new CompiledArgument([prefix, "arg", i, "_"].join(""), false, false) } //Create temporary data structure for source rewriting var exploded = new Array(src.length) for(var i=0, n=src.length; i<n; ++i) { exploded[i] = src.charAt(i) } //Local variables var localVars = [] var thisVars = [] var computedThis = false //Retrieves a local variable function createLocal(id) { var nstr = prefix + id.replace(/\_/g, "__") localVars.push(nstr) return nstr } //Creates a this variable function createThisVar(id) { var nstr = "this_" + id.replace(/\_/g, "__") thisVars.push(nstr) return nstr } //Rewrites an ast node function rewrite(node, nstr) { var lo = node.range[0], hi = node.range[1] for(var i=lo+1; i<hi; ++i) { exploded[i] = "" } exploded[lo] = nstr } //Remove any underscores function escapeString(str) { return "'"+(str.replace(/\_/g, "\\_").replace(/\'/g, "\'"))+"'" } //Returns the source of an identifier function source(node) { return exploded.slice(node.range[0], node.range[1]).join("") } //Computes the usage of a node var LVALUE = 1 var RVALUE = 2 function getUsage(node) { if(node.parent.type === "AssignmentExpression") { if(node.parent.left === node) { if(node.parent.operator === "=") { return LVALUE } return LVALUE|RVALUE } } if(node.parent.type === "UpdateExpression") { return LVALUE|RVALUE } return RVALUE } //Handle visiting a node (function visit(node, parent) { node.parent = parent if(node.type === "MemberExpression") { //Handle member expression if(node.computed) { visit(node.object, node) visit(node.property, node) } else if(node.object.type === "ThisExpression") { rewrite(node, createThisVar(node.property.name)) } else { visit(node.object, node) } } else if(node.type === "ThisExpression") { throw new Error("cwise-parser: Computed this is not allowed") } else if(node.type === "Identifier") { //Handle identifier var name = node.name var argNo = argNames.indexOf(name) if(argNo >= 0) { var carg = compiledArgs[argNo] var usage = getUsage(node) if(usage & LVALUE) { carg.lvalue = true } if(usage & RVALUE) { carg.rvalue = true } ++carg.count rewrite(node, carg.name) } else if(isGlobal(name)) { //Don't rewrite globals } else { rewrite(node, createLocal(name)) } } else if(node.type === "Literal") { if(typeof node.value === "string") { rewrite(node, escapeString(node.value)) } } else if(node.type === "WithStatement") { throw new Error("cwise-parser: with() statements not allowed") } else { //Visit all children var keys = Object.keys(node) for(var i=0, n=keys.length; i<n; ++i) { if(keys[i] === "parent") { continue } var value = node[keys[i]] if(value) { if(value instanceof Array) { for(var j=0; j<value.length; ++j) { if(value[j] && typeof value[j].type === "string") { visit(value[j], node) } } } else if(typeof value.type === "string") { visit(value, node) } } } } })(ast.body[0].expression.callee.body, undefined) //Remove duplicate variables uniq(localVars) uniq(thisVars) //Return body var routine = new CompiledRoutine(source(ast.body[0].expression.callee.body), compiledArgs, thisVars, localVars) return routine } module.exports = preprocess