UNPKG

callback-heaven

Version:
626 lines (532 loc) 12.5 kB
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; })();