diff --git a/hscript/Async.hx b/hscript/Async.hx index 6f220e53..313de3eb 100644 --- a/hscript/Async.hx +++ b/hscript/Async.hx @@ -1,536 +1,536 @@ -/* - * 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; - - -enum VarMode { - Defined; - ForceSync; -} - -class Async { - - var definedVars : Array<{ n : String, prev : Null }>; - var vars : Map; - var currentFun : String; - var currentLoop : Expr; - var currentBreak : Expr -> Expr; - var uid = 0; - public var asyncIdents : Map; - - 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 - - object access such obj.bar(a,b,c) are translated to obj.a_bar(function(r) ...rest, a, b, c) - - @async expr will execute the expression but continue without waiting for it to finish - - @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 - - 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 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 dynamic function getTopLevelEnd() { - return ignore(); - } - - public function build( e : Expr, topLevelSync = false ) { - if( topLevelSync ) { - return buildSync(e,null); - } else { - var end = getTopLevelEnd(); - return toCps(e, end, end); - } - } - - function defineVar( v : String, mode ) { - definedVars.push({ n : v, prev : vars.get(v) }); - vars.set(v, mode); - } - - function lookupFunctions( el : Array ) { - for( e in el ) - switch( expr(e) ) { - case EFunction(_, _, name, _) if( name != null ): defineVar(name, Defined); - case EMeta("sync",_,expr(_) => EFunction(_,_,name,_)) if( name != null ): defineVar(name, ForceSync); - default: - } - } - - function buildSync( e : Expr, exit : Expr ) : Expr { - switch( expr(e) ) { - case EFunction(_,_,name,_): - if( name != null ) - return toCps(e, null, null); - return e; - case EBlock(el): - var v = saveVars(); - lookupFunctions(el); - 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 EMeta("sync", args, ef = expr(_) => EFunction(fargs, body, name, ret)): - return mk(EMeta("sync",args,mk(EFunction(fargs, buildSync(body,null), name, ret),ef)),e); - case EBreak if( currentBreak != null ): - return currentBreak(e); - case EContinue if( currentLoop != null ): - return block([retNull(currentLoop, e), mk(EReturn(),e)],e); - case EFor(_), EWhile(_): - var oldLoop = currentLoop, oldBreak = currentBreak; - currentLoop = null; - currentBreak = null; - e = Tools.map(e, buildSync.bind(_, exit)); - currentLoop = oldLoop; - currentBreak = oldBreak; - return e; - case EReturn(eret) if( exit != null ): - return block([eret == null ? retNull(exit, e) : call(exit,[eret], e), mk(EReturn(),e)], e); - default: - return Tools.map(e, buildSync.bind(_, exit)); - } - } - - public function new() { - vars = new Map(); - definedVars = []; - } - - function ignore(?e) : Expr { - var inf = e == null ? nullExpr : e; - return fun("_", block(e == null ? [] : [e],inf)); - } - - inline function ident(str, e) { - return mk(EIdent(str), e); - } - - inline function fun(arg:String, e, ?name) { - return mk(EFunction([{ name : arg, t : null, opt: false, value: null }], e, name), e); - } - - inline function funs(arg:Array, e, ?name) { - return mk(EFunction([for( a in arg ) { name : a, t : null, opt: false, value: null }], e, name), e); - } - - inline function block(arr:Array, e) { - if( arr.length == 1 && expr(arr[0]).match(EBlock(_)) ) - return arr[0]; - 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,?pos) : Expr { - switch( expr(e) ) { - case EFunction([{name:"_"}], e, _, _): return e; - default: - } - return call(e, [nullId], pos == null ? e : pos); - } - - 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 ) ident(names[i],ecall)]; - if( !sync ) - rargs.unshift(rest); - 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], fun(names[i], rest), exit); - i--; - } - return rest; - } - - var syncFlag : Bool; - - function isSync( e : Expr ) { - syncFlag = true; - checkSync(e); - return syncFlag; - } - - inline function isAsyncIdent( id : String ) { - return asyncIdents == null || asyncIdents.exists(id); - } - - function checkSync( e : Expr ) { - if( !syncFlag ) - return; - switch( expr(e) ) { - case ECall(expr(_) => EIdent(i),_) if( isAsyncIdent(i) || vars.get(i) == Defined ): - syncFlag = false; - case ECall(expr(_) => EField(_,i),_) if( isAsyncIdent(i) ): - syncFlag = false; - case EFunction(_,_,name,_) if( name != null ): - syncFlag = false; - case EMeta("sync" | "async", _, _): - // isolated from the sync part - default: - Tools.iter(e, checkSync); - } - } - - function saveVars() { - return definedVars.length; - } - - function restoreVars(k) { - while( definedVars.length > k ) { - var v = definedVars.pop(); - if( v.prev == null ) vars.remove(v.n) else vars.set(v.n, v.prev); - } - } - - public function toCps( e : Expr, rest : Expr, exit : Expr ) : Expr { - if( isSync(e) ) - return call(rest, [buildSync(e, exit)],e); - switch( expr(e) ) { - case EBlock(el): - var el = el.copy(); - var vold = saveVars(); - lookupFunctions(el); - 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 ) - defineVar(name, Defined); - for( a in args ) - defineVar(a.name, Defined); - args.unshift( { name : "_onEnd", t : null, opt: false, value: null } ); - var frest = ident("_onEnd",e); - var oldFun = currentFun; - currentFun = name; - var body = toCps(body, frest, frest); - var f = mk(EFunction(args, body, name, t),e); - restoreVars(vold); - return rest == null ? f : call(rest, [f],e); - case EParent(e): - return mk(EParent(toCps(e, rest, exit)),e); - case EMeta("sync", _, e): - return call(rest,[buildSync(e,exit)],e); - case EMeta("async", _, e): - var nothing = ignore(); - return block([toCps(e,nothing,nothing),retNull(rest)],e); - case EMeta("split", _, 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): - var mode = vars.get(i); - return makeCall( ident( mode != null ? i : "a_" + i,e) , args, rest, exit, mode == ForceSync); - 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 = ident("_i" + id,e); - var oldLoop = currentLoop, oldBreak = currentBreak; - var loop = ident("_loop" + id,e); - currentLoop = loop; - currentBreak = function(inf) return block([retNull(rest, 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), - retNull(loop, e), - ], e); - currentLoop = oldLoop; - currentBreak = oldBreak; - 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( expr(e1) ) { - case EIdent(_): - var id = "_r" + uid++; - 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(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,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, 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, 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, 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, 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 = ident("_loop" + id, cond); - var oldLoop = currentLoop, oldBreak = currentBreak; - currentLoop = loop; - currentBreak = function(e) return block([retNull(rest,e), mk(EReturn(),e)],e); - var ewhile = block([ - fun("_r", - toCps(cond, fun("_c", mk(EIf(ident("_c", cond), toCps(ewh, loop, exit), retNull(rest,cond)),cond)), exit) - , "_loop"+id), - retNull(loop, cond), - ],e); - currentLoop = oldLoop; - currentBreak = oldBreak; - return ewhile; - case EReturn(eret): - return eret == null ? retNull(exit, e) : toCps(eret, exit, exit); - case EObject(fields): - var id = "_o" + uid++; - var rest = call(rest, [ident(id,e)], e); - fields.reverse(); - for( f in fields ) - 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, - ],f.e)),exit); - return block([ - mk(EVar(id, mk(EObject([]),e)),e), - rest, - ],e); - case EArrayDecl(el): - var id = "_a" + uid++; - var rest = call(rest, [ident(id,e)], e); - var i = el.length - 1; - while( i >= 0 ) { - 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, - ],e)), exit); - i--; - } - return block([ - mk(EVar(id, mk(EArrayDecl([]),e)),e), - rest, - ],e); - case EArray(earr, eindex): - var id1 = "_r" + uid++; - var id2 = "_r" + uid++; - 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 block([e, retNull(rest, e)], e); - return block([ - mk(EVar(v, t),e), - toCps(ev, fun("_r", block([binop("=", ident(v,e), ident("_r",e), e), retNull(rest,e)], e)), exit), - ],e); - case EConst(_), EIdent(_), EUnop(_), EField(_): - 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 ) 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], fun(names[i], rest), exit); - i--; - } - return rest; - case EBreak: - if( currentBreak == null ) throw "Break outside loop"; - return currentBreak(e); - case EContinue: - if( currentLoop == null ) throw "Continue outside loop"; - return block([retNull(currentLoop, e), mk(EReturn(),e)], e); - case ESwitch(v, cases, def): - var cases:Array = [for( c in cases ) { values : c.values, expr : toCps(c.expr, rest, exit) } ]; - return toCps(v, mk(EFunction([ { name : "_c", t : null, opt: false, value: null } ], mk(ESwitch(ident("_c",v), cases, def == null ? retNull(rest) : toCps(def, rest, exit)),e)),e), exit ); - case EThrow(v): - return toCps(v, mk(EFunction([ { name : "_v", t : null, opt: false, value: null } ], mk(EThrow(v),v)), v), exit); - case EMeta(name,_,e) if( name.charCodeAt(0) == ":".code ): // ignore custom ":" metadata - return toCps(e, rest, exit); - //case EDoWhile(_), ETry(_), ECall(_): - default: - throw "Unsupported async expression " + Printer.toString(e); - } - } - -} - - -class AsyncInterp extends Interp { - - public function setContext( api : Dynamic ) { - - var funs = []; - for( v in variables.keys() ) - if( Reflect.isFunction(variables.get(v)) ) - funs.push({ v : v, obj : null }); - - variables.set("split", split); - 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_" ) - funs.push({ v : f, obj : api }); - } - - for( v in funs ) { - if( variables.exists("a_" + v.v) ) continue; - var fv : Dynamic = variables.get(v.v); - var obj = v.obj; - variables.set("a_" + v.v, Reflect.makeVarArgs(function(args:Array) { - var onEnd = args.shift(); - onEnd(Reflect.callMethod(obj, fv, args)); - })); - } - } - - public function hasMethod( name : String ) { - var v = variables.get(name); - return v != null && Reflect.isFunction(v); - } - - public function callValue( value : Dynamic, args : Array, ?onResult : Dynamic -> Void, ?vthis : {} ) { - var oldThis = variables.get("this"); - if( vthis != null ) - variables.set("this", vthis); - if( onResult == null ) - onResult = function(_) {}; - args.unshift(onResult); - Reflect.callMethod(null, value, args); - variables.set("this", oldThis); - } - - public function callAsync( id : String, args, ?onResult, ?vthis : {} ) { - var v = variables.get(id); - if( v == null ) - throw "Missing function " + id + "()"; - callValue(v, args, onResult, vthis); - } - - function split( rest : Dynamic -> Void, args : Array ) { - 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 { - 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; - } - // fallback on generic script - m = Reflect.field(o, "scriptCall"); - if( m != null ) { - call(o, m, [args.shift(), f.substr(2), args]); - return null; - } - } else { - // fallback on generic script - m = Reflect.field(o, "scriptCall"); - if( m != null ) { - var result : Dynamic = null; - call(o, m, [function(r) result = r, f, args]); - return result; - } - } - error(ECustom(o + " has no method " + f)); - } - return call(o, m, args); - } - -} +/* + * 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; + + +enum VarMode { + Defined; + ForceSync; +} + +class Async { + + var definedVars : Array<{ n : String, prev : Null }>; + var vars : Map; + var currentFun : String; + var currentLoop : Expr; + var currentBreak : Expr -> Expr; + var uid = 0; + public var asyncIdents : Map; + + 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 + - object access such obj.bar(a,b,c) are translated to obj.a_bar(function(r) ...rest, a, b, c) + - @async expr will execute the expression but continue without waiting for it to finish + - @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 + - 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 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 dynamic function getTopLevelEnd() { + return ignore(); + } + + public function build( e : Expr, topLevelSync = false ) { + if( topLevelSync ) { + return buildSync(e,null); + } else { + var end = getTopLevelEnd(); + return toCps(e, end, end); + } + } + + function defineVar( v : String, mode ) { + definedVars.push({ n : v, prev : vars.get(v) }); + vars.set(v, mode); + } + + function lookupFunctions( el : Array ) { + for( e in el ) + switch( expr(e) ) { + case EFunction(_, _, name, _) if( name != null ): defineVar(name, Defined); + case EMeta("sync",_,expr(_) => EFunction(_,_,name,_)) if( name != null ): defineVar(name, ForceSync); + default: + } + } + + function buildSync( e : Expr, exit : Expr ) : Expr { + switch( expr(e) ) { + case EFunction(_,_,name,_): + if( name != null ) + return toCps(e, null, null); + return e; + case EBlock(el): + var v = saveVars(); + lookupFunctions(el); + 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 EMeta("sync", args, ef = expr(_) => EFunction(fargs, body, name, ret)): + return mk(EMeta("sync",args,mk(EFunction(fargs, buildSync(body,null), name, ret),ef)),e); + case EBreak if( currentBreak != null ): + return currentBreak(e); + case EContinue if( currentLoop != null ): + return block([retNull(currentLoop, e), mk(EReturn(),e)],e); + case EFor(_), EWhile(_): + var oldLoop = currentLoop, oldBreak = currentBreak; + currentLoop = null; + currentBreak = null; + e = Tools.map(e, buildSync.bind(_, exit)); + currentLoop = oldLoop; + currentBreak = oldBreak; + return e; + case EReturn(eret) if( exit != null ): + return block([eret == null ? retNull(exit, e) : call(exit,[eret], e), mk(EReturn(),e)], e); + default: + return Tools.map(e, buildSync.bind(_, exit)); + } + } + + public function new() { + vars = new Map(); + definedVars = []; + } + + function ignore(?e) : Expr { + var inf = e == null ? nullExpr : e; + return fun("_", block(e == null ? [] : [e],inf)); + } + + inline function ident(str, e) { + return mk(EIdent(str), e); + } + + inline function fun(arg:String, e, ?name) { + return mk(EFunction([{ name : arg, t : null, opt: false, value: null }], e, name), e); + } + + inline function funs(arg:Array, e, ?name) { + return mk(EFunction([for( a in arg ) { name : a, t : null, opt: false, value: null }], e, name), e); + } + + inline function block(arr:Array, e) { + if( arr.length == 1 && expr(arr[0]).match(EBlock(_)) ) + return arr[0]; + return mk(EBlock(arr), e); + } + + inline function field(e, f, inf) { + return mk(EField(e, f), inf); + } + + inline function binop(op:Binop, 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,?pos) : Expr { + switch( expr(e) ) { + case EFunction([{name:"_"}], e, _, _): return e; + default: + } + return call(e, [nullId], pos == null ? e : pos); + } + + 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 ) ident(names[i],ecall)]; + if( !sync ) + rargs.unshift(rest); + 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], fun(names[i], rest), exit); + i--; + } + return rest; + } + + var syncFlag : Bool; + + function isSync( e : Expr ) { + syncFlag = true; + checkSync(e); + return syncFlag; + } + + inline function isAsyncIdent( id : String ) { + return asyncIdents == null || asyncIdents.exists(id); + } + + function checkSync( e : Expr ) { + if( !syncFlag ) + return; + switch( expr(e) ) { + case ECall(expr(_) => EIdent(i),_) if( isAsyncIdent(i) || vars.get(i) == Defined ): + syncFlag = false; + case ECall(expr(_) => EField(_,i),_) if( isAsyncIdent(i) ): + syncFlag = false; + case EFunction(_,_,name,_) if( name != null ): + syncFlag = false; + case EMeta("sync" | "async", _, _): + // isolated from the sync part + default: + Tools.iter(e, checkSync); + } + } + + function saveVars() { + return definedVars.length; + } + + function restoreVars(k) { + while( definedVars.length > k ) { + var v = definedVars.pop(); + if( v.prev == null ) vars.remove(v.n) else vars.set(v.n, v.prev); + } + } + + public function toCps( e : Expr, rest : Expr, exit : Expr ) : Expr { + if( isSync(e) ) + return call(rest, [buildSync(e, exit)],e); + switch( expr(e) ) { + case EBlock(el): + var el = el.copy(); + var vold = saveVars(); + lookupFunctions(el); + 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 ) + defineVar(name, Defined); + for( a in args ) + defineVar(a.name, Defined); + args.unshift( { name : "_onEnd", t : null, opt: false, value: null } ); + var frest = ident("_onEnd",e); + var oldFun = currentFun; + currentFun = name; + var body = toCps(body, frest, frest); + var f = mk(EFunction(args, body, name, t),e); + restoreVars(vold); + return rest == null ? f : call(rest, [f],e); + case EParent(e): + return mk(EParent(toCps(e, rest, exit)),e); + case EMeta("sync", _, e): + return call(rest,[buildSync(e,exit)],e); + case EMeta("async", _, e): + var nothing = ignore(); + return block([toCps(e,nothing,nothing),retNull(rest)],e); + case EMeta("split", _, 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): + var mode = vars.get(i); + return makeCall( ident( mode != null ? i : "a_" + i,e) , args, rest, exit, mode == ForceSync); + 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 = ident("_i" + id,e); + var oldLoop = currentLoop, oldBreak = currentBreak; + var loop = ident("_loop" + id,e); + currentLoop = loop; + currentBreak = function(inf) return block([retNull(rest, 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), + retNull(loop, e), + ], e); + currentLoop = oldLoop; + currentBreak = oldBreak; + 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 OpAssign, OpAddAssign, OpSubAssign, OpDivAssign, OpMultAssign, OpModAssign, OpAndAssign, OpOrAssign, OpXorAssign: + switch( expr(e1) ) { + case EIdent(_): + var id = "_r" + uid++; + 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(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,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 OpBoolOr: + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(e1, fun(id1, mk(EIf(binop(OpEq, ident(id1,e1), ident("true",e1), e1),call(rest,[ident("true",e1)],e1),toCps(e2, rest, exit)),e)), exit); + case OpBoolAnd: + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + return toCps(e1, fun(id1, mk(EIf(binop(OpNeq, 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, 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, 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 = ident("_loop" + id, cond); + var oldLoop = currentLoop, oldBreak = currentBreak; + currentLoop = loop; + currentBreak = function(e) return block([retNull(rest,e), mk(EReturn(),e)],e); + var ewhile = block([ + fun("_r", + toCps(cond, fun("_c", mk(EIf(ident("_c", cond), toCps(ewh, loop, exit), retNull(rest,cond)),cond)), exit) + , "_loop"+id), + retNull(loop, cond), + ],e); + currentLoop = oldLoop; + currentBreak = oldBreak; + return ewhile; + case EReturn(eret): + return eret == null ? retNull(exit, e) : toCps(eret, exit, exit); + case EObject(fields): + var id = "_o" + uid++; + var rest = call(rest, [ident(id,e)], e); + fields.reverse(); + for( f in fields ) + rest = toCps(f.e, fun("_r", block([ + binop(OpAssign, mk(EField(ident(id,f.e), f.name),f.e), ident("_r",f.e), f.e), + rest, + ],f.e)),exit); + return block([ + mk(EVar(id, mk(EObject([]),e)),e), + rest, + ],e); + case EArrayDecl(el): + var id = "_a" + uid++; + var rest = call(rest, [ident(id,e)], e); + var i = el.length - 1; + while( i >= 0 ) { + var e = el[i]; + rest = toCps(e, fun("_r", block([ + binop(OpAssign, mk(EArray(ident(id,e), mk(EConst(CInt(i)),e)),e), ident("_r",e), e), + rest, + ],e)), exit); + i--; + } + return block([ + mk(EVar(id, mk(EArrayDecl([]),e)),e), + rest, + ],e); + case EArray(earr, eindex): + var id1 = "_r" + uid++; + var id2 = "_r" + uid++; + 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 block([e, retNull(rest, e)], e); + return block([ + mk(EVar(v, t),e), + toCps(ev, fun("_r", block([binop(OpAssign, ident(v,e), ident("_r",e), e), retNull(rest,e)], e)), exit), + ],e); + case EConst(_), EIdent(_), EUnop(_), EField(_): + 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 ) 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], fun(names[i], rest), exit); + i--; + } + return rest; + case EBreak: + if( currentBreak == null ) throw "Break outside loop"; + return currentBreak(e); + case EContinue: + if( currentLoop == null ) throw "Continue outside loop"; + return block([retNull(currentLoop, e), mk(EReturn(),e)], e); + case ESwitch(v, cases, def): + var cases:Array = [for( c in cases ) { values : c.values, expr : toCps(c.expr, rest, exit) } ]; + return toCps(v, mk(EFunction([ { name : "_c", t : null, opt: false, value: null } ], mk(ESwitch(ident("_c",v), cases, def == null ? retNull(rest) : toCps(def, rest, exit)),e)),e), exit ); + case EThrow(v): + return toCps(v, mk(EFunction([ { name : "_v", t : null, opt: false, value: null } ], mk(EThrow(v),v)), v), exit); + case EMeta(name,_,e) if( name.charCodeAt(0) == ":".code ): // ignore custom ":" metadata + return toCps(e, rest, exit); + //case EDoWhile(_), ETry(_), ECall(_): + default: + throw "Unsupported async expression " + Printer.toString(e); + } + } + +} + + +class AsyncInterp extends Interp { + + public function setContext( api : Dynamic ) { + + var funs = []; + for( v in variables.keys() ) + if( Reflect.isFunction(variables.get(v)) ) + funs.push({ v : v, obj : null }); + + variables.set("split", split); + 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_" ) + funs.push({ v : f, obj : api }); + } + + for( v in funs ) { + if( variables.exists("a_" + v.v) ) continue; + var fv : Dynamic = variables.get(v.v); + var obj = v.obj; + variables.set("a_" + v.v, Reflect.makeVarArgs(function(args:Array) { + var onEnd = args.shift(); + onEnd(Reflect.callMethod(obj, fv, args)); + })); + } + } + + public function hasMethod( name : String ) { + var v = variables.get(name); + return v != null && Reflect.isFunction(v); + } + + public function callValue( value : Dynamic, args : Array, ?onResult : Dynamic -> Void, ?vthis : {} ) { + var oldThis = variables.get("this"); + if( vthis != null ) + variables.set("this", vthis); + if( onResult == null ) + onResult = function(_) {}; + args.unshift(onResult); + Reflect.callMethod(null, value, args); + variables.set("this", oldThis); + } + + public function callAsync( id : String, args, ?onResult, ?vthis : {} ) { + var v = variables.get(id); + if( v == null ) + throw "Missing function " + id + "()"; + callValue(v, args, onResult, vthis); + } + + function split( rest : Dynamic -> Void, args : Array ) { + 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 { + 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; + } + // fallback on generic script + m = Reflect.field(o, "scriptCall"); + if( m != null ) { + call(o, m, [args.shift(), f.substr(2), args]); + return null; + } + } else { + // fallback on generic script + m = Reflect.field(o, "scriptCall"); + if( m != null ) { + var result : Dynamic = null; + call(o, m, [function(r) result = r, f, args]); + return result; + } + } + error(ECustom(o + " has no method " + f)); + } + return call(o, m, args); + } + +} diff --git a/hscript/Bytes.hx b/hscript/Bytes.hx index b423d0ec..27b2ec0c 100644 --- a/hscript/Bytes.hx +++ b/hscript/Bytes.hx @@ -156,7 +156,7 @@ class Bytes { doEncode(e); doEncodeString(f); case EBinop(op,e1,e2): - doEncodeString(op); + doEncodeString(op.toString()); doEncode(e1); doEncode(e2); case EUnop(op,prefix,e): @@ -278,7 +278,7 @@ class Bytes { var e = doDecode(); EField(e,doDecodeString()); case 6: - var op = doDecodeString(); + var op = Binop.fromString(doDecodeString()); var e1 = doDecode(); EBinop(op,e1,doDecode()); case 7: diff --git a/hscript/Checker.hx b/hscript/Checker.hx index 9999b21c..a19425ea 100644 --- a/hscript/Checker.hx +++ b/hscript/Checker.hx @@ -1113,11 +1113,11 @@ class Checker { return TVoid; case EBinop(op, e1, e2): switch( op ) { - case "&", "|", "^", ">>", ">>>", "<<": + case OpAnd, OpOr, OpXor, OpShr, OpUshr, OpShl: typeExprWith(e1,TInt); typeExprWith(e2,TInt); return TInt; - case "=": + case OpAssign: if( allowDefine ) { switch( edef(e1) ) { case EIdent(i) if( !locals.exists(i) && !globals.exists(i) ): @@ -1133,7 +1133,7 @@ class Checker { } typeExprWith(e2,vt); return vt; - case "+": + case OpAdd: var t1 = typeExpr(e1,WithType(TInt)); var t2 = typeExpr(e2,WithType(t1)); tryUnify(t1,t2); @@ -1150,14 +1150,14 @@ class Checker { unify(t1, TFloat, e1); unify(t2, TFloat, e2); } - case "-", "*", "/", "%": + case OpSub, OpMult, OpDiv, OpMod: var t1 = typeExpr(e1,WithType(TInt)); var t2 = typeExpr(e2,WithType(t1)); if( !tryUnify(t1,t2) ) unify(t2,t1,e2); switch( [follow(t1), follow(t2)]) { case [TInt, TInt]: - if( op == "/" ) return TFloat; + if( op == OpDiv ) return TFloat; return TInt; case [TFloat|TDynamic, TInt|TDynamic], [TInt|TDynamic, TFloat|TDynamic], [TFloat, TFloat]: return TFloat; @@ -1165,21 +1165,21 @@ class Checker { unify(t1, TFloat, e1); unify(t2, TFloat, e2); } - case "&&", "||": + case OpBoolAnd, OpBoolOr: typeExprWith(e1,TBool); typeExprWith(e2,TBool); return TBool; - case "...": + case OpInterval: typeExprWith(e1,TInt); typeExprWith(e2,TInt); return makeIterator(TInt); - case "==", "!=": + case OpEq, OpNeq: var t1 = typeExpr(e1,Value); var t2 = typeExpr(e2,WithType(t1)); if( !tryUnify(t1,t2) ) unify(t2,t1,e2); return TBool; - case ">", "<", ">=", "<=": + case OpGt, OpLt, OpGte, OpLte: var t1 = typeExpr(e1,Value); var t2 = typeExpr(e2,WithType(t1)); if( !tryUnify(t1,t2) ) @@ -1190,12 +1190,26 @@ class Checker { error("Cannot compare "+typeStr(t1), expr); } return TBool; + case OpAddAssign, OpSubAssign, OpMultAssign, OpDivAssign, OpModAssign, OpAndAssign, OpOrAssign, OpXorAssign, OpShlAssign, OpShrAssign, OpUshrAssign, OpNcoalAssign: + var baseOp = switch(op) { + case OpAddAssign: OpAdd; + case OpSubAssign: OpSub; + case OpMultAssign: OpMult; + case OpDivAssign: OpDiv; + case OpModAssign: OpMod; + case OpAndAssign: OpAnd; + case OpOrAssign: OpOr; + case OpXorAssign: OpXor; + case OpShlAssign: OpShl; + case OpShrAssign: OpShr; + case OpUshrAssign: OpUshr; + case OpNcoalAssign: OpNcoal; + default: op; + }; + var t = typeExpr(mk(EBinop(baseOp,e1,e2),expr),withType); + return typeExpr(mk(EBinop(OpAssign,e1,e2),expr), withType); default: - if( op.charCodeAt(op.length-1) == "=".code ) { - var t = typeExpr(mk(EBinop(op.substr(0,op.length-1),e1,e2),expr),withType); - return typeExpr(mk(EBinop("=",e1,e2),expr), withType); - } - error("Unsupported operation "+op, expr); + error("Unsupported operation "+op.toString(), expr); } case ETry(etry, v, et, ecatch): var vt = typeExpr(etry, withType); diff --git a/hscript/Expr.hx b/hscript/Expr.hx index 1d626598..f8c79682 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -28,6 +28,163 @@ typedef Int64 = #if cpp cpp.Int64 #elseif java java.Int64 #elseif cs cs.Int64 #e typedef UInt8 = #if cpp cpp.UInt8 #elseif cs cs.UInt8 #else Int #end; typedef UInt16 = #if cpp cpp.UInt16 #elseif cs cs.UInt16 #else Int #end; + +enum abstract Binop(Int) from Int to Int { + var OpAdd = 0; + var OpSub = 1; + var OpMult = 2; + var OpDiv = 3; + var OpMod = 4; + var OpAnd = 5; + var OpOr = 6; + var OpXor = 7; + var OpShl = 8; + var OpShr = 9; + var OpUshr = 10; + var OpEq = 11; + var OpNeq = 12; + var OpGte = 13; + var OpLte = 14; + var OpGt = 15; + var OpLt = 16; + var OpBoolOr = 17; + var OpBoolAnd = 18; + var OpIs = 19; + var OpAssign = 20; + var OpNcoal = 21; + var OpInterval = 22; + var OpArrow = 23; + var OpAddAssign = 24; + var OpSubAssign = 25; + var OpMultAssign = 26; + var OpDivAssign = 27; + var OpModAssign = 28; + var OpAndAssign = 29; + var OpOrAssign = 30; + var OpXorAssign = 31; + var OpShlAssign = 32; + var OpShrAssign = 33; + var OpUshrAssign = 34; + var OpNcoalAssign = 35; + var OpArrowFn = 36; + + public static inline function fromString(s:String):Binop { + return switch(s) { + case "+": OpAdd; + case "-": OpSub; + case "*": OpMult; + case "/": OpDiv; + case "%": OpMod; + case "&": OpAnd; + case "|": OpOr; + case "^": OpXor; + case "<<": OpShl; + case ">>": OpShr; + case ">>>": OpUshr; + case "==": OpEq; + case "!=": OpNeq; + case ">=": OpGte; + case "<=": OpLte; + case ">": OpGt; + case "<": OpLt; + case "||": OpBoolOr; + case "&&": OpBoolAnd; + case "is": OpIs; + case "=": OpAssign; + case "??": OpNcoal; + case "...": OpInterval; + case "->": OpArrow; + case "=>": OpArrowFn; + case "+=": OpAddAssign; + case "-=": OpSubAssign; + case "*=": OpMultAssign; + case "/=": OpDivAssign; + case "%=": OpModAssign; + case "&=": OpAndAssign; + case "|=": OpOrAssign; + case "^=": OpXorAssign; + case "<<=": OpShlAssign; + case ">>=": OpShrAssign; + case ">>>=": OpUshrAssign; + case "??=": OpNcoalAssign; + default: -1; + } + } + + public inline function toString():String { + return switch(this) { + case OpAdd: "+"; + case OpSub: "-"; + case OpMult: "*"; + case OpDiv: "/"; + case OpMod: "%"; + case OpAnd: "&"; + case OpOr: "|"; + case OpXor: "^"; + case OpShl: "<<"; + case OpShr: ">>"; + case OpUshr: ">>>"; + case OpEq: "=="; + case OpNeq: "!="; + case OpGte: ">="; + case OpLte: "<="; + case OpGt: ">"; + case OpLt: "<"; + case OpBoolOr: "||"; + case OpBoolAnd: "&&"; + case OpIs: "is"; + case OpAssign: "="; + case OpNcoal: "??"; + case OpInterval: "..."; + case OpArrow: "->"; + case OpArrowFn: "=>"; + case OpAddAssign: "+="; + case OpSubAssign: "-="; + case OpMultAssign: "*="; + case OpDivAssign: "/="; + case OpModAssign: "%="; + case OpAndAssign: "&="; + case OpOrAssign: "|="; + case OpXorAssign: "^="; + case OpShlAssign: "<<="; + case OpShrAssign: ">>="; + case OpUshrAssign: ">>>="; + case OpNcoalAssign: "??="; + default: "?"; + } + } +} + +enum abstract Unop(Int) from Int to Int { + var OpNot = 0; + var OpNeg = 1; + var OpIncrement = 2; + var OpDecrement = 3; + var OpNegBits = 4; + + public static inline function fromString(s:String):Unop { + return switch(s) { + case "!": OpNot; + case "-": OpNeg; + case "++": OpIncrement; + case "--": OpDecrement; + case "~": OpNegBits; + default: -1; + } + } + + public inline function toString():String { + return switch(this) { + case OpNot: "!"; + case OpNeg: "-"; + case OpIncrement: "++"; + case OpDecrement: "--"; + case OpNegBits: "~"; + default: "?"; + } + } +} + typedef UInt32 = #if cpp cpp.UInt32 #else Int #end; typedef UInt64 = #if cpp cpp.UInt64 #else Int #end; @@ -57,8 +214,8 @@ enum Expr { EParent( e : Expr ); EBlock( e : Array ); EField( e : Expr, f : String , ?safe : Bool ); - EBinop( op : String, e1 : Expr, e2 : Expr ); - EUnop( op : String, prefix : Bool, e : Expr ); + EBinop( op : Binop, e1 : Expr, e2 : Expr ); + EUnop( op : Unop, prefix : Bool, e : Expr ); ECall( e : Expr, params : Array ); EIf( cond : Expr, e1 : Expr, ?e2 : Expr ); EWhile( cond : Expr, e : Expr ); diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 35b41765..7d8785dd 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -55,6 +55,20 @@ enum abstract ScriptObjectType(UInt8) { var SNull; } +enum abstract VarLocation(UInt8) { + var VGlobal; + var VPublic; + var VStatic; + var VScriptObject; + var VScriptObjectGetter; + var VCustomClass; + var VCustomClassBypass; + var VBehaviourClass; + var VAccessBehaviour; + var VAccessBehaviourBypass; + var VNotFound; +} + @:structInit class DeclaredVar { public var r:Dynamic; @@ -130,7 +144,6 @@ class Interp { // warning can be null public var locals:Map; - var binops:StringMapExpr->Dynamic>; var depth:Int = 0; var inTry:Bool; @@ -154,6 +167,9 @@ class Interp { var usingHandler:UsingHandler; + var varLocationCache:Map = new Map(); + var cacheValid:Bool = true; + #if hscriptPos var curExpr:Expr; #end @@ -162,7 +178,6 @@ class Interp { locals = new Map(); declared = []; resetVariables(); - initOps(); } private function resetVariables():Void { @@ -193,49 +208,6 @@ class Interp { return cast {fileName: "hscript", lineNumber: 0}; } - function initOps():Void { - var me = this; - binops = new StringMap Expr -> Dynamic>(); - binops.set("+", function(e1, e2) return me.expr(e1) + me.expr(e2)); - binops.set("-", function(e1, e2) return me.expr(e1) - me.expr(e2)); - binops.set("*", function(e1, e2) return me.expr(e1) * me.expr(e2)); - binops.set("/", function(e1, e2) return me.expr(e1) / me.expr(e2)); - binops.set("%", function(e1, e2) return me.expr(e1) % me.expr(e2)); - binops.set("&", function(e1, e2) return me.expr(e1) & me.expr(e2)); - binops.set("|", function(e1, e2) return me.expr(e1) | me.expr(e2)); - binops.set("^", function(e1, e2) return me.expr(e1) ^ me.expr(e2)); - binops.set("<<", function(e1, e2) return me.expr(e1) << me.expr(e2)); - binops.set(">>", function(e1, e2) return me.expr(e1) >> me.expr(e2)); - binops.set(">>>", function(e1, e2) return me.expr(e1) >>> me.expr(e2)); - binops.set("==", function(e1, e2) return me.expr(e1) == me.expr(e2)); - binops.set("!=", function(e1, e2) return me.expr(e1) != me.expr(e2)); - binops.set(">=", function(e1, e2) return me.expr(e1) >= me.expr(e2)); - binops.set("<=", function(e1, e2) return me.expr(e1) <= me.expr(e2)); - binops.set(">", function(e1, e2) return me.expr(e1) > me.expr(e2)); - binops.set("<", function(e1, e2) return me.expr(e1) < me.expr(e2)); - binops.set("||", function(e1, e2) return me.expr(e1) == true || me.expr(e2) == true); - binops.set("&&", function(e1, e2) return me.expr(e1) == true && me.expr(e2) == true); - binops.set("is", checkIsType); - binops.set("=", assign); - binops.set("??", function(e1, e2) { - var expr1:Dynamic = me.expr(e1); - return expr1 == null ? me.expr(e2) : expr1; - }); - binops.set("...", function(e1, e2) return new IntIterator(me.expr(e1), me.expr(e2))); - assignOp("+=", function(v1:Dynamic, v2:Dynamic) return v1 + v2); - assignOp("-=", function(v1:Float, v2:Float) return v1 - v2); - assignOp("*=", function(v1:Float, v2:Float) return v1 * v2); - assignOp("/=", function(v1:Float, v2:Float) return v1 / v2); - assignOp("%=", function(v1:Float, v2:Float) return v1 % v2); - assignOp("&=", function(v1, v2) return v1 & v2); - assignOp("|=", function(v1, v2) return v1 | v2); - assignOp("^=", function(v1, v2) return v1 ^ v2); - assignOp("<<=", function(v1, v2) return v1 << v2); - assignOp(">>=", function(v1, v2) return v1 >> v2); - assignOp(">>>=", function(v1, v2) return v1 >>> v2); - assignOp("??" + "=", function(v1, v2) return v1 == null ? v2 : v1); - } - function checkIsType(e1:Expr,e2:Expr): Bool { var expr1:Dynamic = expr(e1); @@ -312,6 +284,7 @@ class Interp { } else if (__instanceFields.contains('set_$id')) { // setter return UnsafeReflect.getProperty(scriptObject, 'set_$id')(v); } else { + varLocationCache.remove(id); setVar(id, v); } } else { @@ -320,6 +293,7 @@ class Interp { var prop:Property = cast obj; return prop.callSetter(id, v); } + varLocationCache.remove(id); setVar(id, v); } } else if (l.r is Property) { @@ -328,6 +302,7 @@ class Interp { } else { l.r = v; if (l.depth == 0) { + varLocationCache.remove(id); setVar(id, v); } } @@ -351,12 +326,7 @@ class Interp { return v; } - function assignOp(op:String, fop:Dynamic->Dynamic->Dynamic):Void { - var me = this; - binops.set(op, function(e1, e2) return me.evalAssignOp(op, fop, e1, e2)); - } - - function evalAssignOp(op:String, fop:Dynamic->Dynamic->Dynamic, e1:Expr, e2:Expr):Dynamic { + function evalAssignOp(op:Binop, fop:Dynamic->Dynamic->Dynamic, e1:Expr, e2:Expr):Dynamic { var v; switch (Tools.expr(e1)) { case EIdent(id): @@ -395,6 +365,7 @@ class Interp { } else if (__instanceFields.contains('set_$id')) { // setter return UnsafeReflect.getProperty(scriptObject, 'set_$id')(v); } else { + varLocationCache.remove(id); setVar(id, v); } } else { @@ -403,6 +374,7 @@ class Interp { var prop:Property = cast obj; return prop.callSetter(id, v); } + varLocationCache.remove(id); setVar(id, v); } } @@ -414,6 +386,7 @@ class Interp { } l.r = v; if (l.depth == 0) { + varLocationCache.remove(id); setVar(id, v); } } @@ -435,7 +408,7 @@ class Interp { arr[index] = v; } default: - return error(EInvalidOp(op)); + return error(EInvalidOp(op.toString())); } return v; } @@ -468,6 +441,7 @@ class Interp { else l.r = v + delta; } + if (l.depth == 0) varLocationCache.remove(id); return v; } else { var v:Dynamic = resolve(id, true, false); @@ -481,13 +455,17 @@ class Interp { v += delta; if (prop != null) prop.callSetter(id, v); - else + else { + varLocationCache.remove(id); setVar(id, v); + } } else { if (prop != null) prop.callSetter(id, v + delta); - else + else { + varLocationCache.remove(id); setVar(id, v + delta); + } } return v; } @@ -540,34 +518,37 @@ class Interp { function exprReturn(e):Dynamic { try { - try { - return expr(e); - } catch (e:Stop) { - switch (e) { - case SBreak: - throw "Invalid break"; - case SContinue: - throw "Invalid continue"; - case SReturn: - var v = returnValue; - returnValue = null; - return v; - } - } catch(e) { - if(printCallStack) - error(ECustom('${e.toString()}\n${CallStack.toString(CallStack.exceptionStack(true))}')); - else - error(ECustom(e.toString())); - return null; + return expr(e); + } catch (e:Stop) { + switch (e) { + case SBreak: + throw "Invalid break"; + case SContinue: + throw "Invalid continue"; + case SReturn: + var v = returnValue; + returnValue = null; + return v; } - } catch(e:Error) { - if (errorHandler != null) + } catch (e:Error) { + if (errorHandler != null) { errorHandler(e); - else + } else { + throw e; + } + return null; + } catch (e:Dynamic) { + var errStr = printCallStack ? Std.string(e) + "\n" + CallStack.toString(CallStack.exceptionStack(true)) : Std.string(e); + if (errorHandler != null) { + #if hscriptPos + errorHandler(new Error(ECustom(errStr), curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line)); + #else + errorHandler(new Error(ECustom(errStr))); + #end + } else { throw e; + } return null; - } catch(e) { - trace(e); } return null; } @@ -644,12 +625,53 @@ class Interp { } } - if (variables.exists(id)) + if(cacheValid) { + var loc = varLocationCache.get(id); + if(loc != null) { + return switch(loc) { + case VGlobal: getProperty(variables.get(id), id, allowProperty); + case VPublic: getProperty(publicVariables.get(id), id, allowProperty); + case VStatic: getProperty(staticVariables.get(id), id, allowProperty); + case VScriptObject: isBypassAccessor ? UnsafeReflect.field(scriptObject, id) : UnsafeReflect.getProperty(scriptObject, id); + case VScriptObjectGetter: UnsafeReflect.getProperty(scriptObject, 'get_$id')(); + case VCustomClass: (cast scriptObject:IHScriptCustomAccessBehaviour).hget(id); + case VCustomClassBypass: + var obj:IHScriptCustomAccessBehaviour = cast scriptObject; + obj.__allowSetGet = false; + var res = obj.hget(id); + obj.__allowSetGet = true; + res; + case VBehaviourClass: (cast scriptObject:IHScriptCustomBehaviour).hget(id); + case VAccessBehaviour: (cast scriptObject:IHScriptCustomAccessBehaviour).hget(id); + case VAccessBehaviourBypass: + var obj:IHScriptCustomAccessBehaviour = cast scriptObject; + obj.__allowSetGet = false; + var res = obj.hget(id); + obj.__allowSetGet = true; + res; + case VNotFound: + var cl = Type.resolveClass(id); + if(cl != null) return cl; + var en = Type.resolveEnum(id); + if(en != null) return en; + if (doException) error(EUnknownVariable(id)); + null; + } + } + } + + if (variables.exists(id)) { + varLocationCache.set(id, VGlobal); return getProperty(variables.get(id), id, allowProperty); - if (publicVariables.exists(id)) + } + if (publicVariables.exists(id)) { + varLocationCache.set(id, VPublic); return getProperty(publicVariables.get(id), id, allowProperty); - if (staticVariables.exists(id)) + } + if (staticVariables.exists(id)) { + varLocationCache.set(id, VStatic); return getProperty(staticVariables.get(id), id, allowProperty); + } if(customClasses.exists(id)) return customClasses.get(id); @@ -662,31 +684,39 @@ class Interp { var instanceHasField = __instanceFields.contains(id); if (_scriptObjectType == SObject && instanceHasField) { + varLocationCache.set(id, VScriptObject); return UnsafeReflect.field(scriptObject, id); } else if((_scriptObjectType == SCustomClass && instanceHasField) || _scriptObjectType == SAccessBehaviourObject) { var obj:IHScriptCustomAccessBehaviour = cast scriptObject; if(isBypassAccessor) { + varLocationCache.set(id, VCustomClassBypass); obj.__allowSetGet = false; var res = obj.hget(id); obj.__allowSetGet = true; return res; } + varLocationCache.set(id, VCustomClass); return obj.hget(id); } else if(_scriptObjectType == SBehaviourClass) { + varLocationCache.set(id, VBehaviourClass); var obj:IHScriptCustomBehaviour = cast scriptObject; return obj.hget(id); } if (instanceHasField) { if(isBypassAccessor) { + varLocationCache.set(id, VScriptObject); return UnsafeReflect.field(scriptObject, id); } else { + varLocationCache.set(id, VScriptObject); return UnsafeReflect.getProperty(scriptObject, id); } } else if (__instanceFields.contains('get_$id')) { // getter return UnsafeReflect.getProperty(scriptObject, 'get_$id')(); } } + + varLocationCache.set(id, VNotFound); var cl = Type.resolveClass(id); if(cl != null) return cl; var en = Type.resolveEnum(id); @@ -696,6 +726,15 @@ class Interp { return null; } + public function invalidateCache():Void { + varLocationCache = new Map(); + cacheValid = true; + } + + public function setCacheValid(valid:Bool):Void { + cacheValid = valid; + } + public static var importRedirects:Map = new Map(); public static function getImportRedirect(className:String):String { return importRedirects.exists(className) ? importRedirects.get(className) : className; @@ -982,6 +1021,7 @@ class Interp { }; locals.set(n, declVar); if (depth == 0) { + varLocationCache.remove(n); if(allowStaticVariables && isStatic == true) { if(!staticVariables.exists(n)) // make it so it only sets it once staticVariables.set(n, locals[n].r); @@ -1031,24 +1071,55 @@ class Interp { } return get(field, f); case EBinop(op, e1, e2): - var fop = binops.get(op); - if (fop == null) - error(EInvalidOp(op)); - return fop(e1, e2); + return switch(op) { + case OpAdd: expr(e1) + expr(e2); + case OpSub: expr(e1) - expr(e2); + case OpMult: expr(e1) * expr(e2); + case OpDiv: expr(e1) / expr(e2); + case OpMod: expr(e1) % expr(e2); + case OpAnd: expr(e1) & expr(e2); + case OpOr: expr(e1) | expr(e2); + case OpXor: expr(e1) ^ expr(e2); + case OpShl: expr(e1) << expr(e2); + case OpShr: expr(e1) >> expr(e2); + case OpUshr: expr(e1) >>> expr(e2); + case OpEq: expr(e1) == expr(e2); + case OpNeq: expr(e1) != expr(e2); + case OpGte: expr(e1) >= expr(e2); + case OpLte: expr(e1) <= expr(e2); + case OpGt: expr(e1) > expr(e2); + case OpLt: expr(e1) < expr(e2); + case OpBoolOr: expr(e1) == true || expr(e2) == true; + case OpBoolAnd: expr(e1) == true && expr(e2) == true; + case OpIs: checkIsType(e1, e2); + case OpAssign: assign(e1, e2); + case OpNcoal: + var expr1:Dynamic = expr(e1); + expr1 == null ? expr(e2) : expr1; + case OpInterval: new IntIterator(expr(e1), expr(e2)); + case OpArrow: null; + case OpAddAssign: evalAssignOp(OpAddAssign, function(v1:Dynamic, v2:Dynamic) return v1 + v2, e1, e2); + case OpSubAssign: evalAssignOp(OpSubAssign, function(v1:Float, v2:Float) return v1 - v2, e1, e2); + case OpMultAssign: evalAssignOp(OpMultAssign, function(v1:Float, v2:Float) return v1 * v2, e1, e2); + case OpDivAssign: evalAssignOp(OpDivAssign, function(v1:Float, v2:Float) return v1 / v2, e1, e2); + case OpModAssign: evalAssignOp(OpModAssign, function(v1:Float, v2:Float) return v1 % v2, e1, e2); + case OpAndAssign: evalAssignOp(OpAndAssign, function(v1, v2) return v1 & v2, e1, e2); + case OpOrAssign: evalAssignOp(OpOrAssign, function(v1, v2) return v1 | v2, e1, e2); + case OpXorAssign: evalAssignOp(OpXorAssign, function(v1, v2) return v1 ^ v2, e1, e2); + case OpShlAssign: evalAssignOp(OpShlAssign, function(v1, v2) return v1 << v2, e1, e2); + case OpShrAssign: evalAssignOp(OpShrAssign, function(v1, v2) return v1 >> v2, e1, e2); + case OpUshrAssign: evalAssignOp(OpUshrAssign, function(v1, v2) return v1 >>> v2, e1, e2); + case OpNcoalAssign: evalAssignOp(OpNcoalAssign, function(v1, v2) return v1 == null ? v2 : v1, e1, e2); + default: error(EInvalidOp(op.toString())); + } case EUnop(op, prefix, e): switch (op) { - case "!": - return expr(e) != true; - case "-": - return -expr(e); - case "++": - return increment(e, prefix, 1); - case "--": - return increment(e, prefix, -1); - case "~": - return ~expr(e); - default: - error(EInvalidOp(op)); + case OpNot: return expr(e) != true; + case OpNeg: return -expr(e); + case OpIncrement: return increment(e, prefix, 1); + case OpDecrement: return increment(e, prefix, -1); + case OpNegBits: return ~expr(e); + default: error(EInvalidOp(op.toString())); } case ECall(e, params): var args:Array = makeArgs(params); @@ -1189,7 +1260,7 @@ class Interp { } if (!isMap && arr.length > 0) { - isMap = Tools.expr(arr[0]).match(EBinop("=>", _)); + isMap = Tools.expr(arr[0]).match(EBinop(OpArrowFn, _)); } // TODO: separate this into a function @@ -1203,7 +1274,7 @@ class Interp { for (e in arr) { switch (Tools.expr(e)) { - case EBinop("=>", eKey, eValue): + case EBinop(OpArrowFn, eKey, eValue): var key:Dynamic = expr(eKey); var value:Dynamic = expr(eValue); isAllString = isAllString && (key is String); @@ -1395,7 +1466,7 @@ class Interp { restore(old); } - function makeIterator(v:Dynamic, ?allowKeyValue = false):Iterator { + inline function makeIterator(v:Dynamic, ?allowKeyValue = false):Iterator { #if js // don't use try/catch (very slow) if(v is Array) { @@ -1417,7 +1488,7 @@ class Interp { return v; } - function makeArgs(params:Array):Array { + inline function makeArgs(params:Array):Array { var args:Array = []; for (p in params) { switch (Tools.expr(p)) { @@ -1437,7 +1508,7 @@ class Interp { return args; } - function forLoop(n:String, it:Expr, e:Expr, ?ithv:String):Void { + inline function forLoop(n:String, it:Expr, e:Expr, ?ithv:String):Void { var isKeyValue = ithv != null; var old = declared.length; if(isKeyValue) diff --git a/hscript/Macro.hx b/hscript/Macro.hx index 708f507e..38e3a9bf 100644 --- a/hscript/Macro.hx +++ b/hscript/Macro.hx @@ -151,8 +151,8 @@ class Macro { case EField(e, f): EField(convert(e), f); case EBinop(op, e1, e2): - var b = binops.get(op); - if( b == null ) throw EInvalidOp(op); + var b = binops.get(op.toString()); + if( b == null ) throw EInvalidOp(op.toString()); EBinop(b, convert(e1), convert(e2)); case EUnop(op, prefix, e): var u = unops.get(op); diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 9d83eab2..33ae19fe 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -604,27 +604,28 @@ class Parser { return switch( expr(e) ) { case EBinop(bop, e1, e2): mk(EBinop(bop, makeUnop(op, e1), e2), pmin(e1), pmax(e2)); case ETernary(e1, e2, e3): mk(ETernary(makeUnop(op, e1), e2, e3), pmin(e1), pmax(e3)); - default: mk(EUnop(op,true,e),pmin(e),pmax(e)); + default: mk(EUnop(Unop.fromString(op),true,e),pmin(e),pmax(e)); } } @:analyzer(fusion) inline function makeBinop( op:String, e1:Expr, e:Expr ):Expr { + var binop = Binop.fromString(op); if( e == null && resumeErrors ) - return mk(EBinop(op,e1,e),pmin(e1),pmax(e1)); + return mk(EBinop(binop,e1,e),pmin(e1),pmax(e1)); return switch( expr(e) ) { case EBinop(op2,e2,e3): - var delta = opPriority.get(op) - opPriority.get(op2); + var delta = opPriority.get(op) - opPriority.get(op2.toString()); if( delta < 0 || (delta == 0 && !opRightAssoc.exists(op)) ) mk(EBinop(op2,makeBinop(op,e1,e2),e3),pmin(e1),pmax(e3)); else - mk(EBinop(op, e1, e), pmin(e1), pmax(e)); + mk(EBinop(binop, e1, e), pmin(e1), pmax(e)); case ETernary(e2,e3,e4): if( opRightAssoc.exists(op) ) - mk(EBinop(op,e1,e),pmin(e1),pmax(e)); + mk(EBinop(binop,e1,e),pmin(e1),pmax(e)); else mk(ETernary(makeBinop(op, e1, e2), e3, e4), pmin(e1), pmax(e)); default: - mk(EBinop(op,e1,e),pmin(e1),pmax(e)); + mk(EBinop(binop,e1,e),pmin(e1),pmax(e)); } } @@ -1385,7 +1386,7 @@ class Parser { push(tk); return e1; } - return parseExprNext(mk(EUnop(op,false,e1),pmin(e1))); + return parseExprNext(mk(EUnop(Unop.fromString(op),false,e1),pmin(e1))); } return makeBinop(op,e1,parseExpr()); case TId(op) if( opPriority.exists(op) ): @@ -1737,7 +1738,7 @@ class Parser { var expr:Null = exprs.shift(); while(true) { if(exprs.length == 0) break; - expr = mk(EBinop('+', expr, exprs.shift())); + expr = mk(EBinop(OpAdd, expr, exprs.shift())); } return expr; } @@ -2421,7 +2422,7 @@ class Parser { } mk(EIdent(buf.toString()), tokenMin, tokenMax); case TOp("!"): - mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax); + mk(EUnop(OpNot, true, parsePreproCond()), tokenMin, tokenMax); default: unexpected(tk); } @@ -2439,13 +2440,13 @@ class Parser { error(EInvalidPreprocessor("Can't eval " + expr(e).getName() + " with " + expr(e2).getName()), readPos, readPos); return false; } - case EUnop("!", _, e): + case EUnop(OpNot, _, e): return !evalPreproCond(e); case EParent(e): return evalPreproCond(e); - case EBinop("&&", e1, e2): + case EBinop(OpBoolAnd, e1, e2): return evalPreproCond(e1) && evalPreproCond(e2); - case EBinop("||", e1, e2): + case EBinop(OpBoolOr, e1, e2): return evalPreproCond(e1) || evalPreproCond(e2); default: error(EInvalidPreprocessor("Can't eval " + expr(e).getName()), readPos, readPos); diff --git a/hscript/Printer.hx b/hscript/Printer.hx index b153fddf..8861f474 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -1,494 +1,494 @@ -/* - * 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 Printer { - - var buf : StringBuf; - var tabs : String; - - public function new() { - } - - public function exprToString( e : Expr ):String { - buf = new StringBuf(); - tabs = ""; - expr(e); - return buf.toString(); - } - - public function typeToString( t : CType ):String { - buf = new StringBuf(); - tabs = ""; - type(t); - return buf.toString(); - } - - inline function add(s:T):Void buf.add(s); - - function type( t : CType ):Void { - switch( t ) { - case CTOpt(t): - add('?'); - type(t); - case CTPath(path, params): - add(path.join(".")); - if( params != null ) { - add("<"); - var first = true; - for( p in params ) { - if( first ) first = false else add(", "); - type(p); - } - add(">"); - } - case CTNamed(name, t): - add(name); - add(':'); - type(t); - case CTFun(args, ret) if (Lambda.exists(args, function (a) return a.match(CTNamed(_, _)))): - add('('); - for (a in args) - switch a { - case CTNamed(_, _): type(a); - default: type(CTNamed('_', a)); - } - add(')->'); - type(ret); - case CTFun(args, ret): - if( args.length == 0 ) - add("Void -> "); - else { - for( a in args ) { - type(a); - add(" -> "); - } - } - type(ret); - case CTAnon(fields): - add("{"); - var first = true; - for( f in fields ) { - if( first ) { first = false; add(" "); } else add(", "); - add(f.name + " : "); - type(f.t); - } - add(first ? "}" : " }"); - case CTParent(t): - add("("); - type(t); - add(")"); - case CTExpr(e): - expr(e); - } - } - - function addType( t : CType ):Void { - if( t != null ) { - add(" : "); - type(t); - } - } - - function expr( e : Expr ):Void { - if( e == null ) { - add("??NULL??"); - return; - } - switch(Tools.expr(e)) { - case EPackage(n): - add('package'); - if(n != null) - add(' $n'); - add(';\n'); - case EImport(c, n, u): - add('${u ? 'using' : 'import'} $c'); - if(n != null) - add(' as $n'); - case EClass(name, fields, extend, interfaces, fnal): - var isFinal = fnal != null && fnal; - if(isFinal) - add('final '); - add('class $name'); - if (extend != null) - add(' extends $extend'); - for(_interface in interfaces) { - add(' implements $_interface'); - } - tabs += "\t"; - add(" {\n"); - for( e in fields ) { - add(tabs); - expr(e); - //add(";\n"); - } - //for(field in fields) { - // expr(field); - //} - - tabs = tabs.substr(1); - add("}"); - case EEnum(en, isAbstract): - if(isAbstract) { - add('enum abstract ${en.name}('); - if(en.underlyingType != null) - type(en.underlyingType); - else - add('Int'); - add(')'); - if(en.fields.length == 0) { - add(' {}'); - return; - } - tabs += "\t"; - add(" {\n"); - for(e in en.fields) { - add(tabs); - add(e.name); - if(e.value != null) { - add(" = "); - expr(e.value); - } - add(";\n"); - } - tabs = tabs.substr(1); - add("}"); - } else { - add('enum ${en.name}'); - if(en.fields.length == 0) { - add(' {}'); - return; - } - tabs += "\t"; - add(" {\n"); - for(e in en.fields) { - add(tabs); - add(e.name); - if(e.args.length > 0) { - add("("); - var first = true; - for( a in e.args ) { - if( first ) first = false else add(", "); - if( a.opt ) add("?"); - add(a.name); - addType(a.t); - } - add(')'); - } - add(";\n"); - } - tabs = tabs.substr(1); - add("}"); - } - case ECast(e, t): - var safe = t != null; - add("cast "); - if(safe) add("("); - expr(e); - if(safe) { - add(", "); - addType(t); - add(")"); - } - case ERegex(e, f): - add('~/$e/$f'); - add(';\n'); - case EConst(c): - switch( c ) { - case CInt(i): add(i); - case CFloat(f): add(f); - case CString(s): add('"'); add(s.split('"').join('\\"').split("\n").join("\\n").split("\r").join("\\r").split("\t").join("\\t")); add('"'); - } - case EIdent(v): - add(v); - case EVar(n, t, e, p, s, pr, isFinal, isInline, get, set, _): - if(p) add("public "); - else if(pr) add("private "); - if(s) add("static "); - if(isInline) add("inline "); - if(isFinal) add("final " + n); - else add("var " + n); - - if(get != null || set != null) { - add("("); - switch(get) { - case ADefault: add("default, "); - case ANull: add("null, "); - case AGet: add("get, "); - case ADynamic: add("dynamic, "); - case ANever: add("never, "); - default: - } - switch(set) { - case ADefault: add("default"); - case ANull: add("null"); - case ASet: add("set"); - case ADynamic: add("dynamic"); - case ANever: add("never"); - default: - } - add(")"); - } - - addType(t); - if( e != null ) { - add(" = "); - expr(e); - } - case EParent(e): - add("("); expr(e); add(")"); - case EBlock(el): - if( el.length == 0 ) { - add("{}"); - } else { - tabs += "\t"; - add("{\n"); - for( e in el ) { - add(tabs); - expr(e); - add(";\n"); - } - tabs = tabs.substr(1); - add("}"); - } - case EField(e, f, s): - expr(e); - add((s == true ? "?." : ".") + f); - case EBinop(op, e1, e2): - expr(e1); - add(" " + op + " "); - expr(e2); - case EUnop(op, pre, e): - if( pre ) { - add(op); - expr(e); - } else { - expr(e); - add(op); - } - case ECall(e, args): - if( e == null ) - expr(e); - else switch( Tools.expr(e)) { - case EField(_), EIdent(_), EConst(_): - expr(e); - default: - add("("); - expr(e); - add(")"); - } - add("("); - var first = true; - for( a in args ) { - if( first ) first = false else add(", "); - expr(a); - } - add(")"); - case EIf(cond,e1,e2): - add("if( "); - expr(cond); - add(" ) "); - expr(e1); - if( e2 != null ) { - add(" else "); - expr(e2); - } - case EWhile(cond,e): - add("while( "); - expr(cond); - add(" ) "); - expr(e); - case EDoWhile(cond,e): - add("do "); - expr(e); - add(" while ( "); - expr(cond); - add(" )"); - case EFor(v, it, e, ithv): - if(ithv != null) - add("for( "+ithv+" => "+v+" in "); - else - add("for( "+v+" in "); - expr(it); - add(" ) "); - expr(e); - case EBreak: - add("break"); - case EContinue: - add("continue"); - case EFunction(params, e, name, ret): // TODO: static, public, override - add("function"); - if( name != null ) - add(" " + name); - add("("); - var first = true; - for( a in params ) { - if( first ) first = false else add(", "); - if( a.opt ) add("?"); - add(a.name); - addType(a.t); - } - add(")"); - addType(ret); - add(" "); - expr(e); - case EReturn(e): - add("return"); - if( e != null ) { - add(" "); - expr(e); - } - case EArray(e,index): - expr(e); - add("["); - expr(index); - add("]"); - case EArrayDecl(el, _): - add("["); - var first = true; - for( e in el ) { - if( first ) first = false else add(", "); - expr(e); - } - add("]"); - case ENew(cl, args, params): - add("new " + cl); - if(params != null) { - add("<"); - var first = true; - for( p in params ) { - if( first ) first = false else add(", "); - type(p); - } - add(">"); - } - add("("); - var first = true; - for( e in args ) { - if( first ) first = false else add(", "); - expr(e); - } - add(")"); - case EThrow(e): - add("throw "); - expr(e); - case ETry(e, v, t, ecatch): - add("try "); - expr(e); - add(" catch( " + v); - addType(t); - add(") "); - expr(ecatch); - case EObject(fl): - if( fl.length == 0 ) { - add("{}"); - } else { - tabs += "\t"; - add("{\n"); - for( f in fl ) { - add(tabs); - add(f.name+" : "); - expr(f.e); - add(",\n"); - } - tabs = tabs.substr(1); - add("}"); - } - case ETernary(c,e1,e2): - expr(c); - add(" ? "); - expr(e1); - add(" : "); - expr(e2); - case ESwitch(e, cases, def): - add("switch( "); - expr(e); - add(") {"); - for( c in cases ) { - add("case "); - var first = true; - for( v in c.values ) { - if( first ) first = false else add(", "); - expr(v); - } - add(": "); - expr(c.expr); - add(";\n"); - } - if( def != null ) { - add("default: "); - expr(def); - add(";\n"); - } - add("}"); - case EMeta(name, args, e): - add("@"); - add(name); - if( args != null && args.length > 0 ) { - add("("); - var first = true; - for( a in args ) { - if( first ) first = false else add(", "); - expr(e); - } - add(")"); - } - add(" "); - expr(e); - case ECheckType(e, t): - add("("); - expr(e); - add(" : "); - addType(t); - add(")"); - } - } - - public static function toString( e : Expr ):String { - return new Printer().exprToString(e); - } - - public static function errorToString( e : Expr.Error ):String { - var message = switch( #if hscriptPos e.e #else e #end ) { - case EInvalidChar(c): "Invalid character: '"+(StringTools.isEof(c) ? "EOF (End Of File)" : String.fromCharCode(c))+"' ("+c+")"; - case EUnexpected(s): "Unexpected token: \""+s+"\""; - case EUnterminatedString: "Unterminated string"; - case EUnterminatedComment: "Unterminated comment"; - case EInvalidPreprocessor(str): "Invalid preprocessor (" + str + ")"; - case EUnknownVariable(v): "Unknown variable: "+v; - case EInvalidIterator(v): "Invalid iterator: "+v; - case EInvalidOp(op): "Invalid operator: "+op; - case EInvalidAccess(f): "Invalid access to field " + f; - case ECustom(msg): msg; - case EInvalidClass(cla): "Invalid class: " + cla + " was not found."; - case EAlreadyExistingClass(cla): 'Custom Class named $cla already exists.'; - }; - #if hscriptPos - return e.origin + ":" + e.line + ": " + message; - #else - return message; - #end - } - - -} +/* + * 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 Printer { + + var buf : StringBuf; + var tabs : String; + + public function new() { + } + + public function exprToString( e : Expr ):String { + buf = new StringBuf(); + tabs = ""; + expr(e); + return buf.toString(); + } + + public function typeToString( t : CType ):String { + buf = new StringBuf(); + tabs = ""; + type(t); + return buf.toString(); + } + + inline function add(s:T):Void buf.add(s); + + function type( t : CType ):Void { + switch( t ) { + case CTOpt(t): + add('?'); + type(t); + case CTPath(path, params): + add(path.join(".")); + if( params != null ) { + add("<"); + var first = true; + for( p in params ) { + if( first ) first = false else add(", "); + type(p); + } + add(">"); + } + case CTNamed(name, t): + add(name); + add(':'); + type(t); + case CTFun(args, ret) if (Lambda.exists(args, function (a) return a.match(CTNamed(_, _)))): + add('('); + for (a in args) + switch a { + case CTNamed(_, _): type(a); + default: type(CTNamed('_', a)); + } + add(')->'); + type(ret); + case CTFun(args, ret): + if( args.length == 0 ) + add("Void -> "); + else { + for( a in args ) { + type(a); + add(" -> "); + } + } + type(ret); + case CTAnon(fields): + add("{"); + var first = true; + for( f in fields ) { + if( first ) { first = false; add(" "); } else add(", "); + add(f.name + " : "); + type(f.t); + } + add(first ? "}" : " }"); + case CTParent(t): + add("("); + type(t); + add(")"); + case CTExpr(e): + expr(e); + } + } + + function addType( t : CType ):Void { + if( t != null ) { + add(" : "); + type(t); + } + } + + function expr( e : Expr ):Void { + if( e == null ) { + add("??NULL??"); + return; + } + switch(Tools.expr(e)) { + case EPackage(n): + add('package'); + if(n != null) + add(' $n'); + add(';\n'); + case EImport(c, n, u): + add('${u ? 'using' : 'import'} $c'); + if(n != null) + add(' as $n'); + case EClass(name, fields, extend, interfaces, fnal): + var isFinal = fnal != null && fnal; + if(isFinal) + add('final '); + add('class $name'); + if (extend != null) + add(' extends $extend'); + for(_interface in interfaces) { + add(' implements $_interface'); + } + tabs += "\t"; + add(" {\n"); + for( e in fields ) { + add(tabs); + expr(e); + //add(";\n"); + } + //for(field in fields) { + // expr(field); + //} + + tabs = tabs.substr(1); + add("}"); + case EEnum(en, isAbstract): + if(isAbstract) { + add('enum abstract ${en.name}('); + if(en.underlyingType != null) + type(en.underlyingType); + else + add('Int'); + add(')'); + if(en.fields.length == 0) { + add(' {}'); + return; + } + tabs += "\t"; + add(" {\n"); + for(e in en.fields) { + add(tabs); + add(e.name); + if(e.value != null) { + add(" = "); + expr(e.value); + } + add(";\n"); + } + tabs = tabs.substr(1); + add("}"); + } else { + add('enum ${en.name}'); + if(en.fields.length == 0) { + add(' {}'); + return; + } + tabs += "\t"; + add(" {\n"); + for(e in en.fields) { + add(tabs); + add(e.name); + if(e.args.length > 0) { + add("("); + var first = true; + for( a in e.args ) { + if( first ) first = false else add(", "); + if( a.opt ) add("?"); + add(a.name); + addType(a.t); + } + add(')'); + } + add(";\n"); + } + tabs = tabs.substr(1); + add("}"); + } + case ECast(e, t): + var safe = t != null; + add("cast "); + if(safe) add("("); + expr(e); + if(safe) { + add(", "); + addType(t); + add(")"); + } + case ERegex(e, f): + add('~/$e/$f'); + add(';\n'); + case EConst(c): + switch( c ) { + case CInt(i): add(i); + case CFloat(f): add(f); + case CString(s): add('"'); add(s.split('"').join('\\"').split("\n").join("\\n").split("\r").join("\\r").split("\t").join("\\t")); add('"'); + } + case EIdent(v): + add(v); + case EVar(n, t, e, p, s, pr, isFinal, isInline, get, set, _): + if(p) add("public "); + else if(pr) add("private "); + if(s) add("static "); + if(isInline) add("inline "); + if(isFinal) add("final " + n); + else add("var " + n); + + if(get != null || set != null) { + add("("); + switch(get) { + case ADefault: add("default, "); + case ANull: add("null, "); + case AGet: add("get, "); + case ADynamic: add("dynamic, "); + case ANever: add("never, "); + default: + } + switch(set) { + case ADefault: add("default"); + case ANull: add("null"); + case ASet: add("set"); + case ADynamic: add("dynamic"); + case ANever: add("never"); + default: + } + add(")"); + } + + addType(t); + if( e != null ) { + add(" = "); + expr(e); + } + case EParent(e): + add("("); expr(e); add(")"); + case EBlock(el): + if( el.length == 0 ) { + add("{}"); + } else { + tabs += "\t"; + add("{\n"); + for( e in el ) { + add(tabs); + expr(e); + add(";\n"); + } + tabs = tabs.substr(1); + add("}"); + } + case EField(e, f, s): + expr(e); + add((s == true ? "?." : ".") + f); + case EBinop(op, e1, e2): + expr(e1); + add(" " + op.toString() + " "); + expr(e2); + case EUnop(op, pre, e): + if( pre ) { + add(op); + expr(e); + } else { + expr(e); + add(op); + } + case ECall(e, args): + if( e == null ) + expr(e); + else switch( Tools.expr(e)) { + case EField(_), EIdent(_), EConst(_): + expr(e); + default: + add("("); + expr(e); + add(")"); + } + add("("); + var first = true; + for( a in args ) { + if( first ) first = false else add(", "); + expr(a); + } + add(")"); + case EIf(cond,e1,e2): + add("if( "); + expr(cond); + add(" ) "); + expr(e1); + if( e2 != null ) { + add(" else "); + expr(e2); + } + case EWhile(cond,e): + add("while( "); + expr(cond); + add(" ) "); + expr(e); + case EDoWhile(cond,e): + add("do "); + expr(e); + add(" while ( "); + expr(cond); + add(" )"); + case EFor(v, it, e, ithv): + if(ithv != null) + add("for( "+ithv+" => "+v+" in "); + else + add("for( "+v+" in "); + expr(it); + add(" ) "); + expr(e); + case EBreak: + add("break"); + case EContinue: + add("continue"); + case EFunction(params, e, name, ret): // TODO: static, public, override + add("function"); + if( name != null ) + add(" " + name); + add("("); + var first = true; + for( a in params ) { + if( first ) first = false else add(", "); + if( a.opt ) add("?"); + add(a.name); + addType(a.t); + } + add(")"); + addType(ret); + add(" "); + expr(e); + case EReturn(e): + add("return"); + if( e != null ) { + add(" "); + expr(e); + } + case EArray(e,index): + expr(e); + add("["); + expr(index); + add("]"); + case EArrayDecl(el, _): + add("["); + var first = true; + for( e in el ) { + if( first ) first = false else add(", "); + expr(e); + } + add("]"); + case ENew(cl, args, params): + add("new " + cl); + if(params != null) { + add("<"); + var first = true; + for( p in params ) { + if( first ) first = false else add(", "); + type(p); + } + add(">"); + } + add("("); + var first = true; + for( e in args ) { + if( first ) first = false else add(", "); + expr(e); + } + add(")"); + case EThrow(e): + add("throw "); + expr(e); + case ETry(e, v, t, ecatch): + add("try "); + expr(e); + add(" catch( " + v); + addType(t); + add(") "); + expr(ecatch); + case EObject(fl): + if( fl.length == 0 ) { + add("{}"); + } else { + tabs += "\t"; + add("{\n"); + for( f in fl ) { + add(tabs); + add(f.name+" : "); + expr(f.e); + add(",\n"); + } + tabs = tabs.substr(1); + add("}"); + } + case ETernary(c,e1,e2): + expr(c); + add(" ? "); + expr(e1); + add(" : "); + expr(e2); + case ESwitch(e, cases, def): + add("switch( "); + expr(e); + add(") {"); + for( c in cases ) { + add("case "); + var first = true; + for( v in c.values ) { + if( first ) first = false else add(", "); + expr(v); + } + add(": "); + expr(c.expr); + add(";\n"); + } + if( def != null ) { + add("default: "); + expr(def); + add(";\n"); + } + add("}"); + case EMeta(name, args, e): + add("@"); + add(name); + if( args != null && args.length > 0 ) { + add("("); + var first = true; + for( a in args ) { + if( first ) first = false else add(", "); + expr(e); + } + add(")"); + } + add(" "); + expr(e); + case ECheckType(e, t): + add("("); + expr(e); + add(" : "); + addType(t); + add(")"); + } + } + + public static function toString( e : Expr ):String { + return new Printer().exprToString(e); + } + + public static function errorToString( e : Expr.Error ):String { + var message = switch( #if hscriptPos e.e #else e #end ) { + case EInvalidChar(c): "Invalid character: '"+(StringTools.isEof(c) ? "EOF (End Of File)" : String.fromCharCode(c))+"' ("+c+")"; + case EUnexpected(s): "Unexpected token: \""+s+"\""; + case EUnterminatedString: "Unterminated string"; + case EUnterminatedComment: "Unterminated comment"; + case EInvalidPreprocessor(str): "Invalid preprocessor (" + str + ")"; + case EUnknownVariable(v): "Unknown variable: "+v; + case EInvalidIterator(v): "Invalid iterator: "+v; + case EInvalidOp(op): "Invalid operator: "+op; + case EInvalidAccess(f): "Invalid access to field " + f; + case ECustom(msg): msg; + case EInvalidClass(cla): "Invalid class: " + cla + " was not found."; + case EAlreadyExistingClass(cla): 'Custom Class named $cla already exists.'; + }; + #if hscriptPos + return e.origin + ":" + e.line + ": " + message; + #else + return message; + #end + } + + +} diff --git a/hscript/Tools.hx b/hscript/Tools.hx index 0d3dd3ee..af5c28ea 100644 --- a/hscript/Tools.hx +++ b/hscript/Tools.hx @@ -1,144 +1,144 @@ -/* - * 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 Tools { - - public static function iter( e : Expr, f : Expr -> Void ):Void { - switch( expr(e) ) { - case EConst(_), EIdent(_): - case EImport(c): f(e); - case EClass(_, e, _, _): for( a in e ) f(a); - case EVar(_, _, e): if( e != null ) f(e); - case EParent(e): f(e); - case EBlock(el): for( e in el ) f(e); - case EField(e, _): f(e); - case EBinop(_, e1, e2): f(e1); f(e2); - case EUnop(_, _, e): f(e); - case ECall(e, args): f(e); for( a in args ) f(a); - case EIf(c, e1, e2): f(c); f(e1); if( e2 != null ) f(e2); - case EWhile(c, e): f(c); f(e); - case EDoWhile(c, e): f(c); f(e); - case EFor(_, it, e): f(it); f(e); - case EBreak,EContinue: - case EFunction(_, e, _, _): f(e); - case EReturn(e): if( e != null ) f(e); - case EArray(e, i): f(e); f(i); - case EArrayDecl(el): for( e in el ) f(e); - case ENew(_,el): for( e in el ) f(e); - case EThrow(e): f(e); - case ETry(e, _, _, c): f(e); f(c); - case EObject(fl): for( fi in fl ) f(fi.e); - case ETernary(c, e1, e2): f(c); f(e1); f(e2); - case ESwitch(e, cases, def): - f(e); - for( c in cases ) { - for( v in c.values ) f(v); - f(c.expr); - } - if( def != null ) f(def); - case EMeta(name, args, e): if( args != null ) for( a in args ) f(a); f(e); - case ECheckType(e,_): f(e); - default: - } - } - - public static function map( e : Expr, f : Expr -> Expr ):Expr { - var edef = switch( expr(e) ) { - case EConst(_), EIdent(_), EBreak, EContinue: expr(e); - case EVar(n, t, e, isPublic, isStatic, isPrivate): EVar(n, t, if( e != null ) f(e) else null, isPublic, isStatic, isPrivate); - case EParent(e): EParent(f(e)); - case EBlock(el): EBlock([for( e in el ) f(e)]); - case EField(e, fi): EField(f(e),fi); - case EBinop(op, e1, e2): EBinop(op, f(e1), f(e2)); - case EUnop(op, pre, e): EUnop(op, pre, f(e)); - case ECall(e, args): ECall(f(e),[for( a in args ) f(a)]); - case EIf(c, e1, e2): EIf(f(c),f(e1),if( e2 != null ) f(e2) else null); - 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 EFunction(args, e, name, t, isPublic, isStatic, isOverride, isPrivate): EFunction(args, f(e), name, t, isPublic, isStatic, isOverride, isPrivate); - case EReturn(e): EReturn(if( e != null ) f(e) else null); - case EArray(e, i): EArray(f(e),f(i)); - case EArrayDecl(el): EArrayDecl([for( e in el ) f(e)]); - case ENew(cl,el): ENew(cl,[for( e in el ) f(e)]); - case EThrow(e): EThrow(f(e)); - case ETry(e, v, t, c): ETry(f(e), v, t, f(c)); - case EObject(fl): EObject([for( fi in fl ) { name : fi.name, e : f(fi.e) }]); - case ETernary(c, e1, e2): ETernary(f(c), f(e1), f(e2)); - 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)); - case ECheckType(e,t): ECheckType(f(e), t); - case EImport(c): EImport(c); - case EClass(name, el, extend, interfaces): EClass(name, [for( e in el ) f(e)], extend, interfaces); - default: expr(e); - } - return mk(edef, e); - } - - public static inline function expr( e : Expr ) : ExprDef { - #if hscriptPos - return e.e; - #else - return e; - #end - } - - public static inline function mk( e : ExprDef, p : Expr ):Expr { - #if hscriptPos - return { e : e, pmin : p.pmin, pmax : p.pmax, origin : p.origin, line : p.line }; - #else - return e; - #end - } - - /** - * DO NOT USE INLINE ON THIS FUNCTION - **/ - public static function argCount(func: haxe.Constraints.Function): Int { - // https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Tools.hx#L206 - #if cpp - return untyped __cpp__("{0}->__ArgCount()", func); - #elseif js - return untyped js.Syntax.code("{0}.length", func); - #elseif hl - var ft = hl.Type.getDynamic(func); - if (ft.kind != HFun) - return -1; - return ft.getArgsCount(); - #else - return -1; - #end - } - - public static function isUppercase(s:String) { - if(s.length == 0) return false; - var c:Int = StringTools.fastCodeAt(s, 0); - if(StringTools.isEof(c)) return false; // Just in case :3 - return c >= 65 && c <= 90; // A-Z - } - - public static inline function isCustomAbstract(obj:Dynamic):Bool - return obj != null && obj is IHScriptAbstractBehaviour; - +/* + * 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 Tools { + + public static function iter( e : Expr, f : Expr -> Void ):Void { + switch( expr(e) ) { + case EConst(_), EIdent(_): + case EImport(c): f(e); + case EClass(_, e, _, _): for( a in e ) f(a); + case EVar(_, _, e): if( e != null ) f(e); + case EParent(e): f(e); + case EBlock(el): for( e in el ) f(e); + case EField(e, _): f(e); + case EBinop(_, e1, e2): f(e1); f(e2); + case EUnop(_, _, e): f(e); + case ECall(e, args): f(e); for( a in args ) f(a); + case EIf(c, e1, e2): f(c); f(e1); if( e2 != null ) f(e2); + case EWhile(c, e): f(c); f(e); + case EDoWhile(c, e): f(c); f(e); + case EFor(_, it, e): f(it); f(e); + case EBreak,EContinue: + case EFunction(_, e, _, _): f(e); + case EReturn(e): if( e != null ) f(e); + case EArray(e, i): f(e); f(i); + case EArrayDecl(el): for( e in el ) f(e); + case ENew(_,el): for( e in el ) f(e); + case EThrow(e): f(e); + case ETry(e, _, _, c): f(e); f(c); + case EObject(fl): for( fi in fl ) f(fi.e); + case ETernary(c, e1, e2): f(c); f(e1); f(e2); + case ESwitch(e, cases, def): + f(e); + for( c in cases ) { + for( v in c.values ) f(v); + f(c.expr); + } + if( def != null ) f(def); + case EMeta(name, args, e): if( args != null ) for( a in args ) f(a); f(e); + case ECheckType(e,_): f(e); + default: + } + } + + public static function map( e : Expr, f : Expr -> Expr ):Expr { + var edef = switch( expr(e) ) { + case EConst(_), EIdent(_), EBreak, EContinue: expr(e); + case EVar(n, t, e, isPublic, isStatic, isPrivate): EVar(n, t, if( e != null ) f(e) else null, isPublic, isStatic, isPrivate); + case EParent(e): EParent(f(e)); + case EBlock(el): EBlock([for( e in el ) f(e)]); + case EField(e, fi): EField(f(e),fi); + case EBinop(op, e1, e2): EBinop(op, f(e1), f(e2)); + case EUnop(op, pre, e): EUnop(op, pre, f(e)); + case ECall(e, args): ECall(f(e),[for( a in args ) f(a)]); + case EIf(c, e1, e2): EIf(f(c),f(e1),if( e2 != null ) f(e2) else null); + 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 EFunction(args, e, name, t, isPublic, isStatic, isOverride, isPrivate): EFunction(args, f(e), name, t, isPublic, isStatic, isOverride, isPrivate); + case EReturn(e): EReturn(if( e != null ) f(e) else null); + case EArray(e, i): EArray(f(e),f(i)); + case EArrayDecl(el): EArrayDecl([for( e in el ) f(e)]); + case ENew(cl,el): ENew(cl,[for( e in el ) f(e)]); + case EThrow(e): EThrow(f(e)); + case ETry(e, v, t, c): ETry(f(e), v, t, f(c)); + case EObject(fl): EObject([for( fi in fl ) { name : fi.name, e : f(fi.e) }]); + case ETernary(c, e1, e2): ETernary(f(c), f(e1), f(e2)); + 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)); + case ECheckType(e,t): ECheckType(f(e), t); + case EImport(c): EImport(c); + case EClass(name, el, extend, interfaces): EClass(name, [for( e in el ) f(e)], extend, interfaces); + default: expr(e); + } + return mk(edef, e); + } + + public static inline function expr( e : Expr ) : ExprDef { + #if hscriptPos + return e.e; + #else + return e; + #end + } + + public static inline function mk( e : ExprDef, p : Expr ):Expr { + #if hscriptPos + return { e : e, pmin : p.pmin, pmax : p.pmax, origin : p.origin, line : p.line }; + #else + return e; + #end + } + + /** + * DO NOT USE INLINE ON THIS FUNCTION + **/ + public static function argCount(func: haxe.Constraints.Function): Int { + // https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Tools.hx#L206 + #if cpp + return untyped __cpp__("{0}->__ArgCount()", func); + #elseif js + return untyped js.Syntax.code("{0}.length", func); + #elseif hl + var ft = hl.Type.getDynamic(func); + if (ft.kind != HFun) + return -1; + return ft.getArgsCount(); + #else + return -1; + #end + } + + public static function isUppercase(s:String) { + if(s.length == 0) return false; + var c:Int = StringTools.fastCodeAt(s, 0); + if(StringTools.isEof(c)) return false; // Just in case :3 + return c >= 65 && c <= 90; // A-Z + } + + public static inline function isCustomAbstract(obj:Dynamic):Bool + return obj != null && obj is IHScriptAbstractBehaviour; + } \ No newline at end of file