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