From fdec33d0867ae45fe967cb94bdc2b638688c0406 Mon Sep 17 00:00:00 2001 From: ncannasse Date: Fri, 2 Mar 2018 17:25:43 +0100 Subject: [PATCH] support async transform with -D hscriptPos --- hscript/Async.hx | 261 ++++++++++++++++++++++++++--------------------- hscript/Tools.hx | 12 ++- 2 files changed, 156 insertions(+), 117 deletions(-) diff --git a/hscript/Async.hx b/hscript/Async.hx index 9819129..de3f264 100644 --- a/hscript/Async.hx +++ b/hscript/Async.hx @@ -26,10 +26,21 @@ class Async { var varNames : Array; var currentFun : String; - var currentLoop : hscript.Expr; - var currentBreak : hscript.Expr; + var currentLoop : Expr; + var currentBreak : Expr -> Expr; var uid = 0; + static var nullExpr : Expr = #if hscriptPos { e : null, pmin : 0, pmax : 0, origin : "", line : 0 } #else null #end; + static var nullId = mk(EIdent("null"), nullExpr); + + inline static function expr( e : Expr ) { + return #if hscriptPos e.e #else e #end; + } + + inline static function mk( e, inf : Expr ) : Expr { + return #if hscriptPos { e : e, pmin : inf.pmin, pmax : inf.pmax, origin : inf.origin, line : inf.line } #else e #end; + } + /** 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 @@ -66,38 +77,38 @@ class Async { } } - function buildSync( e : Expr, exit : Expr ) { - switch( e ) { + function buildSync( e : Expr, exit : Expr ) : Expr { + switch( expr(e) ) { case EFunction(_): return toCps(e, null, null); case EBlock(el): var v = saveVars(); for( e in el ) - switch( e ) { + switch( expr(e) ) { case EFunction(_, _, name, _) if( name != null ): varNames.push(name); default: } - var e = EBlock([for(e in el) buildSync(e,exit)]); + var e = block([for(e in el) buildSync(e,exit)], e); restoreVars(v); return e; case EMeta("async", _, e): return toCps(e, ignore(), ignore()); case EBreak if( currentBreak != null ): - return currentBreak; + return currentBreak(e); case EContinue if( currentLoop != null ): - return EBlock([ECall(currentLoop, [EIdent("null")]), EReturn()]); + return block([call(currentLoop, [nullId], e), mk(EReturn(),e)],e); case EFor(_), EWhile(_): var oldLoop = currentLoop, oldBreak = currentBreak; currentLoop = null; currentBreak = null; - e = hscript.Tools.map(e, buildSync.bind(_, exit)); + e = Tools.map(e, buildSync.bind(_, exit)); currentLoop = oldLoop; currentBreak = oldBreak; return e; - case EReturn(e) if( exit != null ): - return EBlock([ECall(exit,[e == null ? EIdent("null") : e]), EReturn()]); + case EReturn(eret) if( exit != null ): + return block([call(exit,[eret == null ? nullId : eret], e), mk(EReturn(),e)], e); default: - return hscript.Tools.map(e, buildSync.bind(_, exit)); + return Tools.map(e, buildSync.bind(_, exit)); } } @@ -105,23 +116,52 @@ class Async { varNames = []; } - function ignore(?e) { - return EFunction([{ name : "_", t : null }], EBlock(e == null ? [] : [e])); + function ignore(?e) : Expr { + var inf = e == null ? nullExpr : e; + return fun("_", block(e == null ? [] : [e],inf)); } - function retNull(e) { - return ECall(e, [EIdent("null")]); + inline function ident(str, e) { + return mk(EIdent(str), e); } - function makeCall( ecall, args : Array, rest : hscript.Expr, exit, sync = false ) { + inline function fun(arg:String, e, ?name) { + return mk(EFunction([{ name : arg, t : null }], e, name), e); + } + + inline function funs(arg:Array, e, ?name) { + return mk(EFunction([for( a in arg ) { name : a, t : null }], e, name), e); + } + + inline function block(arr, e) { + return mk(EBlock(arr), e); + } + + inline function field(e, f, inf) { + return mk(EField(e, f), inf); + } + + inline function binop(op, e1, e2, inf) { + return mk(EBinop(op, e1, e2), inf); + } + + inline function call(e, args, inf) { + return mk(ECall(e, args), inf); + } + + function retNull(e) : Expr { + return call(e, [nullId],e); + } + + function makeCall( ecall, args : Array, rest : 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])]; + var rargs = [for( i in 0...args.length ) ident(names[i],ecall)]; if( !sync ) rargs.unshift(rest); - var rest = sync ? ECall(rest,[ECall(ecall, rargs)]) : ECall(ecall, rargs); + var rest = mk(sync ? ECall(rest,[call(ecall, rargs,ecall)]) : ECall(ecall, rargs), ecall); var i = args.length - 1; while( i >= 0 ) { - rest = toCps(args[i], EFunction([ { name : names[i], t : null } ], rest), exit); + rest = toCps(args[i], fun(names[i], rest), exit); i--; } return rest; @@ -138,7 +178,7 @@ class Async { function checkSync( e : Expr ) { if( !syncFlag ) return; - switch( e ) { + switch( expr(e) ) { case ECall(_), EFunction(_): syncFlag = false; case EMeta("sync" | "async", _, _): @@ -156,16 +196,16 @@ class Async { while( varNames.length > k ) varNames.pop(); } - public function toCps( e : hscript.Expr, rest : hscript.Expr, exit : hscript.Expr ) { + public function toCps( e : Expr, rest : Expr, exit : Expr ) : Expr { if( isSync(e) ) - return ECall(rest, [buildSync(e, exit)]); - switch( e ) { + return call(rest, [buildSync(e, exit)],e); + switch( expr(e) ) { case EBlock(el): var el = el.copy(); var vold = saveVars(); // local recursion for( e in el ) - switch( e ) { + switch( expr(e) ) { case EFunction(_, _, name, null): varNames.push(name); default: } @@ -182,172 +222,165 @@ class Async { for( a in args ) varNames.push(a.name); args.unshift( { name : "_onEnd", t : null } ); - var frest = EIdent("_onEnd"); + var frest = ident("_onEnd",e); var oldFun = currentFun; currentFun = name; var body = toCps(body, frest, frest); - var f = EFunction(args, body, name, t); + var f = mk(EFunction(args, body, name, t),e); restoreVars(vold); - return rest == null ? f : ECall(rest, [f]); + return rest == null ? f : call(rest, [f],e); case EParent(e): - return EParent(toCps(e, rest, exit)); + return mk(EParent(toCps(e, rest, exit)),e); case EMeta("sync", _, e): - return ECall(rest,[buildSync(e,exit)]); + return call(rest,[buildSync(e,exit)],e); case EMeta("async", _, e): var nothing = ignore(); - return EBlock([toCps(e,nothing,nothing),retNull(rest)]); + return block([toCps(e,nothing,nothing),retNull(rest)],e); case EMeta("split", _, e): - var args = switch( e ) { case EArrayDecl(el): el; default: throw "@split expression should be an array"; }; - var args = [for( a in args ) EFunction([ { name : "_rest", t : null } ], toCps(EBlock([a]), EIdent("_rest"), exit))]; - return ECall(EIdent("split"), [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 args = switch( expr(e) ) { case EArrayDecl(el): el; default: throw "@split expression should be an array"; }; + var args = [for( a in args ) fun("_rest", toCps(block([a],a), ident("_rest",a), exit))]; + return call(ident("split",e), [rest, mk(EArrayDecl(args),e)],e); + case ECall(expr(_) => EIdent(i), args): + return makeCall( ident( varNames.indexOf(i) < 0 ? "a_" + i : i,e) , args, rest, exit); + case ECall(expr(_) => EField(e, f), args): + return makeCall(field(e,"a_"+f,e), args, rest, exit); + case EFor(v, eit, eloop): var id = ++uid; - var it = EIdent("_i" + id); + var it = ident("_i" + id,e); var oldLoop = currentLoop, oldBreak = currentBreak; - var loop = EIdent("_loop" + id); + var loop = ident("_loop" + id,e); 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")]), - ]); + currentBreak = function(inf) return block([call(rest, [nullId], inf), mk(EReturn(),inf)], inf); + var efor = block([ + mk(EVar("_i" + id, call(ident("makeIterator",eit),[eit],eit)),eit), + fun("_", block([ + mk(EIf(mk(EUnop("!", true, call( field(it, "hasNext", it), [], it)),it), currentBreak(it)),it), + mk(EVar(v, call(field(it, "next",it), [], it)), it), + toCps(eloop, loop, exit), + ], it),"_loop" + id), + call(loop, [nullId], e), + ], e); 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); + return efor; + case EUnop(op = "!", prefix, eop): + return toCps(eop, fun("_r",call(rest, [mk(EUnop(op, prefix, ident("_r",e)),e)], e)), exit); case EBinop(op, e1, e2): switch( op ) { case "=", "+=", "-=", "/=", "*=", "%=", "&=", "|=", "^=": - switch( e1 ) { + switch( expr(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): + return toCps(e2, fun(id, call(rest, [binop(op, e1, ident(id,e1),e1)], e1)), exit); + case EField(ef1, 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); + return toCps(ef1, fun(id1, toCps(e2, fun(id2, call(rest, [binop(op, field(ident(id1, e1), f, ef1), ident(id2, e2), e)], e)), 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); + return toCps(earr,fun(idArr, toCps(eindex, fun(idIndex, toCps(e2, + fun(idVal, call(rest, [binop(op, mk(EArray(ident(idArr,earr), ident(idIndex,eindex)),e1), ident(idVal,e1), e)], e)) + , 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); + return toCps(e1, fun(id1, mk(EIf(binop("==", ident(id1,e1), ident("true",e1), e1),call(rest,[ident("true",e1)],e1),toCps(e2, rest, exit)),e)), 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); + return toCps(e1, fun(id1, mk(EIf(binop("!=", ident(id1,e1), ident("true",e1), e1),call(rest,[ident("false",e1)],e1),toCps(e2, rest, exit)),e)), 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); + return toCps(e1, fun(id1, toCps(e2, fun(id2, call(rest, [binop(op, ident(id1,e1), ident(id2,e2), e)], e)), 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): + return toCps(cond, fun("_c", mk(EIf(ident("_c",cond), toCps(e1, rest, exit), e2 == null ? retNull(rest) : toCps(e2, rest, exit)),e)), exit); + case EWhile(cond, ewh): var id = ++uid; - var loop = EIdent("_loop" + id); + var loop = ident("_loop" + id, cond); 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) + currentBreak = function(e) return block([call(rest, [nullId], e), mk(EReturn(),e)],e); + var ewhile = block([ + fun("_r", + toCps(cond, fun("_c", mk(EIf(ident("_c", cond), toCps(ewh, loop, exit), call(rest, [nullId], cond)),cond)), exit) , "_loop"+id), - ECall(loop, [EIdent("null")]), - ]); + call(loop, [nullId], cond), + ],e); currentLoop = oldLoop; currentBreak = oldBreak; return ewhile; - case EReturn(e): - return e == null ? ECall(exit, [EIdent("null")]) : toCps(e, exit, exit); + case EReturn(eret): + return eret == null ? call(exit, [nullId], e) : toCps(eret, exit, exit); case EObject(fields): var id = "_o" + uid++; - var rest = ECall(rest, [EIdent(id)]); + var rest = call(rest, [ident(id,e)], e); 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 = toCps(f.e, fun("_r", block([ + binop("=", mk(EField(ident(id,f.e), f.name),f.e), ident("_r",f.e), f.e), rest, - ])),exit); - return EBlock([ - EVar(id, EObject([])), + ],f.e)),exit); + return block([ + mk(EVar(id, mk(EObject([]),e)),e), rest, - ]); + ],e); case EArrayDecl(el): var id = "_a" + uid++; - var rest = ECall(rest, [EIdent(id)]); + var rest = call(rest, [ident(id,e)], e); 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")), + var e = el[i]; + rest = toCps(e, fun("_r", block([ + binop("=", mk(EArray(ident(id,e), mk(EConst(CInt(i)),e)),e), ident("_r",e), e), rest, - ])), exit); + ],e)), exit); i--; } - return EBlock([ - EVar(id, EArrayDecl([])), + return block([ + mk(EVar(id, mk(EArrayDecl([]),e)),e), rest, - ]); - case EArray(e, eindex): + ],e); + case EArray(earr, 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); + return toCps(earr, fun(id1, toCps(eindex, fun(id2, call(rest, [mk(EArray(ident(id1,e), ident(id2,e)),e)], e)), 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), - ]); + return block([e, call(rest, [nullId],e)], e); + return block([ + mk(EVar(v, t),e), + toCps(ev, fun("_r", block([binop("=", ident(v,e), ident("_r",e), e), call(rest,[nullId],e)], e)), exit), + ],e); case EConst(_), EIdent(_), EUnop(_), EField(_): - return ECall(rest, [e]); + return call(rest, [e], 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 rargs = [for( i in 0...args.length ) ident(names[i], args[i])]; + var rest = call(rest,[mk(ENew(cl, rargs),e)],e); var i = args.length - 1; while( i >= 0 ) { - rest = toCps(args[i], EFunction([ { name : names[i], t : null } ], rest), exit); + rest = toCps(args[i], fun(names[i], rest), exit); i--; } return rest; case EBreak: if( currentBreak == null ) throw "Break outside loop"; - return currentBreak; + return currentBreak(e); case EContinue: if( currentLoop == null ) throw "Continue outside loop"; - return EBlock([ECall(currentLoop, [EIdent("null")]), EReturn()]); + return block([call(currentLoop, [nullId], e), mk(EReturn(),e)], e); 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 ); + return toCps(v, mk(EFunction([ { name : "_c", t : null } ], mk(ESwitch(ident("_c",v), cases, def == null ? retNull(rest) : toCps(def, rest, exit)),e)),e), exit ); case EThrow(v): - return toCps(v, EFunction([ { name : "_v", t : null } ], EThrow(v)), exit); + return toCps(v, mk(EFunction([ { name : "_v", t : null } ], mk(EThrow(v),v)), v), exit); //case EDoWhile(_), ETry(_), ECall(_): default: throw "Unsupported async expression " + Printer.toString(e); @@ -357,7 +390,7 @@ class Async { } -class AsyncInterp extends hscript.Interp { +class AsyncInterp extends Interp { public function setContext( api : Dynamic ) { @@ -453,7 +486,7 @@ class AsyncInterp extends hscript.Interp { return result; } } - throw o + " has no method " + f; + error(ECustom(o + " has no method " + f)); } return call(o, m, args); } diff --git a/hscript/Tools.hx b/hscript/Tools.hx index e83e2fe..04c39b5 100644 --- a/hscript/Tools.hx +++ b/hscript/Tools.hx @@ -25,7 +25,7 @@ import hscript.Expr; class Tools { public static function iter( e : Expr, f : Expr -> Void ) { - switch( e ) { + switch( #if hscriptPos e.e #else e #end ) { case EConst(_), EIdent(_): case EVar(_, _, e): if( e != null ) f(e); case EParent(e): f(e); @@ -60,8 +60,12 @@ class Tools { } public static function map( e : Expr, f : Expr -> Expr ) { + #if hscriptPos + var edef = switch( e.e ) { + #else return switch( e ) { - case EConst(_), EIdent(_): e; + #end + case EConst(_), EIdent(_), EBreak, EContinue: #if hscriptPos e.e #else e #end; case EVar(n, t, e): EVar(n, t, if( e != null ) f(e) else null); case EParent(e): EParent(f(e)); case EBlock(el): EBlock([for( e in el ) f(e)]); @@ -73,7 +77,6 @@ class Tools { case EWhile(c, e): EWhile(f(c),f(e)); case EDoWhile(c, e): EDoWhile(f(c),f(e)); case EFor(v, it, e): EFor(v, f(it), f(e)); - case EBreak, EContinue: e; case EFunction(args, e, name, t): EFunction(args, f(e), name, t); case EReturn(e): EReturn(if( e != null ) f(e) else null); case EArray(e, i): EArray(f(e),f(i)); @@ -86,6 +89,9 @@ class Tools { case ESwitch(e, cases, def): ESwitch(f(e), [for( c in cases ) { values : [for( v in c.values ) f(v)], expr : f(c.expr) } ], def == null ? null : f(def)); case EMeta(name, args, e): EMeta(name, args == null ? null : [for( a in args ) f(a)], f(e)); } + #if hscriptPos + return { e : edef, pmin : e.pmin, pmax : e.pmax, origin : e.origin, line : e.line }; + #end } } \ No newline at end of file