diff --git a/hscript/Error.hx b/hscript/Error.hx index 5c8cdd5..5fafa8a 100644 --- a/hscript/Error.hx +++ b/hscript/Error.hx @@ -51,6 +51,7 @@ enum Error EInvalidPreprocessor( msg : String ); EUnknownVariable( v : String ); EInvalidIterator( v : String ); + EInvalidType( t : String ); EInvalidOp( op : String ); EInvalidAccess( f : String ); ECustom( msg : String ); diff --git a/hscript/Interp.hx b/hscript/Interp.hx index e89b025..ff3530c 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -159,6 +159,7 @@ class Interp { variables.set("true", true); variables.set("false", false); #if !NO_FROM_CHAR_CODE_FIX + // DONT CALL THIS DIRECTLY, USE String.fromCharCode, the preprocessor will call it for you variables.set("__StringWorkaround__fromCharCode", function(a:Int) { // TODO: make hscript only add this if its used return String.fromCharCode(a); }); diff --git a/hscript/Optimizer.hx b/hscript/Optimizer.hx index 8e9e234..6d3879f 100644 --- a/hscript/Optimizer.hx +++ b/hscript/Optimizer.hx @@ -6,6 +6,8 @@ import hscript.Parser; @:access(hscript.Parser) class Optimizer { + static inline function expr(e:Expr) return Tools.expr(e); + public static function optimize(s:Expr):Expr { if(s == null) return null; @@ -54,8 +56,8 @@ class Optimizer { var econd = optimize(econd); var e1 = optimize(e1); var e2 = optimize(e2); - if(isConstant(econd)) { - var econd = getConstant(econd); + if(isBool(econd)) { + var econd = getBool(econd); if(econd == true) { if(e1 == null) return mk(EBlock([]), s); @@ -66,6 +68,27 @@ class Optimizer { return mk(EBlock([e2]), s); } } + if(e2 != null && isBool(e1) && isBool(e2)) { + var c1 = getBool(e1); + var c2 = getBool(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)) { // Side effect free + 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(EIf(econd, e1, e2), s); case ETernary(econd, e1, e2): @@ -94,7 +117,7 @@ class Optimizer { if(c1 == true && c2 == false) { // (VAR ? true : false) return optimize(mk(Tools.expr(econd), s)); } - if(isConstant(econd)) { + if(isConstant(econd)) { // Side effect free if(c1 == true && c2 == true) { // (CONST ? true : true) return mk(convertConstant(true), s); } @@ -146,23 +169,86 @@ class Optimizer { 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: + + function p(i:Int, t:ConstType, opt:Bool = false):Dynamic { + var p = params[i]; + if(t != getConstType(p)) + if(opt) + return null; + else + throw Parser.getBaseError(EInvalidType(getTypeName(p))); + return switch(t) { + case CTInt: getInt(p); + case CTFloat: getFloat(p); + case CTBool: getBool(p); + case CTString: getStringConstant(p); + case CTNull: null; } } + + switch(Tools.expr(e)) { + case EField(expr(_) => EConst(CString(str)), field): + switch(field) { + case "toString": + if(params.length != 0) throw Parser.getBaseError(ECustom("String.toString() takes no arguments")); + return mk(convertConstant(str.toString()), s); + + case "toUpperCase": + if(params.length != 0) throw Parser.getBaseError(ECustom("String.toUpperCase() takes no arguments")); + return mk(convertConstant(str.toUpperCase()), s); + + case "toLowerCase": + if(params.length != 0) throw Parser.getBaseError(ECustom("String.toLowerCase() takes no arguments")); + return mk(convertConstant(str.toLowerCase()), s); + + case "charAt": + var index = p(0, CTInt); + return mk(convertConstant(str.charAt(index)), s); + + case "charCodeAt": + var index = p(0, CTInt); + return mk(convertConstant(str.charCodeAt(index)), s); + + case "indexOf": + var value = p(0, CTString); + var startIndex = p(1, CTInt, true); + return mk(convertConstant(str.indexOf(value, startIndex)), s); + + case "lastIndexOf": + var value = p(0, CTString); + var startIndex = p(1, CTInt, true); + return mk(convertConstant(str.lastIndexOf(value, startIndex)), s); + + case "split": + var delimiter = p(0, CTString); + var strArr = str == "" ? [] : str.split(delimiter); // fix platform dependent behavior + return mk(EArrayDecl([for(st in strArr) mk(EConst(CString(st)), s)]), s); + + case "substr": + var pos = p(0, CTInt); + var len = p(1, CTInt, true); + return mk(convertConstant(str.substr(pos, len)), s); + + case "substring": + var startIndex = p(0, CTInt); + var endIndex = p(1, CTInt, true); + return mk(convertConstant(str.substring(startIndex, endIndex)), s); + } + default: + } + switch(Tools.expr(e)) { + + default: + } return mk(ECall(e, params), s); case EField(e, f, safe): e = optimize(e); + switch(Tools.expr(e)) { + case EConst(CString(str)) if(f == "length"): + return mk(convertConstant(str.length), s); + default: + } return mk(EField(e, f, safe), s); case EIdent(_) | EConst(_): @@ -226,6 +312,7 @@ class Optimizer { } } + // Maybe convert this to a Lambda.foreach? var isAllCasesConstant = true; for(c in cases) { for(v in c.values) { @@ -268,19 +355,23 @@ class Optimizer { }; var constant = Lambda.exists(arr, isConstant); - if(constant) { - return mk(Tools.expr(arr[getConstant(index)]), s); + var index = getInt(index); + if(constant && index != null) { + return mk(Tools.expr(arr[index]), s); } } if(Tools.expr(e).match(EMapDecl(_))) { var map = switch(Tools.expr(e)) { - case EMapDecl(type, keys, vals): [keys, vals]; + case EMapDecl(type, keys, vals): [for(i in 0...keys.length) [keys[i], vals[i]]]; default: null; }; var constant = Lambda.exists(map, (v) -> isConstant(v[0]) && isConstant(v[1])); if(constant) { - return mk(Tools.expr(map[getConstant(index)][1]), s); + var idx = Lambda.findIndex(map, (v) -> Type.enumEq(Tools.expr(v[0]), Tools.expr(index))); + if(idx == -1) + return mk(EIdent("null"), s); + return mk(Tools.expr(map[idx][1]), s); } } } @@ -301,6 +392,8 @@ class Optimizer { var c1 = getNumber(e1); if(compareNumber(c1, 0) && op == "+") return mk(Tools.expr(e2), s); + if(compareNumber(c1, 1) && op == "*") + return mk(Tools.expr(e2), s); //if(compareNumber(c1, 0) && op == "*") // return mk(convertConstant(0), s); } @@ -311,6 +404,8 @@ class Optimizer { return mk(Tools.expr(e1), s); if(op == "/" && compareNumber(c2, 1)) return mk(Tools.expr(e1), s); + if(op == "*" && compareNumber(c2, 1)) + return mk(Tools.expr(e1), s); //if(op == "*" && compareNumber(c2, 0)) // return mk(convertConstant(0), s); } @@ -366,6 +461,35 @@ class Optimizer { } } + static function getTypeName(e:Expr):String { + if(e == null) + return null; + return switch(Tools.expr(e)) { + case EConst(CInt(_)): "Int"; + case EConst(CFloat(_)): "Float"; + case EConst(CString(_)): "String"; + case EIdent("true") | EIdent("false"): "Bool"; + case EIdent("null"): "Null"; + case EIdent(_): "Dynamic"; + case EParent(e): getTypeName(e); + default: Std.string(Tools.expr(e)); + } + } + + static function getConstType(e:Expr):ConstType { + if(e == null) + return null; + return switch(Tools.expr(e)) { + case EConst(CInt(_)): CTInt; + case EConst(CFloat(_)): CTFloat; + case EConst(CString(_)): CTString; + case EIdent("true") | EIdent("false"): CTBool; + case EIdent("null"): CTNull; + case EParent(e): getConstType(e); + default: throw "Unknown type " + Tools.expr(e); + } + } + static function isConstant(e:Expr):Bool { return switch(Tools.expr(e)) { case EIdent("true") | EIdent("false") | EIdent("null"): true; @@ -404,15 +528,44 @@ class Optimizer { static function getBool(e:Expr):Bool { return switch(Tools.expr(e)) { case EIdent("true"): true; - case EIdent("false"): true; + case EIdent("false"): false; case EParent(e): getBool(e); default: throw "Unknown type " + Tools.expr(e); } } + static function getInt(e:Expr):Null { + if(e == null) + return null; + return switch(Tools.expr(e)) { + case EConst(CInt(value)): value; + case EParent(e): getInt(e); + default: null; + } + } + + static function getFloat(e:Expr):Null { + if(e == null) + return null; + return switch(Tools.expr(e)) { + case EConst(CFloat(value)): value; + case EParent(e): getFloat(e); + default: null; + } + } + + static function isString(e:Expr):Bool { + return switch(Tools.expr(e)) { + case EConst(CString(_)): true; + case EParent(e): isString(e); + default: false; + } + } + static function getStringConstant(e:Expr):String { return switch(Tools.expr(e)) { case EConst(CString(value)): value; + case EParent(e): getStringConstant(e); default: null; } } @@ -476,4 +629,12 @@ class Optimizer { default: null; } } +} + +enum ConstType { + CTInt; + CTFloat; + CTBool; + CTString; + CTNull; } \ No newline at end of file diff --git a/hscript/Printer.hx b/hscript/Printer.hx index 47212e7..011f8ab 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -537,6 +537,7 @@ class Printer { case EInvalidPreprocessor(str): "Invalid preprocessor (" + str + ")"; case EUnknownVariable(v): "Unknown variable: "+v; case EInvalidIterator(v): "Invalid iterator: "+v; + case EInvalidType(t): "Invalid type: "+t; case EInvalidOp(op): "Invalid operator: "+op; case EInvalidAccess(f): "Invalid access to field " + f; case ECustom(msg): msg; diff --git a/tests/src/Util.hx b/tests/src/Util.hx index 095a546..2cb2f60 100644 --- a/tests/src/Util.hx +++ b/tests/src/Util.hx @@ -37,10 +37,10 @@ class Util { var b_args = EnumValueTools.getParameters(cast b); return deepEqual(a_args, b_args); case TClass(_): - if (Std.isOfType(a, String) && Std.isOfType(b, String)) { + if ((a is String) && (b is String)) { return a == b; } - if (Std.isOfType(a, Array) && Std.isOfType(b, Array)) { + if ((a is Array) && (b is Array)) { var a = cast(a, Array); var b = cast(b, Array); if (a.length != b.length) { return false; } @@ -52,9 +52,9 @@ class Util { return true; } - if (Std.isOfType(a, IMap) && Std.isOfType(b, IMap)) { - var a = cast(a, Map); - var b = cast(b, Map); + if ((a is IMap) && (b is IMap)) { + var a = cast(a, IMap); + var b = cast(b, IMap); var a_keys = [ for (key in a.keys()) key ]; var b_keys = [ for (key in b.keys()) key ]; a_keys.sort(Reflect.compare); @@ -68,11 +68,11 @@ class Util { return true; } - if (Std.isOfType(a, Date) && Std.isOfType(b, Date)) { + if ((a is Date) && (b is Date)) { return cast(a, Date).getTime() == cast(b, Date).getTime(); } - if (Std.isOfType(a, haxe.io.Bytes) && Std.isOfType(b, haxe.io.Bytes)) { + if ((a is haxe.io.Bytes) && (b is haxe.io.Bytes)) { return deepEqual(cast(a, haxe.io.Bytes).getData(), cast(b, haxe.io.Bytes).getData()); } diff --git a/tests/src/tests/FinalCase.hx b/tests/src/tests/FinalCase.hx index f1e0623..825758c 100644 --- a/tests/src/tests/FinalCase.hx +++ b/tests/src/tests/FinalCase.hx @@ -83,6 +83,7 @@ class FinalCase extends TestCase { assertEq("[0=>'hello', 1=>'world']", [0=>'hello', 1=>'world']); assertEq("[0=>'hello', 1=>'world'][1]", [0=>'hello', 1=>'world'][1]); + assertEq("[0=>'hello', 5=>'world'][1]", [0=>'hello', 5=>'world'][1]); // Test EOF with preprocessor } diff --git a/tests/src/tests/StringCase.hx b/tests/src/tests/StringCase.hx index c9c60c1..ca6cbd7 100644 --- a/tests/src/tests/StringCase.hx +++ b/tests/src/tests/StringCase.hx @@ -151,15 +151,27 @@ class StringCase extends TestCase { assertEq('String.fromCharCode(101)', String.fromCharCode(101)); assertEq('String.fromCharCode(102)', String.fromCharCode(102)); assertEq('String.fromCharCode(103)', String.fromCharCode(103)); - assertEq('var a = 103; String.fromCharCode(a)', String.fromCharCode(test)); + assertEq('var a = 103; String.fromCharCode(a)', String.fromCharCode(a)); assertError("'hello ${}'", Parser.getBaseError(EPreset(EMPTY_INTERPOLATION))); - } - static var test = 103; + assertEq('"".charAt(0)', "".charAt(0)); + assertEq('"".charAt(1)', "".charAt(1)); + assertEq('"".charAt(-1)', "".charAt(-1)); - static function fromCharCode(a:Int) { - return String.fromCharCode(a); + headerCode = ''; + + assertEq("'foo1bar'.charCodeAt(0)", 'foo1bar'.charCodeAt(0)); + assertEq("'foo1bar'.charCodeAt(1)", 'foo1bar'.charCodeAt(1)); + assertEq("'foo1bar'.charCodeAt(2)", 'foo1bar'.charCodeAt(2)); + assertEq("'foo1bar'.charCodeAt(3)", 'foo1bar'.charCodeAt(3)); + assertEq("'foo1bar'.charCodeAt(4)", 'foo1bar'.charCodeAt(4)); + assertEq("'foo1bar'.charCodeAt(5)", 'foo1bar'.charCodeAt(5)); + assertEq("'foo1bar'.charCodeAt(6)", 'foo1bar'.charCodeAt(6)); + assertEq("'foo1bar'.charCodeAt(7)", 'foo1bar'.charCodeAt(7)); + assertEq("'foo1bar'.charCodeAt(-1)", 'foo1bar'.charCodeAt(-1)); + assertEq("'foo1bar'.length", 'foo1bar'.length); + assertEq("'foo1bar'.split('1')", 'foo1bar'.split('1')); } override function teardown() {