atomize-client
Version:
Client library for AtomizeJS: JavaScript DSTM
1,006 lines (907 loc) • 34.5 kB
JavaScript
/*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();
};
}());