diff --git a/TestHScript.hx b/TestHScript.hx index db27955..d58998e 100644 --- a/TestHScript.hx +++ b/TestHScript.hx @@ -8,6 +8,14 @@ import hscript.Checker; import haxe.unit.*; class TestHScript extends TestCase { + + var optimize : Bool; + + function new(optimize=false) { + this.optimize = optimize; + super(); + } + function assertScript(x,v:Dynamic,?vars : Dynamic, allowTypes=false, ?pos:haxe.PosInfos) { var p = new hscript.Parser(); p.allowTypes = allowTypes; @@ -15,6 +23,9 @@ class TestHScript extends TestCase { var bytes = hscript.Bytes.encode(program); program = hscript.Bytes.decode(bytes); var interp = new hscript.Interp(); + #if js + if( optimize ) interp = new hscript.JsInterp(); + #end if( vars != null ) for( v in Reflect.fields(vars) ) interp.variables.set(v,Reflect.field(vars,v)); @@ -81,11 +92,7 @@ class TestHScript extends TestCase { assertScript("var a = [1,[2,[3,[4,null]]]]; var t = 0; while( a != null ) { t += a[0]; a = a[1]; }; t",10); assertScript("var a = false; do { a = true; } while (!a); a;",true); assertScript("var t = 0; for( x in 1...10 ) t += x; t", 45); - #if haxe3 assertScript("var t = 0; for( x in new IntIterator(1,10) ) t +=x; t", 45); - #else - assertScript("var t = 0; for( x in new IntIter(1,10) ) t +=x; t", 45); - #end assertScript("var x = 1; try { var x = 66; throw 789; } catch( e : Dynamic ) e + x",790); assertScript("var x = 1; var f = function(x) throw x; try f(55) catch( e : Dynamic ) e + x",56); assertScript("var i=2; if( true ) --i; i",1); @@ -93,7 +100,7 @@ class TestHScript extends TestCase { assertScript("var a = 5/2; a",2.5); assertScript("{ x = 3; x; }", 3); assertScript("{ x : 3, y : {} }.x", 3); - assertScript("function bug(){ \n }\nbug().x", null); + assertScript("function bug() return { \n }\nbug().x", null); assertScript("1 + 2 == 3", true); assertScript("-2 == 3 - 5", true); assertScript("var x=-3; x", -3); @@ -113,6 +120,9 @@ class TestHScript extends TestCase { assertScript("var f:(x:Int)->(Int, Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2)", 6, null, true); assertScript("var a = 10; var b = 5; a - -b", 15); assertScript("var a = 10; var b = 5; a - b / 2", 7.5); + assertScript("false && xxx", false); + assertScript("true || xxx", true); + assertScript("[for( x in arr ) switch( x ) { case 1: 55; case 3: 66; default: 0; }].join(':')",'55:0:66',{ arr : [1,2,3] }); } function testNullFieldAccess():Void { @@ -222,6 +232,7 @@ class TestHScript extends TestCase { var runner = new TestRunner(); runner.add(new TestHScript()); + runner.add(new TestHScript(true)); var succeed = runner.run(); #if sys diff --git a/hscript/JsInterp.hx b/hscript/JsInterp.hx new file mode 100644 index 0000000..6dc0df4 --- /dev/null +++ b/hscript/JsInterp.hx @@ -0,0 +1,287 @@ +package hscript; + +class JsInterp extends Interp { + + override function execute( expr : Expr ) : Dynamic { + depth = 0; + locals = new Map(); + var str = '({ _exec : ($$i) => ${exprValue(expr)} })'; + trace(str); + var obj : Dynamic = js.Lib.eval(str); + return obj._exec(this); + } + + function escapeString(s:String) { + return s.split("\\").join("\\\\").split("\r").join("\\r").split("\n").join("\\n").split('"').join('\\"'); + } + + function exprValue( expr : Expr ) { + switch( Tools.expr(expr) ) { + case EBlock([]): + return "null"; + case EIf(cond,e1,e2): + return exprJS(Tools.mk(ETernary(cond,e1,e2),expr)); + case EBlock(el): + var el = el.copy(); + var last = el[el.length-1]; + el[el.length - 1] = Tools.mk(EReturn(last),last); + var ebl = Tools.mk(EBlock(el),expr); + return '(() => ${exprJS(ebl)})()'; // todo : return/break/continue inside expr here won't work ! + case ETry(e,v,t,ecatch): + var expr = Tools.mk(ETry(Tools.mk(EReturn(e),e),v,t,Tools.mk(EReturn(ecatch),ecatch)),expr); + return '(() => { ${exprJS(expr)} })()'; // todo : return/break/continue inside expr here won't work ! + case EVar(_,_,e): + return e == null ? "null" : '(${exprValue(e)},null)'; + case EWhile(_), EFor(_), EDoWhile(_), EThrow(_): + return '(() => {${exprJS(expr)}})()'; // todo : return/break/continue inside expr here won't work ! + case EMeta(_,_,e), ECheckType(e,_): + return exprValue(e); + default: + return exprJS(expr); + } + } + + function exprBlock( expr : Expr ) { + switch( Tools.expr(expr) ) { + case EBlock(_): + return exprJS(expr); + default: + return '{${exprJS(expr)};}'; + } + } + + function addPos( estr : String ) { + #if hscriptPos + var expr = curExpr; + var p = '{pmin:,pmax:,origin:"",line:}'; + return '($$i._p(${expr.pmin},${expr.pmax},${expr.origin},${expr.line}),$estr)'; + #else + return estr; + #end + } + + function exprJS( expr : Expr ) : String { + #if hscriptPos + curExpr = e; + var expr = expr.e; + #end + switch( expr ) { + case EConst(c): + return switch c { + case CInt(v): Std.string(v); + case CFloat(f): Std.string(f); + case CString(s): '"'+escapeString(s)+'"'; + } + case EIdent(v): + if( locals.exists(v) ) + return v; + switch( v ) { + case "null", "true", "false": return v; + default: + } + return '$$i.resolve("$v")'; + case EVar(n, t, e): + locals.set(n, null); + return e == null ? 'let $n' : 'let $n = ${exprValue(e)}'; + case EParent(e): + return '(${exprValue(e)})'; + case EBlock(el): + var old = locals.copy(); + var buf = new StringBuf(); + buf.add('{'); + for( e in el ) { + buf.add(exprJS(e)); + buf.add(";"); + } + buf.add('}'); + locals = old; + return buf.toString(); + case EField(e, f): + return '$$i.get(${exprValue(e)},"$f")'; + case EBinop(op, e1, e2): + switch( op ) { + case "+","-","*","/","%","&","|","^",">>","<<",">>>","==","!=",">=","<=",">","<": + return '((${exprValue(e1)}) $op (${exprValue(e2)}))'; + case "||","&&": + return '((${exprValue(e1)}) == true $op (${exprValue(e2)}) == true)'; + case "=": + switch( Tools.expr(e1) ) { + case EIdent(id) if( locals.exists(id) ): + return id+" = "+exprValue(e2); + case EIdent(id): + return '$$i.setVar("$id",${exprValue(e2)})'; + case EField(e,f): + return addPos('$$i.set(${exprValue(e)},"$f")'); + case EArray(e, index): + return '$$i.setArray(${exprValue(e)},${exprValue(index)},${exprValue(e2)})'; + default: + error(EInvalidOp("=")); + } + case "+=","-=","*=","/=","%=","|=","&=","^=","<<=",">>=",">>>=": + var aop = op.substr(0, op.length - 1); + switch( Tools.expr(e1) ) { + case EIdent(id) if( locals.exists(id) ): + return id+" "+op+" "+exprValue(e2); + case EIdent(id): + return '$$i.setVar("$id",$$i.resolve("$id") $aop (${exprValue(e2)}))'; + case EField(e, f): + return '(($$o,$$v) => $$i.set($$o,"$f",$$i.get($$o,"$f") $aop $$v)(${exprValue(e)},${exprValue(e2)})'; + case EArray(e, index): + return '(($$a,$$idx,$$v) => $$i.setArray($$a,$$idx,$$i.getArray($$a,$$idx) $aop $$v))(${exprValue(e)},${exprValue(index)},${exprValue(e2)})'; + default: + error(EInvalidOp(op)); + } + case "...": + return '$$i.intIterator(${exprValue(e1)},${exprValue(e2)})'; + case "is": + return '$$i.isOfType(${exprValue(e1)},${exprValue(e2)})'; + default: + error(EInvalidOp(op)); + } + case EUnop(op, prefix, e): + switch( op ) { + case "!": + return '${exprValue(e)} != true'; + case "-","~": + return op+exprValue(e); + case "++", "--": + switch( Tools.expr(e) ) { + case EIdent(id) if( locals.exists(id) ): + return prefix ? op + id : id + op; + case _ if( prefix ): + var one = Tools.mk(EConst(CInt(1)),e); + return exprJS(Tools.mk(EBinop(op.charAt(0)+"=",e,one),e)); + case EIdent(id): + var op = op.charAt(1); + return '(($$v) => ($$i.setVar("$id",$$v $op 1),$$v))($$i.resolve("$id"))'; + case EArray(e, index): + var op = op.charAt(0); + return '(($$a,$$idx) => { var $$v = $$i.getArray($$a,$$idx); $$i.setArray($$a,$$idx,$$v $op 1); return $$v; })(${exprValue(e)},${exprValue(index)})'; + case EField(e, f): + return '(($$o) => { var $$v = $$i.get($$o,"$f"); $$i.set($$o,"$f",$$v $op 1); return $$v; })(${exprValue(e)})'; + default: + error(EInvalidOp(op)); + } + default: + error(EInvalidOp(op)); + } + case ECall(e, params): + var args = [for( p in params ) exprValue(p)]; + switch( Tools.expr(e) ) { + case EField(e,f): + return addPos('$$i.fcall2(${exprValue(e)},"$f",[${args.join(',')}])'); + default: + return '$$i.call(null,${exprValue(e)},[${args.join(',')}])'; + } + case EIf(cond,e1,e2): + return 'if( ${exprValue(cond)} == true ) ${exprJS(e1)}'+(e2 == null ? "" : 'else ${exprJS(e2)}'); + case ETernary(cond, e1, e2): + return '((${exprValue(cond)} == true) ? ${exprValue(e1)} : ${exprValue(e2)})'; + case EWhile(cond, e): + return 'while( ${exprValue(cond)} == true ) ${exprJS(e)}'; + case EFor(v, it, e): + var prev = locals.exists(v); + locals.set(v, null); + var block = exprJS(e); + if( !prev ) locals.remove(v); + var iter = '$$i.makeIterator(${exprValue(it)})'; + return '{ var $$it = ${addPos(iter)}; while( $$it.hasNext() ) { var $v = $$it.next(); $block; } }'; + case EBreak: + return 'break'; + case EContinue: + return 'continue'; + case EReturn(null): + return 'return'; + case EReturn(e): + return 'return '+exprValue(e); + case EArray(e, index): + return '$$i.getArray(${exprValue(e)},${exprValue(index)})'; + case EArrayDecl(arr): + if( arr.length > 0 && Tools.expr(arr[0]).match(EBinop("=>", _)) ) { + var keys = [], values = []; + for( e in arr ) { + switch(Tools.expr(e)) { + case EBinop("=>", eKey, eValue): + keys.push(exprValue(eKey)); + values.push(exprValue(eValue)); + default: + #if hscriptPos + curExpr = e; + #end + error(ECustom("Invalid map key=>value expression")); + } + } + return '$$i.makeMap([${keys.join(',')}],[${values.join(',')}])'; + } + return '['+[for( e in arr ) exprValue(e)].join(',')+']'; + case ENew(cl, params): + var args = [for( e in params ) exprValue(e)]; + return '$$i.cnew("$cl",[${args.join(',')}])'; + case EThrow(e): + return "throw "+exprValue(e); + case ETry(e, v, t, ecatch): + var prev = locals.exists(v); + locals.set(v, null); + var ec = exprBlock(ecatch); + if( !prev ) locals.remove(v); + return 'try ${exprBlock(e)} catch( $v ) $ec'; + case EObject(fl): + var fields = [for( f in fl ) f.name+":"+exprValue(f.e)]; + return '{${fields.join(',')}}'; // do not use 'set' here + case EDoWhile(cond, e): + return 'do ${exprBlock(e)} while( ${exprValue(cond)} == true )'; + case EMeta(_, _, e), ECheckType(e,_): + return exprJS(e); + case EFunction(args, e, name, ret): + var prev = locals.copy(); + for( a in args ) + locals.set(a.name, null); + var bl = exprBlock(e); + locals = prev; + var fname = if( name == null ) "" else { locals.set(name,null); " "+name; } + return 'function$fname(${[for( a in args ) a.name].join(",")}) $bl'; + case ESwitch(e, cases, defaultExpr): + var checks = [for( c in cases ) 'if( ${[for( v in c.values ) '$$v == ${exprValue(v)}'].join(" || ")} ) return ${exprValue(c.expr)};']; + if( defaultExpr != null ) + checks.push('return '+exprValue(defaultExpr)); + return '(($$v) => { ${[for( c in checks ) c+";"].join(" ")} })(${exprValue(e)})'; + default: + throw "TODO"; + } + } + + function fcall2( o : Dynamic, f : String, args : Array ) : Dynamic { + if( o == null ) { + error(EInvalidAccess(f)); + return null; + } + return fcall(o,f,args); + } + + function getArray( arr : Dynamic, index : Dynamic ) { + return isMap(arr) ? getMapValue(arr,index) : arr[index]; + } + + function setArray( arr : Dynamic, index : Dynamic, v : Dynamic ) { + if(isMap(arr) ) + setMapValue(arr, index, v); + else + arr[index] = v; + return v; + } + + function intIterator(v1,v2) { + return new IntIterator(v1,v2); + } + + function isOfType(v1,v2) { + return Std.isOfType(v1,v2); + } + + #if hscriptPos + function _p( pmin, pmax, origin, line ) { + curExpr = { expr : null, pmin : pmin, pmax: pmax, origin:origin, line:line }; + } + #end + +} \ No newline at end of file