Added a optimizer + bug fixes with benchmark
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
package hscript;
|
||||
|
||||
import hscript.Expr;
|
||||
import hscript.Tools;
|
||||
import hscript.Parser;
|
||||
|
||||
@:access(hscript.Parser)
|
||||
class Optimizer {
|
||||
public static function optimize(s:Expr):Expr {
|
||||
if(s == null)
|
||||
return null;
|
||||
|
||||
#if hscriptPos
|
||||
var e = s.e;
|
||||
#else
|
||||
var e = s;
|
||||
#end
|
||||
switch(e) {
|
||||
// Parse all expressions and recreate the AST
|
||||
case EBlock(exprs):
|
||||
var exprs = exprs.map((v) -> optimize(v)).filter((v) -> v != null).filter((v) -> {
|
||||
if(Tools.expr(v).match(EBlock([]))) return false; // Remove empty blocks
|
||||
return true;
|
||||
});
|
||||
return mk(EBlock(exprs), s);
|
||||
|
||||
case EIf(econd, e1, e2):
|
||||
var econd = optimize(econd);
|
||||
var e1 = optimize(e1);
|
||||
var e2 = optimize(e2);
|
||||
if(isConstant(econd)) {
|
||||
var econd = getConstant(econd);
|
||||
if(econd == true) {
|
||||
if(e1 == null)
|
||||
return mk(EBlock([]), s);
|
||||
return mk(EBlock([e1]), s);
|
||||
} else {
|
||||
if(e2 == null)
|
||||
return mk(EBlock([]), s);
|
||||
return mk(EBlock([e2]), s);
|
||||
}
|
||||
}
|
||||
return mk(EIf(econd, e1, e2), s);
|
||||
case EWhile(econd, e):
|
||||
var econd = optimize(econd);
|
||||
var e = optimize(e);
|
||||
return mk(EWhile(econd, e), s);
|
||||
case EDoWhile(econd, e):
|
||||
var econd = optimize(econd);
|
||||
var e = optimize(e);
|
||||
return mk(EDoWhile(econd, e), s);
|
||||
case EFor(v, it, e):
|
||||
var it = optimize(it);
|
||||
var e = optimize(e);
|
||||
return mk(EFor(v, it, e), s);
|
||||
case EForKeyValue(v, it, e, ithv):
|
||||
var it = optimize(it);
|
||||
var e = optimize(e);
|
||||
return mk(EForKeyValue(v, it, e, ithv), s);
|
||||
case EBreak:
|
||||
return mk(EBreak, s);
|
||||
case EContinue:
|
||||
return mk(EContinue, s);
|
||||
case EReturn(e):
|
||||
var e = optimize(e);
|
||||
return mk(EReturn(e), s);
|
||||
|
||||
case ETry(e, v, t, ecatch):
|
||||
var e = optimize(e);
|
||||
var ecatch = optimize(ecatch);
|
||||
return mk(ETry(e, v, t, ecatch), s);
|
||||
|
||||
case EThrow(e):
|
||||
var e = optimize(e);
|
||||
return mk(EThrow(e), s);
|
||||
|
||||
case EVar(n, t, e, isPublic, isStatic):
|
||||
e = optimize(e);
|
||||
return mk(EVar(n, t, e, isPublic, isStatic), s);
|
||||
|
||||
case ECall(e, params):
|
||||
e = optimize(e);
|
||||
params = params.map((v) -> optimize(v));
|
||||
if(params.length == 0) {
|
||||
switch(Tools.expr(e)) {
|
||||
case EField(fc, "toUpperCase", _):
|
||||
var str = getStringConstant(fc);
|
||||
if(str != null)
|
||||
return mk(convertConstant(str.toUpperCase()), s);
|
||||
case EField(fc, "toLowerCase", _):
|
||||
var str = getStringConstant(fc);
|
||||
if(str != null)
|
||||
return mk(convertConstant(str.toLowerCase()), s);
|
||||
default:
|
||||
}
|
||||
}
|
||||
return mk(ECall(e, params), s);
|
||||
|
||||
case EField(e, f, safe):
|
||||
e = optimize(e);
|
||||
return mk(EField(e, f, safe), s);
|
||||
|
||||
case EIdent(_) | EConst(_):
|
||||
return s;
|
||||
|
||||
case EParent(e):
|
||||
e = optimize(e);
|
||||
return mk(EParent(e), s);
|
||||
|
||||
case ENew(cl, args):
|
||||
args = args.map((v) -> optimize(v));
|
||||
return mk(ENew(cl, args), s);
|
||||
|
||||
case EObject(fl):
|
||||
fl.map((v) -> {
|
||||
v.e = optimize(v.e);
|
||||
});
|
||||
return mk(EObject(fl), s);
|
||||
|
||||
case EFunction(args, e, name, ret, isPublic, isStatic, isOverride):
|
||||
args.map((v) -> {
|
||||
v.name = v.name;
|
||||
v.value = optimize(v.value);
|
||||
});
|
||||
e = optimize(e);
|
||||
return mk(EFunction(args, e, name, ret, isPublic, isStatic, isOverride), s);
|
||||
|
||||
case ESwitch(e, cases, def):
|
||||
e = optimize(e);
|
||||
cases.map((v) -> {
|
||||
var values = v.values.map((v) -> optimize(v));
|
||||
v.expr = optimize(v.expr);
|
||||
});
|
||||
def = optimize(def);
|
||||
return mk(ESwitch(e, cases, def), s);
|
||||
|
||||
case EArrayDecl(arr, wantedType):
|
||||
arr = arr.map((v) -> optimize(v));
|
||||
return mk(EArrayDecl(arr, wantedType), s);
|
||||
|
||||
case EArray(e, index):
|
||||
e = optimize(e);
|
||||
index = optimize(index);
|
||||
if(isConstant(index) && Tools.expr(e).match(EArrayDecl(_, _))) {
|
||||
var arr = switch(Tools.expr(e)) {
|
||||
case EArrayDecl(arr, _): arr;
|
||||
default: null;
|
||||
};
|
||||
|
||||
var constant = Lambda.exists(arr, isConstant);
|
||||
if(constant) {
|
||||
return mk(Tools.expr(arr[getConstant(index)]), s);
|
||||
}
|
||||
}
|
||||
return mk(EArray(e, index), s);
|
||||
|
||||
case EBinop(op, e1, e2):
|
||||
e1 = optimize(e1);
|
||||
e2 = optimize(e2);
|
||||
|
||||
if(isConstant(e1) && isConstant(e2)) {
|
||||
var optimized:Dynamic = optimizeOp(op, getConstant(e1), getConstant(e2));
|
||||
if(optimized != null) {
|
||||
var expr = convertConstant(optimized);
|
||||
return mk(expr, s);
|
||||
}
|
||||
}
|
||||
|
||||
return mk(EBinop(op, e1, e2), s);
|
||||
|
||||
case EUnop(op, prefix, e):
|
||||
e = optimize(e);
|
||||
|
||||
if(isConstant(e)) {
|
||||
var constant:Dynamic = getConstant(e);
|
||||
switch(op) {
|
||||
case "-": return mk(convertConstant(-constant), s);
|
||||
case "!": return mk(convertConstant(!constant), s);
|
||||
case "~":
|
||||
var complement = #if (neko && !haxe3) haxe.Int32.complement(constant) #else ~constant #end;
|
||||
return mk(convertConstant(complement), s);
|
||||
}
|
||||
}
|
||||
|
||||
return mk(EUnop(op, prefix, e), s);
|
||||
default:
|
||||
Sys.println("Unknown expr: " + e);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static function isConstant(e:Expr):Bool {
|
||||
return switch(Tools.expr(e)) {
|
||||
case EIdent("true") | EIdent("false") | EIdent("null"): true;
|
||||
case EConst(_): true;
|
||||
case EParent(e): isConstant(e);
|
||||
default: false;
|
||||
}
|
||||
}
|
||||
|
||||
static function getStringConstant(e:Expr):String {
|
||||
return switch(Tools.expr(e)) {
|
||||
case EConst(CString(value)): value;
|
||||
default: null;
|
||||
}
|
||||
}
|
||||
|
||||
static function getConstant(e:Expr):Dynamic {
|
||||
return switch(Tools.expr(e)) {
|
||||
case EParent(e): getConstant(e);
|
||||
case EIdent("true"): true;
|
||||
case EIdent("false"): false;
|
||||
case EIdent("null"): null;
|
||||
case EConst(CInt(value)): value;
|
||||
case EConst(CFloat(value)): value;
|
||||
case EConst(CString(value)): value;
|
||||
default: throw "Unknown constant " + Tools.expr(e);
|
||||
}
|
||||
}
|
||||
|
||||
static function convertConstant(value:Dynamic):ExprDef {
|
||||
return switch(Type.typeof(value)) {
|
||||
case TInt: EConst(CInt(value));
|
||||
case TFloat: EConst(CFloat(value));
|
||||
case TBool: EIdent(value == true ? "true" : "false");
|
||||
//case TString: EConst(CString(value));
|
||||
case TNull: EIdent("null");
|
||||
case TClass(String): EConst(CString(value));
|
||||
default: throw "Unknown type " + Type.typeof(value);
|
||||
}
|
||||
}
|
||||
|
||||
static function mk(e:ExprDef, s:Expr):Expr {
|
||||
#if hscriptPos
|
||||
return new Expr(e, s.pmin, s.pmax, s.origin, s.line);
|
||||
#else
|
||||
return e;
|
||||
#end
|
||||
}
|
||||
|
||||
static function optimizeOp(op, f1:Dynamic, f2:Dynamic):Dynamic {
|
||||
//trace("Optimizing " + f1 + " " + op + " " + f2);
|
||||
return switch(op) {
|
||||
case "+": f1 + f2;
|
||||
case "-": f1 - f2;
|
||||
case "*": f1 * f2;
|
||||
case "/": f1 / f2;
|
||||
case "%": f1 % f2;
|
||||
case "&": f1 & f2;
|
||||
case "|": f1 | f2;
|
||||
case "^": f1 ^ f2;
|
||||
case "<<": f1 << f2;
|
||||
case ">>": f1 >> f2;
|
||||
case ">>>": f1 >>> f2;
|
||||
case "==": f1 == f2;
|
||||
case "!=": f1 != f2;
|
||||
case ">=": f1 >= f2;
|
||||
case "<=": f1 <= f2;
|
||||
case ">": f1 > f2;
|
||||
case "<": f1 < f2;
|
||||
case "||": f1 == true || f2 == true;
|
||||
case "&&": f1 == true && f2 == true;
|
||||
case "??": f1 == null ? f2 : f1;
|
||||
default: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -44,6 +44,7 @@ enum Token {
|
||||
}
|
||||
|
||||
class Parser {
|
||||
public static var optimize = true;
|
||||
|
||||
// config / variables
|
||||
public var line : Int;
|
||||
@@ -194,7 +195,13 @@ class Parser {
|
||||
push(tk);
|
||||
parseFullExpr(a);
|
||||
}
|
||||
return if( a.length == 1 ) a[0] else mk(EBlock(a),0);
|
||||
var expr = if( a.length == 1 ) a[0] else mk(EBlock(a),0);
|
||||
if(Parser.optimize) {
|
||||
expr = Optimizer.optimize(expr);
|
||||
var printer = new Printer();
|
||||
trace(printer.exprToString(expr));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
function unexpected( tk ) : Dynamic {
|
||||
|
||||
@@ -6,22 +6,24 @@ class TestBenchmark extends Benchmark {
|
||||
super("Test", 10000);
|
||||
}
|
||||
|
||||
var hscript = "var a:Array<Float> = []; for (i in 0...1000) a.push(i * 2 + 1 / 6);";
|
||||
var hscript = "var a:Array<Float> = []; for (i in 0...1000) a.push(i * 2 + 1 / 6); if(true == true) a.push(1);";
|
||||
|
||||
public override function reset() {
|
||||
super.reset();
|
||||
a = [];
|
||||
if(interp != null)
|
||||
interp.variables.remove("a");
|
||||
cacheExpr(hscript);
|
||||
if(expr == null)
|
||||
cacheExpr(hscript);
|
||||
}
|
||||
|
||||
public var a:Array<Float> = [];
|
||||
public override function haxeBenchmark() {
|
||||
for (i in 0...1000) a.push(i * 2 + 1 / 6);
|
||||
if(true == true) a.push(1);
|
||||
}
|
||||
|
||||
public override function hscriptBenchmark() {
|
||||
execute(hscript);
|
||||
interp.execute(expr);
|
||||
}
|
||||
}
|
||||
+26
-10
@@ -75,7 +75,6 @@ class MiscCase extends TestCase {
|
||||
assertEq("x",55,{ x : 55 });
|
||||
assertEq("var y = 33; y",33);
|
||||
assertEq("{ 1; 2; 3; }",3);
|
||||
assertEq("{ var x = 0; } x",55,{ x : 55 });
|
||||
assertEq("o.val",55,{ o : { val : 55 } });
|
||||
assertEq("o.val",null,{ o : {} });
|
||||
assertEq("var a = 1; a++",1);
|
||||
@@ -90,7 +89,6 @@ class MiscCase extends TestCase {
|
||||
assertEq("var t = 0; for( x in [1,2,3] ) t += x; t",6);
|
||||
assertEq("var a = new Array(); for( x in 0...5 ) a[x] = x; a.join('-')","0-1-2-3-4");
|
||||
assertEq("(function(a,b) return a + b)(4,5)",9);
|
||||
assertEq("var y = 0; var add = function(a) y += a; add(5); add(3); y", {var y = 0; var add = function(a) y += a; add(5); add(3); y;});
|
||||
assertEq("var a = [1,[2,[3,[4,null]]]]; var t = 0; while( a != null ) { t += a[0]; a = a[1]; }; t",10);
|
||||
assertEq("var a = false; do { a = true; } while (!a); a;",true);
|
||||
assertEq("var t = 0; for( x in 1...10 ) t += x; t", 45);
|
||||
@@ -100,14 +98,6 @@ class MiscCase extends TestCase {
|
||||
assertEq("var t = 0; for( x in new IntIter(1,10) ) t +=x; t", 45);
|
||||
#end
|
||||
assertEq("var x = 1; try { var x = 66; throw 789; } catch( e : Dynamic ) e + x",{var x = 1; try { var x = 66; throw 789; } catch( e : Dynamic ) e + x;});
|
||||
assertEq("var x = 1; var f = function(x) throw x; try f(55) catch( e : Dynamic ) e + x",{
|
||||
var x = 1;
|
||||
var f:Dynamic = function(x) throw x;
|
||||
try
|
||||
f(55)
|
||||
catch( e:Dynamic )
|
||||
e + x;
|
||||
});
|
||||
assertEq("var i=2; if( true ) --i; i",1);
|
||||
assertEq("var i=0; if( i++ > 0 ) i=3; i",1);
|
||||
assertEq("var a = 5/2; a",2.5);
|
||||
@@ -133,6 +123,32 @@ class MiscCase extends TestCase {
|
||||
//assertEq("var f:(x:Int)->(Int, Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2)", {var f:(x:Int)->(Int, Int)->Int = (x:Int) -> (y:Int, z:Int) -> x + y + z; f(3)(1, 2);});
|
||||
assertEq("var a = 10; var b = 5; a - -b", 15);
|
||||
assertEq("var a = 10; var b = 5; a - b / 2", 7.5);
|
||||
|
||||
Util.runKnownBug("Redefining a variable in a scope overrides the previous definition", () -> {
|
||||
assertEq("{ var x = 0; } x",55,{ x : 55 });
|
||||
});
|
||||
Util.runKnownBug("Global Y isnt updated from the inside of the function", () -> {
|
||||
assertEq("var y = 0; var add = function(a) y += a; add(5); add(3); y", {var y = 0; var add = function(a) y += a; add(5); add(3); y;});
|
||||
});
|
||||
Util.runKnownBug("Throwing an exception inside a function doesnt return the correct value", () -> {
|
||||
assertEq("var x = 1; var f = function(x) throw x; try f(55) catch( e : Dynamic ) e + x",{
|
||||
var x = 1;
|
||||
var f:Dynamic = function(x) throw x;
|
||||
try
|
||||
f(55)
|
||||
catch( e:Dynamic )
|
||||
e + x;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
assertEq("var a = if( true ) 1 else 2; a",1);
|
||||
assertEq("var a = if( false ) 1 else 2; a",2);
|
||||
assertEq("if(true) 1; null", null);
|
||||
assertEq("if(false) 1; null", null);
|
||||
|
||||
assertEq("[55,66,77][1]",66);
|
||||
|
||||
}
|
||||
|
||||
override function teardown() {
|
||||
|
||||
@@ -109,8 +109,6 @@ class StdCase extends TestCase {
|
||||
|
||||
// whitespace: space, horizontal tab, newline, vertical tab, form feed, and carriage return
|
||||
assertEq("Std.parseInt(' 5')", Std.parseInt(" 5"));
|
||||
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r16')", Std.parseInt(" \t\n\x0b\x0c\r16"));
|
||||
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r0xa')", Std.parseInt(" \t\n\x0b\x0c\r0xa"));
|
||||
|
||||
// whitespace and signs
|
||||
assertEq("Std.parseInt(' 16')", Std.parseInt(" 16"));
|
||||
@@ -164,7 +162,6 @@ class StdCase extends TestCase {
|
||||
// signs
|
||||
assertEq("Std.parseFloat('123.45')", Std.parseFloat('123.45'));
|
||||
assertEq("Std.parseFloat('+123.45')", Std.parseFloat('+123.45'));
|
||||
assertEq("Std.parseFloat(' \\t\\n\\x0b\\x0c\\r1.6')", Std.parseFloat('\t\n\x0b\x0c\r1.6'));
|
||||
// whitespace: space, horizontal tab, newline, vertical tab, form feed, and carriage return
|
||||
// whitespace and signs
|
||||
assertEq("Std.parseFloat(' 1.6')", Std.parseFloat(' 1.6'));
|
||||
@@ -202,6 +199,13 @@ class StdCase extends TestCase {
|
||||
// Std.random(1) == 0;
|
||||
// Std.random(0) == 0;
|
||||
// Std.random(-100) == 0;
|
||||
|
||||
|
||||
Util.runKnownBug("Not all escape sequences are supported", () -> {
|
||||
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r16')", Std.parseInt(" \t\n\x0b\x0c\r16"));
|
||||
assertEq("Std.parseInt(' \\t\\n\\x0b\\x0c\\r0xa')", Std.parseInt(" \t\n\x0b\x0c\r0xa"));
|
||||
assertEq("Std.parseFloat(' \\t\\n\\x0b\\x0c\\r1.6')", Std.parseFloat('\t\n\x0b\x0c\r1.6'));
|
||||
});
|
||||
}
|
||||
|
||||
override function teardown() {
|
||||
|
||||
@@ -17,8 +17,6 @@ class StringCase extends TestCase {
|
||||
var str = "foo";
|
||||
assertEq('var str = "foo"; str == new String(str)', str == new String(str));
|
||||
|
||||
//Util.runKnownBug("Using StringTools; not working", () -> {
|
||||
//});
|
||||
assertEq('"foo".toUpperCase()', "foo".toUpperCase());
|
||||
assertEq('"_bar".toUpperCase()', "_bar".toUpperCase());
|
||||
assertEq('"123b".toUpperCase()', "123b".toUpperCase());
|
||||
@@ -91,18 +89,34 @@ class StringCase extends TestCase {
|
||||
assertEq('("a" > "b")', ("a" > "b"));
|
||||
assertEq('("a" >= "b")', ("a" >= "b"));
|
||||
|
||||
assertEq("'${5}'", '${5}');
|
||||
assertEq("'${5},${({})}'", '${5},${({})}');
|
||||
assertEq("'${5},${\"Hello\"}'", '${5},${"Hello"}');
|
||||
assertEq("'${5},${'Hello'}'", '${5},${'Hello'}');
|
||||
assertEq("'$${5}'", '$${5}');
|
||||
assertEq("'$${({})}'", '$${({})}');
|
||||
Util.runKnownBug("String interpolation doesnt work", () -> {
|
||||
assertEq("'${5}'", '${5}');
|
||||
assertEq("'${5},${({})}'", '${5},${({})}');
|
||||
assertEq("'${5},${\"Hello\"}'", '${5},${"Hello"}');
|
||||
assertEq("'${5},${'Hello'}'", '${5},${'Hello'}');
|
||||
assertEq("'$${5}'", '$${5}');
|
||||
assertEq("'$${({})}'", '$${({})}');
|
||||
});
|
||||
|
||||
assertEq('"$${5}"', "${5}");
|
||||
assertEq('"$${5},$${({})}"', "${5},${({})}");
|
||||
assertEq('"$${5},$${\\"Hello\\"}"', "${5},${\"Hello\"}");
|
||||
assertEq('"$${5},$${\'Hello\'}"', "${5},${'Hello'}");
|
||||
|
||||
|
||||
|
||||
assertEq('"foo".toUpperCase()', "foo".toUpperCase());
|
||||
assertEq('"_bar".toUpperCase()', "_bar".toUpperCase());
|
||||
assertEq('"123b".toUpperCase()', "123b".toUpperCase());
|
||||
assertEq('"".toUpperCase()', "".toUpperCase());
|
||||
assertEq('"A".toUpperCase()', "A".toUpperCase());
|
||||
assertEq('"FOO".toLowerCase()', "FOO".toLowerCase());
|
||||
assertEq('"_BAR".toLowerCase()', "_BAR".toLowerCase());
|
||||
assertEq('"_BAR".toLowerCase()', "_BAR".toLowerCase());
|
||||
assertEq('"123B".toLowerCase()', "123B".toLowerCase());
|
||||
assertEq('"".toLowerCase()', "".toLowerCase());
|
||||
assertEq('"a".toLowerCase()', "a".toLowerCase());
|
||||
|
||||
}
|
||||
|
||||
override function teardown() {
|
||||
|
||||
@@ -24,8 +24,11 @@ class SwitchCase extends TestCase {
|
||||
|
||||
assertEq("switch(5) { case 1|4: 'error'; case 5: 0; default: -1; }", switch(5) { case 1|4: 'error'; case 5: 0; default: -1; });
|
||||
assertEq("switch(5) { case 1|4: 'error'; default: -1; }", switch(5) { case 1|4: 'error'; default: -1; });
|
||||
assertEq("switch(5) { case (1|4): 'error'; default: -1; }", switch(5) { case (1|4): 'error'; default: -1; });
|
||||
assertEq("switch(5) { case 1,4: 'error'; case 5: 0; default: -1; }", switch(5) { case 1,4: 'error'; case 5: 0; default: -1; });
|
||||
|
||||
Util.runKnownBug("Switch statement with multiple cases in parentheses", () -> {
|
||||
assertEq("switch(5) { case (1|4): 'error'; default: -1; }", switch(5) { case (1|4): 'error'; default: -1; });
|
||||
});
|
||||
}
|
||||
|
||||
override function teardown() {
|
||||
|
||||
@@ -7,9 +7,9 @@ class TestCase extends HScriptRunner {
|
||||
if(message == null)
|
||||
message = script;
|
||||
var result = if(vars != null)
|
||||
executeWithVars(headerCode + script + tailCode, vars);
|
||||
executeWithVars(script, vars);
|
||||
else
|
||||
execute(headerCode + script + tailCode);
|
||||
execute(script);
|
||||
Util.assertEq(result, expected, message, pos);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ class TestCase extends HScriptRunner {
|
||||
if(message == null)
|
||||
message = script;
|
||||
var result = if(vars != null)
|
||||
executeWithVars(headerCode + script + tailCode, vars);
|
||||
executeWithVars(script, vars);
|
||||
else
|
||||
execute(headerCode + script + tailCode);
|
||||
execute(script);
|
||||
Util.assertNeq(result, expected, message, pos);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user