Skip to content

Commit

Permalink
Introduce cons-string to make string concatenation more efficient
Browse files Browse the repository at this point in the history
  • Loading branch information
hns committed Sep 23, 2011
1 parent 702abfe commit 141dc4f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 49 deletions.
61 changes: 61 additions & 0 deletions src/org/mozilla/javascript/ConsString.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.mozilla.javascript;

public class ConsString implements CharSequence {

private CharSequence s1, s2;
private final int length, depth;

public ConsString(CharSequence str1, CharSequence str2) {
s1 = str1;
s2 = str2;
length = str1.length() + str2.length();
int d = 1;
if (str1 instanceof ConsString) d += ((ConsString)str1).depth;
if (str2 instanceof ConsString) d += ((ConsString)str2).depth;
if (d > 100) {
flatten();
depth = 1;
} else {
depth = d;
}
}

public String toString() {
if (!(s1 instanceof String) || s2 != "") {
flatten();
}
return (String) s1;
}

private synchronized void flatten() {
StringBuilder b = new StringBuilder(length);
appendTo(b);
s1 = b.toString();
s2 = "";
}

private synchronized void appendTo(StringBuilder b) {
appendFragment(s1, b);
appendFragment(s2, b);
}

private static void appendFragment(CharSequence s, StringBuilder b) {
if (s instanceof ConsString) {
((ConsString)s).appendTo(b);
} else {
b.append(s);
}
}

public int length() {
return length;
}

public char charAt(int index) {
throw new UnsupportedOperationException();
}

public CharSequence subSequence(int start, int end) {
throw new UnsupportedOperationException();
}
}
22 changes: 9 additions & 13 deletions src/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3037,14 +3037,10 @@ private static void do_add(Object[] stack, double[] sDbl, int stackTop,
} else {
if (lhs instanceof Scriptable || rhs instanceof Scriptable) {
stack[stackTop] = ScriptRuntime.add(lhs, rhs, cx);
} else if (lhs instanceof String) {
String lstr = (String)lhs;
String rstr = ScriptRuntime.toString(rhs);
stack[stackTop] = lstr.concat(rstr);
} else if (rhs instanceof String) {
String lstr = ScriptRuntime.toString(lhs);
String rstr = (String)rhs;
stack[stackTop] = lstr.concat(rstr);
} else if (lhs instanceof CharSequence || rhs instanceof CharSequence) {
CharSequence lstr = ScriptRuntime.toCharSequence(lhs);
CharSequence rstr = ScriptRuntime.toCharSequence(rhs);
stack[stackTop] = new ConsString(lstr, rstr);
} else {
double lDbl = (lhs instanceof Number)
? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs);
Expand All @@ -3065,13 +3061,13 @@ private static void do_add(Object[] stack, double[] sDbl, int stackTop,
rhs = tmp;
}
stack[stackTop] = ScriptRuntime.add(lhs, rhs, cx);
} else if (lhs instanceof String) {
String lstr = (String)lhs;
String rstr = ScriptRuntime.toString(d);
} else if (lhs instanceof CharSequence) {
CharSequence lstr = (CharSequence)lhs;
CharSequence rstr = ScriptRuntime.toCharSequence(d);
if (leftRightOrder) {
stack[stackTop] = lstr.concat(rstr);
stack[stackTop] = new ConsString(lstr, rstr);
} else {
stack[stackTop] = rstr.concat(lstr);
stack[stackTop] = new ConsString(rstr, lstr);
}
} else {
double lDbl = (lhs instanceof Number)
Expand Down
61 changes: 33 additions & 28 deletions src/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@ public static boolean toBoolean(Object val)
return ((Boolean) val).booleanValue();
if (val == null || val == Undefined.instance)
return false;
if (val instanceof String)
return ((String) val).length() != 0;
if (val instanceof CharSequence)
return ((CharSequence) val).length() != 0;
if (val instanceof Number) {
double d = ((Number) val).doubleValue();
return (d == d && d != 0.0);
Expand Down Expand Up @@ -397,8 +397,8 @@ public static double toNumber(Object val)
return +0.0;
if (val == Undefined.instance)
return NaN;
if (val instanceof String)
return toNumber((String) val);
if (val instanceof CharSequence)
return toNumber(val.toString());
if (val instanceof Boolean)
return ((Boolean) val).booleanValue() ? 1 : +0.0;
if (val instanceof Scriptable) {
Expand Down Expand Up @@ -769,6 +769,10 @@ static boolean isValidIdentifierName(String s)
return !TokenStream.isKeyword(s);
}

public static CharSequence toCharSequence(Object val) {
return val instanceof CharSequence ? (CharSequence) val : toString(val);
}

/**
* Convert the value to a string.
*
Expand All @@ -782,8 +786,8 @@ public static String toString(Object val) {
if (val == Undefined.instance) {
return "undefined";
}
if (val instanceof String) {
return (String)val;
if (val instanceof CharSequence) {
return val.toString();
}
if (val instanceof Number) {
// XXX should we just teach NativeNumber.stringValue()
Expand Down Expand Up @@ -1012,8 +1016,9 @@ public static Scriptable toObject(Context cx, Scriptable scope, Object val)
if (val instanceof Scriptable) {
return (Scriptable) val;
}
if (val instanceof String) {
NativeString result = new NativeString((String)val);
if (val instanceof CharSequence) {
// FIXME we want to avoid toString() here, especially for concat()
NativeString result = new NativeString(val.toString());
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.String);
return result;
}
Expand Down Expand Up @@ -2520,7 +2525,7 @@ public static Object evalSpecial(Context cx, Scriptable scope,
if (args.length < 1)
return Undefined.instance;
Object x = args[0];
if (!(x instanceof String)) {
if (!(x instanceof CharSequence)) {
if (cx.hasFeature(Context.FEATURE_STRICT_MODE) ||
cx.hasFeature(Context.FEATURE_STRICT_EVAL))
{
Expand Down Expand Up @@ -2553,7 +2558,7 @@ public static Object evalSpecial(Context cx, Scriptable scope,

// Compile with explicit interpreter instance to force interpreter
// mode.
Script script = cx.compileString((String)x, evaluator,
Script script = cx.compileString(x.toString(), evaluator,
reporter, sourceName, 1, null);
evaluator.setEvalScriptFlag(script);
Callable c = (Callable)script;
Expand All @@ -2573,7 +2578,7 @@ public static String typeof(Object value)
return ((ScriptableObject) value).getTypeOf();
if (value instanceof Scriptable)
return (value instanceof Callable) ? "function" : "object";
if (value instanceof String)
if (value instanceof CharSequence)
return "string";
if (value instanceof Number)
return "number";
Expand Down Expand Up @@ -2628,21 +2633,21 @@ public static Object add(Object val1, Object val2, Context cx)
val1 = ((Scriptable) val1).getDefaultValue(null);
if (val2 instanceof Scriptable)
val2 = ((Scriptable) val2).getDefaultValue(null);
if (!(val1 instanceof String) && !(val2 instanceof String))
if (!(val1 instanceof CharSequence) && !(val2 instanceof CharSequence))
if ((val1 instanceof Number) && (val2 instanceof Number))
return wrapNumber(((Number)val1).doubleValue() +
((Number)val2).doubleValue());
else
return wrapNumber(toNumber(val1) + toNumber(val2));
return toString(val1).concat(toString(val2));
return new ConsString(toCharSequence(val1), toCharSequence(val2));
}

public static String add(String val1, Object val2) {
return val1.concat(toString(val2));
public static CharSequence add(CharSequence val1, Object val2) {
return new ConsString(val1, toCharSequence(val2));
}

public static String add(Object val1, String val2) {
return toString(val1).concat(val2);
public static CharSequence add(Object val1, CharSequence val2) {
return new ConsString(toCharSequence(val1), val2);
}

/**
Expand Down Expand Up @@ -2833,8 +2838,8 @@ public static boolean eq(Object x, Object y)
return false;
} else if (x instanceof Number) {
return eqNumber(((Number)x).doubleValue(), y);
} else if (x instanceof String) {
return eqString((String)x, y);
} else if (x instanceof CharSequence) {
return eqString(x.toString(), y);
} else if (x instanceof Boolean) {
boolean b = ((Boolean)x).booleanValue();
if (y instanceof Boolean) {
Expand Down Expand Up @@ -2935,8 +2940,8 @@ private static boolean eqString(String x, Object y)
for (;;) {
if (y == null || y == Undefined.instance) {
return false;
} else if (y instanceof String) {
return x.equals(y);
} else if (y instanceof CharSequence) {
return x.equals(y.toString());
} else if (y instanceof Number) {
return toNumber(x) == ((Number)y).doubleValue();
} else if (y instanceof Boolean) {
Expand Down Expand Up @@ -2972,9 +2977,9 @@ public static boolean shallowEq(Object x, Object y)
if (y instanceof Number) {
return ((Number)x).doubleValue() == ((Number)y).doubleValue();
}
} else if (x instanceof String) {
if (y instanceof String) {
return x.equals(y);
} else if (x instanceof CharSequence) {
if (y instanceof CharSequence) {
return x.toString().equals(y.toString());
}
} else if (x instanceof Boolean) {
if (y instanceof Boolean) {
Expand Down Expand Up @@ -3060,8 +3065,8 @@ public static boolean cmp_LT(Object val1, Object val2)
val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
if (val2 instanceof Scriptable)
val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
if (val1 instanceof String && val2 instanceof String) {
return ((String)val1).compareTo((String)val2) < 0;
if (val1 instanceof CharSequence && val2 instanceof CharSequence) {
return val1.toString().compareTo(val2.toString()) < 0;
}
d1 = toNumber(val1);
d2 = toNumber(val2);
Expand All @@ -3080,8 +3085,8 @@ public static boolean cmp_LE(Object val1, Object val2)
val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
if (val2 instanceof Scriptable)
val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
if (val1 instanceof String && val2 instanceof String) {
return ((String)val1).compareTo((String)val2) <= 0;
if (val1 instanceof CharSequence && val2 instanceof CharSequence) {
return val1.toString().compareTo(val2.toString()) <= 0;
}
d1 = toNumber(val1);
d2 = toNumber(val2);
Expand Down
8 changes: 4 additions & 4 deletions src/org/mozilla/javascript/optimizer/Codegen.java
Original file line number Diff line number Diff line change
Expand Up @@ -2537,14 +2537,14 @@ private void generateExpression(Node node, Node parent)
default:
if (child.getType() == Token.STRING) {
addScriptRuntimeInvoke("add",
"(Ljava/lang/String;"
"(Ljava/lang/CharSequence;"
+"Ljava/lang/Object;"
+")Ljava/lang/String;");
+")Ljava/lang/CharSequence;");
} else if (child.getNext().getType() == Token.STRING) {
addScriptRuntimeInvoke("add",
"(Ljava/lang/Object;"
+"Ljava/lang/String;"
+")Ljava/lang/String;");
+"Ljava/lang/CharSequence;"
+")Ljava/lang/CharSequence;");
} else {
cfw.addALoad(contextLocal);
addScriptRuntimeInvoke("add",
Expand Down
8 changes: 4 additions & 4 deletions src/org/mozilla/javascript/optimizer/OptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,18 @@ public static Object add(Object val1, double val2)
{
if (val1 instanceof Scriptable)
val1 = ((Scriptable) val1).getDefaultValue(null);
if (!(val1 instanceof String))
if (!(val1 instanceof CharSequence))
return wrapDouble(toNumber(val1) + val2);
return ((String)val1).concat(toString(val2));
return new ConsString((CharSequence)val1, toString(val2));
}

public static Object add(double val1, Object val2)
{
if (val2 instanceof Scriptable)
val2 = ((Scriptable) val2).getDefaultValue(null);
if (!(val2 instanceof String))
if (!(val2 instanceof CharSequence))
return wrapDouble(toNumber(val2) + val1);
return toString(val1).concat((String)val2);
return new ConsString(toString(val1), (CharSequence)val2);
}

public static Object elemIncrDecr(Object obj, double index,
Expand Down

0 comments on commit 141dc4f

Please sign in to comment.