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