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