From 04dc3ad1baa0ae7e07fbd08d4191e77002d0a917 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:11:10 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Interp.hx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hscript/Interp.hx | 97 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 35b41765..0cf592e6 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; @@ -154,6 +168,9 @@ class Interp { var usingHandler:UsingHandler; + var varLocationCache:Map = new Map(); + var cacheValid:Bool = true; + #if hscriptPos var curExpr:Expr; #end @@ -312,6 +329,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 +338,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 +347,7 @@ class Interp { } else { l.r = v; if (l.depth == 0) { + varLocationCache.remove(id); setVar(id, v); } } @@ -395,6 +415,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 +424,7 @@ class Interp { var prop:Property = cast obj; return prop.callSetter(id, v); } + varLocationCache.remove(id); setVar(id, v); } } @@ -414,6 +436,7 @@ class Interp { } l.r = v; if (l.depth == 0) { + varLocationCache.remove(id); setVar(id, v); } } @@ -468,6 +491,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 +505,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; } @@ -644,12 +672,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 +731,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 +773,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 +1068,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); From 7abbdcce3ac76d4c60a8191e734087e3b5d27473 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Wed, 1 Apr 2026 23:56:02 +0800 Subject: [PATCH 2/3] exprReturn --- hscript/Interp.hx | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 0cf592e6..7373dcc4 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -568,34 +568,33 @@ 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) { + if (printCallStack) { + var stack = CallStack.exceptionStack(true); + error(ECustom(Std.string(e) + "\n" + CallStack.toString(stack))); + } else { + error(ECustom(Std.string(e))); + } return null; - } catch(e) { - trace(e); } return null; } From aa38e73beb01b11e072c59ad9f10fb1b916e9f66 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:46:34 +0800 Subject: [PATCH 3/3] Binop --- hscript/Async.hx | 1072 ++++++++++++++++++++++---------------------- hscript/Bytes.hx | 4 +- hscript/Checker.hx | 42 +- hscript/Expr.hx | 128 +++++- hscript/Interp.hx | 103 ++--- hscript/Macro.hx | 4 +- hscript/Parser.hx | 17 +- hscript/Printer.hx | 2 +- 8 files changed, 750 insertions(+), 622 deletions(-) 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..8e5e7d3d 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -28,6 +28,132 @@ 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: "?"; + } + } +} typedef UInt32 = #if cpp cpp.UInt32 #else Int #end; typedef UInt64 = #if cpp cpp.UInt64 #else Int #end; @@ -57,7 +183,7 @@ enum Expr { EParent( e : Expr ); EBlock( e : Array ); EField( e : Expr, f : String , ?safe : Bool ); - EBinop( op : String, e1 : Expr, e2 : Expr ); + EBinop( op : Binop, e1 : Expr, e2 : Expr ); EUnop( op : String, prefix : Bool, e : Expr ); ECall( e : Expr, params : Array ); EIf( cond : Expr, e1 : Expr, ?e2 : Expr ); diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 7373dcc4..33c751c4 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -144,7 +144,6 @@ class Interp { // warning can be null public var locals:Map; - var binops:StringMapExpr->Dynamic>; var depth:Int = 0; var inTry:Bool; @@ -179,7 +178,6 @@ class Interp { locals = new Map(); declared = []; resetVariables(); - initOps(); } private function resetVariables():Void { @@ -210,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); @@ -371,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): @@ -458,7 +408,7 @@ class Interp { arr[index] = v; } default: - return error(EInvalidOp(op)); + return error(EInvalidOp(op.toString())); } return v; } @@ -1117,10 +1067,47 @@ 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 "!": @@ -1275,7 +1262,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 @@ -1289,7 +1276,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); 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..605de233 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -609,22 +609,23 @@ class Parser { } @: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)); } } @@ -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; } @@ -2443,9 +2444,9 @@ class Parser { 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..45905bc7 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -276,7 +276,7 @@ class Printer { add((s == true ? "?." : ".") + f); case EBinop(op, e1, e2): expr(e1); - add(" " + op + " "); + add(" " + op.toString() + " "); expr(e2); case EUnop(op, pre, e): if( pre ) {