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.codegen.CompilerConstants.staticCall; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; 30 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 31 import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError; 32 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 33 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 34 35 import java.lang.invoke.MethodHandle; 36 import java.lang.reflect.Array; 37 import java.util.Collections; 38 import java.util.Iterator; 39 import java.util.Locale; 40 import java.util.NoSuchElementException; 41 import java.util.Objects; 42 import jdk.internal.dynalink.beans.StaticClass; 43 import jdk.nashorn.internal.codegen.CompilerConstants.Call; 44 import jdk.nashorn.internal.ir.debug.JSONWriter; 45 import jdk.nashorn.internal.parser.Lexer; 46 import jdk.nashorn.internal.runtime.linker.Bootstrap; 47 48 49 /** 50 * Utilities to be called by JavaScript runtime API and generated classes. 51 */ 52 53 public final class ScriptRuntime { 54 private ScriptRuntime() { 55 } 56 57 /** Singleton representing the empty array object '[]' */ 58 public static final Object[] EMPTY_ARRAY = new Object[0]; 59 60 /** Unique instance of undefined. */ 61 public static final Undefined UNDEFINED = Undefined.getUndefined(); 62 63 /** 64 * Unique instance of undefined used to mark empty array slots. 65 * Can't escape the array. 66 */ 67 public static final Undefined EMPTY = Undefined.getEmpty(); 68 69 /** Method handle to generic + operator, operating on objects */ 70 public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class); 71 72 /** Method handle to generic === operator, operating on objects */ 73 public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class); 74 75 /** Method handle used to enter a {@code with} scope at runtime. */ 76 public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class); 77 78 /** Method handle used to exit a {@code with} scope at runtime. */ 79 public static final Call CLOSE_WITH = staticCallNoLookup(ScriptRuntime.class, "closeWith", ScriptObject.class, ScriptObject.class); 80 81 /** 82 * Method used to place a scope's variable into the Global scope, which has to be done for the 83 * properties declared at outermost script level. 84 */ 85 public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class); 86 87 /** 88 * Return an appropriate iterator for the elements in a for-in construct 89 */ 90 public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class); 91 92 /** 93 * Return an appropriate iterator for the elements in a for-each construct 94 */ 95 public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class); 96 97 /** 98 * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to 99 * call sites that are known to be megamorphic. Using an invoke dynamic here would 100 * lead to the JVM deoptimizing itself to death 101 */ 102 public static final Call APPLY = staticCall(ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class); 103 104 /** 105 * Converts a switch tag value to a simple integer. deflt value if it can't. 106 * 107 * @param tag Switch statement tag value. 108 * @param deflt default to use if not convertible. 109 * @return int tag value (or deflt.) 110 */ 111 public static int switchTagAsInt(final Object tag, final int deflt) { 112 if (tag instanceof Number) { 113 final double d = ((Number)tag).doubleValue(); 114 if (isRepresentableAsInt(d)) { 115 return (int)d; 116 } 117 } 118 119 return deflt; 120 } 121 122 /** 123 * Converts a switch tag value to a simple integer. deflt value if it can't. 124 * 125 * @param tag Switch statement tag value. 126 * @param deflt default to use if not convertible. 127 * @return int tag value (or deflt.) 128 */ 129 public static int switchTagAsInt(final long tag, final int deflt) { 130 return isRepresentableAsInt(tag) ? (int)tag : deflt; 131 } 132 133 /** 134 * Converts a switch tag value to a simple integer. deflt value if it can't. 135 * 136 * @param tag Switch statement tag value. 137 * @param deflt default to use if not convertible. 138 * @return int tag value (or deflt.) 139 */ 140 public static int switchTagAsInt(final double tag, final int deflt) { 141 return isRepresentableAsInt(tag) ? (int)tag : deflt; 142 } 143 144 /** 145 * This is the builtin implementation of {@code Object.prototype.toString} 146 * @param self reference 147 * @return string representation as object 148 */ 149 public static String builtinObjectToString(final Object self) { 150 String className; 151 // Spec tells us to convert primitives by ToObject.. 152 // But we don't need to -- all we need is the right class name 153 // of the corresponding primitive wrapper type. 154 155 final JSType type = JSType.of(self); 156 157 switch (type) { 158 case BOOLEAN: 159 className = "Boolean"; 160 break; 161 case NUMBER: 162 className = "Number"; 163 break; 164 case STRING: 165 className = "String"; 166 break; 167 // special case of null and undefined 168 case NULL: 169 className = "Null"; 170 break; 171 case UNDEFINED: 172 className = "Undefined"; 173 break; 174 case OBJECT: 175 case FUNCTION: 176 if (self instanceof ScriptObject) { 177 className = ((ScriptObject)self).getClassName(); 178 } else { 179 className = self.getClass().getName(); 180 } 181 break; 182 default: 183 // Nashorn extension: use Java class name 184 className = self.getClass().getName(); 185 break; 186 } 187 188 final StringBuilder sb = new StringBuilder(); 189 sb.append("[object "); 190 sb.append(className); 191 sb.append(']'); 192 193 return sb.toString(); 194 } 195 196 /** 197 * This is called whenever runtime wants to throw an error and wants to provide 198 * meaningful information about an object. We don't want to call toString which 199 * ends up calling "toString" from script world which may itself throw error. 200 * When we want to throw an error, we don't additional error from script land 201 * -- which may sometimes lead to infinite recursion. 202 * 203 * @param obj Object to converted to String safely (without calling user script) 204 * @return safe String representation of the given object 205 */ 206 public static String safeToString(final Object obj) { 207 return JSType.toStringImpl(obj, true); 208 } 209 210 /** 211 * Used to determine property iterator used in for in. 212 * @param obj Object to iterate on. 213 * @return Iterator. 214 */ 215 public static Iterator<String> toPropertyIterator(final Object obj) { 216 if (obj instanceof ScriptObject) { 217 return ((ScriptObject)obj).propertyIterator(); 218 } 219 220 if (obj != null && obj.getClass().isArray()) { 221 final int length = Array.getLength(obj); 222 223 return new Iterator<String>() { 224 private int index = 0; 225 226 @Override 227 public boolean hasNext() { 228 return index < length; 229 } 230 231 @Override 232 public String next() { 233 return "" + index++; //TODO numeric property iterator? 234 } 235 236 @Override 237 public void remove() { 238 throw new UnsupportedOperationException(); 239 } 240 }; 241 } 242 243 return Collections.emptyIterator(); 244 } 245 246 /** 247 * Used to determine property value iterator used in for each in. 248 * @param obj Object to iterate on. 249 * @return Iterator. 250 */ 251 public static Iterator<?> toValueIterator(final Object obj) { 252 if (obj instanceof ScriptObject) { 253 return ((ScriptObject)obj).valueIterator(); 254 } 255 256 if (obj != null && obj.getClass().isArray()) { 257 final Object array = obj; 258 final int length = Array.getLength(obj); 259 260 return new Iterator<Object>() { 261 private int index = 0; 262 263 @Override 264 public boolean hasNext() { 265 return index < length; 266 } 267 268 @Override 269 public Object next() { 270 if (index >= length) { 271 throw new NoSuchElementException(); 272 } 273 return Array.get(array, index++); 274 } 275 276 @Override 277 public void remove() { 278 throw new UnsupportedOperationException(); 279 } 280 }; 281 } 282 283 if (obj instanceof Iterable) { 284 return ((Iterable<?>)obj).iterator(); 285 } 286 287 return Collections.emptyIterator(); 288 } 289 290 /** 291 * Merge a scope into its prototype's map. 292 * Merge a scope into its prototype. 293 * 294 * @param scope Scope to merge. 295 * @return prototype object after merge 296 */ 297 public static ScriptObject mergeScope(final ScriptObject scope) { 298 final ScriptObject global = scope.getProto(); 299 global.addBoundProperties(scope); 300 return global; 301 } 302 303 /** 304 * Check that the target function is associated with current Context. And also make sure that 'self', if 305 * ScriptObject, is from current context. 306 * 307 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 308 * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker} 309 * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead. 310 * 311 * @param target ScriptFunction object. 312 * @param self Receiver in call. 313 * @param args Call arguments. 314 * @return Call result. 315 */ 316 public static Object checkAndApply(final ScriptFunction target, final Object self, final Object... args) { 317 final ScriptObject global = Context.getGlobalTrusted(); 318 if (! (global instanceof GlobalObject)) { 319 throw new IllegalStateException("No current global set"); 320 } 321 322 if (target.getContext() != global.getContext()) { 323 throw new IllegalArgumentException("'target' function is not from current Context"); 324 } 325 326 if (self instanceof ScriptObject && ((ScriptObject)self).getContext() != global.getContext()) { 327 throw new IllegalArgumentException("'self' object is not from current Context"); 328 } 329 330 // all in order - call real 'apply' 331 return apply(target, self, args); 332 } 333 334 /** 335 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve 336 * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker} 337 * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead. 338 * 339 * @param target ScriptFunction object. 340 * @param self Receiver in call. 341 * @param args Call arguments. 342 * @return Call result. 343 */ 344 public static Object apply(final ScriptFunction target, final Object self, final Object... args) { 345 try { 346 return target.invoke(self, args); 347 } catch (final RuntimeException | Error e) { 348 throw e; 349 } catch (final Throwable t) { 350 throw new RuntimeException(t); 351 } 352 } 353 354 /** 355 * Generic implementation of ECMA 9.12 - SameValue algorithm 356 * 357 * @param x first value to compare 358 * @param y second value to compare 359 * 360 * @return true if both objects have the same value 361 */ 362 public static boolean sameValue(final Object x, final Object y) { 363 final JSType xType = JSType.of(x); 364 final JSType yType = JSType.of(y); 365 366 if (xType != yType) { 367 return false; 368 } 369 370 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 371 return true; 372 } 373 374 if (xType == JSType.NUMBER) { 375 final double xVal = ((Number)x).doubleValue(); 376 final double yVal = ((Number)y).doubleValue(); 377 378 if (Double.isNaN(xVal) && Double.isNaN(yVal)) { 379 return true; 380 } 381 382 // checking for xVal == -0.0 and yVal == +0.0 or vice versa 383 if (xVal == 0.0 && (Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal))) { 384 return false; 385 } 386 387 return xVal == yVal; 388 } 389 390 if (xType == JSType.STRING || yType == JSType.BOOLEAN) { 391 return x.equals(y); 392 } 393 394 return (x == y); 395 } 396 397 /** 398 * Returns AST as JSON compatible string. This is used to 399 * implement "parse" function in resources/parse.js script. 400 * 401 * @param code code to be parsed 402 * @param name name of the code source (used for location) 403 * @param includeLoc tells whether to include location information for nodes or not 404 * @return JSON string representation of AST of the supplied code 405 */ 406 public static String parse(final String code, final String name, final boolean includeLoc) { 407 return JSONWriter.parse(Context.getContextTrusted().getEnv(), code, name, includeLoc); 408 } 409 410 /** 411 * Test whether a char is valid JavaScript whitespace 412 * @param ch a char 413 * @return true if valid JavaScript whitespace 414 */ 415 public static boolean isJSWhitespace(final char ch) { 416 return Lexer.isJSWhitespace(ch); 417 } 418 419 /** 420 * Entering a {@code with} node requires new scope. This is the implementation 421 * 422 * @param scope existing scope 423 * @param expression expression in with 424 * 425 * @return {@link WithObject} that is the new scope 426 */ 427 public static ScriptObject openWith(final ScriptObject scope, final Object expression) { 428 final ScriptObject global = Context.getGlobalTrusted(); 429 if (expression == UNDEFINED) { 430 throw typeError(global, "cant.apply.with.to.undefined"); 431 } else if (expression == null) { 432 throw typeError(global, "cant.apply.with.to.null"); 433 } 434 435 final ScriptObject withObject = new WithObject(scope, JSType.toScriptObject(global, expression)); 436 437 return withObject; 438 } 439 440 /** 441 * Exiting a {@code with} node requires restoring scope. This is the implementation 442 * 443 * @param scope existing scope 444 * 445 * @return restored scope 446 */ 447 public static ScriptObject closeWith(final ScriptObject scope) { 448 if (scope instanceof WithObject) { 449 return scope.getProto(); 450 } 451 return scope; 452 } 453 454 /** 455 * ECMA 11.6.1 - The addition operator (+) - generic implementation 456 * Compiler specializes using {@link jdk.nashorn.internal.codegen.RuntimeCallSite} 457 * if any type information is available for any of the operands 458 * 459 * @param x first term 460 * @param y second term 461 * 462 * @return result of addition 463 */ 464 public static Object ADD(final Object x, final Object y) { 465 // This prefix code to handle Number special is for optimization. 466 final boolean xIsNumber = x instanceof Number; 467 final boolean yIsNumber = y instanceof Number; 468 469 if (xIsNumber && yIsNumber) { 470 return ((Number)x).doubleValue() + ((Number)y).doubleValue(); 471 } 472 473 final boolean xIsUndefined = x == UNDEFINED; 474 final boolean yIsUndefined = y == UNDEFINED; 475 476 if ((xIsNumber && yIsUndefined) || (xIsUndefined && yIsNumber) || (xIsUndefined && yIsUndefined)) { 477 return Double.NaN; 478 } 479 480 // code below is as per the spec. 481 final Object xPrim = JSType.toPrimitive(x); 482 final Object yPrim = JSType.toPrimitive(y); 483 484 if (xPrim instanceof String || yPrim instanceof String 485 || xPrim instanceof ConsString || yPrim instanceof ConsString) { 486 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim)); 487 } 488 489 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim); 490 } 491 492 /** 493 * Debugger hook. 494 * TODO: currently unimplemented 495 * 496 * @return undefined 497 */ 498 public static Object DEBUGGER() { 499 return UNDEFINED; 500 } 501 502 /** 503 * New hook 504 * 505 * @param clazz type for the clss 506 * @param args constructor arguments 507 * 508 * @return undefined 509 */ 510 public static Object NEW(final Object clazz, final Object... args) { 511 return UNDEFINED; 512 } 513 514 /** 515 * ECMA 11.4.3 The typeof Operator - generic implementation 516 * 517 * @param object the object from which to retrieve property to type check 518 * @param property property in object to check 519 * 520 * @return type name 521 */ 522 public static Object TYPEOF(final Object object, final Object property) { 523 Object obj = object; 524 525 if (property != null) { 526 if (obj instanceof ScriptObject) { 527 obj = ((ScriptObject)obj).get(property); 528 } else if (object instanceof Undefined) { 529 obj = ((Undefined)obj).get(property); 530 } else if (object == null) { 531 throw typeError("cant.get.property", safeToString(property), "null"); 532 } else if (JSType.isPrimitive(obj)) { 533 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property); 534 } else { 535 obj = UNDEFINED; 536 } 537 } 538 539 return JSType.of(obj).typeName(); 540 } 541 542 /** 543 * ECMA 11.4.2 - void operator 544 * 545 * @param object object to evaluate 546 * 547 * @return Undefined as the object type 548 */ 549 public static Object VOID(final Object object) { 550 if (object instanceof Number) { 551 if (Double.isNaN(((Number)object).doubleValue())) { 552 return Double.NaN; 553 } 554 } 555 556 return UNDEFINED; 557 } 558 559 /** 560 * Throw ReferenceError when LHS of assignment or increment/decrement 561 * operator is not an assignable node (say a literal) 562 * 563 * @param lhs Evaluated LHS 564 * @param rhs Evaluated RHS 565 * @param msg Additional LHS info for error message 566 * @return undefined 567 */ 568 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) { 569 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg)); 570 } 571 572 /** 573 * ECMA 11.4.1 - delete operation, generic implementation 574 * 575 * @param obj object with property to delete 576 * @param property property to delete 577 * @param strict are we in strict mode 578 * 579 * @return true if property was successfully found and deleted 580 */ 581 public static boolean DELETE(final Object obj, final Object property, final Object strict) { 582 if (obj instanceof ScriptObject) { 583 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict)); 584 } 585 586 if (obj instanceof Undefined) { 587 return ((Undefined)obj).delete(property, false); 588 } 589 590 if (obj == null) { 591 throw typeError("cant.delete.property", safeToString(property), "null"); 592 } 593 594 if (JSType.isPrimitive(obj)) { 595 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict)); 596 } 597 598 // if object is not reference type, vacuously delete is successful. 599 return true; 600 } 601 602 /** 603 * ECMA 11.4.1 - delete operator, special case 604 * 605 * This is 'delete' that always fails. We have to check strict mode and throw error. 606 * That is why this is a runtime function. Or else we could have inlined 'false'. 607 * 608 * @param property property to delete 609 * @param strict are we in strict mode 610 * 611 * @return false always 612 */ 613 public static boolean FAIL_DELETE(final Object property, final Object strict) { 614 if (Boolean.TRUE.equals(strict)) { 615 throw syntaxError("strict.cant.delete", safeToString(property)); 616 } 617 return false; 618 } 619 620 /** 621 * ECMA 11.9.1 - The equals operator (==) - generic implementation 622 * 623 * @param x first object to compare 624 * @param y second object to compare 625 * 626 * @return true if type coerced versions of objects are equal 627 */ 628 public static boolean EQ(final Object x, final Object y) { 629 return equals(x, y); 630 } 631 632 /** 633 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation 634 * 635 * @param x first object to compare 636 * @param y second object to compare 637 * 638 * @return true if type coerced versions of objects are not equal 639 */ 640 public static boolean NE(final Object x, final Object y) { 641 return !EQ(x, y); 642 } 643 644 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */ 645 private static boolean equals(final Object x, final Object y) { 646 final JSType xType = JSType.of(x); 647 final JSType yType = JSType.of(y); 648 649 if (xType == yType) { 650 651 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 652 return true; 653 } 654 655 if (xType == JSType.NUMBER) { 656 final double xVal = ((Number)x).doubleValue(); 657 final double yVal = ((Number)y).doubleValue(); 658 if (Double.isNaN(xVal) || Double.isNaN(yVal)) { 659 return false; 660 } 661 662 return xVal == yVal; 663 } 664 665 if (xType == JSType.STRING) { 666 // String may be represented by ConsString 667 return x.toString().equals(y.toString()); 668 } 669 670 if (xType == JSType.BOOLEAN) { 671 // Boolean comparison 672 return x.equals(y); 673 } 674 675 return x == y; 676 } 677 678 if ((xType == JSType.UNDEFINED && yType == JSType.NULL) || 679 (xType == JSType.NULL && yType == JSType.UNDEFINED)) { 680 return true; 681 } 682 683 if (xType == JSType.NUMBER && yType == JSType.STRING) { 684 return EQ(x, JSType.toNumber(y)); 685 } 686 687 if (xType == JSType.STRING && yType == JSType.NUMBER) { 688 return EQ(JSType.toNumber(x), y); 689 } 690 691 if (xType == JSType.BOOLEAN) { 692 return EQ(JSType.toNumber(x), y); 693 } 694 695 if (yType == JSType.BOOLEAN) { 696 return EQ(x, JSType.toNumber(y)); 697 } 698 699 if ((xType == JSType.STRING || xType == JSType.NUMBER) && 700 (y instanceof ScriptObject)) { 701 return EQ(x, JSType.toPrimitive(y)); 702 } 703 704 if ((x instanceof ScriptObject) && 705 (yType == JSType.STRING || yType == JSType.NUMBER)) { 706 return EQ(JSType.toPrimitive(x), y); 707 } 708 709 return false; 710 } 711 712 /** 713 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation 714 * 715 * @param x first object to compare 716 * @param y second object to compare 717 * 718 * @return true if objects are equal 719 */ 720 public static boolean EQ_STRICT(final Object x, final Object y) { 721 return strictEquals(x, y); 722 } 723 724 /** 725 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation 726 * 727 * @param x first object to compare 728 * @param y second object to compare 729 * 730 * @return true if objects are not equal 731 */ 732 public static boolean NE_STRICT(final Object x, final Object y) { 733 return !EQ_STRICT(x, y); 734 } 735 736 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */ 737 private static boolean strictEquals(final Object x, final Object y) { 738 final JSType xType = JSType.of(x); 739 final JSType yType = JSType.of(y); 740 741 if (xType != yType) { 742 return false; 743 } 744 745 if (xType == JSType.UNDEFINED || xType == JSType.NULL) { 746 return true; 747 } 748 749 if (xType == JSType.NUMBER) { 750 final double xVal = ((Number)x).doubleValue(); 751 final double yVal = ((Number)y).doubleValue(); 752 753 if (Double.isNaN(xVal) || Double.isNaN(yVal)) { 754 return false; 755 } 756 757 return xVal == yVal; 758 } 759 760 if (xType == JSType.STRING) { 761 // String may be represented by ConsString 762 return x.toString().equals(y.toString()); 763 } 764 765 if (xType == JSType.BOOLEAN) { 766 return x.equals(y); 767 } 768 769 // finally, the object identity comparison 770 return x == y; 771 } 772 773 /** 774 * ECMA 11.8.6 - The in operator - generic implementation 775 * 776 * @param property property to check for 777 * @param obj object in which to check for property 778 * 779 * @return true if objects are equal 780 */ 781 public static boolean IN(final Object property, final Object obj) { 782 final JSType rvalType = JSType.of(obj); 783 784 if (rvalType == JSType.OBJECT || rvalType == JSType.FUNCTION) { 785 if (obj instanceof ScriptObject) { 786 return ((ScriptObject)obj).has(property); 787 } 788 789 return false; 790 } 791 792 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH)); 793 } 794 795 /** 796 * ECMA 11.8.6 - The strict instanceof operator - generic implementation 797 * 798 * @param obj first object to compare 799 * @param clazz type to check against 800 * 801 * @return true if {@code obj} is an instanceof {@code clazz} 802 */ 803 public static boolean INSTANCEOF(final Object obj, final Object clazz) { 804 if (clazz instanceof ScriptFunction) { 805 if (obj instanceof ScriptObject) { 806 return ((ScriptObject)clazz).isInstance((ScriptObject)obj); 807 } 808 return false; 809 } 810 811 if (clazz instanceof StaticClass) { 812 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj); 813 } 814 815 throw typeError("instanceof.on.non.object"); 816 } 817 818 /** 819 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation 820 * 821 * @param x first object to compare 822 * @param y second object to compare 823 * 824 * @return true if x is less than y 825 */ 826 public static boolean LT(final Object x, final Object y) { 827 final Object value = lessThan(x, y, true); 828 return (value == UNDEFINED) ? false : (Boolean)value; 829 } 830 831 /** 832 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation 833 * 834 * @param x first object to compare 835 * @param y second object to compare 836 * 837 * @return true if x is greater than y 838 */ 839 public static boolean GT(final Object x, final Object y) { 840 final Object value = lessThan(y, x, false); 841 return (value == UNDEFINED) ? false : (Boolean)value; 842 } 843 844 /** 845 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation 846 * 847 * @param x first object to compare 848 * @param y second object to compare 849 * 850 * @return true if x is less than or equal to y 851 */ 852 public static boolean LE(final Object x, final Object y) { 853 final Object value = lessThan(y, x, false); 854 return (!(Boolean.TRUE.equals(value) || value == UNDEFINED)); 855 } 856 857 /** 858 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation 859 * 860 * @param x first object to compare 861 * @param y second object to compare 862 * 863 * @return true if x is greater than or equal to y 864 */ 865 public static boolean GE(final Object x, final Object y) { 866 final Object value = lessThan(x, y, true); 867 return (!(Boolean.TRUE.equals(value) || value == UNDEFINED)); 868 } 869 870 /** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */ 871 private static Object lessThan(final Object x, final Object y, final boolean leftFirst) { 872 Object px, py; 873 874 //support e.g. x < y should throw exception correctly if x or y are not numeric 875 if (leftFirst) { 876 px = JSType.toPrimitive(x, Number.class); 877 py = JSType.toPrimitive(y, Number.class); 878 } else { 879 py = JSType.toPrimitive(y, Number.class); 880 px = JSType.toPrimitive(x, Number.class); 881 } 882 883 if (JSType.of(px) == JSType.STRING && JSType.of(py) == JSType.STRING) { 884 // May be String or ConsString 885 return (px.toString()).compareTo(py.toString()) < 0; 886 } 887 888 final double nx = JSType.toNumber(px); 889 final double ny = JSType.toNumber(py); 890 891 if (Double.isNaN(nx) || Double.isNaN(ny)) { 892 return UNDEFINED; 893 } 894 895 if (nx == ny) { 896 return false; 897 } 898 899 if (nx > 0 && ny > 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { 900 return false; 901 } 902 903 if (nx < 0 && ny < 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { 904 return false; 905 } 906 907 return nx < ny; 908 } 909 910 } --- EOF ---