blob: 4483885c350293311f53c0fbc61d6dd4c7d97c4d [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Locale;
/**
* Utilities used by Global class.
*/
public final class GlobalFunctions {
/** Methodhandle to implementation of ECMA 15.1.2.2, parseInt */
public static final MethodHandle PARSEINT = findOwnMH("parseInt", double.class, Object.class, Object.class, Object.class);
/** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */
public static final MethodHandle PARSEINT_OI = findOwnMH("parseInt", double.class, Object.class, Object.class, int.class);
/** ParseInt - NaN for booleans (thru string conversion to number conversion) */
public static final MethodHandle PARSEINT_Z = MH.dropArguments(MH.dropArguments(MH.constant(double.class, Double.NaN), 0, boolean.class), 0, Object.class);
/** ParseInt - identity for ints */
public static final MethodHandle PARSEINT_I = MH.dropArguments(MH.identity(int.class), 0, Object.class);
/** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */
public static final MethodHandle PARSEINT_O = findOwnMH("parseInt", double.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.1.2.3, parseFloat */
public static final MethodHandle PARSEFLOAT = findOwnMH("parseFloat", double.class, Object.class, Object.class);
/** isNan for integers - always false */
public static final MethodHandle IS_NAN_I = MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class);
/** isNan for longs - always false */
public static final MethodHandle IS_NAN_J = MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class);
/** IsNan for doubles - use Double.isNaN */
public static final MethodHandle IS_NAN_D = MH.dropArguments(MH.findStatic(MethodHandles.lookup(), Double.class, "isNaN", MH.type(boolean.class, double.class)), 0, Object.class);
/** Methodhandle to implementation of ECMA 15.1.2.4, isNaN */
public static final MethodHandle IS_NAN = findOwnMH("isNaN", boolean.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.1.2.5, isFinite */
public static final MethodHandle IS_FINITE = findOwnMH("isFinite", boolean.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.1.3.3, encodeURI */
public static final MethodHandle ENCODE_URI = findOwnMH("encodeURI", Object.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.1.3.4, encodeURIComponent */
public static final MethodHandle ENCODE_URICOMPONENT = findOwnMH("encodeURIComponent", Object.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.1.3.1, decodeURI */
public static final MethodHandle DECODE_URI = findOwnMH("decodeURI", Object.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.1.3.2, decodeURIComponent */
public static final MethodHandle DECODE_URICOMPONENT = findOwnMH("decodeURIComponent", Object.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA B.2.1, escape */
public static final MethodHandle ESCAPE = findOwnMH("escape", String.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA B.2.2, unescape */
public static final MethodHandle UNESCAPE = findOwnMH("unescape", String.class, Object.class, Object.class);
/** Methodhandle to implementation of ECMA 15.3.4, "anonymous" - Properties of the Function Prototype Object. */
public static final MethodHandle ANONYMOUS = findOwnMH("anonymous", Object.class, Object.class);
private static final String UNESCAPED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
private GlobalFunctions() {
}
/**
* ECMA 15.1.2.2 parseInt implementation
*
* @param self self reference
* @param string string to parse
* @param rad radix
*
* @return numeric type representing string contents as an int
*/
public static double parseInt(final Object self, final Object string, final Object rad) {
return parseIntInternal(JSType.trimLeft(JSType.toString(string)), JSType.toInt32(rad));
}
/**
* ECMA 15.1.2.2 parseInt implementation specialized for int radix
*
* @param self self reference
* @param string string to parse
* @param rad radix
*
* @return numeric type representing string contents as an int
*/
public static double parseInt(final Object self, final Object string, final int rad) {
return parseIntInternal(JSType.trimLeft(JSType.toString(string)), rad);
}
/**
* ECMA 15.1.2.2 parseInt implementation specialized for no radix argument
*
* @param self self reference
* @param string string to parse
*
* @return numeric type representing string contents as an int
*/
public static double parseInt(final Object self, final Object string) {
return parseIntInternal(JSType.trimLeft(JSType.toString(string)), 0);
}
private static double parseIntInternal(final String str, final int rad) {
final int length = str.length();
int radix = rad;
// empty string is not valid
if (length == 0) {
return Double.NaN;
}
boolean negative = false;
int idx = 0;
// checking for the sign character
final char firstChar = str.charAt(idx);
if (firstChar < '0') {
// Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
} else if (firstChar != '+') {
return Double.NaN;
}
// skip the sign character
idx++;
}
boolean stripPrefix = true;
if (radix != 0) {
if (radix < 2 || radix > 36) {
return Double.NaN;
}
if (radix != 16) {
stripPrefix = false;
}
} else {
// default radix
radix = 10;
}
// strip "0x" or "0X" and treat radix as 16
if (stripPrefix && ((idx + 1) < length)) {
final char c1 = str.charAt(idx);
final char c2 = str.charAt(idx + 1);
if (c1 == '0' && (c2 == 'x' || c2 == 'X')) {
radix = 16;
// skip "0x" or "0X"
idx += 2;
}
}
double result = 0.0;
int digit;
// we should see at least one valid digit
boolean entered = false;
while (idx < length) {
digit = fastDigit(str.charAt(idx++), radix);
if (digit < 0) {
break;
}
// we have seen at least one valid digit in the specified radix
entered = true;
result *= radix;
result += digit;
}
return entered ? (negative ? -result : result) : Double.NaN;
}
/**
* ECMA 15.1.2.3 parseFloat implementation
*
* @param self self reference
* @param string string to parse
*
* @return numeric type representing string contents
*/
public static double parseFloat(final Object self, final Object string) {
final String str = JSType.trimLeft(JSType.toString(string));
final int length = str.length();
// empty string is not valid
if (length == 0) {
return Double.NaN;
}
int start = 0;
boolean negative = false;
char ch = str.charAt(0);
if (ch == '-') {
start++;
negative = true;
} else if (ch == '+') {
start++;
} else if (ch == 'N') {
if (str.startsWith("NaN")) {
return Double.NaN;
}
}
if (start == length) {
// just the sign character
return Double.NaN;
}
ch = str.charAt(start);
if (ch == 'I') {
if (str.substring(start).startsWith("Infinity")) {
return negative? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
}
}
boolean dotSeen = false;
boolean exponentOk = false;
int exponentOffset = -1;
int end;
loop:
for (end = start; end < length; end++) {
ch = str.charAt(end);
switch (ch) {
case '.':
// dot allowed only once
if (exponentOffset != -1 || dotSeen) {
break loop;
}
dotSeen = true;
break;
case 'e':
case 'E':
// 'e'/'E' allow only once
if (exponentOffset != -1) {
break loop;
}
exponentOffset = end;
break;
case '+':
case '-':
// Sign of the exponent. But allowed only if the
// previous char in the string was 'e' or 'E'.
if (exponentOffset != end - 1) {
break loop;
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (exponentOffset != -1) {
// seeing digit after 'e' or 'E'
exponentOk = true;
}
break;
default: // ignore garbage at the end
break loop;
}
}
// ignore 'e'/'E' followed by '+/-' if not real exponent found
if (exponentOffset != -1 && !exponentOk) {
end = exponentOffset;
}
if (start == end) {
return Double.NaN;
}
try {
final double result = Double.valueOf(str.substring(start, end));
return negative ? -result : result;
} catch (final NumberFormatException e) {
return Double.NaN;
}
}
/**
* ECMA 15.1.2.4, isNaN implementation
*
* @param self self reference
* @param number number to check
*
* @return true if number is NaN
*/
public static boolean isNaN(final Object self, final Object number) {
return Double.isNaN(JSType.toNumber(number));
}
/**
* ECMA 15.1.2.5, isFinite implementation
*
* @param self self reference
* @param number number to check
*
* @return true if number is infinite
*/
public static boolean isFinite(final Object self, final Object number) {
final double value = JSType.toNumber(number);
return ! (Double.isInfinite(value) || Double.isNaN(value));
}
/**
* ECMA 15.1.3.3, encodeURI implementation
*
* @param self self reference
* @param uri URI to encode
*
* @return encoded URI
*/
public static Object encodeURI(final Object self, final Object uri) {
return URIUtils.encodeURI(self, JSType.toString(uri));
}
/**
* ECMA 15.1.3.4, encodeURIComponent implementation
*
* @param self self reference
* @param uri URI component to encode
*
* @return encoded URIComponent
*/
public static Object encodeURIComponent(final Object self, final Object uri) {
return URIUtils.encodeURIComponent(self, JSType.toString(uri));
}
/**
* ECMA 15.1.3.1, decodeURI implementation
*
* @param self self reference
* @param uri URI to decode
*
* @return decoded URI
*/
public static Object decodeURI(final Object self, final Object uri) {
return URIUtils.decodeURI(self, JSType.toString(uri));
}
/**
* ECMA 15.1.3.2, decodeURIComponent implementation
*
* @param self self reference
* @param uri URI component to encode
*
* @return decoded URI
*/
public static Object decodeURIComponent(final Object self, final Object uri) {
return URIUtils.decodeURIComponent(self, JSType.toString(uri));
}
/**
* ECMA B.2.1, escape implementation
*
* @param self self reference
* @param string string to escape
*
* @return escaped string
*/
public static String escape(final Object self, final Object string) {
final String str = JSType.toString(string);
final int length = str.length();
if (length == 0) {
return str;
}
final StringBuilder sb = new StringBuilder();
for (int k = 0; k < length; k++) {
final char ch = str.charAt(k);
if (UNESCAPED.indexOf(ch) != -1) {
sb.append(ch);
} else if (ch < 256) {
sb.append('%');
if (ch < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH));
} else {
sb.append("%u");
if (ch < 4096) {
sb.append('0');
}
sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH));
}
}
return sb.toString();
}
/**
* ECMA B.2.2, unescape implementation
*
* @param self self reference
* @param string string to unescape
*
* @return unescaped string
*/
public static String unescape(final Object self, final Object string) {
final String str = JSType.toString(string);
final int length = str.length();
if (length == 0) {
return str;
}
final StringBuilder sb = new StringBuilder();
for (int k = 0; k < length; k++) {
char ch = str.charAt(k);
if (ch != '%') {
sb.append(ch);
} else {
if (k < (length - 5)) {
if (str.charAt(k + 1) == 'u') {
try {
ch = (char) Integer.parseInt(str.substring(k + 2, k + 6), 16);
sb.append(ch);
k += 5;
continue;
} catch (final NumberFormatException e) {
//ignored
}
}
}
if (k < (length - 2)) {
try {
ch = (char) Integer.parseInt(str.substring(k + 1, k + 3), 16);
sb.append(ch);
k += 2;
continue;
} catch (final NumberFormatException e) {
//ignored
}
}
// everything fails
sb.append(ch);
}
}
return sb.toString();
}
/**
* ECMA 15.3.4 Properties of the Function Prototype Object.
* The Function prototype object is itself a Function object
* (its [[Class]] is "Function") that, when invoked, accepts
* any arguments and returns undefined. This method is used to
* implement that anonymous function.
*
* @param self self reference
*
* @return undefined
*/
public static Object anonymous(final Object self) {
return ScriptRuntime.UNDEFINED;
}
private static int fastDigit(final int ch, final int radix) {
int n = -1;
if (ch >= '0' && ch <= '9') {
n = ch - '0';
} else if (radix > 10) {
if (ch >= 'a' && ch <= 'z') {
n = ch - 'a' + 10;
} else if (ch >= 'A' && ch <= 'Z') {
n = ch - 'A' + 10;
}
}
return n < radix ? n : -1;
}
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(MethodHandles.lookup(), GlobalFunctions.class, name, MH.type(rtype, types));
}
}