callback-heaven
Version:
Transcode callbacks into synchronous code
626 lines (532 loc) • 12.5 kB
JavaScript
var ast = (function(){
function isString(stringToCheck){
if(stringToCheck.constructor == String)
return true;
return typeof stringToCheck == 'string' || stringToCheck instanceof String;
}
if(!AtomEnumerator){
var AtomEnumerator = function(a){
this.array = a;
this.index = -1;
};
AtomEnumerator.prototype = {
next: function(){
this.index++;
return this.index < this.array.length;
},
current: function(){
return this.array[this.index];
},
currentIndex: function(){
return this.index;
}
};
}
function walk(tree, f, parent, name){
if(tree.length !== undefined){
var ae =new AtomEnumerator(tree);
while(ae.next()){
walk(ae.current(),f, tree, ae.currentIndex());
}
return;
}
for(var i in tree){
if(!tree.hasOwnProperty(i)) continue;
if(/^(parent|parentfield|tree)$/i.test(i)) continue;
var v = tree[i];
if(v && v!==tree){
if(isString(v))
continue;
if(v.type !== undefined || v.length !== undefined){
walk(v,f, tree,i);
}
}
}
f(tree,parent, name);
}
function prepareDom(node, tree){
walk(node,function(item,parent,name){
item.parent = parent;
item.parentField = name;
if(tree){
item.tree = tree;
}
});
}
function findParent(tree, f){
if(!tree) return;
var p = f(tree);
if(p) return p;
return findParent(tree.parent,f);
}
function isCallExpression(signature){
return function(item){
if(item.type == 'Identifier'){
if(item.name == signature && item.parent.type == 'CallExpression'){
return true;
}
}
return false;
};
}
var isPromise = isCallExpression('$wait');
function findParentType(p, rg){
return findParent(p, function(e){
return rg.test(e.type);
});
}
function findParentTypeNot(p, rg){
return findParent(p, function(e){
return !rg.test(e.type);
});
}
function hasPromise(p){
var list = [];
walk(p, function(item){
if(item.isPromise || isPromise(item)){
list.push(item);
}
});
return list.length ? list : null;
}
function AST(tree){
this.tree = tree;
}
AST.prototype = {
process: function(){
var item;
var p;
var tree = this.tree;
prepareDom(this.tree, this.tree);
var list = hasPromise(tree);
if(!list){
return tree;
}
console.log('promises found ' + list.length);
var ae = new AtomEnumerator(list);
while(ae.next()){
item = ae.current();
item.isPromise = true;
p = item.parent;
while(p){
p.hasPromise = true;
p = p.parent;
}
}
ae = new AtomEnumerator(list);
while(ae.next()){
item = ae.current();
p = item.parent;
while(p){
if(p.isPromise)
throw new Error('Recursive calls not supported');
p = p.parent;
}
}
var self = this;
walk(tree,function(t){
if(/^function/i.test(t.type)){
self.functionExpression(t);
}
});
return this.visit(tree);
},
visit: function(e){
if(!e)
return e;
var type = e.type;
if(type){
type = type.substr(0,1).toLowerCase() + type.substr(1);
var visitor = this[type] || this.expression;
return visitor.call(this,e);
}
if(e.length){
var ae = new AtomEnumerator(e);
var list = [];
while(ae.next()){
var item = ae.current();
list.push(this.visit(item));
}
return list;
}
return e;
},
expression: function(e){
//console.log('default expression ' + e.type);
for(var i in e){
if(/parent|tree/i.test(i))
continue;
var v = e[i];
if(!v)
continue;
if(isString(v))
continue;
e[i] = this.visit(v);
}
return e;
},
expressionStatement: function(e){
return this.transformPromise(e);
},
callExpression: function(e){
return this.transformPromise(e);
},
assignmentExpression: function(e){
return this.transformPromise(e);
},
transformPromise: function(e){
var list = [];
walk(e,function(ex){
if(ex.isPromise){
list.push(ex);
}
});
if(!list.length)
return e;
var s = this.createAsyncStatement("async",null);
var sa = [];
s.elements.push({
type: 'arrayExpression',
elements:sa
});
var vars = [];
var ae =new AtomEnumerator(list);
while(ae.next()){
var p = ae.current();
var invoke = p.parent.arguments[0];
sa.push(this.toFunction(invoke));
p = p.parent;
var f = p.parentField;
p = p.parent;
if(!/statement/i.test(p.type)){
var v = "__v" + (vars.length+1);
vars.push(v);
p[f] = {
type: 'identifier',
name: v
};
}
}
if(vars.length){
var rf = {
type: 'functionDeclaration',
params: vars.map(function(i){
return { type: 'literal', raw: i };
}),
body:{
type: 'blockStatement',
body:[e]
}
};
s.elements.push(rf);
}
//debugger;
return s;
},
functionExpression: function(e){
var t = e.type;
e = this.functionDeclaration(e);
e.type = t;
return e;
},
functionDeclaration: function(e){
if(e.transformPromise)
return e;
var self = this;
//debugger;
walk(e,function(t){
if(t==e)
return;
if(/^function/i.test(t.type)){
console.log('nested function processing');
self.functionDeclaration(t);
}
});
if(!hasPromise(e.body)){
return e;
}
e.transformPromise = true;
// remove all variables...
var vars = [];
walk(e, function(t,p){
// if(/catchClause/i.test(t.type)){
// vars.push({
// type: 'variableDeclarator',
// id: {
// type: 'identifier',
// name: t.param.name
// }
// });
// }
if(/variableDeclarator$/i.test(t.type)){
vars.push({
type: 'variableDeclarator',
id: {
type: 'identifier',
name: t.id.name
}
});
if(!t.init){
var i = p.indexOf(t);
p.splice(i,1);
}else{
t.type = 'assignmentExpression';
t.left = {
type: 'identifier',
name: t.id.name
};
t.right = t.init;
t.right.parent = t;
t.right.parentField = "right";
delete t.init;
}
}
});
var body = this.visit(e.body);
e.body = {
type:'blockStatement',
body:[
{
type: 'returnStatement',
argument: {
type: 'callExpression',
callee:{
type: 'identifier',
name: 'avm'
},
arguments:[
{
type: 'thisExpression',
},
body
]
}
}
]
};
var ae = new AtomEnumerator(vars);
while(ae.next()){
var item = ae.current();
e.body.body.unshift(item);
}
return e;
},
toFunction: function(e, assign, field){
var stmt = {
type: 'returnStatement',
argument: e
};
var r = {
type:'functionExpression',
body: { type: 'blockStatement',
body:[stmt]
}
};
if(assign){
r.params = [{ type: 'literal', raw: '___v' }];
assign[field] = {
type: 'identifier',
name: '___v'
};
}
return r;
},
toAsync: function(e){
e.type = 'arrayExpression';
delete e.callee;
var arg = e.arguments[0];
e.arguments.unshift({
type: 'literal',
raw: 'async'
});
e.arguments[1] = this.toFunction(arg);
return e;
},
createFunction: function(stmt){
var s = {
type: 'functionExpression',
id: '',
body:{
type: 'blockStatement',
body:[]
}
};
s.statements = s.body.body;
if(stmt) s.statements.push(stmt);
return s;
},
createAsyncStatement: function(name,props, func){
var s = {
type: 'arrayExpression',
elements:[
{
type: 'literal',
raw: '"' + name + '"'
}
]
};
if(props){
var objExp = {
type: 'objectExpression',
properties:[]
};
for(var i in props){
if(props.hasOwnProperty(i)){
var v = props[i];
if(v){
objExp.properties.push({
type: 'property',
key: { type: 'literal', raw: '"' + i + '"' },
value: v
});
}
}
}
s.elements.push(objExp);
}
if(func){
func = this.toFunction(func);
s.elements.push(func);
}
//debugger;
return s;
},
ifStatement: function(e){
var testPromise = false;
var consequentPromise = false;
var alternatePromise = false;
if(hasPromise(e.test)){
testPromise = true;
e.test = this.transformPromise(e.test);
}else{
e.test = this.toFunction(e.test);
}
if(hasPromise(e.consequent)){
consequentPromise = true;
e.consequent = this.visit(e.consequent);
}else{
e.consequent = this.createFunction(e.consequent);
}
if(e.alternate){
if(hasPromise(e.alternate)){
alternatePromise = true;
e.alternate = this.visit(e.alternate);
}else{
e.alternate = this.createFunction(e.alternate);
}
}
if(!(testPromise || consequentPromise || alternatePromise))
return e;
return this.createAsyncStatement("if",{
test: e.test,
then: e.consequent,
"else": e.alternate
});
},
forStatement: function(e){
var initPromise = hasPromise(e.init);
var testPromise = hasPromise(e.test);
var updatePromise = hasPromise(e.update);
var bodyPromise = hasPromise(e.body);
if(initPromise || testPromise || updatePromise)
throw new Error('Waitable promise not supported in init,test and update of for loop');
if(!bodyPromise)
return e;
var body = this.visit(e.body);
var s = this.createAsyncStatement("for",{
init: this.toFunction(e.init),
test: this.toFunction(e.test),
update: this.toFunction(e.update),
body: body
});
return s;
},
whileStatement: function(e){
var testPromise = hasPromise(e.test);
var bodyPromise = hasPromise(e.body);
if(testPromise){
throw new Error('Waitable promise not supported test part of while loop');
}
if(!bodyPromise)
return e;
var s = this.createAsyncStatement("while",{
test: this.toFunction(e.test),
body: this.visit(e.body)
});
return s;
},
doWhileStatement: function(e){
var testPromise = hasPromise(e.test);
var bodyPromise = hasPromise(e.body);
if(testPromise){
throw new Error('Waitable promise not supported test part of while loop');
}
if(!bodyPromise)
return e;
var s = this.createAsyncStatement("do",{
test: this.toFunction(e.test),
body: this.visit(e.body)
});
return s;
},
forInStatement: function(e){
var leftPromise = hasPromise(e.left);
var rightPromise = hasPromise(e.right);
var bodyPromise = hasPromise(e.body);
if(leftPromise || rightPromise || bodyPromise)
throw new Error("Waitable promises are not supported in for-in statement");
return e;
},
tryStatement: function(e){
var bodyPromise = hasPromise(e.block);
if(!bodyPromise)
return e;
var tb = {
"try": this.visit(e.block)
};
if(e.handler){
var c = e.handler;
tb["catch"] = c;
c.type = 'FunctionExpression';
c.params = [c.param];
}
if(e.finalizer){
var f = {
type: 'FunctionExpression',
body: e.finalizer
};
tb["finally"] = f;
}
var s = this.createAsyncStatement("try",tb);
return s;
},
blockStatement: function(e){
if(!hasPromise(e))
return e;
var list = e.body;
e = {
type: 'arrayExpression',
elements:[]
};
var body = e.elements;
var current = null;
var ae = new AtomEnumerator(list);
while(ae.next()){
var s = ae.current();
if(!hasPromise(s)){
if(!current){
current = this.createFunction();
body.push(current);
}
current.statements.push(s);
}else{
s = this.visit(s);
body.push(s);
current = null;
}
}
return e;
},
};
return AST;
})();