| /* |
| * 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)); |
| } |
| } |