402 lines
14 KiB
Haxe
402 lines
14 KiB
Haxe
/*
|
|
* Copyright (C)2008-2017 Haxe Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
package hscript;
|
|
import hscript.Expr;
|
|
|
|
class Async {
|
|
|
|
var varNames : Array<String>;
|
|
var currentFun : String;
|
|
var currentLoop : hscript.Expr;
|
|
var currentBreak : hscript.Expr;
|
|
var uid = 0;
|
|
|
|
/**
|
|
Convert a script into asynchronous one.
|
|
- calls such as foo(a,b,c) are translated to a_foo(function(r) ...rest, a,b,c) where r is the result value
|
|
- object access such obj.bar(a,b,c) are translated to obj.a_bar(function(r) ...rest, a, b, c)
|
|
- @split { e1; e2; e3; } is transformed to split(function(_) ...rest, [e1, e2, e3]) which
|
|
should execute asynchronously all expressions - until they return - before continuing the execution
|
|
- @async { e1; e2; e3; } is similar to @split but execution will continue immediately without wait
|
|
- for(i in v) block; loops are translated to the following:
|
|
var _i = makeIterator(v);
|
|
function _loop() {
|
|
if( !_i.hasNext() ) return;
|
|
var v = _i.next();
|
|
block(function(_) _loop());
|
|
}
|
|
_loop()
|
|
- while loops are translated similar to for loops
|
|
- break and continue are correctly handled
|
|
- you can use @sync <expr> to disable async transformation in some code parts (for performance reason)
|
|
- a few expressions are still not supported (complex calls, try/catch, and a few others)
|
|
|
|
In these examples ...rest represents the continuation of execution of the script after the expression
|
|
**/
|
|
public static function toAsync( e : Expr, topLevelSync = false ) {
|
|
var a = new Async();
|
|
return a.build(e, topLevelSync);
|
|
}
|
|
|
|
public function build( e : Expr, topLevelSync = false ) {
|
|
if( topLevelSync ) {
|
|
return buildSync(e);
|
|
} else {
|
|
var nothing = EFunction([{ name : "_", t : null }], EBlock([]));
|
|
return new Async().toCps(e, nothing, nothing);
|
|
}
|
|
}
|
|
|
|
function buildSync( e : Expr ) {
|
|
switch( e ) {
|
|
case EFunction(_):
|
|
return toCps(e, null, null);
|
|
case EBlock(el):
|
|
var v = saveVars();
|
|
for( e in el )
|
|
switch( e ) {
|
|
case EFunction(_, _, name, _) if( name != null ): varNames.push(name);
|
|
default:
|
|
}
|
|
var e = EBlock([for(e in el) buildSync(e)]);
|
|
restoreVars(v);
|
|
return e;
|
|
default:
|
|
return hscript.Tools.map(e, buildSync);
|
|
}
|
|
}
|
|
|
|
public function new() {
|
|
varNames = [];
|
|
}
|
|
|
|
function ignore(e) {
|
|
return EFunction([{ name : "_", t : null }], EBlock([e]));
|
|
}
|
|
|
|
function retNull(e) {
|
|
return ECall(e, [EIdent("null")]);
|
|
}
|
|
|
|
function makeCall( ecall, args : Array<hscript.Expr>, rest : hscript.Expr, exit, sync = false ) {
|
|
var names = [for( i in 0...args.length ) "_a"+uid++];
|
|
var rargs = [for( i in 0...args.length ) EIdent(names[i])];
|
|
if( !sync )
|
|
rargs.unshift(rest);
|
|
var rest = sync ? ECall(rest,[ECall(ecall, rargs)]) : ECall(ecall, rargs);
|
|
var i = args.length - 1;
|
|
while( i >= 0 ) {
|
|
rest = toCps(args[i], EFunction([ { name : names[i], t : null } ], rest), exit);
|
|
i--;
|
|
}
|
|
return rest;
|
|
}
|
|
|
|
var syncFlag : Bool;
|
|
|
|
function isSync( e : Expr ) {
|
|
syncFlag = true;
|
|
checkSync(e);
|
|
return syncFlag;
|
|
}
|
|
|
|
function checkSync( e : Expr ) {
|
|
if( !syncFlag )
|
|
return;
|
|
switch( e ) {
|
|
case ECall(_):
|
|
syncFlag = false;
|
|
case EReturn(_):
|
|
// require specific handling to pass it to exit expr
|
|
syncFlag = false;
|
|
default:
|
|
Tools.iter(e, checkSync);
|
|
}
|
|
}
|
|
|
|
function saveVars() {
|
|
return varNames.length;
|
|
}
|
|
|
|
function restoreVars(k) {
|
|
while( varNames.length > k ) varNames.pop();
|
|
}
|
|
|
|
public function toCps( e : hscript.Expr, rest : hscript.Expr, exit : hscript.Expr ) {
|
|
if( isSync(e) )
|
|
return ECall(rest, [e]);
|
|
switch( e ) {
|
|
case EBlock(el):
|
|
var el = el.copy();
|
|
var vold = saveVars();
|
|
// local recursion
|
|
for( e in el )
|
|
switch( e ) {
|
|
case EFunction(_, _, name): varNames.push(name);
|
|
default:
|
|
}
|
|
while( el.length > 0 ) {
|
|
var e = toCps(el.pop(), rest, exit);
|
|
rest = ignore(e);
|
|
}
|
|
restoreVars(vold);
|
|
return retNull(rest);
|
|
case EFunction(args, body, name, t):
|
|
var vold = saveVars();
|
|
if( name != null )
|
|
varNames.push(name);
|
|
for( a in args )
|
|
varNames.push(a.name);
|
|
args.unshift( { name : "_onEnd", t : null } );
|
|
var rest = EIdent("_onEnd");
|
|
var oldFun = currentFun;
|
|
currentFun = name;
|
|
var body = toCps(body, rest, rest);
|
|
var f = EFunction(args, body, "a_" + name, t);
|
|
restoreVars(vold);
|
|
return rest == null ? f : ECall(rest, [f]);
|
|
case EParent(e):
|
|
return EParent(toCps(e, rest, exit));
|
|
case EMeta("sync", _, e):
|
|
return buildSync(e);
|
|
case EMeta(name = "split" | "async", _, e):
|
|
var args = switch( e ) { case EBlock(el): el; default: [e]; };
|
|
var args = [for( a in args ) EFunction([ { name : "_rest", t : null } ], toCps(EBlock([a]), EIdent("_rest"), exit))];
|
|
return ECall(EIdent(name), [rest, EArrayDecl(args)]);
|
|
case ECall(EIdent(i), args):
|
|
return makeCall( EIdent( varNames.indexOf(i) < 0 ? "a_" + i : i) , args, rest, exit);
|
|
case ECall(EField(e, f), args):
|
|
return makeCall(EField(e,"a_"+f), args, rest, exit);
|
|
case EFor(v, eit, e):
|
|
var id = ++uid;
|
|
var it = EIdent("_i" + id);
|
|
var oldLoop = currentLoop, oldBreak = currentBreak;
|
|
var loop = EIdent("_loop" + id);
|
|
currentLoop = loop;
|
|
currentBreak = EBlock([ECall(rest, [EIdent("null")]), EReturn()]);
|
|
var e = EBlock([
|
|
EVar("_i" + id, ECall(EIdent("makeIterator"),[eit])),
|
|
EFunction([{ name : "_", t : null }], EBlock([
|
|
EIf(EUnop("!", true, ECall(EField(it, "hasNext"), [])), currentBreak),
|
|
EVar(v, ECall(EField(it, "next"), [])),
|
|
toCps(e, loop, exit),
|
|
]),"_loop" + id),
|
|
ECall(loop, [EIdent("null")]),
|
|
]);
|
|
currentLoop = oldLoop;
|
|
currentBreak = oldBreak;
|
|
return e;
|
|
case EUnop(op = "!", prefix, e):
|
|
return toCps(e, EFunction([ { name:"_r", t:null } ], ECall(rest, [EUnop(op, prefix, EIdent("_r"))])), exit);
|
|
case EBinop(op, e1, e2):
|
|
switch( op ) {
|
|
case "=", "+=", "-=", "/=", "*=", "%=", "&=", "|=", "^=":
|
|
switch( e1 ) {
|
|
case EIdent(_):
|
|
var id = "_r" + uid++;
|
|
return toCps(e2, EFunction([ { name:id, t:null } ], ECall(rest, [EBinop(op, e1, EIdent(id))])), exit);
|
|
case EField(e1, f):
|
|
var id1 = "_r" + uid++;
|
|
var id2 = "_r" + uid++;
|
|
return toCps(e1, EFunction([ { name:id1, t:null } ], toCps(e2, EFunction([ { name : id2, t : null } ], ECall(rest, [EBinop(op, EField(EIdent(id1),f), EIdent(id2))])), exit)), exit);
|
|
case EArray(earr, eindex):
|
|
var idArr = "_r" + uid++;
|
|
var idIndex = "_r" + uid++;
|
|
var idVal = "_r" + uid++;
|
|
return toCps(earr,
|
|
EFunction([ { name:idArr, t:null } ], toCps(eindex,
|
|
EFunction([ { name : idIndex, t : null } ], toCps(e2,
|
|
EFunction([ { name : idVal, t : null } ],
|
|
ECall(rest, [EBinop(op, EArray(EIdent(idArr), EIdent(idIndex)), EIdent(idVal))])
|
|
), exit)
|
|
), exit)
|
|
),exit);
|
|
default:
|
|
throw "assert " + e1;
|
|
}
|
|
case "||":
|
|
var id1 = "_r" + uid++;
|
|
var id2 = "_r" + uid++;
|
|
return toCps(e1, EFunction([ { name:id1, t:null } ], EIf(EBinop("==", EIdent(id1), EIdent("true")),ECall(rest,[EIdent("true")]),toCps(e2, rest, exit))), exit);
|
|
case "&&":
|
|
var id1 = "_r" + uid++;
|
|
var id2 = "_r" + uid++;
|
|
return toCps(e1, EFunction([ { name:id1, t:null } ], EIf(EBinop("!=", EIdent(id1), EIdent("true")),ECall(rest,[EIdent("false")]),toCps(e2, rest, exit))), exit);
|
|
default:
|
|
var id1 = "_r" + uid++;
|
|
var id2 = "_r" + uid++;
|
|
return toCps(e1, EFunction([ { name:id1, t:null } ], toCps(e2, EFunction([ { name : id2, t : null } ], ECall(rest, [EBinop(op, EIdent(id1), EIdent(id2))])), exit)), exit);
|
|
}
|
|
case EIf(cond, e1, e2), ETernary(cond, e1, e2):
|
|
return toCps(cond, EFunction([ { name : "_c", t : null } ], EIf(EIdent("_c"), toCps(e1, rest, exit), e2 == null ? retNull(rest) : toCps(e2, rest, exit))), exit);
|
|
case EWhile(cond, e):
|
|
var id = ++uid;
|
|
var loop = EIdent("_loop" + id);
|
|
var oldLoop = currentLoop, oldBreak = currentBreak;
|
|
currentLoop = loop;
|
|
currentBreak = EBlock([ECall(rest, [EIdent("null")]), EReturn()]);
|
|
var ewhile = EBlock([
|
|
EFunction([{ name : "_r", t : null }],
|
|
toCps(cond, EFunction([ { name : "_c", t : null } ], EIf(EIdent("_c"), toCps(e, loop, exit), ECall(rest,[EIdent("null")]))), exit)
|
|
, "_loop"+id),
|
|
ECall(loop, [EIdent("null")]),
|
|
]);
|
|
currentLoop = oldLoop;
|
|
currentBreak = oldBreak;
|
|
return ewhile;
|
|
case EReturn(e):
|
|
return e == null ? ECall(exit, [EIdent("null")]) : toCps(e, exit, exit);
|
|
case EObject(fields):
|
|
var id = "_o" + uid++;
|
|
var rest = ECall(rest, [EIdent(id)]);
|
|
fields.reverse();
|
|
for( f in fields )
|
|
rest = toCps(f.e, EFunction([ { name : "_r", t : null } ], EBlock([
|
|
EBinop("=", EField(EIdent(id), f.name), EIdent("_r")),
|
|
rest,
|
|
])),exit);
|
|
return EBlock([
|
|
EVar(id, EObject([])),
|
|
rest,
|
|
]);
|
|
case EArrayDecl(el):
|
|
var id = "_a" + uid++;
|
|
var rest = ECall(rest, [EIdent(id)]);
|
|
var i = el.length - 1;
|
|
while( i >= 0 ) {
|
|
rest = toCps(el[i], EFunction([ { name : "_r", t : null } ], EBlock([
|
|
EBinop("=", EArray(EIdent(id), EConst(CInt(i))), EIdent("_r")),
|
|
rest,
|
|
])), exit);
|
|
i--;
|
|
}
|
|
return EBlock([
|
|
EVar(id, EArrayDecl([])),
|
|
rest,
|
|
]);
|
|
case EArray(e, eindex):
|
|
var id1 = "_r" + uid++;
|
|
var id2 = "_r" + uid++;
|
|
return toCps(e, EFunction([ { name:id1, t:null } ], toCps(eindex, EFunction([ { name : id2, t : null } ], ECall(rest, [EArray(EIdent(id1), EIdent(id2))])), exit)), exit);
|
|
case EVar(v, t, ev):
|
|
if( ev == null )
|
|
return EBlock([e, ECall(rest, [EIdent("null")])]);
|
|
return EBlock([
|
|
EVar(v, t),
|
|
toCps(ev, EFunction([ { name : "_r", t : null } ], EBlock([
|
|
EBinop("=", EIdent(v), EIdent("_r")),
|
|
ECall(rest,[EIdent("null")]),
|
|
])), exit),
|
|
]);
|
|
case EConst(_), EIdent(_), EUnop(_), EField(_):
|
|
return ECall(rest, [e]);
|
|
case ENew(cl, args):
|
|
var names = [for( i in 0...args.length ) "_a"+uid++];
|
|
var rargs = [for( i in 0...args.length ) EIdent(names[i])];
|
|
var rest = ECall(rest,[ENew(cl, rargs)]);
|
|
var i = args.length - 1;
|
|
while( i >= 0 ) {
|
|
rest = toCps(args[i], EFunction([ { name : names[i], t : null } ], rest), exit);
|
|
i--;
|
|
}
|
|
return rest;
|
|
case EBreak:
|
|
if( currentBreak == null ) throw "Break outside loop";
|
|
return currentBreak;
|
|
case EContinue:
|
|
if( currentLoop == null ) throw "Continue outside loop";
|
|
return EBlock([ECall(currentLoop, [EIdent("null")]), EReturn()]);
|
|
case ESwitch(v, cases, def):
|
|
var cases = [for( c in cases ) { values : c.values, expr : toCps(c.expr, rest, exit) } ];
|
|
return toCps(v, EFunction([ { name : "_c", t : null } ], ESwitch(EIdent("_c"), cases, def == null ? retNull(rest) : toCps(def, rest, exit))), exit );
|
|
case EThrow(v):
|
|
return toCps(v, EFunction([ { name : "_v", t : null } ], EThrow(v)), exit);
|
|
//case EDoWhile(_), ETry(_), ECall(_):
|
|
default:
|
|
throw "Unsupported async expression " + Printer.toString(e);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class AsyncInterp extends hscript.Interp {
|
|
|
|
public function setContext( api : Dynamic ) {
|
|
|
|
variables.set("split", split);
|
|
variables.set("async", async);
|
|
variables.set("makeIterator", makeIterator);
|
|
|
|
var c = Type.getClass(api);
|
|
for( f in (c == null ? Reflect.fields(api) : Type.getInstanceFields(c)) ) {
|
|
var fv = Reflect.field(api, f);
|
|
if( !Reflect.isFunction(fv) ) continue;
|
|
if( f.charCodeAt(0) == "_".code ) f = f.substr(1);
|
|
variables.set(f, fv);
|
|
// create the async wrapper if doesn't exists
|
|
if( f.substr(0, 2) != "a_" && !variables.exists("a_"+f) )
|
|
variables.set("a_" + f, Reflect.makeVarArgs(function(args) {
|
|
var onEnd = args.shift();
|
|
onEnd(Reflect.callMethod(api, fv, args));
|
|
}));
|
|
}
|
|
}
|
|
|
|
function async( rest : Dynamic -> Void, args : Array<Dynamic> ) {
|
|
for( a in args )
|
|
a(function(_) {});
|
|
rest(null);
|
|
}
|
|
|
|
function split( rest : Dynamic -> Void, args : Array<Dynamic> ) {
|
|
if( args.length == 0 )
|
|
rest(null);
|
|
else {
|
|
var count = args.length;
|
|
function next(_) {
|
|
if( --count == 0 ) rest(null);
|
|
}
|
|
for( a in args )
|
|
a(next);
|
|
}
|
|
}
|
|
|
|
override function fcall( o : Dynamic, f : String, args : Array<Dynamic> ) : Dynamic {
|
|
var m = Reflect.field(o, f);
|
|
if( m == null ) {
|
|
if( f.substr(0, 2) == "a_" ) {
|
|
m = Reflect.field(o, f.substr(2));
|
|
// fallback on sync version
|
|
if( m != null ) {
|
|
var onEnd = args.shift();
|
|
onEnd(call(o, m, args));
|
|
return null;
|
|
}
|
|
}
|
|
throw o + " has no method " + f;
|
|
}
|
|
return call(o, m, args);
|
|
}
|
|
|
|
}
|