Optimization on string constants
This commit is contained in:
@@ -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 );
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
+180
-19
@@ -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<Int> {
|
||||
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<Float> {
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+7
-7
@@ -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<Dynamic>);
|
||||
var b = cast(b, Array<Dynamic>);
|
||||
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<Dynamic, Dynamic>);
|
||||
var b = cast(b, Map<Dynamic, Dynamic>);
|
||||
if ((a is IMap) && (b is IMap)) {
|
||||
var a = cast(a, IMap<Dynamic, Dynamic>);
|
||||
var b = cast(b, IMap<Dynamic, Dynamic>);
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user