UNPKG

atomize-client

Version:

Client library for AtomizeJS: JavaScript DSTM

1,006 lines (907 loc) 34.5 kB
/*global exports, require */ /*jslint devel: true */ (function () { 'use strict'; var parser = require('./javascript_parser'); // In general keep in mind that the stack-based nature of this // visitor is reverse-polish-notation form. I.e. in a sequence of // calls to walk or invoke, they'll actually get visited in LIFO // order. function TreeWalker(ast, recv) { var self = this; this.ast = ast; this.recv = recv; this.stack = [{traverse: ast, parent: undefined, name: undefined}, {fun: function () { if (self.hasProp(self.recv, 'finished') && Function === self.recv.finished.constructor) { return self.recv.finished(); } }}]; this.current = undefined; } TreeWalker.prototype = { hasOwnProp: function (obj, prop) { return ({}).hasOwnProperty.call(obj, prop); }, hasProp: function (obj, prop) { return undefined !== obj[prop]; }, isPrimitive: function (obj) { return obj !== Object(obj); }, traverse: function () { var item, parent, obj, name, keys, i, key; while (0 !== this.stack.length) { item = this.stack.shift(); if (this.hasOwnProp(item, 'traverse')) { obj = item.traverse; parent = item.parent; name = item.name; this.current = obj; if (this.hasOwnProp(obj, 'type')) { if (this.hasProp(this.recv, obj.type) && Function === this.recv[obj.type].constructor) { this.recv[obj.type](this, parent, obj, name); } else { keys = Object.keys(obj); for (i = keys.length - 1; i >= 0; i -= 1) { key = keys[i]; if (!this.isPrimitive(obj[key])) { this.stack.unshift({traverse: obj[key], parent: obj, name: key}); } } } } else { if (Array === obj.constructor) { for (i = obj.length - 1; i >= 0; i -= 1) { this.stack.unshift({traverse: obj[i], parent: obj, name: i}); } } else { if (this.hasProp(this.recv, 'unknown') && Function === this.recv.unknown.constructor) { this.recv.unknown(this, parent, obj); } } } } else if (this.hasOwnProp(item, 'fun')) { this.current = item.current; item.fun(this, item.current); } } }, walk: function (obj, key) { if (undefined === key) { // the obj is in fact the name this.stack.unshift({traverse: this.current[obj], parent: this.current, name: obj}); } else { this.stack.unshift({traverse: obj[key], parent: obj, name: key}); } }, invoke: function (fun) { this.stack.unshift({fun: fun, current: this.current}); } }; function PrettyPrinter(ast) { this.ast = ast; this.treeWalker = new TreeWalker(ast, this); this.indentation = 0; this.needsTermination = true; } PrettyPrinter.prototype = { indentUnit: ' ', print: function () { this.text = []; this.whiteSpaceOnly = true; this.lines = []; this.linesState = []; this.treeWalker.traverse(); return this.toString(); }, toString: function () { return this.lines.join("\n") + this.text.join(""); }, append: function (str) { this.whiteSpaceOnly = false; this.text.push(str); }, incDepth: function () { this.indentation += 1; }, decDepth: function () { this.indentation -= 1; }, freshScope: function () { this.lines.push(this.text); this.linesState.push(this.whiteSpaceOnly); this.text = []; this.whiteSpaceOnly = true; this.needsTermination = true; }, popScope: function () { this.text = this.lines.pop(); this.whiteSpaceOnly = this.linesState.pop(); }, popAndFreshScope: function () { this.popScope(); this.freshScope(); }, indent: function () { var i; for (i = 0; i < this.indentation; i += 1) { this.text.push(this.indentUnit); } }, terminate: function () { if (this.needsTermination) { this.append(";"); } }, withFreshScopeJoin: function (fun) { this.freshScope(); fun(); var text = this.text.join(""); this.popScope(); this.append(text); }, formatStatements: function (tw, statements, obj) { var i, self = this; for (i = obj.length - 1; i >= 0; i -= 1) { tw.invoke(function () { if (self.whiteSpaceOnly) { statements.push("\n"); } else { self.terminate(); statements.push(self.text.join("") + "\n"); } self.popScope(); }); tw.walk(obj, i); tw.invoke(function () { self.freshScope(); self.indent(); }); } }, forWhileLoop: function (tw, elements, obj, init) { var i, self = this; tw.invoke(function () { var body = self.text.join(""); self.popScope(); self.withFreshScopeJoin(function () { self.append(init(elements)); if ("Block" === obj.statement.type) { self.append(" "); self.needsTermination = false; } else { self.append("\n"); self.incDepth(); self.indent(); self.decDepth(); } self.append(body); }); }); tw.walk('statement'); for (i = 0; i < elements.length; i += 1) { (function () { var j = i; if (obj[elements[j]]) { tw.invoke(function () { elements[elements[j]] = self.text.join(""); self.popAndFreshScope(); }); tw.walk(elements[j]); } else { elements[elements[j]] = ''; } }()); } this.freshScope(); }, // Visitor Pattern ArrayLiteral: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var text = self.text.join(", "); self.popScope(); self.append("[" + text + "]"); }); tw.walk('elements'); this.freshScope(); }, AssignmentExpression: function (tw, parent, obj, name) { var result, self = this; tw.invoke(function () { self.needsTermination = true; }); result = this.BinaryExpression(tw, parent, obj, name); }, BinaryExpression: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var lhs, rhs; rhs = self.text.join(""); if (obj.right.type === "BinaryExpression") { rhs = "(" + rhs + ")"; } self.popScope(); lhs = self.text.join(""); if (obj.left.type === "BinaryExpression") { lhs = "(" + lhs + ")"; } self.popScope(); self.append(lhs + " " + obj.operator + " " + rhs); }); tw.walk('right'); tw.invoke(function () { self.freshScope(); }); tw.walk('left'); this.freshScope(); }, Block: function (tw, parent, obj, name) { var i, self = this, statements = []; this.incDepth(); tw.invoke(function () { self.decDepth(); var text = statements.join(""); self.withFreshScopeJoin(function () { self.append("{\n"); self.append(text); self.indent(); self.append("}"); }); self.needsTermination = false; }); this.formatStatements(tw, statements, obj.statements); }, BooleanLiteral: function (tw, parent, obj, name) { this.append(obj.value); }, BreakStatement: function (tw, parent, obj, name) { if (obj.label) { this.append("break " + obj.label); } else { this.append("break"); } }, CaseClause: function (tw, parent, obj, name) { var i, self = this, statements = []; this.incDepth(); tw.invoke(function () { var text, selector; self.decDepth(); text = statements.join(""); selector = self.text.join(""); self.popScope(); self.append("case " + selector + ":\n" + text); self.needsTermination = false; }); tw.walk('selector'); tw.invoke(function () { self.freshScope(); }); this.formatStatements(tw, statements, obj.statements); }, Catch: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var body = self.text.join(""); self.popScope(); self.append(" catch (" + obj.identifier + ") " + body); self.needsTermination = false; }); tw.walk('block'); this.freshScope(); }, ConditionalExpression: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var cond, trueExpr, falseExpr; falseExpr = self.text.join(""); self.popScope(); trueExpr = self.text.join(""); self.popScope(); cond = self.text.join(""); self.popScope(); // TODO: the surrounding parenthesis are not always needed self.append("(" + cond + " ? " + trueExpr + " : " + falseExpr + ")"); }); tw.walk('falseExpression'); tw.invoke(function () { self.freshScope(); }); tw.walk('trueExpression'); tw.invoke(function () { self.freshScope(); }); tw.walk('condition'); this.freshScope(); }, ContinueStatement: function (tw, parent, obj, name) { if (obj.label) { this.append("continue " + obj.label); } else { this.append("continue"); } }, DebuggerStatement: function (tw, parent, obj, name) { this.append("debugger"); }, DefaultClause: function (tw, parent, obj, name) { var i, self = this, statements = []; this.incDepth(); tw.invoke(function () { self.decDepth(); var text = statements.join(""); self.append("default:\n" + text); self.needsTermination = false; }); this.formatStatements(tw, statements, obj.statements); }, DoWhileStatement: function (tw, parent, obj, name) { var condition, self = this; tw.invoke(function () { var body = self.text.join(""); self.popScope(); self.withFreshScopeJoin(function () { self.append("do"); if (obj.statement.type === "Block") { self.append(" "); self.append(body); self.append(" "); } else { self.append("\n"); self.incDepth(); self.indent(); self.decDepth(); self.append(body); self.terminate(); self.append("\n"); self.indent(); } self.append("while (" + condition + ")"); }); self.needsTermination = true; }); tw.walk('statement'); tw.invoke(function () { condition = self.text.join(""); self.popAndFreshScope(); }); tw.walk('condition'); this.freshScope(); }, EmptyStatement: function (tw, parent, obj, name) { this.needsTermination = false; }, Finally: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var body = self.text.join(""); self.popScope(); self.append(" finally " + body); self.needsTermination = false; }); tw.walk('block'); this.freshScope(); }, ForInStatement: function (tw, parent, obj, name) { this.forWhileLoop( tw, ['iterator', 'collection'], obj, function (elements) { if ("VariableDeclaration" === obj.iterator.type) { elements.iterator = "var " + elements.iterator; } return "for (" + elements.iterator + " in " + elements.collection + ")"; }); }, ForStatement: function (tw, parent, obj, name) { this.forWhileLoop( tw, ['initializer', 'test', 'counter'], obj, function (elements) { return "for (" + elements.initializer + "; " + elements.test + "; " + elements.counter + ")"; }); }, Function: function (tw, parent, obj, name) { var i, self = this, statements = []; this.incDepth(); tw.invoke(function () { self.decDepth(); var text = statements.join(""); self.withFreshScopeJoin(function () { self.append("function "); if (obj.name) { self.append(obj.name + " "); } self.append("("); self.append(obj.params.join(", ")); self.append(") {\n"); self.append(text); self.indent(); self.append("}"); }); self.needsTermination = false; }); this.formatStatements(tw, statements, obj.elements); }, FunctionCall: function (tw, parent, obj, name) { var args, self = this; tw.invoke(function () { var name, postfix; name = self.text.join(""); postfix = ""; self.popScope(); self.withFreshScopeJoin(function () { if ("Function" === obj.name.type) { self.append("(" + name); postfix = ")"; } else { self.append(name); } self.append("(" + args + ")" + postfix); }); }); tw.walk('name'); tw.invoke(function () { var text = self.text.join(", "); args = text; self.popAndFreshScope(); }); tw.walk('arguments'); this.freshScope(); }, GetterDefinition: function (tw, parent, obj, name) { var i, self = this, statements = []; this.incDepth(); tw.invoke(function () { self.decDepth(); var text = statements.join(""); self.withFreshScopeJoin(function () { self.append("get " + obj.name + "() {\n"); self.append(text); self.indent(); self.append("}"); }); }); this.formatStatements(tw, statements, obj.body); }, IfStatement: function (tw, parent, obj, name) { var condition, trueSt, falseSt, formatBranch, self = this; formatBranch = function (text, branch, postfix) { if ("Block" === branch.type) { self.append(" "); } else { self.append("\n"); self.incDepth(); self.indent(); self.decDepth(); } self.append(text); if (postfix) { if ("Block" === branch.type) { self.append(" "); self.append(postfix); } else { self.append("\n"); self.indent(); self.append(postfix); } } }; this.incDepth(); if (obj.elseStatement) { tw.invoke(function () { falseSt = self.text.join(""); self.popScope(); self.withFreshScopeJoin(function () { self.append("if (" + condition + ")"); formatBranch(trueSt, obj.ifStatement, "else"); formatBranch(falseSt, obj.elseStatement); }); self.needsTermination = false; }); tw.walk('elseStatement'); tw.invoke(function () { trueSt = self.text.join(""); self.popAndFreshScope(); }); } else { tw.invoke(function () { trueSt = self.text.join(""); self.popScope(); self.withFreshScopeJoin(function () { self.append("if (" + condition + ")"); formatBranch(trueSt, obj.ifStatement); condition = self.text.join(""); }); self.needsTermination = false; }); } tw.walk('ifStatement'); tw.invoke(function () { self.decDepth(); condition = self.text.join(""); self.popAndFreshScope(); }); tw.walk('condition'); this.freshScope(); }, LabelledStatement: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var statement = self.text.join(""); self.popScope(); self.append(obj.label + ": " + statement); }); tw.walk('statement'); this.freshScope(); }, NewOperator: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var ctr, args; args = self.text.join(", "); self.popScope(); ctr = self.text.join(""); self.popScope(); self.append("new " + ctr + "(" + args + ")"); }); tw.walk('arguments'); tw.invoke(function () { self.freshScope(); }); tw.walk('constructor'); this.freshScope(); }, NullLiteral: function (tw, parent, obj, name) { this.append("null"); }, NumericLiteral: function (tw, parent, obj, name) { this.append(obj.value); }, ObjectLiteral: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var props = self.text.join(", "); self.popScope(); self.append("{" + props + "}"); }); tw.walk('properties'); this.freshScope(); }, PostfixExpression: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var expression = self.text.join(""); self.popScope(); self.append(expression + obj.operator); }); tw.walk('expression'); this.freshScope(); }, Program: function (tw, parent, obj, name) { var i, self = this, elements = []; tw.invoke(function () { self.append(elements.join("")); }); this.formatStatements(tw, elements, obj.elements); }, PropertyAccess: function (tw, parent, obj, name) { var field, self = this, simple = String === obj.name.constructor; tw.invoke(function () { var base = self.text.join(""); self.popScope(); if (simple) { self.append(base + "." + field); } else { self.append(base + "[" + field + "]"); } }); tw.walk('base'); this.freshScope(); if (simple) { field = obj.name; } else { tw.invoke(function () { field = self.text.join(""); self.popAndFreshScope(); }); tw.walk('name'); } }, PropertyAssignment: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var value = self.text.join(""); self.popScope(); self.append(obj.name + ": " + value); }); tw.walk('value'); this.freshScope(); }, RegularExpressionLiteral: function (tw, parent, obj, name) { this.append("/" + obj.body + "/" + obj.flags); }, ReturnStatement: function (tw, parent, obj, name) { var self; if (obj.value) { self = this; tw.invoke(function () { var value = self.text.join(""); self.popScope(); self.append("return " + value); }); tw.walk('value'); this.freshScope(); } else { this.append("return"); } }, SetterDefinition: function (tw, parent, obj, name) { var i, self = this, statements = []; this.incDepth(); tw.invoke(function () { self.decDepth(); var text = statements.join(""); self.withFreshScopeJoin(function () { self.append("set " + obj.name + "(" + obj.param + ") {\n"); self.append(text); self.indent(); self.append("}"); }); }); this.formatStatements(tw, statements, obj.body); }, StringLiteral: function (tw, parent, obj, name) { var str = obj.value; str = str.replace(/\\/g, "\\\\"); str = str.replace(/\'/g, "\\\'"); str = str.replace(/\"/g, '\\\"'); str = str.replace(/[\b]/g, "\\b"); // \b on its own matches boundary str = str.replace(/\f/g, "\\f"); str = str.replace(/\r/g, "\\r"); str = str.replace(/\n/g, "\\n"); str = str.replace(/\t/g, "\\t"); this.append('"' + str + '"'); }, SwitchStatement: function (tw, parent, obj, name) { var i, self = this, clauses = []; tw.invoke(function () { var condition, body; self.decDepth(); condition = self.text.join(""); self.popScope(); self.withFreshScopeJoin(function () { body = clauses.join(""); self.append("switch (" + condition + ") {\n"); self.append(body); self.indent(); self.append("}"); }); self.needsTermination = false; }); tw.walk('expression'); tw.invoke(function () { self.freshScope(); self.incDepth(); }); this.formatStatements(tw, clauses, obj.clauses); }, This: function (tw, parent, obj, name) { this.append("this"); }, ThrowStatement: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var body = self.text.join(""); self.popScope(); self.append("throw " + body); }); tw.walk('exception'); this.freshScope(); }, TryStatement: function (tw, parent, obj, name) { var self = this; if (obj['finally']) { tw.walk('finally'); } tw.walk('catch'); tw.invoke(function () { var body = self.text.join(""); self.popScope(); self.append("try " + body); self.needsTermination = false; }); tw.walk('block'); this.freshScope(); }, UnaryExpression: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var text = self.text.join(""); self.popScope(); if (obj.operator.length > 2) { // delete, typeof, or void self.append(obj.operator + " " + text); } else { // ++, --, +, -, ~, ! self.append(obj.operator + text); } }); tw.walk('expression'); this.freshScope(); }, Variable: function (tw, parent, obj, name) { this.append(obj.name); }, VariableDeclaration: function (tw, parent, obj, name) { var self = this; if (obj.value) { tw.invoke(function () { var value = self.text.join(""); self.popScope(); self.append(obj.name + " = " + value); }); tw.walk('value'); this.freshScope(); } else { this.append(obj.name); } }, VariableStatement: function (tw, parent, obj, name) { var i, self = this; self = this; tw.invoke(function () { var decls = self.text.join(", "); self.popScope(); self.append("var " + decls); self.needsTermination = true; }); tw.walk('declarations'); this.freshScope(); }, WhileStatement: function (tw, parent, obj, name) { this.forWhileLoop( tw, ['condition'], obj, function (elements) { return "while (" + elements.condition + ")"; }); }, WithStatement: function (tw, parent, obj, name) { this.forWhileLoop( tw, ['environment'], obj, function (elements) { return "with (" + elements.environment + ")"; }); } }; function ProxyInjector(ast, atomize, ignore) { this.ast = ast; this.treeWalker = new TreeWalker(ast, this); if (undefined !== ignore) { this.ignore = ignore; } if (undefined !== atomize) { this.atomize = atomize; this.ignore.push(atomize); } } ProxyInjector.prototype = { atomize: 'atomize', ignore: ['atomize'], transform: function () { this.treeWalker.traverse(); }, ignored: function (name) { return -1 !== this.ignore.indexOf(name); }, AssignmentExpression: function (tw, parent, obj, name) { var simple, self = this; if (obj.left.type === 'PropertyAccess') { simple = String === obj.left.name.constructor; tw.invoke(function () { var rhs, lhs, body, base, field, setter; base = new PrettyPrinter(obj.left.base).print(); if (self.ignored(base)) { return; } rhs = new PrettyPrinter(obj.right).print(); if (simple) { field = obj.left.name; setter = "'" + field + "'"; } else { field = new PrettyPrinter(obj.left.name).print(); setter = field; } if ("=" !== obj.operator) { rhs = self.atomize + ".access(" + base + ", " + setter + ") " + obj.operator.substring(0, obj.operator.length - 1) + " " + rhs; } body = self.atomize + ".assign(" + base + ", " + setter + ", " + rhs + ")"; parent[name] = parser.parse(body).elements[0]; }); tw.walk(obj.left, 'base'); if (!simple) { tw.walk(obj.left, 'name'); } } else { tw.walk('left'); } tw.walk('right'); }, PropertyAccess: function (tw, parent, obj, name) { var self = this, simple = String === obj.name.constructor; tw.invoke(function () { var body, base, field, getter; base = new PrettyPrinter(obj.base).print(); if (self.ignored(base)) { return; } if (simple) { field = obj.name; getter = "'" + field + "'"; } else { field = new PrettyPrinter(obj.name).print(); getter = field; } body = self.atomize + ".access(" + base + ", " + getter + ")"; parent[name] = parser.parse(body).elements[0]; }); tw.walk('base'); if (!simple) { tw.walk('name'); } }, ForInStatement: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { var body, collection; collection = new PrettyPrinter(obj.collection).print(); if (self.ignored(collection)) { return; } body = self.atomize + ".enumerate(" + collection + ")"; obj.collection = parser.parse(body).elements[0]; }); tw.walk('iterator'); tw.walk('collection'); }, BinaryExpression: function (tw, parent, obj, name) { var self = this; tw.invoke(function () { if ("in" === obj.operator) { var body, container; container = new PrettyPrinter(obj.right).print(); if (self.ignored(container)) { return; } body = self.atomize + ".has(" + container + ", " + (new PrettyPrinter(obj.left).print()) + ")"; parent[name] = parser.parse(body).elements[0]; } }); tw.walk('right'); tw.walk('left'); }, UnaryExpression: function (tw, parent, obj, name) { var self = this, deleting;; deleting = "delete" === obj.operator && "PropertyAccess" === obj.expression.type; if (deleting) { tw.invoke(function () { // we just want to swap the 'access' with a 'erase' call... parent[name].expression.name.name = 'erase' // ... and wipe out the delete UnaryExpression parent[name] = parent[name].expression; }); } tw.walk('expression'); } }; exports.parse = parser.parse; exports.TreeWalker = TreeWalker; exports.PrettyPrinter = PrettyPrinter; exports.ProxyInjector = ProxyInjector; exports.test = function () { var ast, pi, pp; ast = parser.parse("a.b['c'] = d.e = f['' + g]; for (var j in a) { console.log(j); }; console.log('b' in a);"); pi = new ProxyInjector(ast); pi.transform(); pp = new PrettyPrinter(pi.ast); return pp.print(); }; }());