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