--- a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java Thu Jan 24 17:49:03 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java Thu Jan 24 14:55:57 2013 +0100
@@ -36,6 +36,7 @@
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import jdk.nashorn.internal.objects.annotations.Attribute;
@@ -428,17 +429,30 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object charAt(final Object self, final Object pos) {
- try {
- return String.valueOf(((String)self).charAt(((Number)pos).intValue()));
- } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
- final int at = JSType.toInteger(pos);
- if (at < 0 || at >= str.length()) {
- return "";
- }
- return String.valueOf(str.charAt(at));
- }
+ return charAt(self, JSType.toInteger(pos));
+ }
+
+ /**
+ * ECMA 15.5.4.4 String.prototype.charAt (pos) - specialized version for double position
+ * @param self self reference
+ * @param pos position in string
+ * @return string representing the char at the given position
+ */
+ @SpecializedFunction
+ public static String charAt(final Object self, final double pos) {
+ return charAt(self, (int)pos);
+ }
+
+ /**
+ * ECMA 15.5.4.4 String.prototype.charAt (pos) - specialized version for int position
+ * @param self self reference
+ * @param pos position in string
+ * @return string representing the char at the given position
+ */
+ @SpecializedFunction
+ public static String charAt(final Object self, final int pos) {
+ final String str = checkObjectToString(self);
+ return (pos < 0 || pos >= str.length()) ? "" : String.valueOf(str.charAt(pos));
}
/**
@@ -449,18 +463,30 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object charCodeAt(final Object self, final Object pos) {
- try {
- return (int)((String)self).charAt(((Number)pos).intValue());
- } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
- final int at = JSType.toInteger(pos);
- if (at < 0 || at >= str.length()) {
- return Double.NaN;
- }
+ return charCodeAt(self, JSType.toInteger(pos));
+ }
- return JSType.toObject(str.charAt(at));
- }
+ /**
+ * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) - specialized version for double position
+ * @param self self reference
+ * @param pos position in string
+ * @return number representing charcode at position
+ */
+ @SpecializedFunction
+ public static double charCodeAt(final Object self, final double pos) {
+ return charCodeAt(self, (int) pos);
+ }
+
+ /**
+ * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) - specialized version for int position
+ * @param self self reference
+ * @param pos position in string
+ * @return number representing charcode at position
+ */
+ @SpecializedFunction
+ public static double charCodeAt(final Object self, final int pos) {
+ final String str = checkObjectToString(self);
+ return (pos < 0 || pos >= str.length()) ? Double.NaN : str.charAt(pos);
}
/**
@@ -471,14 +497,13 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
public static Object concat(final Object self, final Object... args) {
- Global.checkObjectCoercible(self);
- final StringBuilder sb = new StringBuilder(JSType.toString(self));
+ CharSequence cs = checkObjectToString(self);
if (args != null) {
for (final Object obj : args) {
- sb.append(JSType.toString(obj));
+ cs = new ConsString(cs, JSType.toCharSequence(obj));
}
}
- return sb.toString();
+ return cs;
}
/**
@@ -490,12 +515,43 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
public static Object indexOf(final Object self, final Object search, final Object pos) {
- try {
- return ((String)self).indexOf((String)search, ((Number)pos).intValue()); //assuming that the conversions really mean "toInteger" and not "toInt32" this is ok.
- } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
- Global.checkObjectCoercible(self);
- return JSType.toString(self).indexOf(JSType.toString(search), JSType.toInteger(pos));
- }
+ final String str = checkObjectToString(self);
+ return str.indexOf(JSType.toString(search), JSType.toInteger(pos));
+ }
+
+ /**
+ * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for no position parameter
+ * @param self self reference
+ * @param search string to search for
+ * @return position of first match or -1
+ */
+ @SpecializedFunction
+ public static int indexOf(final Object self, final Object search) {
+ return indexOf(self, search, 0);
+ }
+
+ /**
+ * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for double position parameter
+ * @param self self reference
+ * @param search string to search for
+ * @param pos position to start search
+ * @return position of first match or -1
+ */
+ @SpecializedFunction
+ public static int indexOf(final Object self, final Object search, final double pos) {
+ return indexOf(self, search, (int) pos);
+ }
+
+ /**
+ * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for int position parameter
+ * @param self self reference
+ * @param search string to search for
+ * @param pos position to start search
+ * @return position of first match or -1
+ */
+ @SpecializedFunction
+ public static int indexOf(final Object self, final Object search, final int pos) {
+ return checkObjectToString(self).indexOf(JSType.toString(search), pos);
}
/**
@@ -507,9 +563,8 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
public static Object lastIndexOf(final Object self, final Object search, final Object pos) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
+ final String str = checkObjectToString(self);
final String searchStr = JSType.toString(search);
int from;
@@ -532,9 +587,8 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object localeCompare(final Object self, final Object that) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
+ final String str = checkObjectToString(self);
final Collator collator = Collator.getInstance(Global.getThisContext().getLocale());
collator.setStrength(Collator.IDENTICAL);
@@ -551,9 +605,8 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object match(final Object self, final Object regexp) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
+ final String str = checkObjectToString(self);
NativeRegExp nativeRegExp;
if (regexp == UNDEFINED) {
@@ -599,9 +652,8 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object replace(final Object self, final Object string, final Object replacement) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
+ final String str = checkObjectToString(self);
final NativeRegExp nativeRegExp;
if (string instanceof NativeRegExp) {
@@ -626,9 +678,8 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object search(final Object self, final Object string) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
+ final String str = checkObjectToString(self);
final NativeRegExp nativeRegExp = Global.toRegExp(string == UNDEFINED ? "" : string);
return nativeRegExp.search(str);
@@ -644,30 +695,70 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object slice(final Object self, final Object start, final Object end) {
- Global.checkObjectCoercible(self);
+
+ final String str = checkObjectToString(self);
+ if (end == UNDEFINED) {
+ return slice(str, JSType.toInteger(start));
+ }
+ return slice(str, JSType.toInteger(start), JSType.toInteger(end));
+ }
- final String str = JSType.toString(self);
- final int len = str.length();
- final int intStart = JSType.toInteger(start);
- final int intEnd = (end == UNDEFINED) ? len : JSType.toInteger(end);
+ /**
+ * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for single int parameter
+ *
+ * @param self self reference
+ * @param start start position for slice
+ * @return sliced out substring
+ */
+ @SpecializedFunction
+ public static Object slice(final Object self, final int start) {
+ final String str = checkObjectToString(self);
+ final int from = (start < 0) ? Math.max(str.length() + start, 0) : Math.min(start, str.length());
- int from;
+ return str.substring(from);
+ }
- if (intStart < 0) {
- from = Math.max(len + intStart, 0);
- } else {
- from = Math.min(intStart, len);
- }
-
- int to;
+ /**
+ * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for single double parameter
+ *
+ * @param self self reference
+ * @param start start position for slice
+ * @return sliced out substring
+ */
+ @SpecializedFunction
+ public static Object slice(final Object self, final double start) {
+ return slice(self, (int)start);
+ }
- if (intEnd < 0) {
- to = Math.max(len + intEnd,0);
- } else {
- to = Math.min(intEnd, len);
- }
+ /**
+ * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for two int parameters
+ *
+ * @param self self reference
+ * @param start start position for slice
+ * @return sliced out substring
+ */
+ @SpecializedFunction
+ public static Object slice(final Object self, final int start, final int end) {
+
+ final String str = checkObjectToString(self);
+ final int len = str.length();
- return str.substring(Math.min(from, to), to);
+ final int from = (start < 0) ? Math.max(len + start, 0) : Math.min(start, len);
+ final int to = (end < 0) ? Math.max(len + end, 0) : Math.min(end, len);
+
+ return str.substring(Math.min(from, to), to);
+ }
+
+ /**
+ * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for two double parameters
+ *
+ * @param self self reference
+ * @param start start position for slice
+ * @return sliced out substring
+ */
+ @SpecializedFunction
+ public static Object slice(final Object self, final double start, final double end) {
+ return slice(self, (int)start, (int)end);
}
/**
@@ -680,9 +771,8 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object split(final Object self, final Object separator, final Object limit) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
+ final String str = checkObjectToString(self);
if (separator == UNDEFINED) {
return new NativeArray(new Object[]{str});
@@ -694,9 +784,40 @@
return ((NativeRegExp) separator).split(str, lim);
}
- // when separator is a string, it has to be treated as a
- // literal search string to be used for splitting.
- return new NativeRegExp(Pattern.compile(JSType.toString(separator), Pattern.LITERAL)).split(str, lim);
+ // when separator is a string, it is treated as a literal search string to be used for splitting.
+ return splitString(str, JSType.toString(separator), lim);
+ }
+
+ private static Object splitString(String str, String separator, long limit) {
+
+ if (separator.equals("")) {
+ Object[] array = new Object[str.length()];
+ for (int i = 0; i < array.length; i++) {
+ array[i] = String.valueOf(str.charAt(i));
+ }
+ return new NativeArray(array);
+ }
+
+ final List<String> elements = new LinkedList<>();
+ final int strLength = str.length();
+ final int sepLength = separator.length();
+ int pos = 0;
+ int count = 0;
+
+ while (pos < strLength && count < limit) {
+ int found = str.indexOf(separator, pos);
+ if (found == -1) {
+ break;
+ }
+ elements.add(str.substring(pos, found));
+ count++;
+ pos = found + sepLength;
+ }
+ if (pos <= strLength && count < limit) {
+ elements.add(str.substring(pos));
+ }
+
+ return new NativeArray(elements.toArray());
}
/**
@@ -732,25 +853,78 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object substring(final Object self, final Object start, final Object end) {
- Global.checkObjectCoercible(self);
+
+ final String str = checkObjectToString(self);
+ if (end == UNDEFINED) {
+ return substring(str, JSType.toInteger(start));
+ }
+ return substring(str, JSType.toInteger(start), JSType.toInteger(end));
+ }
- final String str = JSType.toString(self);
- final int len = str.length();
- final int intStart = JSType.toInteger(start);
- final int intEnd = (end == UNDEFINED) ? len : JSType.toInteger(end);
- final int finalStart = Math.min((intStart < 0) ? 0 : intStart, len);
- final int finalEnd = Math.min((intEnd < 0) ? 0 : intEnd, len);
+ /**
+ * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start parameter
+ *
+ * @param self self reference
+ * @param start start position of substring
+ * @return substring given start and end indexes
+ */
+ @SpecializedFunction
+ public static String substring(final Object self, final int start) {
+ final String str = checkObjectToString(self);
+ if (start < 0) {
+ return str;
+ } else if (start >= str.length()) {
+ return "";
+ } else {
+ return str.substring(start);
+ }
+ }
- int from, to;
+ /**
+ * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start parameter
+ *
+ * @param self self reference
+ * @param start start position of substring
+ * @return substring given start and end indexes
+ */
+ @SpecializedFunction
+ public static String substring(final Object self, final double start) {
+ return substring(self, (int)start);
+ }
- if (finalStart < finalEnd) {
- from = finalStart;
- to = finalEnd;
+ /**
+ * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start and end parameters
+ *
+ * @param self self reference
+ * @param start start position of substring
+ * @param end end position of substring
+ * @return substring given start and end indexes
+ */
+ @SpecializedFunction
+ public static String substring(final Object self, final int start, final int end) {
+ final String str = checkObjectToString(self);
+ final int len = str.length();
+ final int validStart = start < 0 ? 0 : (start > len ? len : start);
+ final int validEnd = end < 0 ? 0 : (end > len ? len : end);
+
+ if (validStart < validEnd) {
+ return str.substring(validStart, validEnd);
} else {
- from = finalEnd;
- to = finalStart;
+ return str.substring(validEnd, validStart);
}
- return str.substring(from, to);
+ }
+
+ /**
+ * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start and end parameters
+ *
+ * @param self self reference
+ * @param start start position of substring
+ * @param end end position of substring
+ * @return substring given start and end indexes
+ */
+ @SpecializedFunction
+ public static String substring(final Object self, final double start, final double end) {
+ return substring(self, (int)start, (int)end);
}
/**
@@ -760,8 +934,7 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object toLowerCase(final Object self) {
- Global.checkObjectCoercible(self);
- return JSType.toString(self).toLowerCase();
+ return checkObjectToString(self).toLowerCase();
}
/**
@@ -771,8 +944,7 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object toLocaleLowerCase(final Object self) {
- Global.checkObjectCoercible(self);
- return JSType.toString(self).toLowerCase(Global.getThisContext().getLocale());
+ return checkObjectToString(self).toLowerCase(Global.getThisContext().getLocale());
}
/**
@@ -782,8 +954,7 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object toUpperCase(final Object self) {
- Global.checkObjectCoercible(self);
- return JSType.toString(self).toUpperCase();
+ return checkObjectToString(self).toUpperCase();
}
/**
@@ -793,8 +964,7 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object toLocaleUpperCase(final Object self) {
- Global.checkObjectCoercible(self);
- return JSType.toString(self).toUpperCase(Global.getThisContext().getLocale());
+ return checkObjectToString(self).toUpperCase(Global.getThisContext().getLocale());
}
/**
@@ -804,12 +974,11 @@
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static Object trim(final Object self) {
- Global.checkObjectCoercible(self);
- final String str = JSType.toString(self);
-
+ final String str = checkObjectToString(self);
+ final int len = str.length();
int start = 0;
- int end = str.length() - 1;
+ int end = len - 1;
while (start <= end && Lexer.isJSWhitespace(str.charAt(start))) {
start++;
@@ -818,7 +987,7 @@
end--;
}
- return str.substring(start, end + 1);
+ return start == 0 && end + 1 == len ? str : str.substring(start, end + 1);
}
private static Object newObj(final Object self, final CharSequence str) {
@@ -860,7 +1029,22 @@
return newObj ? newObj(self, "") : "";
}
- //TODO - why is there no String with one String arg constructor?
+ /**
+ * ECMA 15.5.2.1 new String ( [ value ] ) - special version with one arg
+ *
+ * Constructor
+ *
+ * @param newObj is this constructor invoked with the new operator
+ * @param self self reference
+ * @param arg argument
+ *
+ * @return new NativeString (arg)
+ */
+ @SpecializedConstructor
+ public static Object constructor(final boolean newObj, final Object self, final Object arg) {
+ final CharSequence cs = JSType.toCharSequence(arg);
+ return newObj ? newObj(self, cs) : cs;
+ }
/**
* ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg
@@ -924,6 +1108,23 @@
}
}
+ /**
+ * Combines ECMA 9.10 CheckObjectCoercible and ECMA 9.8 ToString with a shortcut for strings.
+ *
+ * @param self the object
+ * @return the object as string
+ */
+ private static String checkObjectToString(final Object self) {
+ if (self instanceof String) {
+ return (String)self;
+ } else if (self instanceof ConsString) {
+ return self.toString();
+ } else {
+ Global.checkObjectCoercible(self);
+ return JSType.toString(self);
+ }
+ }
+
private boolean isValid(final int key) {
return key >= 0 && key < value.length();
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/examples/string-micro.js Thu Jan 24 14:55:57 2013 +0100
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of Oracle nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+var str = "The quick gray nashorn jumps over the lazy zebra.";
+
+function bench(name, func) {
+ var start = Date.now();
+ for (var iter = 0; iter < 5000000; iter++) {
+ func();
+ }
+ print((Date.now() - start) + "\t" + name);
+}
+
+bench("[]", function() {
+ str[0];
+ str[1];
+ str[2];
+});
+
+bench("fromCharCode", function() {
+ String.fromCharCode(97);
+ String.fromCharCode(98);
+ String.fromCharCode(99);
+});
+
+bench("charAt 1", function() {
+ str.charAt(0);
+ str.charAt(1);
+ str.charAt(2);
+});
+
+bench("charAt 2", function() {
+ str.charAt(100);
+ str.charAt(-1);
+});
+
+bench("charCodeAt 1", function() {
+ str.charCodeAt(0);
+ str.charCodeAt(1);
+ str.charCodeAt(2);
+});
+
+bench("charCodeAt 2", function() {
+ str.charCodeAt(100);
+ str.charCodeAt(-1);
+});
+
+bench("indexOf 1", function() {
+ str.indexOf("T");
+ str.indexOf("h");
+ str.indexOf("e");
+});
+
+bench("indexOf 2", function() {
+ str.indexOf("T", 0);
+ str.indexOf("h", 1);
+ str.indexOf("e", 2);
+});
+
+bench("lastIndexOf", function() {
+ str.indexOf("a");
+ str.indexOf("r");
+ str.indexOf("b");
+});
+
+bench("slice", function() {
+ str.slice(5);
+ str.slice(5);
+ str.slice(5);
+});
+
+bench("split 1", function() {
+ str.split();
+});
+
+bench("split 2", function() {
+ str.split("foo");
+});
+
+bench("split 3", function() {
+ str.split(/foo/);
+});
+
+bench("substring 1", function() {
+ str.substring(0);
+ str.substring(0);
+ str.substring(0);
+});
+
+bench("substring 2", function() {
+ str.substring(0, 5);
+ str.substring(0, 5);
+ str.substring(0, 5);
+});
+
+bench("substr", function() {
+ str.substr(0);
+ str.substr(0);
+ str.substr(0);
+});
+
+bench("slice", function() {
+ str.slice(0);
+ str.slice(0);
+ str.slice(0);
+});
+
+bench("concat", function() {
+ str.concat(str);
+ str.concat(str);
+ str.concat(str);
+});
+
+bench("trim", function() {
+ str.trim();
+ str.trim();
+ str.trim();
+});
+
+bench("toUpperCase", function() {
+ str.toUpperCase();
+});
+
+bench("toLowerCase", function() {
+ str.toLowerCase();
+});
+
+bench("valueOf", function() {
+ str.valueOf();
+ str.valueOf();
+ str.valueOf();
+});
+
+bench("toString", function() {
+ str.toString();
+ str.toString();
+ str.toString();
+});
+
+bench("String", function() {
+ String(str);
+ String(str);
+ String(str);
+});
+
+bench("new String", function() {
+ new String(str);
+ new String(str);
+ new String(str);
+});