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