diff --git a/hscript/Optimizer.hx b/hscript/Optimizer.hx index 2614045..c17f293 100644 --- a/hscript/Optimizer.hx +++ b/hscript/Optimizer.hx @@ -13,6 +13,13 @@ class Optimizer { var e = Tools.expr(s); // TODO: Convert EBinOp(+, a, EUnop(-, b)) to EBinOp(-, a, b) aka (a - b) + // TODO: Make it so if EFor & EForKeyValue iterator variable isnt used its replaced with a $, which the code handles as doesnt exist + // TODO: Make it so == ?; gets removed, IF it isnt stored + // TODO: Optimize ?. and ?? + // TODO: Optimize Std.string(CONSTANT) to "CONSTANT" + // TODO: Remove Std.string if its the right side of a binop of a string + // TODO: Add EForComprehension, EWhileComprehension, EDoWhileComprehension for better optimization + // TODO: Optimize EFor(v, it, EBlock([e1])) to EFor(v, it, e1) switch(e) { // Parse all expressions and recreate the AST @@ -26,15 +33,7 @@ class Optimizer { for(e in exprs) { switch(Tools.expr(e)) { case EBlock(iex): - var declares = false; - for(ie in iex) { - declares = switch(Tools.expr(ie)) { - case EVar(_): true;// has a declaration - case EFunction(_): true;// has a declaration - default: declares; - } - if(declares) break; - } + var declares = Lambda.exists(iex, ie -> hasDecl(ie)); if(!declares) { for(ex in iex) newExprs.push(ex); @@ -84,6 +83,27 @@ class Optimizer { return mk(EParent(e2), s); } } + if(isConstant(e1) && isConstant(e2)) { + var c1 = getConstant(e1); + var c2 = getConstant(e2); + + if(c1 == false && c2 == true) { // (VAR ? false : true) + return optimize(mk(EUnop("!", true, econd), s)); + } + if(c1 == true && c2 == false) { // (VAR ? true : false) + return optimize(mk(Tools.expr(econd), s)); + } + if(isConstant(econd)) { + if(c1 == true && c2 == true) { // (CONST ? true : true) + return mk(convertConstant(true), s); + } + if(c1 == false && c2 == false) { // (CONST ? false : false) + return mk(convertConstant(false), s); + } + // TODO: Check if local variables are used in the condition + // Since they cant have side effects, they can be optimized + } + } return mk(ETernary(econd, e1, e2), s); case EWhile(econd, e): var econd = optimize(econd); @@ -260,12 +280,30 @@ class Optimizer { } } + if(isNumber(e1) && !isConstant(e2)) { + var c1 = getNumber(e1); + if(c1 == 0 && op == "+") + return mk(Tools.expr(e2), s); + //if(c1 == 0 && op == "*") + // return mk(convertConstant(0), s); + } + + if(!isConstant(e1) && isNumber(e2)) { + var c2 = getNumber(e2); + if(op == "+" && c2 == 0) + return mk(Tools.expr(e1), s); + if(op == "/" && c2 == 1) + return mk(Tools.expr(e1), s); + //if(op == "*" && c2 == 0) + // return mk(convertConstant(0), s); + } + return mk(EBinop(op, e1, e2), s); case EUnop(op, prefix, e): e = optimize(e); - if(isConstant(e)) { + if(isConstant(e) && prefix) { var constant:Dynamic = getConstant(e); switch(op) { case "-": return mk(convertConstant(-constant), s); @@ -283,6 +321,14 @@ class Optimizer { return s; } + static function hasDecl(e:Expr):Bool { + return switch(Tools.expr(e)) { + case EVar(_): true;// has a declaration + case EFunction(_): true;// has a declaration + default: false; + } + } + static function isConstant(e:Expr):Bool { return switch(Tools.expr(e)) { case EIdent("true") | EIdent("false") | EIdent("null"): true; @@ -292,6 +338,24 @@ class Optimizer { } } + static function isNumber(e:Expr):Bool { + return switch(Tools.expr(e)) { + case EConst(CInt(_)): true; + case EConst(CFloat(_)): true; + case EParent(e): isNumber(e); + default: false; + } + } + + static function getNumber(e:Expr):Dynamic { + return switch(Tools.expr(e)) { + case EConst(CInt(value)): value; + case EConst(CFloat(value)): value; + case EParent(e): getNumber(e); + default: throw "Unknown type " + Tools.expr(e); + } + } + static function getStringConstant(e:Expr):String { return switch(Tools.expr(e)) { case EConst(CString(value)): value; diff --git a/hscript/Printer.hx b/hscript/Printer.hx index 0e6cf1f..f77558a 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -37,6 +37,11 @@ class Printer { return buf.toString(); } + public static function convertExprToString( e : Expr ) { + var printer = new Printer(); + return printer.exprToString(e); + } + public function typeToString( t : CType ) { buf = new StringBuf(); tabs = ""; @@ -108,11 +113,53 @@ class Printer { } } + function block(e : Expr, addSpaceIfBlock : Bool = true) { + var isBlock = Tools.expr(e).match(EBlock(_)); + if(isBlock) { + if(addSpaceIfBlock) add(" "); + } else { + add("\n"); + tabs += "\t"; + add(tabs); + } + expr(e); + + if(!isBlock) { + //add("\n"); + tabs = tabs.substr(1); + //add(tabs); + } + + return isBlock; + } + + function isJson(s:String) { + var len = s.length; + var i = 0; + while (i < len) { + switch (StringTools.fastCodeAt(s, i++)) { + // [a-zA-Z0-9_]+ + case "a".code | "b".code | "c".code | "d".code | "e".code | "f".code | "g".code | "h".code | "i".code | "j".code | "k".code | "l".code | "m".code + | "n".code | "o".code | "p".code | "q".code | "r".code | "s".code | "t".code | "u".code | "v".code | "w".code | "x".code | "y".code | "z".code + | "A".code | "B".code | "C".code | "D".code | "E".code | "F".code | "G".code | "H".code | "I".code | "J".code | "K".code | "L".code | "M".code + | "N".code | "O".code | "P".code | "Q".code | "R".code | "S".code | "T".code | "U".code | "V".code | "W".code | "X".code | "Y".code | "Z".code + | "0".code | "1".code | "2".code | "3".code | "4".code | "5".code | "6".code | "7".code | "8".code | "9".code | "_".code: + case _: + return true; + } + + } + return false; + } + function expr( e : Expr ) { if( e == null ) { add("??NULL??"); return; } + + // TODO: make else if print correctly + switch( Tools.expr(e) ) { case EImport(c, n): add("import " + c); @@ -229,33 +276,35 @@ class Printer { case EIf(cond,e1,e2): add("if( "); expr(cond); - add(" ) "); - expr(e1); + add(" )"); + var wasBlock = block(e1, true); + if( e2 != null ) { + if(!wasBlock) add("\n" + tabs); add(" else "); - expr(e2); + block(e2); } case EWhile(cond,e): add("while( "); expr(cond); - add(" ) "); - expr(e); + add(" )"); + block(e, true); case EDoWhile(cond,e): - add("do "); - expr(e); + add("do"); + block(e, true); add(" while ( "); expr(cond); add(" )"); case EFor(v, it, e): add("for( "+v+" in "); expr(it); - add(" ) "); - expr(e); + add(" )"); + block(e, true); case EForKeyValue(v, it, e, ithv): add("for( "+ithv+" => "+v+" in "); expr(it); - add(" ) "); - expr(e); + add(" )"); + block(e, true); case EBreak: add("break"); case EContinue: @@ -307,12 +356,12 @@ class Printer { add("throw "); expr(e); case ETry(e, v, t, ecatch): - add("try "); - expr(e); + add("try"); + block(e); add(" catch( " + v); addType(t); - add(") "); - expr(ecatch); + add(")"); + block(ecatch); case EObject(fl): if( fl.length == 0 ) { add("{}"); @@ -321,7 +370,8 @@ class Printer { add("{\n"); for( i=>f in fl ) { add(tabs); - add(f.name+" : "); + var name = isJson(f.name) ? "\"" + f.name + "\"" : f.name; + add(name+" : "); expr(f.e); if( i != fl.length - 1 ) add(","); add("\n"); @@ -349,14 +399,14 @@ class Printer { if( first ) first = false else add(", "); expr(v); } - add(": "); - expr(c.expr); + add(":"); + block(c.expr, true); add(";\n"); } if( def != null ) { add(tabs); add("default: "); - expr(def); + block(def, true); add(";\n"); } tabs = tabs.substr(1); diff --git a/tests/src/HScriptRunner.hx b/tests/src/HScriptRunner.hx index 1672169..32211ee 100644 --- a/tests/src/HScriptRunner.hx +++ b/tests/src/HScriptRunner.hx @@ -1,5 +1,8 @@ package; +import hscript.Interp; +import hscript.Expr; + @:access(hscript.Interp) @:access(hscript.Parser) class HScriptRunner { @@ -23,27 +26,29 @@ class HScriptRunner { public function run() {} public function teardown() {} + private var lastExpr:Expr; + public function execute(script:String):Dynamic { var interp = clearPrevious ? getNewInterp() : this.interp; - var expr = Util.parse(headerCode + script + tailCode); - if(expr == null) + lastExpr = Util.parse(headerCode + script + tailCode); + if(lastExpr == null) return "ERROR"; if (clearPrevious) - return interp.execute(expr); + return interp.execute(lastExpr); else - return interp.exprReturn(expr); + return interp.exprReturn(lastExpr); } public function executeWithVars(script:String, vars:Dynamic):Dynamic { var interp = clearPrevious ? getNewInterp() : this.interp; - var expr = Util.parse(headerCode + script + tailCode); - if(expr == null) + lastExpr = Util.parse(headerCode + script + tailCode); + if(lastExpr == null) return "ERROR"; for(v in Reflect.fields(vars)) interp.variables.set(v, Reflect.field(vars, v)); if (clearPrevious) - return interp.execute(expr); + return interp.execute(lastExpr); else - return interp.exprReturn(expr); + return interp.exprReturn(lastExpr); } } \ No newline at end of file diff --git a/tests/src/Util.hx b/tests/src/Util.hx index d70ce8f..eea5ee0 100644 --- a/tests/src/Util.hx +++ b/tests/src/Util.hx @@ -6,10 +6,12 @@ class Util { public static function assert(value:Bool, message:String, ?pos:haxe.PosInfos) { if (value) { passedTestUnits++; + return true; } else { Sys.println("Assertion failed: " + message); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); failedTestUnits++; + return false; } } @@ -26,10 +28,12 @@ class Util { if (passed) { passedTestUnits++; + return true; } else { Sys.println("Assertion failed: " + message + " Expected: " + expected + " Got: " + value); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); failedTestUnits++; + return false; } } @@ -44,10 +48,12 @@ class Util { if (passed) { passedTestUnits++; + return true; } else { Sys.println("Assertion failed: " + message + " Expected: " + expected + " Got: " + value); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); failedTestUnits++; + return false; } } diff --git a/tests/src/benchmark/TestBenchmark.hx b/tests/src/benchmark/TestBenchmark.hx index 00d9e93..53bec16 100644 --- a/tests/src/benchmark/TestBenchmark.hx +++ b/tests/src/benchmark/TestBenchmark.hx @@ -9,11 +9,12 @@ class TestBenchmark extends Benchmark { //var hscript = "var a:Array = []; for (i in 0...1000) a.push(i * 2 + 1 / 6); if(true == true) a.push(1);"; var hscript = " var a:Array = []; - for(i in 0...1000) + for(i in 0...1000) { switch(5) { case true: a.push(1); - default: a.push(2); + default: a.push(i / 1); } + } if(('a' == 'a') ? true : false) a.push(1); if(true == true) a.push(1);"; @@ -40,11 +41,12 @@ class TestBenchmark extends Benchmark { var a:Array = []; //for (i in 0...1000) a.push(i * 2 + 1 / 6); //for (i in 0...1000) a.push(i / 2 + 3 / 6); - for(i in 0...1000) + for(i in 0...1000) { switch(5) { case 0: a.push(1); - default: a.push(2); + default: a.push(i / 1); } + } if(('a' == 'a') ? true : false) a.push(1); if(true == true) a.push(1); } diff --git a/tests/src/tests/FinalCase.hx b/tests/src/tests/FinalCase.hx index 9355b66..bae4f37 100644 --- a/tests/src/tests/FinalCase.hx +++ b/tests/src/tests/FinalCase.hx @@ -22,8 +22,6 @@ class FinalCase extends TestCase { var i = 5; var x = 10; - hscript.Parser.optimize = false; - assertDisplay("(i - x) * 30 + 90", ((i-x) * 30) + 90); assertDisplay("i - x * 30 + 90", i-x * 30 + 90); @@ -33,6 +31,13 @@ class FinalCase extends TestCase { assertDisplay("x * 30 / i + 90", x * 30 / i + 90); //assertEq("(x * 30) / i + 90", x * 30 / i + 90); + + // (_) -> {a = false;} + // Shouldnt be converted to (_) -> return {a = false;} + + Util.parse("openCodesList(false, codesOpened ? false : true, previousOpen != open ? true : false);"); + Util.parse("openCodesList(false, 'HELLO' ? false : false, 'WORLD' ? true : true);"); + Util.parse('FlxTween.tween(newSprite, {"scale.x": 1, "scale.y": 1, alpha: 1, angle: 0}, 0.3, {ease: FlxEase.qaudInOut});'); } function assertDisplay(script:String, expected:Dynamic) { diff --git a/tests/src/tests/TestCase.hx b/tests/src/tests/TestCase.hx index 502a99d..50d71f1 100644 --- a/tests/src/tests/TestCase.hx +++ b/tests/src/tests/TestCase.hx @@ -1,5 +1,7 @@ package tests; +import hscript.Printer; + @:access(hscript.Interp) @:access(hscript.Parser) class TestCase extends HScriptRunner { @@ -10,7 +12,11 @@ class TestCase extends HScriptRunner { executeWithVars(script, vars); else execute(script); - Util.assertEq(result, expected, message, pos); + if(!Util.assertEq(result, expected, message, pos)) { + Sys.println("> " + Printer.convertExprToString(lastExpr)); + return false; + } + return true; } public function assertNeq(script:String, expected:Dynamic, ?message:String, ?vars:Dynamic, ?pos:haxe.PosInfos) { @@ -20,6 +26,10 @@ class TestCase extends HScriptRunner { executeWithVars(script, vars); else execute(script); - Util.assertNeq(result, expected, message, pos); + if(!Util.assertNeq(result, expected, message, pos)) { + Sys.println("> " + Printer.convertExprToString(lastExpr)); + return false; + } + return true; } } \ No newline at end of file