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