UNPKG

callback-heaven

Version:
938 lines (794 loc) 19.7 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; })();var CallbackHeaven = (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 CBHeaven(tree){ this.tree = tree; this.indent = ''; } CBHeaven.prototype = { convert: function(input){ return this.visit(this.tree); }, visit: function(p){ if(!p) return ''; var type = p.type; type = type.substr(0,1).toLowerCase() + type.substr(1); var visitor = this[type] || this.expression; return visitor.call(this,p); }, visitArray: function(p,s){ if(!p) return ""; var r = []; var ae = new AtomEnumerator(p); while(ae.next()){ var item = ae.current(); item = this.visit(item); if(item){ r.push(item); } } return r.join(s || ','); }, expression: function(p){ return p.type + '\n' + JSON.stringify(p); //return ''; }, objectExpression: function(e){ var oldIndent = this.indent; this.indent += '\t'; var plist = this.visitArray(e.properties, ',\n' + this.indent); this.indent = oldIndent; return "{\n" + this.indent + "\t" + plist + "\n" + this.indent + "}"; }, forStatement: function(e){ var init = this.visit(e.init); var test = this.visit(e.test); var update = this.visit(e.update); var body = this.visit(e.body); return "for(" + init + ", " + test + ", " + update + ")" + body; }, forInStatement: function(e){ var left = this.visit(e.left); var right = this.visit(e.right); var body = this.visit(e.body); return "for(" + left + " in " + right + ")" + body; }, whileStatement: function(e){ var test = this.visit(e.test); var body = this.visit(e.body); return "while(" + test + ")" + body; }, doWhileStatement: function(e){ var body = this.visit(e.body); var test = this.visit(e.test); return "do" + body + "while(" + test + ")"; }, switchStatement: function(e){ var dis = this.visit(e.discriminant); var oldIndent = this.indent; this.indent += '\t'; var cases = this.visitArray(e.cases, ";\n" + this.indent); this.indent = oldIndent; return "switch(" + dis + "){\n" + this.indent + cases + "}"; }, switchCase: function(e){ var c = this.visitArray(e.consequent, ';\n' + this.indent ); var test = this.visit(e.test); if(test){ test = "case " + test; }else{ test = "default"; } return test + ":\n\t" + this.indent + c; }, breakStatement: function(e){ return "break"; }, property: function(e){ var key = this.visit(e.key); var value = this.visit(e.value); return key + ": " + value; }, expressionStatement: function(e){ var exp = e.expression; exp = this.visit(exp); return exp; }, returnStatement: function(e){ return "return " + this.visit(e.argument); }, identifier: function(e){ return e.name; }, continueStatement: function(e){ return "continue"; }, blockStatement: function(e){ var oldIndent = this.indent; this.indent += '\t'; var body = this.program(e); this.indent = oldIndent; return "{\n" + this.indent + '\t' + body + ";\n" + this.indent + "}"; }, functionExpression: function(e){ var body = this.visit(e.body); var params = this.visitArray(e.params, ', '); var id = this.visit(e.id); return "(function " + id + "("+ params +")" + body + ")"; }, functionDeclaration: function(e){ var body = this.visit(e.body); var params = this.visitArray(e.params, ', '); var id = this.visit(e.id); return "function " + id + "("+ params +")" + body ; }, thisExpression: function(e){ return "this"; }, ifStatement: function(e){ var test = this.visit(e.test); var c = this.visit(e.consequent); var a = this.visit(e.alternate); if(a){ a = "else" + a; } return "if(" + test + ") " + c + a; }, binaryExpression: function(e){ var left = this.visit(e.left); var right = this.visit(e.right); return left + " " + e.operator + " " + right; }, logicalExpression: function(e){ return this.binaryExpression(e); }, unaryExpression: function(e){ var op = e.operator; var arg = this.visit(e.argument); return e.prefix ? op + arg : arg + op; }, assignmentExpression: function(e){ var left = this.visit(e.left); var right = this.visit(e.right); return left + " = " + right; }, updateExpression: function(e){ var op = e.operator; var exp = this.visit(e.argument); return e.prefix ? op + exp : exp + op; }, arrayExpression: function(e){ var oldIndent = this.indent; this.indent += '\t'; var elist = this.visitArray(e.elements, ',\n' + this.indent); this.indent = oldIndent; return "[" + elist + "]\n" + this.indent; }, newExpression: function(e){ var exp = this.callExpression(e); return "new " + exp; }, variableDeclaration: function(e){ var d = this.visitArray(e.declarations,';\n' + this.indent); return d; }, conditionalExpression: function(e){ var test= this.visit(e.test); var left = this.visit(e.left); var right = this.visit(e.right); return test + "?" + left + ":" + right; }, variableDeclarator : function(e){ var id = this.visit(e.id); var init = this.visit(e.init); if(init){ id += " = " + init; } return "var " + id; }, memberExpression: function(e){ var obj = this.visit(e.object); var property = this.visit(e.property); if(e.computed){ return obj + "[" + property + "]"; } return obj + "." + property; }, literal: function(e){ return e.raw; }, callExpression: function(e){ var callee = this.visit(e.callee); var args = this.visitArray(e.arguments,', '); return callee + '(' + args + ')'; }, tryStatement: function(e){ var tryBlock = this.visit(e.block); var handler = this.visit(e.handler); var finalizer = ""; if(e.finalizer){ var oldIndent = this.indent; finalizer = "\n" + this.indent; this.indent += "\t"; finalizer += "finally{\n" + this.indent + this.visit(e.finalizer) + "\n"; this.indent = oldIndent; finalizer += this.indent + "}"; } return "try" + this.indent + "\t" + tryBlock + this.indent + "\n" + handler + finalizer; }, catchClause: function(e){ var arg = this.visit(e.param); var body = this.visit(e.body); return "catch (" + arg + ")" + body; }, throwStatement: function(e){ var arg = this.visit(e.argument); return "throw " + arg; }, program: function(e){ return this.visitArray(e.body,';\n' + this.indent); } }; return CBHeaven; })();var acorn = require('acorn'); exports.compile = function (input){ var tree = acorn.parse(input); var p = new ast(tree); tree = p.process(); var cbh = new CallbackHeaven(tree); return cbh.convert(); }