1/* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.lookup.Lookup.MH; 29 30import java.lang.invoke.MethodHandle; 31import java.lang.invoke.MethodHandles; 32import java.util.Locale; 33 34/** 35 * Utilities used by Global class. 36 */ 37public final class GlobalFunctions { 38 39 /** Methodhandle to implementation of ECMA 15.1.2.2, parseInt */ 40 public static final MethodHandle PARSEINT = findOwnMH("parseInt", double.class, Object.class, Object.class, Object.class); 41 42 /** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */ 43 public static final MethodHandle PARSEINT_OI = findOwnMH("parseInt", double.class, Object.class, Object.class, int.class); 44 45 /** ParseInt - NaN for booleans (thru string conversion to number conversion) */ 46 public static final MethodHandle PARSEINT_Z = MH.dropArguments(MH.dropArguments(MH.constant(double.class, Double.NaN), 0, boolean.class), 0, Object.class); 47 48 /** ParseInt - identity for ints */ 49 public static final MethodHandle PARSEINT_I = MH.dropArguments(MH.identity(int.class), 0, Object.class); 50 51 /** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */ 52 public static final MethodHandle PARSEINT_O = findOwnMH("parseInt", double.class, Object.class, Object.class); 53 54 /** Methodhandle to implementation of ECMA 15.1.2.3, parseFloat */ 55 public static final MethodHandle PARSEFLOAT = findOwnMH("parseFloat", double.class, Object.class, Object.class); 56 57 /** isNan for integers - always false */ 58 public static final MethodHandle IS_NAN_I = MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class); 59 60 /** isNan for longs - always false */ 61 public static final MethodHandle IS_NAN_J = MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class); 62 63 /** IsNan for doubles - use Double.isNaN */ 64 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); 65 66 /** Methodhandle to implementation of ECMA 15.1.2.4, isNaN */ 67 public static final MethodHandle IS_NAN = findOwnMH("isNaN", boolean.class, Object.class, Object.class); 68 69 /** Methodhandle to implementation of ECMA 15.1.2.5, isFinite */ 70 public static final MethodHandle IS_FINITE = findOwnMH("isFinite", boolean.class, Object.class, Object.class); 71 72 /** Methodhandle to implementation of ECMA 15.1.3.3, encodeURI */ 73 public static final MethodHandle ENCODE_URI = findOwnMH("encodeURI", Object.class, Object.class, Object.class); 74 75 /** Methodhandle to implementation of ECMA 15.1.3.4, encodeURIComponent */ 76 public static final MethodHandle ENCODE_URICOMPONENT = findOwnMH("encodeURIComponent", Object.class, Object.class, Object.class); 77 78 /** Methodhandle to implementation of ECMA 15.1.3.1, decodeURI */ 79 public static final MethodHandle DECODE_URI = findOwnMH("decodeURI", Object.class, Object.class, Object.class); 80 81 /** Methodhandle to implementation of ECMA 15.1.3.2, decodeURIComponent */ 82 public static final MethodHandle DECODE_URICOMPONENT = findOwnMH("decodeURIComponent", Object.class, Object.class, Object.class); 83 84 /** Methodhandle to implementation of ECMA B.2.1, escape */ 85 public static final MethodHandle ESCAPE = findOwnMH("escape", String.class, Object.class, Object.class); 86 87 /** Methodhandle to implementation of ECMA B.2.2, unescape */ 88 public static final MethodHandle UNESCAPE = findOwnMH("unescape", String.class, Object.class, Object.class); 89 90 /** Methodhandle to implementation of ECMA 15.3.4, "anonymous" - Properties of the Function Prototype Object. */ 91 public static final MethodHandle ANONYMOUS = findOwnMH("anonymous", Object.class, Object.class); 92 93 private static final String UNESCAPED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./"; 94 95 private GlobalFunctions() { 96 } 97 98 /** 99 * ECMA 15.1.2.2 parseInt implementation 100 * 101 * @param self self reference 102 * @param string string to parse 103 * @param rad radix 104 * 105 * @return numeric type representing string contents as an int 106 */ 107 public static double parseInt(final Object self, final Object string, final Object rad) { 108 return parseIntInternal(JSType.trimLeft(JSType.toString(string)), JSType.toInt32(rad)); 109 } 110 111 /** 112 * ECMA 15.1.2.2 parseInt implementation specialized for int radix 113 * 114 * @param self self reference 115 * @param string string to parse 116 * @param rad radix 117 * 118 * @return numeric type representing string contents as an int 119 */ 120 public static double parseInt(final Object self, final Object string, final int rad) { 121 return parseIntInternal(JSType.trimLeft(JSType.toString(string)), rad); 122 } 123 124 /** 125 * ECMA 15.1.2.2 parseInt implementation specialized for no radix argument 126 * 127 * @param self self reference 128 * @param string string to parse 129 * 130 * @return numeric type representing string contents as an int 131 */ 132 public static double parseInt(final Object self, final Object string) { 133 return parseIntInternal(JSType.trimLeft(JSType.toString(string)), 0); 134 } 135 136 private static double parseIntInternal(final String str, final int rad) { 137 final int length = str.length(); 138 int radix = rad; 139 140 // empty string is not valid 141 if (length == 0) { 142 return Double.NaN; 143 } 144 145 boolean negative = false; 146 int idx = 0; 147 148 // checking for the sign character 149 final char firstChar = str.charAt(idx); 150 if (firstChar < '0') { 151 // Possible leading "+" or "-" 152 if (firstChar == '-') { 153 negative = true; 154 } else if (firstChar != '+') { 155 return Double.NaN; 156 } 157 // skip the sign character 158 idx++; 159 } 160 161 boolean stripPrefix = true; 162 163 if (radix != 0) { 164 if (radix < 2 || radix > 36) { 165 return Double.NaN; 166 } 167 if (radix != 16) { 168 stripPrefix = false; 169 } 170 } else { 171 // default radix 172 radix = 10; 173 } 174 // strip "0x" or "0X" and treat radix as 16 175 if (stripPrefix && ((idx + 1) < length)) { 176 final char c1 = str.charAt(idx); 177 final char c2 = str.charAt(idx + 1); 178 if (c1 == '0' && (c2 == 'x' || c2 == 'X')) { 179 radix = 16; 180 // skip "0x" or "0X" 181 idx += 2; 182 } 183 } 184 185 double result = 0.0; 186 int digit; 187 // we should see at least one valid digit 188 boolean entered = false; 189 while (idx < length) { 190 digit = fastDigit(str.charAt(idx++), radix); 191 if (digit < 0) { 192 break; 193 } 194 // we have seen at least one valid digit in the specified radix 195 entered = true; 196 result *= radix; 197 result += digit; 198 } 199 200 return entered ? (negative ? -result : result) : Double.NaN; 201 } 202 203 /** 204 * ECMA 15.1.2.3 parseFloat implementation 205 * 206 * @param self self reference 207 * @param string string to parse 208 * 209 * @return numeric type representing string contents 210 */ 211 public static double parseFloat(final Object self, final Object string) { 212 final String str = JSType.trimLeft(JSType.toString(string)); 213 final int length = str.length(); 214 215 // empty string is not valid 216 if (length == 0) { 217 return Double.NaN; 218 } 219 220 int start = 0; 221 boolean negative = false; 222 char ch = str.charAt(0); 223 224 if (ch == '-') { 225 start++; 226 negative = true; 227 } else if (ch == '+') { 228 start++; 229 } else if (ch == 'N') { 230 if (str.startsWith("NaN")) { 231 return Double.NaN; 232 } 233 } 234 235 if (start == length) { 236 // just the sign character 237 return Double.NaN; 238 } 239 240 ch = str.charAt(start); 241 if (ch == 'I') { 242 if (str.substring(start).startsWith("Infinity")) { 243 return negative? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; 244 } 245 } 246 247 boolean dotSeen = false; 248 boolean exponentOk = false; 249 int exponentOffset = -1; 250 int end; 251 252loop: 253 for (end = start; end < length; end++) { 254 ch = str.charAt(end); 255 256 switch (ch) { 257 case '.': 258 // dot allowed only once 259 if (exponentOffset != -1 || dotSeen) { 260 break loop; 261 } 262 dotSeen = true; 263 break; 264 265 case 'e': 266 case 'E': 267 // 'e'/'E' allow only once 268 if (exponentOffset != -1) { 269 break loop; 270 } 271 exponentOffset = end; 272 break; 273 274 case '+': 275 case '-': 276 // Sign of the exponent. But allowed only if the 277 // previous char in the string was 'e' or 'E'. 278 if (exponentOffset != end - 1) { 279 break loop; 280 } 281 break; 282 283 case '0': 284 case '1': 285 case '2': 286 case '3': 287 case '4': 288 case '5': 289 case '6': 290 case '7': 291 case '8': 292 case '9': 293 if (exponentOffset != -1) { 294 // seeing digit after 'e' or 'E' 295 exponentOk = true; 296 } 297 break; 298 299 default: // ignore garbage at the end 300 break loop; 301 } 302 } 303 304 // ignore 'e'/'E' followed by '+/-' if not real exponent found 305 if (exponentOffset != -1 && !exponentOk) { 306 end = exponentOffset; 307 } 308 309 if (start == end) { 310 return Double.NaN; 311 } 312 313 try { 314 final double result = Double.valueOf(str.substring(start, end)); 315 return negative ? -result : result; 316 } catch (final NumberFormatException e) { 317 return Double.NaN; 318 } 319 } 320 321 /** 322 * ECMA 15.1.2.4, isNaN implementation 323 * 324 * @param self self reference 325 * @param number number to check 326 * 327 * @return true if number is NaN 328 */ 329 public static boolean isNaN(final Object self, final Object number) { 330 return Double.isNaN(JSType.toNumber(number)); 331 } 332 333 /** 334 * ECMA 15.1.2.5, isFinite implementation 335 * 336 * @param self self reference 337 * @param number number to check 338 * 339 * @return true if number is infinite 340 */ 341 public static boolean isFinite(final Object self, final Object number) { 342 final double value = JSType.toNumber(number); 343 return ! (Double.isInfinite(value) || Double.isNaN(value)); 344 } 345 346 347 /** 348 * ECMA 15.1.3.3, encodeURI implementation 349 * 350 * @param self self reference 351 * @param uri URI to encode 352 * 353 * @return encoded URI 354 */ 355 public static Object encodeURI(final Object self, final Object uri) { 356 return URIUtils.encodeURI(self, JSType.toString(uri)); 357 } 358 359 /** 360 * ECMA 15.1.3.4, encodeURIComponent implementation 361 * 362 * @param self self reference 363 * @param uri URI component to encode 364 * 365 * @return encoded URIComponent 366 */ 367 public static Object encodeURIComponent(final Object self, final Object uri) { 368 return URIUtils.encodeURIComponent(self, JSType.toString(uri)); 369 } 370 371 /** 372 * ECMA 15.1.3.1, decodeURI implementation 373 * 374 * @param self self reference 375 * @param uri URI to decode 376 * 377 * @return decoded URI 378 */ 379 public static Object decodeURI(final Object self, final Object uri) { 380 return URIUtils.decodeURI(self, JSType.toString(uri)); 381 } 382 383 /** 384 * ECMA 15.1.3.2, decodeURIComponent implementation 385 * 386 * @param self self reference 387 * @param uri URI component to encode 388 * 389 * @return decoded URI 390 */ 391 public static Object decodeURIComponent(final Object self, final Object uri) { 392 return URIUtils.decodeURIComponent(self, JSType.toString(uri)); 393 } 394 395 /** 396 * ECMA B.2.1, escape implementation 397 * 398 * @param self self reference 399 * @param string string to escape 400 * 401 * @return escaped string 402 */ 403 public static String escape(final Object self, final Object string) { 404 final String str = JSType.toString(string); 405 final int length = str.length(); 406 407 if (length == 0) { 408 return str; 409 } 410 411 final StringBuilder sb = new StringBuilder(); 412 for (int k = 0; k < length; k++) { 413 final char ch = str.charAt(k); 414 if (UNESCAPED.indexOf(ch) != -1) { 415 sb.append(ch); 416 } else if (ch < 256) { 417 sb.append('%'); 418 if (ch < 16) { 419 sb.append('0'); 420 } 421 sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); 422 } else { 423 sb.append("%u"); 424 if (ch < 4096) { 425 sb.append('0'); 426 } 427 sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); 428 } 429 } 430 431 return sb.toString(); 432 } 433 434 /** 435 * ECMA B.2.2, unescape implementation 436 * 437 * @param self self reference 438 * @param string string to unescape 439 * 440 * @return unescaped string 441 */ 442 public static String unescape(final Object self, final Object string) { 443 final String str = JSType.toString(string); 444 final int length = str.length(); 445 446 if (length == 0) { 447 return str; 448 } 449 450 final StringBuilder sb = new StringBuilder(); 451 for (int k = 0; k < length; k++) { 452 char ch = str.charAt(k); 453 if (ch != '%') { 454 sb.append(ch); 455 } else { 456 if (k < (length - 5)) { 457 if (str.charAt(k + 1) == 'u') { 458 try { 459 ch = (char) Integer.parseInt(str.substring(k + 2, k + 6), 16); 460 sb.append(ch); 461 k += 5; 462 continue; 463 } catch (final NumberFormatException e) { 464 //ignored 465 } 466 } 467 } 468 469 if (k < (length - 2)) { 470 try { 471 ch = (char) Integer.parseInt(str.substring(k + 1, k + 3), 16); 472 sb.append(ch); 473 k += 2; 474 continue; 475 } catch (final NumberFormatException e) { 476 //ignored 477 } 478 } 479 480 // everything fails 481 sb.append(ch); 482 } 483 } 484 485 return sb.toString(); 486 } 487 488 489 /** 490 * ECMA 15.3.4 Properties of the Function Prototype Object. 491 * The Function prototype object is itself a Function object 492 * (its [[Class]] is "Function") that, when invoked, accepts 493 * any arguments and returns undefined. This method is used to 494 * implement that anonymous function. 495 * 496 * @param self self reference 497 * 498 * @return undefined 499 */ 500 public static Object anonymous(final Object self) { 501 return ScriptRuntime.UNDEFINED; 502 } 503 504 private static int fastDigit(final int ch, final int radix) { 505 int n = -1; 506 if (ch >= '0' && ch <= '9') { 507 n = ch - '0'; 508 } else if (radix > 10) { 509 if (ch >= 'a' && ch <= 'z') { 510 n = ch - 'a' + 10; 511 } else if (ch >= 'A' && ch <= 'Z') { 512 n = ch - 'A' + 10; 513 } 514 } 515 return n < radix ? n : -1; 516 } 517 518 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 519 return MH.findStatic(MethodHandles.lookup(), GlobalFunctions.class, name, MH.type(rtype, types)); 520 } 521} 522