package com.oracle.truffle.js.runtime;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.EncapsulatingNodeReference;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.JSGuards;
import com.oracle.truffle.js.runtime.array.TypedArrayFactory;
import com.oracle.truffle.js.runtime.builtins.JSAbstractArray;
import com.oracle.truffle.js.runtime.builtins.JSAdapter;
import com.oracle.truffle.js.runtime.builtins.JSArray;
import com.oracle.truffle.js.runtime.builtins.JSArrayBufferView;
import com.oracle.truffle.js.runtime.builtins.JSBigInt;
import com.oracle.truffle.js.runtime.builtins.JSBoolean;
import com.oracle.truffle.js.runtime.builtins.JSClass;
import com.oracle.truffle.js.runtime.builtins.JSError;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSMap;
import com.oracle.truffle.js.runtime.builtins.JSNumber;
import com.oracle.truffle.js.runtime.builtins.JSOrdinary;
import com.oracle.truffle.js.runtime.builtins.JSProxy;
import com.oracle.truffle.js.runtime.builtins.JSSet;
import com.oracle.truffle.js.runtime.builtins.JSString;
import com.oracle.truffle.js.runtime.builtins.JSSymbol;
import com.oracle.truffle.js.runtime.doubleconv.DoubleConversion;
import com.oracle.truffle.js.runtime.external.DToA;
import com.oracle.truffle.js.runtime.interop.InteropFunction;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.JSAttributes;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSLazyString;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.Null;
import com.oracle.truffle.js.runtime.objects.Nullish;
import com.oracle.truffle.js.runtime.objects.PropertyDescriptor;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.JSHashMap;
public final class JSRuntime {
private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0);
private static final long POSITIVE_INFINITY_DOUBLE_BITS = Double.doubleToRawLongBits(Double.POSITIVE_INFINITY);
public static final String INFINITY_STRING = "Infinity";
public static final String NEGATIVE_INFINITY_STRING = "-Infinity";
public static final String POSITIVE_INFINITY_STRING = "+Infinity";
public static final String NAN_STRING = "NaN";
public static final double TWO32 = 4294967296d;
public static final char LINE_SEPARATOR = '\n';
public static final long INVALID_ARRAY_INDEX = -1;
public static final long MAX_ARRAY_LENGTH = 4294967295L;
public static final int MAX_UINT32_DIGITS = 10;
public static final double MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;
public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
public static final long MAX_SAFE_INTEGER_LONG = (long) MAX_SAFE_INTEGER;
public static final long MIN_SAFE_INTEGER_LONG = (long) MIN_SAFE_INTEGER;
public static final long INVALID_INTEGER_INDEX = -1;
public static final int MAX_INTEGER_INDEX_DIGITS = 16;
public static final int MAX_SAFE_INTEGER_DIGITS = 16;
public static final int MAX_SAFE_INTEGER_IN_FLOAT = 1 << 24;
public static final int MIN_SAFE_INTEGER_IN_FLOAT = -MAX_SAFE_INTEGER_IN_FLOAT;
public static final long MAX_BIG_INT_EXPONENT = Integer.MAX_VALUE;
public static final long INVALID_SAFE_INTEGER = Long.MIN_VALUE;
public static final String TO_STRING = "toString";
public static final String VALUE_OF = "valueOf";
public static final String VALUE = "value";
public static final String DONE = "done";
public static final String NEXT = "next";
public static final String HINT_STRING = "string";
public static final String HINT_NUMBER = "number";
public static final String HINT_DEFAULT = "default";
public static final String PRIMITIVE_VALUE = "PrimitiveValue";
public static final HiddenKey ITERATED_OBJECT_ID = new HiddenKey("IteratedObject");
public static final HiddenKey ITERATOR_NEXT_INDEX = new HiddenKey("IteratorNextIndex");
public static final HiddenKey ENUMERATE_ITERATOR_ID = new HiddenKey("EnumerateIterator");
public static final HiddenKey FOR_IN_ITERATOR_ID = new HiddenKey("ForInIterator");
public static final HiddenKey FINALIZATION_GROUP_CLEANUP_ITERATOR_ID = new HiddenKey("CleanupIterator");
public static final int ITERATION_KIND_KEY = 1 << 0;
public static final int ITERATION_KIND_VALUE = 1 << 1;
public static final int ITERATION_KIND_KEY_PLUS_VALUE = ITERATION_KIND_KEY | ITERATION_KIND_VALUE;
public static final int TO_STRING_MAX_DEPTH = 3;
private JSRuntime() {
}
public static boolean doubleIsRepresentableAsInt(double d) {
return doubleIsRepresentableAsInt(d, false);
}
public static boolean doubleIsRepresentableAsInt(double d, boolean ignoreNegativeZero) {
long longValue = (long) d;
return doubleIsRepresentableAsLong(d) && longIsRepresentableAsInt(longValue) && (ignoreNegativeZero || !isNegativeZero(d));
}
public static boolean doubleIsRepresentableAsUnsignedInt(double d, boolean ignoreNegativeZero) {
long longValue = (long) d;
return doubleIsRepresentableAsLong(d) && longIsRepresentableAsInt(longValue) && (ignoreNegativeZero || !isNegativeZero(d));
}
public static boolean isNegativeZero(double d) {
return Double.doubleToRawLongBits(d) == NEGATIVE_ZERO_DOUBLE_BITS;
}
public static boolean isPositiveInfinity(double d) {
return Double.doubleToRawLongBits(d) == POSITIVE_INFINITY_DOUBLE_BITS;
}
public static Number doubleToNarrowestNumber(double d) {
if (doubleIsRepresentableAsInt(d)) {
return (int) d;
}
return d;
}
public static boolean longIsRepresentableAsInt(long value) {
return value == (int) value;
}
public static boolean isRepresentableAsUnsignedInt(long value) {
return (value & 0xffffffffL) == value;
}
public static boolean doubleIsRepresentableAsLong(double d) {
return d == (long) d;
}
public static Object positiveLongToIntOrDouble(long value) {
if (value <= Integer.MAX_VALUE) {
return (int) value;
} else {
return (double) value;
}
}
public static Number longToIntOrDouble(long value) {
if (Integer.MIN_VALUE <= value && value <= Integer.MAX_VALUE) {
return (int) value;
} else {
return (double) value;
}
}
public static boolean isNaN(Object value) {
if (!(value instanceof Double)) {
return false;
}
double d = (Double) value;
return Double.isNaN(d);
}
@TruffleBoundary
public static String typeof(Object value) {
if (value == Null.instance) {
return Null.TYPE_NAME;
} else if (value == Undefined.instance) {
return Undefined.TYPE_NAME;
} else if (isString(value)) {
return JSString.TYPE_NAME;
} else if (isNumber(value)) {
return JSNumber.TYPE_NAME;
} else if (isBigInt(value)) {
return JSBigInt.TYPE_NAME;
} else if (value instanceof Boolean) {
return JSBoolean.TYPE_NAME;
} else if (value instanceof Symbol) {
return JSSymbol.TYPE_NAME;
} else if (JSDynamicObject.isJSDynamicObject(value)) {
DynamicObject object = (DynamicObject) value;
if (JSProxy.isJSProxy(object)) {
Object target = JSProxy.getTarget(object);
if (target == Null.instance) {
return JSRuntime.isRevokedCallableProxy(object) ? JSFunction.TYPE_NAME : JSOrdinary.TYPE_NAME;
} else {
return typeof(target);
}
} else if (JSFunction.isJSFunction(object)) {
return JSFunction.TYPE_NAME;
}
return JSOrdinary.TYPE_NAME;
} else if (value instanceof TruffleObject) {
assert !(value instanceof Symbol);
TruffleObject object = (TruffleObject) value;
InteropLibrary interop = InteropLibrary.getFactory().getUncached();
if (interop.isBoolean(object)) {
return JSBoolean.TYPE_NAME;
} else if (interop.isString(object)) {
return JSString.TYPE_NAME;
} else if (interop.isNumber(object)) {
return JSNumber.TYPE_NAME;
} else if (interop.isExecutable(object) || interop.isInstantiable(object)) {
return JSFunction.TYPE_NAME;
} else {
return JSOrdinary.TYPE_NAME;
}
} else {
CompilerDirectives.transferToInterpreter();
throw new UnsupportedOperationException("typeof: don't know " + value.getClass().getSimpleName());
}
}
public static boolean isObject(Object vo) {
assert vo instanceof JSObject == hasJSDynamicType(vo);
return vo instanceof JSObject;
}
private static boolean hasJSDynamicType(Object vo) {
if (JSDynamicObject.isJSDynamicObject(vo)) {
Object type = ((JSDynamicObject) vo).getShape().getDynamicType();
return (type instanceof JSClass) && (type != Null.NULL_CLASS);
} else {
return false;
}
}
public static boolean isNullOrUndefined(Object value) {
return value instanceof Nullish;
}
public static boolean isNullish(Object value) {
return value == Null.instance || value == Undefined.instance || InteropLibrary.getUncached(value).isNull(value);
}
@TruffleBoundary
public static Object toPrimitive(Object value) {
return toPrimitive(value, HINT_DEFAULT);
}
@TruffleBoundary
public static Object toPrimitive(Object value, String hint) {
if (value == Null.instance || value == Undefined.instance) {
return value;
} else if (value instanceof TruffleObject) {
if (JSDynamicObject.isJSDynamicObject(value)) {
return JSObject.toPrimitive((DynamicObject) value, hint);
} else if (isForeignObject(value)) {
TruffleObject tObj = (TruffleObject) value;
return toPrimitiveFromForeign(tObj, hint);
}
}
return value;
}
@TruffleBoundary
public static Object toPrimitiveFromForeign(Object tObj, String hint) {
TruffleLanguage.Env env;
InteropLibrary interop = InteropLibrary.getFactory().getUncached(tObj);
if (interop.isNull(tObj)) {
return Null.instance;
} else if ((env = JavaScriptLanguage.getCurrentEnv()).isHostObject(tObj)) {
Object javaObject = env.asHostObject(tObj);
if (javaObject == null) {
return Null.instance;
} else if (JSGuards.isJavaPrimitiveNumber(javaObject)) {
return JSRuntime.importValue(javaObject);
} else if (JavaScriptLanguage.getCurrentJSRealm().getContext().isOptionNashornCompatibilityMode() && javaObject instanceof Number) {
return ((Number) javaObject).doubleValue();
} else {
return JSRuntime.toJSNull(javaObject.toString());
}
} else if (interop.isBoolean(tObj) || interop.isString(tObj) || interop.isNumber(tObj)) {
return JSInteropUtil.toPrimitiveOrDefault(tObj, Null.instance, interop, null);
} else {
return foreignOrdinaryToPrimitive(tObj, hint);
}
}
@TruffleBoundary
private static Object foreignOrdinaryToPrimitive(Object obj, String hint) {
JSRealm realm = JavaScriptLanguage.getCurrentJSRealm();
InteropLibrary interop = InteropLibrary.getFactory().getUncached(obj);
String[] methodNames;
if (hint.equals(HINT_STRING)) {
methodNames = new String[]{TO_STRING, VALUE_OF};
} else {
assert JSRuntime.HINT_NUMBER.equals(hint);
methodNames = new String[]{VALUE_OF, TO_STRING};
}
DynamicObject proto;
if (interop.hasArrayElements(obj)) {
proto = realm.getArrayPrototype();
} else if (interop.isExecutable(obj)) {
proto = realm.getFunctionPrototype();
} else if (interop.isInstant(obj)) {
proto = realm.getDatePrototype();
} else {
proto = realm.getObjectPrototype();
}
for (String name : methodNames) {
if (interop.hasMembers(obj) && interop.isMemberInvocable(obj, name)) {
Object result;
try {
result = importValue(interop.invokeMember(obj, name));
} catch (InteropException e) {
result = null;
}
if (result != null && !isObject(result)) {
return result;
}
}
Object method = JSObject.getMethod(proto, name);
if (isCallable(method)) {
Object result = call(method, obj, new Object[]{});
if (!isObject(result)) {
return result;
}
}
}
throw Errors.createTypeErrorCannotConvertToPrimitiveValue();
}
@TruffleBoundary
public static boolean toBoolean(Object value) {
if (value == Boolean.TRUE) {
return true;
} else if (value == Boolean.FALSE || value == Undefined.instance || value == Null.instance) {
return false;
} else if (isNumber(value)) {
return toBoolean((Number) value);
} else if (value instanceof String) {
return ((String) value).length() != 0;
} else if (isLazyString(value)) {
return value.toString().length() != 0;
} else if (value instanceof BigInt) {
return ((BigInt) value).compareTo(BigInt.ZERO) != 0;
} else if (isForeignObject(value)) {
InteropLibrary interop = InteropLibrary.getFactory().getUncached(value);
if (interop.isNull(value)) {
return false;
} else if (interop.isBoolean(value) || interop.isString(value) || interop.isNumber(value)) {
return toBoolean(JSInteropUtil.toPrimitiveOrDefault(value, Null.instance, interop, null));
} else {
return true;
}
} else {
return true;
}
}
public static boolean toBoolean(Number number) {
double val = doubleValue(number);
if (val == 0 || Double.isNaN(val)) {
return false;
}
return Boolean.TRUE;
}
@TruffleBoundary
public static Number toNumber(Object value) {
Object primitive;
if (isObject(value)) {
primitive = JSObject.toPrimitive((DynamicObject) value, HINT_NUMBER);
} else if (isForeignObject(value)) {
primitive = toPrimitiveFromForeign(value, HINT_NUMBER);
} else {
primitive = value;
}
return toNumberFromPrimitive(primitive);
}
@TruffleBoundary
public static Object toNumeric(Object value) {
Object primitive = isObject(value) ? JSObject.toPrimitive((DynamicObject) value, HINT_NUMBER) : value;
if (primitive instanceof BigInt) {
return primitive;
} else {
return toNumberFromPrimitive(primitive);
}
}
@TruffleBoundary
public static Number toNumberFromPrimitive(Object value) {
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.LIKELY_PROBABILITY, isNumber(value))) {
return (Number) value;
} else if (value == Undefined.instance) {
return Double.NaN;
} else if (value == Null.instance) {
return 0;
} else if (value instanceof Boolean) {
return booleanToNumber((Boolean) value);
} else if (value instanceof String) {
return stringToNumber((String) value);
} else if (isLazyString(value)) {
return stringToNumber(value.toString());
} else if (value instanceof Symbol) {
throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value");
} else if (value instanceof BigInt) {
throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value");
} else if (value instanceof Number) {
assert isJavaPrimitive(value) : value.getClass().getName();
return (Number) value;
}
assert false : "should never reach here, type " + value.getClass().getName() + " not handled.";
throw Errors.createTypeErrorCannotConvertToNumber(safeToString(value));
}
public static int booleanToNumber(boolean value) {
return value ? 1 : 0;
}
public static boolean isNumber(Object value) {
return value instanceof Integer || value instanceof Double || value instanceof Long || value instanceof SafeInteger;
}
@TruffleBoundary
public static BigInt toBigInt(Object value) {
Object primitive = toPrimitive(value, HINT_NUMBER);
if (primitive instanceof String) {
try {
return BigInt.valueOf((String) primitive);
} catch (NumberFormatException e) {
throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.SyntaxError, primitive);
}
} else if (primitive instanceof BigInt) {
return (BigInt) primitive;
} else if (primitive instanceof Boolean) {
return (Boolean) primitive ? BigInt.ONE : BigInt.ZERO;
} else {
throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, primitive);
}
}
public static boolean isBigInt(Object value) {
return value instanceof BigInt;
}
public static boolean isJavaNumber(Object value) {
return value instanceof Number;
}
@TruffleBoundary
public static Number stringToNumber(String string) {
String strCamel = trimJSWhiteSpace(string);
if (strCamel.length() == 0) {
return 0;
}
char firstChar = strCamel.charAt(0);
if (strCamel.length() >= INFINITY_STRING.length() && strCamel.length() <= INFINITY_STRING.length() + 1 && strCamel.endsWith(INFINITY_STRING)) {
return identifyInfinity(strCamel, firstChar);
}
if (!(JSRuntime.isAsciiDigit(firstChar) || firstChar == '-' || firstChar == '.' || firstChar == '+')) {
return Double.NaN;
}
return stringToNumberParse(strCamel);
}
private static Number stringToNumberParse(String str) {
assert str.length() > 0;
boolean hex = str.startsWith("0x") || str.startsWith("0X");
int eIndex = firstExpIndexInString(str);
boolean sci = !hex && (0 <= eIndex && eIndex < str.length() - 1);
try {
if (!sci && str.length() <= 18 && str.indexOf('.') == -1) {
if (hex) {
return Long.valueOf(str.substring(2), 16);
} else {
return stringToNumberLong(str);
}
} else {
return parseDoubleOrNaN(str);
}
} catch (NumberFormatException e) {
return Double.NaN;
}
}
private static Number stringToNumberLong(String strLower) throws NumberFormatException {
assert strLower.length() > 0;
long num = Long.parseLong(strLower);
if (longIsRepresentableAsInt(num)) {
if (num == 0 && strLower.charAt(0) == '-') {
return -0.0;
}
return (int) num;
} else {
return (double) num;
}
}
@TruffleBoundary
public static double parseDoubleOrNaN(String input) {
if (input.isEmpty() || input.charAt(input.length() - 1) > '9') {
return Double.NaN;
}
try {
return Double.parseDouble(input);
} catch (NumberFormatException e) {
return Double.NaN;
}
}
@TruffleBoundary
public static int firstExpIndexInString(String str) {
int firstIdx = str.indexOf('e', 0);
if (firstIdx >= 0) {
return firstIdx;
}
return str.indexOf('E', 0);
}
public static double identifyInfinity(String str, char firstChar) {
int len = str.length();
int infinityLength = INFINITY_STRING.length();
if (len == infinityLength) {
return Double.POSITIVE_INFINITY;
} else if (len == (infinityLength + 1)) {
if (firstChar == '+') {
return Double.POSITIVE_INFINITY;
} else if (firstChar == '-') {
return Double.NEGATIVE_INFINITY;
}
}
return Double.NaN;
}
public static long toInteger(Object value) {
Number number = toNumber(value);
return toInteger(number);
}
public static long toInteger(Number number) {
return longValue(number);
}
public static long toLength(Object value) {
long l = toInteger(value);
return toLength(l);
}
public static double toLength(double d) {
if (d <= 0) {
return 0;
}
if (d > MAX_SAFE_INTEGER) {
return MAX_SAFE_INTEGER;
}
return d;
}
public static long toLength(long l) {
if (l <= 0) {
return 0;
}
if (l > MAX_SAFE_INTEGER_LONG) {
return MAX_SAFE_INTEGER_LONG;
}
return l;
}
public static int toLength(int value) {
if (value <= 0) {
return 0;
}
return value;
}
public static int toUInt8(Object value) {
Number number = toNumber(value);
return toUInt8(number);
}
@TruffleBoundary
public static int toUInt8(Number number) {
if (number instanceof Double) {
Double d = (Double) number;
if (isPositiveInfinity(d)) {
return 0;
}
}
return toUInt8(number.longValue());
}
public static int toUInt8(long number) {
return (int) (number & 0x000000FF);
}
public static int toInt8(Object value) {
Number number = toNumber(value);
return toInt8(number);
}
@TruffleBoundary
public static int toInt8(Number number) {
if (number instanceof Double) {
Double d = (Double) number;
if (isPositiveInfinity(d)) {
return 0;
}
}
return toInt8(number.longValue());
}
@TruffleBoundary
public static int toInt8(long number) {
int res = (int) Math.floorMod(number, 256);
if (res >= 128) {
res = res - 256;
}
return res;
}
public static int toUInt16(Object value) {
Number number = toNumber(value);
return toUInt16(number);
}
public static int toUInt16(Number number) {
if (number instanceof Double) {
Double d = (Double) number;
if (isPositiveInfinity(d)) {
return 0;
}
}
return toUInt16(longValue(number));
}
public static int toUInt16(long number) {
return (int) (number & 0x0000FFFF);
}
public static int toInt16(Object value) {
Number number = toNumber(value);
return toInt16(number);
}
@TruffleBoundary()
public static int toInt16(Number number) {
if (number instanceof Double) {
Double d = (Double) number;
if (isPositiveInfinity(d)) {
return 0;
}
}
return toInt16(number.longValue());
}
@TruffleBoundary()
public static int toInt16(long number) {
int res = (int) Math.floorMod(number, 65536);
if (res >= 32768) {
res = res - 65536;
}
return res;
}
public static long toUInt32(Object value) {
return toUInt32(toNumber(value));
}
public static long toUInt32(Number number) {
if (number instanceof Double) {
return toUInt32(((Double) number).doubleValue());
}
return toUInt32(longValue(number));
}
public static long toUInt32(long value) {
return (value & 0xFFFFFFFFL);
}
public static long toUInt32(double value) {
return toUInt32NoTruncate(truncateDouble(value));
}
public static long toUInt32NoTruncate(double value) {
assert !Double.isFinite(value) || value % 1 == 0;
double d = doubleModuloTwo32(value);
return toUInt32((long) d);
}
public static double truncateDouble(double value) {
return Math.signum(value) * JSRuntime.mathFloor(Math.abs(value));
}
public static double truncateDouble2(double thing) {
return (thing < 0) ? JSRuntime.mathCeil(thing) : JSRuntime.mathFloor(thing);
}
public static int toInt32(Object value) {
Number number = toNumber(value);
return toInt32(number);
}
public static int toInt32(Number number) {
if (number instanceof Double) {
return toInt32(((Double) number).doubleValue());
}
if (number instanceof Integer) {
return (int) number;
}
if (number instanceof Long) {
return (int) (long) number;
}
return toInt32Intl(number);
}
@TruffleBoundary
private static int toInt32Intl(Number number) {
return toInt32(number.doubleValue());
}
public static int toInt32(double value) {
return toInt32NoTruncate(truncateDouble(value));
}
public static int toInt32NoTruncate(double value) {
assert !Double.isFinite(value) || value % 1 == 0;
return (int) (long) doubleModuloTwo32(value);
}
private static double doubleModuloTwo32(double value) {
return value - JSRuntime.mathFloor(value / TWO32) * TWO32;
}
public static double toDouble(Object value) {
return doubleValue(toNumber(value));
}
public static double toDouble(Number value) {
return doubleValue(value);
}
@TruffleBoundary
public static String toString(Object value) {
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.LIKELY_PROBABILITY, value instanceof String)) {
return (String) value;
} else if (isLazyString(value)) {
return value.toString();
} else if (value == Undefined.instance) {
return Undefined.NAME;
} else if (value == Null.instance) {
return Null.NAME;
} else if (value instanceof Boolean) {
return booleanToString((Boolean) value);
} else if (isNumber(value)) {
return numberToString((Number) value);
} else if (value instanceof Symbol) {
throw Errors.createTypeErrorCannotConvertToString("a Symbol value");
} else if (value instanceof BigInt) {
return value.toString();
} else if (JSDynamicObject.isJSDynamicObject(value)) {
return toString(JSObject.toPrimitive((DynamicObject) value, HINT_STRING));
} else if (value instanceof TruffleObject) {
assert !isJSNative(value);
return toString(toPrimitiveFromForeign(value, HINT_STRING));
}
throw toStringTypeError(value);
}
@TruffleBoundary
public static String safeToString(Object value) {
return toDisplayStringImpl(value, TO_STRING_MAX_DEPTH, null, false, false);
}
@TruffleBoundary
public static String toDisplayString(Object value, boolean allowSideEffects) {
return toDisplayStringImpl(value, TO_STRING_MAX_DEPTH, null, false, allowSideEffects);
}
@TruffleBoundary
public static String toDisplayString(Object value, int currentDepth, Object parent, boolean allowSideEffects) {
return toDisplayStringImpl(value, currentDepth - 1, parent, true, allowSideEffects);
}
@TruffleBoundary
public static String toDisplayString(Object value, int currentDepth, Object parent, boolean quoteString, boolean allowSideEffects) {
return toDisplayStringImpl(value, currentDepth - 1, parent, quoteString, allowSideEffects);
}
private static String toDisplayStringImpl(Object value, int depth, Object parent, boolean quoteString, boolean allowSideEffects) {
CompilerAsserts.neverPartOfCompilation();
if (value == parent) {
return "(this)";
} else if (value == Undefined.instance) {
return Undefined.NAME;
} else if (value == Null.instance) {
return Null.NAME;
} else if (value instanceof Boolean) {
return booleanToString((Boolean) value);
} else if (isString(value)) {
String string = value.toString();
return quoteString ? quote(string) : string;
} else if (JSDynamicObject.isJSDynamicObject(value)) {
return JSObject.toDisplayString((DynamicObject) value, depth, allowSideEffects);
} else if (value instanceof Symbol) {
return value.toString();
} else if (value instanceof BigInt) {
return value.toString() + 'n';
} else if (isNumber(value)) {
Number number = (Number) value;
if (JSRuntime.isNegativeZero(number.doubleValue())) {
return "-0";
} else {
return numberToString(number);
}
} else if (value instanceof InteropFunction) {
return toDisplayStringImpl(((InteropFunction) value).getFunction(), depth, parent, quoteString, allowSideEffects);
} else if (value instanceof TruffleObject) {
assert !isJSNative(value) : value;
return foreignToString(value, depth, allowSideEffects);
} else {
return String.valueOf(value);
}
}
@TruffleBoundary
public static String objectToConsoleString(DynamicObject obj, String name, int depth, boolean allowSideEffects) {
return objectToConsoleString(obj, name, depth, null, null, allowSideEffects);
}
@TruffleBoundary
public static String objectToConsoleString(DynamicObject obj, String name, int depth, String[] internalKeys, Object[] internalValues, boolean allowSideEffects) {
assert JSDynamicObject.isJSDynamicObject(obj) && !JSFunction.isJSFunction(obj) && !JSProxy.isJSProxy(obj);
StringBuilder sb = new StringBuilder();
if (name != null) {
sb.append(name);
}
boolean isArrayLike = false;
boolean isArray = false;
long length = -1;
if (JSArray.isJSArray(obj)) {
isArrayLike = true;
isArray = true;
length = JSArray.arrayGetLength(obj);
} else if (JSArrayBufferView.isJSArrayBufferView(obj)) {
isArrayLike = true;
length = JSArrayBufferView.typedArrayGetLength(obj);
} else if (JSString.isJSString(obj)) {
length = JSString.getStringLength(obj);
}
boolean isStringObj = JSString.isJSString(obj);
long prevArrayIndex = -1;
if (isArrayLike) {
if (length > 0) {
boolean topLevel = depth == TO_STRING_MAX_DEPTH;
if (depth <= 0 || (!topLevel && length > JSConfig.MaxConsolePrintProperties)) {
if (name == null) {
sb.append("Array");
}
sb.append('(').append(length).append(')');
return sb.toString();
} else if (topLevel && length >= 2) {
sb.append('(').append(length).append(')');
}
}
} else if (depth <= 0) {
sb.append("{...}");
return sb.toString();
}
sb.append(isArrayLike ? '[' : '{');
int propertyCount = 0;
for (Object key : JSObject.ownPropertyKeys(obj)) {
PropertyDescriptor desc = JSObject.getOwnProperty(obj, key);
if ((isArrayLike || isStringObj) && key.equals("length") || (isStringObj && JSRuntime.isArrayIndex(key) && JSRuntime.parseArrayIndexRaw(key.toString()) < length)) {
continue;
}
if (propertyCount > 0) {
sb.append(", ");
if (propertyCount >= JSConfig.MaxConsolePrintProperties) {
sb.append("...");
break;
}
}
if (isArray) {
if (JSRuntime.isArrayIndex(key)) {
long index = JSRuntime.parseArrayIndexRaw(key.toString());
if ((index < length) && fillEmptyArrayElements(sb, index, prevArrayIndex, false)) {
sb.append(", ");
propertyCount++;
if (propertyCount >= JSConfig.MaxConsolePrintProperties) {
sb.append("...");
break;
}
}
prevArrayIndex = index;
} else {
if (fillEmptyArrayElements(sb, length, prevArrayIndex, false)) {
sb.append(", ");
propertyCount++;
if (propertyCount >= JSConfig.MaxConsolePrintProperties) {
sb.append("...");
break;
}
}
prevArrayIndex = Math.max(prevArrayIndex, length);
}
}
if (!isArrayLike || !JSRuntime.isArrayIndex(key)) {
sb.append(key);
sb.append(": ");
}
String valueStr = null;
if (desc.isDataDescriptor()) {
Object value = desc.getValue();
valueStr = toDisplayString(value, depth, obj, allowSideEffects);
} else if (desc.isAccessorDescriptor()) {
valueStr = "accessor";
} else {
valueStr = "empty";
}
sb.append(valueStr);
propertyCount++;
}
if (isArray && propertyCount < JSConfig.MaxConsolePrintProperties) {
if (fillEmptyArrayElements(sb, length, prevArrayIndex, propertyCount > 0)) {
propertyCount++;
}
}
if (internalKeys != null) {
assert internalValues != null && internalKeys.length == internalValues.length;
for (int i = 0; i < internalKeys.length; i++) {
if (propertyCount > 0) {
sb.append(", ");
}
sb.append("[[").append(internalKeys[i]).append("]]: ").append(toDisplayString(internalValues[i], depth, obj, allowSideEffects));
propertyCount++;
}
}
sb.append(isArrayLike ? ']' : '}');
return sb.toString();
}
private static String foreignToString(Object value, int depth, boolean allowSideEffects) {
CompilerAsserts.neverPartOfCompilation();
TruffleLanguage.Env env;
try {
InteropLibrary interop = InteropLibrary.getFactory().getUncached(value);
if (interop.isNull(value)) {
return "null";
} else if (interop.hasArrayElements(value)) {
return foreignArrayToString(value, depth, allowSideEffects);
} else if (interop.isString(value)) {
return interop.asString(value);
} else if (interop.isBoolean(value)) {
return booleanToString(interop.asBoolean(value));
} else if (interop.isNumber(value)) {
Object unboxed = "Number";
if (interop.fitsInInt(value)) {
unboxed = interop.asInt(value);
} else if (interop.fitsInLong(value)) {
unboxed = interop.asLong(value);
} else if (interop.fitsInDouble(value)) {
unboxed = interop.asDouble(value);
}
return JSRuntime.toDisplayString(unboxed, 0, null, allowSideEffects);
} else if ((env = JavaScriptLanguage.getCurrentEnv()).isHostObject(value)) {
Object hostObject = env.asHostObject(value);
Class<?> clazz = hostObject.getClass();
if (clazz == Class.class) {
clazz = (Class<?>) hostObject;
return "JavaClass[" + clazz.getTypeName() + "]";
} else {
return "JavaObject[" + clazz.getTypeName() + "]";
}
} else if (interop.isMetaObject(value)) {
return InteropLibrary.getFactory().getUncached().asString(interop.getMetaQualifiedName(value));
} else if (interop.hasMembers(value) && !(interop.isExecutable(value) || interop.isInstantiable(value))) {
return foreignObjectToString(value, depth, allowSideEffects);
} else {
return InteropLibrary.getFactory().getUncached().asString(interop.toDisplayString(value, allowSideEffects));
}
} catch (InteropException e) {
return "Object";
}
}
private static String foreignArrayToString(Object truffleObject, int depth, boolean allowSideEffects) throws InteropException {
CompilerAsserts.neverPartOfCompilation();
InteropLibrary interop = InteropLibrary.getFactory().getUncached(truffleObject);
assert interop.hasArrayElements(truffleObject);
long size = interop.getArraySize(truffleObject);
if (size == 0) {
return "[]";
} else if (depth <= 0) {
return "Array(" + size + ")";
}
boolean topLevel = depth == TO_STRING_MAX_DEPTH;
StringBuilder sb = new StringBuilder();
if (topLevel && size >= 2) {
sb.append('(').append(size).append(')');
}
sb.append('[');
for (long i = 0; i < size; i++) {
if (i > 0) {
sb.append(", ");
if (i >= JSConfig.MaxConsolePrintProperties) {
sb.append("...");
break;
}
}
Object value = interop.readArrayElement(truffleObject, i);
sb.append(toDisplayString(value, depth, truffleObject, allowSideEffects));
}
sb.append(']');
return sb.toString();
}
@TruffleBoundary
public static String javaArrayToString(Object array) {
return javaArrayToString(array, TO_STRING_MAX_DEPTH, true);
}
private static String javaArrayToString(Object javaArray, int depth, boolean allowSideEffects) {
CompilerAsserts.neverPartOfCompilation();
if (javaArray == null) {
return "null";
}
if (depth <= 0) {
return "[...]";
}
int size = Array.getLength(javaArray) - 1;
if (size == -1) {
return "[]";
}
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0;; i++) {
Object arrayValue = Array.get(javaArray, i);
if (JSGuards.isJavaArray(arrayValue)) {
b.append(javaArrayToString(arrayValue, depth - 1, allowSideEffects));
} else {
b.append(toDisplayString(arrayValue, depth, javaArray, allowSideEffects));
}
if (i == size) {
return b.append(']').toString();
}
b.append(", ");
}
}
private static String foreignObjectToString(Object truffleObject, int depth, boolean allowSideEffects) throws InteropException {
CompilerAsserts.neverPartOfCompilation();
InteropLibrary objInterop = InteropLibrary.getFactory().getUncached(truffleObject);
assert objInterop.hasMembers(truffleObject);
if (allowSideEffects && objInterop.isMemberInvocable(truffleObject, TO_STRING)) {
return objInterop.invokeMember(truffleObject, TO_STRING).toString();
}
Object keys = objInterop.getMembers(truffleObject);
InteropLibrary keysInterop = InteropLibrary.getFactory().getUncached(keys);
long keyCount = keysInterop.getArraySize(keys);
if (keyCount == 0) {
return "{}";
} else if (depth <= 0) {
return "{...}";
}
StringBuilder sb = new StringBuilder();
sb.append('{');
for (long i = 0; i < keyCount; i++) {
if (i > 0) {
sb.append(", ");
if (i >= JSConfig.MaxConsolePrintProperties) {
sb.append("...");
break;
}
}
Object key = keysInterop.readArrayElement(keys, i);
assert InteropLibrary.getFactory().getUncached().isString(key);
String stringKey = key instanceof String ? (String) key : InteropLibrary.getFactory().getUncached().asString(key);
Object value = objInterop.readMember(truffleObject, stringKey);
sb.append(stringKey);
sb.append(": ");
sb.append(toDisplayString(value, depth, truffleObject, allowSideEffects));
}
sb.append('}');
return sb.toString();
}
private static boolean fillEmptyArrayElements(StringBuilder sb, long index, long prevArrayIndex, boolean prependComma) {
if (prevArrayIndex < (index - 1)) {
if (prependComma) {
sb.append(", ");
}
long count = index - prevArrayIndex - 1;
if (count == 1) {
sb.append("empty");
} else {
sb.append("empty \u00d7 ");
sb.append(count);
}
return true;
}
return false;
}
public static String collectionToConsoleString(DynamicObject obj, String name, JSHashMap map, int depth, boolean allowSideEffects) {
assert JSMap.isJSMap(obj) || JSSet.isJSSet(obj);
assert name != null;
int size = map.size();
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append('(').append(size).append(')');
if (size > 0 && depth > 0) {
sb.append('{');
boolean isMap = JSMap.isJSMap(obj);
boolean isFirst = true;
JSHashMap.Cursor cursor = map.getEntries();
while (cursor.advance()) {
Object key = cursor.getKey();
if (key != null) {
if (!isFirst) {
sb.append(", ");
}
sb.append(toDisplayString(key, depth, obj, allowSideEffects));
if (isMap) {
sb.append(" => ");
sb.append(toDisplayString(cursor.getValue(), depth, obj, allowSideEffects));
}
isFirst = false;
}
}
sb.append('}');
}
return sb.toString();
}
@TruffleBoundary
public static JSException toStringTypeError(Object value) {
String what = (value == null ? Null.NAME : (JSDynamicObject.isJSDynamicObject(value) ? JSObject.defaultToString((DynamicObject) value) : value.getClass().getName()));
throw Errors.createTypeErrorCannotConvertToString(what);
}
public static String booleanToString(boolean value) {
return value ? JSBoolean.TRUE_NAME : JSBoolean.FALSE_NAME;
}
public static String toString(DynamicObject value) {
if (value == Undefined.instance) {
return Undefined.NAME;
} else if (value == Null.instance) {
return Null.NAME;
}
return toString(JSObject.toPrimitive(value, HINT_STRING));
}
public static String numberToString(Number number) {
if (number instanceof Integer) {
return Boundaries.stringValueOf(((Integer) number).intValue());
} else if (number instanceof SafeInteger) {
return doubleToString(((SafeInteger) number).doubleValue());
} else if (number instanceof Double) {
return doubleToString((Double) number);
} else if (number instanceof Long) {
return Boundaries.stringValueOf(number.longValue());
}
CompilerDirectives.transferToInterpreter();
throw new UnsupportedOperationException("unknown number value: " + number.toString() + " " + number.getClass().getSimpleName());
}
public static int length(CharSequence cs) {
if (cs instanceof String) {
return ((String) cs).length();
} else if (cs instanceof JSLazyString) {
return ((JSLazyString) cs).length();
}
return lengthIntl(cs);
}
public static int length(CharSequence cs, ConditionProfile stringProfile, ConditionProfile lazyStringProfile) {
if (stringProfile.profile(cs instanceof String)) {
return ((String) cs).length();
} else if (lazyStringProfile.profile(cs instanceof JSLazyString)) {
return ((JSLazyString) cs).length();
}
return lengthIntl(cs);
}
@TruffleBoundary
private static int lengthIntl(CharSequence cs) {
return cs.length();
}
public static char charAt(CharSequence cs, int index) {
if (cs instanceof String) {
return ((String) cs).charAt(index);
} else if (cs instanceof JSLazyString) {
return ((JSLazyString) cs).charAt(index);
}
return charAtIntl(cs, index);
}
@TruffleBoundary
private static char charAtIntl(CharSequence cs, int index) {
return cs.charAt(index);
}
public static String javaToString(Object obj) {
if (obj instanceof String) {
return (String) obj;
} else if (obj instanceof JSLazyString) {
return ((JSLazyString) obj).toString();
}
return Boundaries.javaToString(obj);
}
public static boolean propertyKeyEquals(Object a, Object b) {
assert isPropertyKey(a);
if (a instanceof String) {
if (b instanceof String) {
return ((String) a).equals(b);
} else if (b instanceof JSLazyString) {
return ((String) a).equals(((JSLazyString) b).toString());
} else {
return false;
}
} else if (a instanceof Symbol) {
return ((Symbol) a).equals(b);
} else {
throw Errors.shouldNotReachHere();
}
}
@TruffleBoundary
public static String doubleToString(double d, int radix) {
assert radix >= 2 && radix <= 36;
if (Double.isNaN(d)) {
return NAN_STRING;
} else if (d == Double.POSITIVE_INFINITY) {
return INFINITY_STRING;
} else if (d == Double.NEGATIVE_INFINITY) {
return NEGATIVE_INFINITY_STRING;
} else if (d == 0) {
return "0";
}
return formatDtoA(d, radix);
}
public static String doubleToString(double d) {
if (Double.isNaN(d)) {
return NAN_STRING;
} else if (d == Double.POSITIVE_INFINITY) {
return INFINITY_STRING;
} else if (d == Double.NEGATIVE_INFINITY) {
return NEGATIVE_INFINITY_STRING;
} else if (d == 0) {
return "0";
}
if (doubleIsRepresentableAsInt(d)) {
return Boundaries.stringValueOf((int) d);
}
return formatDtoA(d);
}
@TruffleBoundary
public static String formatDtoA(double value) {
return DoubleConversion.toShortest(value);
}
@TruffleBoundary
public static String formatDtoAPrecision(double value, int precision) {
return DoubleConversion.toPrecision(value, precision);
}
@TruffleBoundary
public static String formatDtoAExponential(double d, int digits) {
return DoubleConversion.toExponential(d, digits);
}
@TruffleBoundary
public static String formatDtoAExponential(double d) {
return DoubleConversion.toExponential(d, -1);
}
@TruffleBoundary
public static String formatDtoAFixed(double value, int digits) {
return DoubleConversion.toFixed(value, digits);
}
@TruffleBoundary
public static String formatDtoA(double d, int radix) {
return DToA.jsDtobasestr(radix, d);
}
public static TruffleObject toObject(JSContext ctx, Object value) {
requireObjectCoercible(value, ctx);
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.LIKELY_PROBABILITY, JSDynamicObject.isJSDynamicObject(value))) {
return (DynamicObject) value;
}
Object unboxedValue = value;
if (isForeignObject(value)) {
InteropLibrary interop = InteropLibrary.getUncached(value);
assert !interop.isNull(value);
unboxedValue = JSInteropUtil.toPrimitiveOrDefault(value, null, interop, null);
if (unboxedValue == null) {
return (TruffleObject) value;
}
}
return toObjectFromPrimitive(ctx, unboxedValue, true);
}
@TruffleBoundary
public static TruffleObject toObjectFromPrimitive(JSContext ctx, Object value, boolean useJavaWrapper) {
if (value instanceof Boolean) {
return JSBoolean.create(ctx, (Boolean) value);
} else if (value instanceof String) {
return JSString.create(ctx, (String) value);
} else if (value instanceof JSLazyString) {
return JSString.create(ctx, (JSLazyString) value);
} else if (value instanceof BigInt) {
return JSBigInt.create(ctx, (BigInt) value);
} else if (isNumber(value)) {
return JSNumber.create(ctx, (Number) value);
} else if (value instanceof Symbol) {
return JSSymbol.create(ctx, (Symbol) value);
} else {
assert !isJSNative(value) && isJavaPrimitive(value) : value;
if (useJavaWrapper) {
return (TruffleObject) ctx.getRealm().getEnv().asBoxedGuestValue(value);
} else {
return null;
}
}
}
@TruffleBoundary
public static boolean isSameValue(Object x, Object y) {
if (x == Undefined.instance && y == Undefined.instance) {
return true;
} else if (x == Null.instance && y == Null.instance) {
return true;
} else if (x instanceof Integer && y instanceof Integer) {
return (int) x == (int) y;
} else if (isNumber(x) && isNumber(y)) {
double xd = doubleValue((Number) x);
double yd = doubleValue((Number) y);
return Double.compare(xd, yd) == 0;
} else if (isString(x) && isString(y)) {
return x.toString().equals(y.toString());
} else if (x instanceof Boolean && y instanceof Boolean) {
return (boolean) x == (boolean) y;
} else if (isBigInt(x) && isBigInt(y)) {
return ((BigInt) x).compareTo((BigInt) y) == 0;
}
return x == y;
}
@TruffleBoundary
public static boolean equal(Object a, Object b) {
if (a == b) {
return true;
} else if (a == Undefined.instance || a == Null.instance) {
return isNullish(b);
} else if (b == Undefined.instance || b == Null.instance) {
return isNullish(a);
} else if (a instanceof Boolean && b instanceof Boolean) {
return a.equals(b);
} else if (isString(a) && isString(b)) {
return a.toString().equals(b.toString());
} else if (isJavaNumber(a) && isJavaNumber(b)) {
double da = doubleValue((Number) a);
double db = doubleValue((Number) b);
return da == db;
} else if (JSDynamicObject.isJSDynamicObject(a) && JSDynamicObject.isJSDynamicObject(b)) {
return a == b;
} else if (isJavaNumber(a) && isString(b)) {
return equal(a, stringToNumber(b.toString()));
} else if (isString(a) && isJavaNumber(b)) {
return equal(stringToNumber(a.toString()), b);
} else if (isBigInt(a) && isBigInt(b)) {
return a.equals(b);
} else if (isBigInt(a) && isString(b)) {
return a.equals(stringToBigInt(b.toString()));
} else if (isString(a) && isBigInt(b)) {
return b.equals(stringToBigInt(a.toString()));
} else if (isJavaNumber(a) && isBigInt(b)) {
return equalBigIntAndNumber((BigInt) b, (Number) a);
} else if (isBigInt(a) && isJavaNumber(b)) {
return equalBigIntAndNumber((BigInt) a, (Number) b);
} else if (a instanceof Boolean) {
return equal(booleanToNumber((Boolean) a), b);
} else if (b instanceof Boolean) {
return equal(a, booleanToNumber((Boolean) b));
} else if (isObject(a)) {
assert b != Undefined.instance && b != Null.instance;
return equal(JSObject.toPrimitive((DynamicObject) a), b);
} else if (isObject(b)) {
assert b != Undefined.instance && b != Null.instance;
return equal(a, JSObject.toPrimitive(((DynamicObject) b)));
} else if (isForeignObject(a) || isForeignObject(b)) {
return equalInterop(a, b);
} else {
return false;
}
}
public static boolean isForeignObject(Object value) {
return value instanceof TruffleObject && isForeignObject((TruffleObject) value);
}
public static boolean isForeignObject(TruffleObject value) {
return !JSDynamicObject.isJSDynamicObject(value) && !(value instanceof Symbol) && !(value instanceof JSLazyString) && !(value instanceof SafeInteger) &&
!(value instanceof BigInt);
}
private static boolean equalInterop(Object a, Object b) {
assert (a != null) && (b != null);
final Object defaultValue = null;
Object primLeft;
if (isForeignObject(a)) {
primLeft = JSInteropUtil.toPrimitiveOrDefault(a, defaultValue, InteropLibrary.getUncached(a), null);
} else {
primLeft = isNullOrUndefined(a) ? Null.instance : a;
}
Object primRight;
if (isForeignObject(b)) {
primRight = JSInteropUtil.toPrimitiveOrDefault(b, defaultValue, InteropLibrary.getUncached(b), null);
} else {
primRight = isNullOrUndefined(b) ? Null.instance : b;
}
if (primLeft == Null.instance || primRight == Null.instance) {
return primLeft == primRight;
} else if (primLeft == defaultValue || primRight == defaultValue) {
if (primLeft == defaultValue && primRight == defaultValue) {
return Boundaries.equals(a, b);
} else {
return false;
}
} else {
assert !isForeignObject(primLeft) && !isForeignObject(primRight);
return equal(primLeft, primRight);
}
}
private static boolean equalBigIntAndNumber(BigInt a, Number b) {
if (b instanceof Double || b instanceof Float) {
double numberVal = doubleValue(b);
return !Double.isNaN(numberVal) && a.compareValueTo(numberVal) == 0;
} else {
return a.compareValueTo(longValue(b)) == 0;
}
}
@TruffleBoundary
public static boolean identical(Object a, Object b) {
if (a == b) {
if (a instanceof Double) {
return !Double.isNaN((Double) a);
}
return true;
}
if (a == Undefined.instance || b == Undefined.instance) {
return false;
}
if (a == Null.instance) {
assert b != Undefined.instance;
return InteropLibrary.getUncached(b).isNull(b);
} else if (b == Null.instance) {
assert a != Undefined.instance;
return InteropLibrary.getUncached(a).isNull(a);
}
if (isBigInt(a) && isBigInt(b)) {
return a.equals(b);
}
if (isJavaNumber(a) && isJavaNumber(b)) {
if (a instanceof Integer && b instanceof Integer) {
return ((Integer) a).intValue() == ((Integer) b).intValue();
} else {
return doubleValue((Number) a) == doubleValue((Number) b);
}
}
if ((a instanceof Boolean && b instanceof Boolean)) {
return a.equals(b);
}
if (isString(a) && isString(b)) {
return a.toString().equals(b.toString());
}
if (isObject(a) || isObject(b)) {
return false;
}
InteropLibrary aInterop = InteropLibrary.getUncached(a);
InteropLibrary bInterop = InteropLibrary.getUncached(b);
return aInterop.isIdentical(a, b, bInterop) || (aInterop.isNull(a) && bInterop.isNull(b));
}
public static <T> T requireObjectCoercible(T argument, JSContext context) {
if (argument == Undefined.instance || argument == Null.instance || (isForeignObject(argument) && InteropLibrary.getUncached(argument).isNull(argument))) {
throw Errors.createTypeErrorNotObjectCoercible(argument, null, context);
}
return argument;
}
@TruffleBoundary
public static PropertyDescriptor toPropertyDescriptor(Object property) {
if (!isObject(property)) {
throw Errors.createTypeErrorNotAnObject(property);
}
DynamicObject obj = (DynamicObject) property;
PropertyDescriptor desc = PropertyDescriptor.createEmpty();
if (JSObject.hasProperty(obj, JSAttributes.ENUMERABLE)) {
desc.setEnumerable(toBoolean(JSObject.get(obj, JSAttributes.ENUMERABLE)));
}
if (JSObject.hasProperty(obj, JSAttributes.CONFIGURABLE)) {
desc.setConfigurable(toBoolean(JSObject.get(obj, JSAttributes.CONFIGURABLE)));
}
boolean hasValue = JSObject.hasProperty(obj, JSAttributes.VALUE);
if (hasValue) {
desc.setValue(JSObject.get(obj, JSAttributes.VALUE));
}
boolean hasWritable = JSObject.hasProperty(obj, JSAttributes.WRITABLE);
if (hasWritable) {
desc.setWritable(toBoolean(JSObject.get(obj, JSAttributes.WRITABLE)));
}
boolean hasGet = JSObject.hasProperty(obj, JSAttributes.GET);
if (hasGet) {
Object getter = JSObject.get(obj, JSAttributes.GET);
if (!JSRuntime.isCallable(getter) && getter != Undefined.instance) {
throw Errors.createTypeError("Getter must be a function");
}
desc.setGet((DynamicObject) getter);
}
boolean hasSet = JSObject.hasProperty(obj, JSAttributes.SET);
if (hasSet) {
Object setter = JSObject.get(obj, JSAttributes.SET);
if (!JSRuntime.isCallable(setter) && setter != Undefined.instance) {
throw Errors.createTypeError("Setter must be a function");
}
desc.setSet((DynamicObject) setter);
}
if (hasGet || hasSet) {
if (hasValue || hasWritable) {
throw Errors.createTypeError("Invalid property. A property cannot both have accessors and be writable or have a value");
}
}
return desc;
}
public static int valueInRadix10(char c) {
if (isAsciiDigit(c)) {
return c - '0';
}
return -1;
}
public static int valueInRadix(char c, int radix) {
int val = valueInRadixIntl(c);
return val < radix ? val : -1;
}
private static int valueInRadixIntl(char c) {
if (isAsciiDigit(c)) {
return c - '0';
}
if ('a' <= c && c <= 'z') {
return c - 'a' + 10;
}
if ('A' <= c && c <= 'Z') {
return c - 'A' + 10;
}
return -1;
}
public static int valueInHex(char c) {
if (isAsciiDigit(c)) {
return c - '0';
}
if ('a' <= c && c <= 'f') {
return c - 'a' + 10;
}
if ('A' <= c && c <= 'F') {
return c - 'A' + 10;
}
return -1;
}
public static boolean isHex(char c) {
return isAsciiDigit(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
}
@TruffleBoundary
public static long parseArrayIndexRaw(String string) {
long value = 0;
int pos = 0;
int len = string.length();
if (len > 1 && string.charAt(pos) == '0') {
return INVALID_ARRAY_INDEX;
}
while (pos < len) {
char c = string.charAt(pos);
if (!isAsciiDigit(c)) {
return INVALID_ARRAY_INDEX;
}
value *= 10;
value += c - '0';
pos++;
}
return value;
}
public static String trimJSWhiteSpace(String string) {
return trimJSWhiteSpace(string, false);
}
@TruffleBoundary
public static String trimJSWhiteSpace(String string, boolean useLineTerminators) {
int firstIdx = firstNonWhitespaceIndex(string, useLineTerminators);
int lastIdx = lastNonWhitespaceIndex(string, useLineTerminators);
if (firstIdx == 0) {
if ((lastIdx + 1) == string.length()) {
return string;
}
} else if (firstIdx > lastIdx) {
return "";
}
return string.substring(firstIdx, lastIdx + 1);
}
public static int firstNonWhitespaceIndex(String string, boolean useLineTerminators) {
int idx = 0;
while ((idx < string.length()) && (isWhiteSpace(string.charAt(idx)) || (useLineTerminators && isLineTerminator(string.charAt(idx))))) {
idx++;
}
return idx;
}
public static int lastNonWhitespaceIndex(String string, boolean useLineTerminators) {
int idx = string.length() - 1;
while ((idx >= 0) && (isWhiteSpace(string.charAt(idx)) || (useLineTerminators && isLineTerminator(string.charAt(idx))))) {
idx--;
}
return idx;
}
@SuppressWarnings("unused")
public static boolean isWhiteSpace(char cp) {
if (isAsciiDigit(cp)) {
return false;
}
return (0x0009 <= cp && cp <= 0x000D) || (0x2000 <= cp && cp <= 0x200A) || cp == 0x0020 || cp == 0x00A0 || cp == 0x1680 || cp == 0x2028 || cp == 0x2029 || cp == 0x202F ||
cp == 0x205F || cp == 0x3000 || cp == 0xFEFF || (JSConfig.U180EWhitespace && cp == 0x180E);
}
private static boolean isLineTerminator(char codePoint) {
switch (codePoint) {
case 0x000A:
case 0x000D:
case 0x2028:
case 0x2029:
return true;
default:
return false;
}
}
public static boolean isValidArrayLength(long longValue) {
return 0L <= longValue && longValue <= MAX_ARRAY_LENGTH;
}
public static boolean isValidArrayLength(double doubleValue) {
long longValue = (long) doubleValue;
return doubleValue == longValue && isValidArrayLength(longValue);
}
public static boolean isValidArrayLength(int intValue) {
return intValue >= 0;
}
public static boolean isIntegerIndex(long longValue) {
return 0L <= longValue && longValue <= MAX_SAFE_INTEGER_LONG;
}
public static boolean isArrayIndex(int intValue) {
return intValue >= 0;
}
public static boolean isArrayIndex(long longValue) {
return 0L <= longValue && longValue < MAX_ARRAY_LENGTH;
}
public static boolean isArrayIndex(double doubleValue) {
long longValue = (long) doubleValue;
return longValue == doubleValue && isArrayIndex(longValue);
}
public static boolean isArrayIndex(String property) {
long idx = propertyNameToArrayIndex(property);
return isArrayIndex(idx);
}
public static boolean isArrayIndex(Object property) {
if (property instanceof Integer) {
return isArrayIndex((int) property);
} else if (property instanceof Long) {
return isArrayIndex((long) property);
} else if (property instanceof Double) {
return isArrayIndex((double) property);
} else if (isString(property)) {
long idx = propertyNameToArrayIndex(toStringIsString(property));
return isArrayIndex(idx);
} else {
return false;
}
}
public static long castArrayIndex(double doubleValue) {
assert isArrayIndex(doubleValue);
return (long) doubleValue & 0xffff_ffffL;
}
public static long castArrayIndex(long longValue) {
assert isArrayIndex(longValue);
return longValue;
}
public static boolean isAsciiDigit(char c) {
return '0' <= c && c <= '9';
}
@TruffleBoundary
public static long propertyNameToArrayIndex(String propertyName) {
if (propertyName != null && arrayIndexLengthInRange(propertyName)) {
if (isAsciiDigit(propertyName.charAt(0))) {
return parseArrayIndexRaw(propertyName);
}
}
return INVALID_ARRAY_INDEX;
}
public static boolean arrayIndexLengthInRange(String index) {
int len = index.length();
return 0 < len && len <= JSRuntime.MAX_UINT32_DIGITS;
}
public static long propertyKeyToArrayIndex(Object propertyKey) {
return propertyKey instanceof String ? propertyNameToArrayIndex((String) propertyKey) : INVALID_ARRAY_INDEX;
}
@TruffleBoundary
public static long propertyNameToIntegerIndex(String propertyName) {
if (propertyName != null && propertyName.length() > 0 && propertyName.length() <= MAX_INTEGER_INDEX_DIGITS) {
if (isAsciiDigit(propertyName.charAt(0))) {
return parseArrayIndexRaw(propertyName);
}
}
return INVALID_INTEGER_INDEX;
}
public static long propertyKeyToIntegerIndex(Object propertyKey) {
return propertyKey instanceof String ? propertyNameToIntegerIndex((String) propertyKey) : INVALID_INTEGER_INDEX;
}
public static boolean isJSNative(Object value) {
return JSDynamicObject.isJSDynamicObject(value) || isJSPrimitive(value);
}
public static boolean isJSPrimitive(Object value) {
return isNumber(value) || value instanceof BigInt || value instanceof Boolean || isString(value) || value == Undefined.instance || value == Null.instance || value instanceof Symbol;
}
public static boolean isString(Object value) {
return value instanceof String || isLazyString(value);
}
public static String toStringIsString(Object value) {
assert isString(value);
if (value instanceof String) {
return (String) value;
} else {
assert isLazyString(value);
return ((JSLazyString) value).toString();
}
}
public static boolean isLazyString(Object value) {
return value instanceof JSLazyString;
}
public static boolean isStringClass(Class<?> clazz) {
return String.class.isAssignableFrom(clazz) || JSLazyString.class.isAssignableFrom(clazz);
}
public static Object nullToUndefined(Object value) {
return value == null ? Undefined.instance : value;
}
public static Object undefinedToNull(Object value) {
return value == Undefined.instance ? null : value;
}
public static Object toJSNull(Object value) {
return value == null ? Null.instance : value;
}
public static Object toJavaNull(Object value) {
return value == Null.instance ? null : value;
}
@TruffleBoundary
public static Object jsObjectToJavaObject(Object obj) {
if (isLazyString(obj)) {
return obj.toString();
} else {
return toJavaNull(undefinedToNull(obj));
}
}
public static boolean isPropertyKey(Object key) {
return key instanceof String || key instanceof Symbol;
}
public static Object boxIndex(long longIndex, ConditionProfile indexInIntRangeConditionProfile) {
if (indexInIntRangeConditionProfile.profile(longIndex <= Integer.MAX_VALUE)) {
return (int) longIndex;
} else {
return (double) longIndex;
}
}
@TruffleBoundary
public static BigInt stringToBigInt(String s) {
try {
return BigInt.valueOf(s);
} catch (NumberFormatException e) {
return null;
}
}
public static int intValue(Number number) {
if (number instanceof Integer) {
return ((Integer) number).intValue();
}
if (number instanceof Double) {
return ((Double) number).intValue();
}
return intValueVirtual(number);
}
@TruffleBoundary
public static int intValueVirtual(Number number) {
return number.intValue();
}
public static double doubleValue(Number number) {
if (number instanceof Double) {
return ((Double) number).doubleValue();
}
if (number instanceof Integer) {
return ((Integer) number).doubleValue();
}
return doubleValueVirtual(number);
}
public static double doubleValue(Number number, BranchProfile profile) {
if (number instanceof Double) {
return ((Double) number).doubleValue();
}
if (number instanceof Integer) {
return ((Integer) number).doubleValue();
}
profile.enter();
return doubleValueVirtual(number);
}
@TruffleBoundary
public static double doubleValueVirtual(Number number) {
return number.doubleValue();
}
public static float floatValue(Number n) {
if (n instanceof Double) {
return ((Double) n).floatValue();
}
if (n instanceof Integer) {
return ((Integer) n).floatValue();
}
return floatValueVirtual(n);
}
@TruffleBoundary
public static float floatValueVirtual(Number n) {
return n.floatValue();
}
public static long longValue(Number n) {
if (n instanceof Integer) {
return ((Integer) n).longValue();
}
if (n instanceof Double) {
return ((Double) n).longValue();
}
if (n instanceof SafeInteger) {
return ((SafeInteger) n).longValue();
}
return longValueVirtual(n);
}
@TruffleBoundary
private static long longValueVirtual(Number n) {
return n.longValue();
}
public static long toLong(Number value) {
return longValue(value);
}
@TruffleBoundary
public static String stringConcat(String first, String second) {
StringBuilder stringBuilder = new StringBuilder(first.length() + second.length());
stringBuilder.append(first).append(second);
return stringBuilder.toString();
}
@TruffleBoundary
public static DynamicObject fromPropertyDescriptor(PropertyDescriptor desc, JSContext context) {
if (desc == null) {
return Undefined.instance;
}
DynamicObject obj = JSOrdinary.create(context);
if (desc.hasValue()) {
JSObject.set(obj, JSAttributes.VALUE, desc.getValue());
}
if (desc.hasWritable()) {
JSObject.set(obj, JSAttributes.WRITABLE, desc.getWritable());
}
if (desc.hasGet()) {
JSObject.set(obj, JSAttributes.GET, desc.getGet());
}
if (desc.hasSet()) {
JSObject.set(obj, JSAttributes.SET, desc.getSet());
}
if (desc.hasEnumerable()) {
JSObject.set(obj, JSAttributes.ENUMERABLE, desc.getEnumerable());
}
if (desc.hasConfigurable()) {
JSObject.set(obj, JSAttributes.CONFIGURABLE, desc.getConfigurable());
}
return obj;
}
public static Object getArgOrUndefined(Object[] args, int i) {
return args.length > i ? args[i] : Undefined.instance;
}
public static Object getArg(Object[] args, int i, Object defaultValue) {
return args.length > i ? args[i] : defaultValue;
}
public static long getOffset(long start, long length, ConditionProfile profile) {
if (profile.profile(start < 0)) {
return Math.max(start + length, 0);
} else {
return Math.min(start, length);
}
}
public static int getOffset(int start, int length, ConditionProfile profile) {
if (profile.profile(start < 0)) {
return Math.max(start + length, 0);
} else {
return Math.min(start, length);
}
}
@TruffleBoundary
public static long parseSafeInteger(String s) {
return parseSafeInteger(s, 0, s.length(), 10);
}
@TruffleBoundary
public static long parseSafeInteger(String s, int beginIndex, int endIndex, int radix) {
return parseLong(s, beginIndex, endIndex, radix, radix == 10, MAX_SAFE_INTEGER_LONG);
}
private static long parseLong(String s, int beginIndex, int endIndex, int radix, boolean parseSign, long limit) {
assert beginIndex >= 0 && beginIndex <= endIndex && endIndex <= s.length();
assert radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX;
assert limit <= Long.MAX_VALUE / radix - radix;
boolean negative = false;
int i = beginIndex;
if (i >= endIndex) {
return INVALID_SAFE_INTEGER;
}
if (parseSign) {
char firstChar = s.charAt(i);
if (firstChar < '0') {
if (firstChar == '-') {
negative = true;
} else if (firstChar != '+') {
return INVALID_SAFE_INTEGER;
}
i++;
}
if (i >= endIndex) {
return INVALID_SAFE_INTEGER;
}
}
long result = 0;
while (i < endIndex) {
char c = s.charAt(i);
int digit = JSRuntime.valueInRadix(c, radix);
if (digit < 0) {
return INVALID_SAFE_INTEGER;
}
result *= radix;
result += digit;
if (result > limit) {
return INVALID_SAFE_INTEGER;
}
i++;
}
assert result >= 0;
if (negative && result == 0) {
return INVALID_SAFE_INTEGER;
}
return negative ? -result : result;
}
@TruffleBoundary
public static Number parseRawFitsLong(String string, int radix, int startPos, int endPos, boolean negate) {
assert startPos < endPos;
int pos = startPos;
long value = 0;
while (pos < endPos) {
char c = string.charAt(pos);
int cval = JSRuntime.valueInRadix(c, radix);
if (cval < 0) {
if (pos != startPos) {
break;
} else {
return Double.NaN;
}
}
value *= radix;
value += cval;
pos++;
}
if (value == 0 && negate && string.charAt(startPos) == '0') {
return -0.0;
}
assert value >= 0;
long signedValue = negate ? -value : value;
if (value <= Integer.MAX_VALUE) {
return (int) signedValue;
} else {
return (double) signedValue;
}
}
@TruffleBoundary
public static double parseRawDontFitLong(String string, int radix, int startPos, int endPos, boolean negate) {
assert startPos < endPos;
int pos = startPos;
double value = 0;
while (pos < endPos) {
char c = string.charAt(pos);
int cval = JSRuntime.valueInRadix(c, radix);
if (cval < 0) {
if (pos != startPos) {
break;
} else {
return Double.NaN;
}
}
value *= radix;
value += cval;
pos++;
}
assert value >= 0;
return negate ? -value : value;
}
public static boolean createDataProperty(DynamicObject o, Object p, Object v) {
assert JSRuntime.isObject(o);
assert JSRuntime.isPropertyKey(p);
return JSObject.defineOwnProperty(o, p, PropertyDescriptor.createDataDefault(v));
}
public static boolean createDataProperty(DynamicObject o, Object p, Object v, boolean doThrow) {
assert JSRuntime.isObject(o);
assert JSRuntime.isPropertyKey(p);
boolean success = JSObject.defineOwnProperty(o, p, PropertyDescriptor.createDataDefault(v), doThrow);
assert !doThrow || success : "should have thrown";
return success;
}
public static boolean createDataPropertyOrThrow(DynamicObject o, Object p, Object v) {
return createDataProperty(o, p, v, true);
}
public static void definePropertyOrThrow(DynamicObject o, Object key, PropertyDescriptor desc) {
assert JSRuntime.isObject(o);
assert JSRuntime.isPropertyKey(key);
boolean success = JSObject.getJSClass(o).defineOwnProperty(o, key, desc, true);
assert success;
}
public static boolean isPrototypeOf(DynamicObject object, DynamicObject prototype) {
DynamicObject prototypeChainObject = object;
do {
prototypeChainObject = JSObject.getPrototype(prototypeChainObject);
if (prototypeChainObject == prototype) {
return true;
}
} while (prototypeChainObject != Null.instance);
return false;
}
public static DynamicObject createArrayFromList(JSContext context, List<? extends Object> list) {
return JSArray.createConstant(context, Boundaries.listToArray(list));
}
public static boolean isCallable(Object value) {
if (JSFunction.isJSFunction(value)) {
return true;
} else if (JSProxy.isJSProxy(value)) {
return isCallableProxy((DynamicObject) value);
} else if (value instanceof TruffleObject) {
return isCallableForeign(value);
}
return false;
}
public static boolean isCallableIsJSObject(DynamicObject value) {
assert JSDynamicObject.isJSDynamicObject(value);
if (JSFunction.isJSFunction(value)) {
return true;
} else if (JSProxy.isJSProxy(value)) {
return isCallableProxy(value);
}
return false;
}
@TruffleBoundary
public static boolean isCallableForeign(Object value) {
if (isForeignObject(value)) {
InteropLibrary interop = InteropLibrary.getFactory().getUncached();
return interop.isExecutable(value) || interop.isInstantiable(value);
}
return false;
}
@TruffleBoundary
public static boolean isCallableProxy(DynamicObject proxy) {
assert JSProxy.isJSProxy(proxy);
Object target = JSProxy.getTarget(proxy);
return (target == Null.instance) ? isRevokedCallableProxy(proxy) : isCallable(target);
}
public static boolean isRevokedCallableProxy(DynamicObject revokedProxy) {
assert JSProxy.isJSProxy(revokedProxy) && JSProxy.isRevoked(revokedProxy);
return Boolean.TRUE == JSDynamicObject.getOrDefault(revokedProxy, JSProxy.REVOKED_CALLABLE, Boolean.FALSE);
}
public static boolean isArray(Object obj) {
if (JSArray.isJSArray(obj)) {
return true;
} else if (JSProxy.isJSProxy(obj)) {
return isProxyAnArray((DynamicObject) obj);
} else if (isForeignObject(obj)) {
return InteropLibrary.getFactory().getUncached().hasArrayElements(obj);
}
return false;
}
@TruffleBoundary
public static boolean isProxyAnArray(DynamicObject proxy) {
assert JSProxy.isJSProxy(proxy);
if (JSProxy.isRevoked(proxy)) {
throw Errors.createTypeErrorProxyRevoked();
}
return isArrayProxyRecurse(proxy);
}
@TruffleBoundary
private static boolean isArrayProxyRecurse(DynamicObject proxy) {
return isArray(JSProxy.getTarget(proxy));
}
@TruffleBoundary
public static Object toPropertyKey(Object arg) {
if (arg instanceof String) {
return arg;
}
Object key = toPrimitive(arg);
if (key instanceof Symbol) {
return key;
} else if (isString(key)) {
return key.toString();
}
return toString(key);
}
public static Object call(Object fnObj, Object holder, Object[] arguments) {
if (JSFunction.isJSFunction(fnObj)) {
return JSFunction.call((DynamicObject) fnObj, holder, arguments);
} else if (JSProxy.isJSProxy(fnObj)) {
return JSProxy.call((DynamicObject) fnObj, holder, arguments);
} else if (isForeignObject(fnObj)) {
return JSInteropUtil.call(fnObj, arguments);
} else {
throw Errors.createTypeErrorNotAFunction(fnObj);
}
}
public static Object call(Object fnObj, Object holder, Object[] arguments, Node encapsulatingNode) {
EncapsulatingNodeReference encapsulating = null;
Node prev = null;
if (encapsulatingNode != null) {
encapsulating = EncapsulatingNodeReference.getCurrent();
prev = encapsulating.set(encapsulatingNode);
}
try {
return call(fnObj, holder, arguments);
} finally {
if (encapsulatingNode != null) {
encapsulating.set(prev);
}
}
}
public static Object construct(Object fnObj, Object[] arguments) {
if (JSFunction.isJSFunction(fnObj)) {
return JSFunction.construct((DynamicObject) fnObj, arguments);
} else if (JSProxy.isJSProxy(fnObj)) {
return JSProxy.construct((DynamicObject) fnObj, arguments);
} else if (isForeignObject(fnObj)) {
return JSInteropUtil.construct(fnObj, arguments);
} else {
throw Errors.createTypeErrorNotAFunction(fnObj);
}
}
@TruffleBoundary
public static Object canonicalNumericIndexString(String s) {
if (s.isEmpty() || !isNumericIndexStart(s.charAt(0))) {
return Undefined.instance;
}
if ("-0".equals(s)) {
return -0.0;
}
Number n = stringToNumber(s);
if (!numberToString(n).equals(s)) {
return Undefined.instance;
}
return n;
}
private static boolean isNumericIndexStart(char c) {
return isAsciiDigit(c) || c == '-' || c == 'I' || c == 'N';
}
public static boolean isInteger(Object obj) {
if (!JSRuntime.isNumber(obj)) {
return false;
}
double d = doubleValue((Number) obj);
if (Double.isInfinite(d) || Double.isNaN(d)) {
return false;
}
return Math.floor(Math.abs(d)) == Math.abs(d);
}
@TruffleBoundary
public static double mathFloor(double d) {
if (Double.isNaN(d)) {
return Double.NaN;
}
if (JSRuntime.isNegativeZero(d)) {
return -0.0;
}
if (JSRuntime.isSafeInteger(d)) {
long i = (long) d;
return d < i ? i - 1 : i;
} else {
return Math.floor(d);
}
}
@TruffleBoundary
public static double mathCeil(double d) {
if (Double.isNaN(d)) {
return Double.NaN;
}
if (JSRuntime.isNegativeZero(d)) {
return -0.0;
}
if (JSRuntime.isSafeInteger(d)) {
long i = (long) d;
long result = d > i ? i + 1 : i;
if (result == 0 && d < 0) {
return -0.0;
}
return result;
} else {
return Math.ceil(d);
}
}
@TruffleBoundary
public static double mathRint(double d) {
return Math.rint(d);
}
public static int comparePropertyKeys(Object key1, Object key2) {
assert isPropertyKey(key1) && isPropertyKey(key2);
boolean isString1 = key1 instanceof String;
boolean isString2 = key2 instanceof String;
if (isString1 && isString2) {
String str1 = (String) key1;
String str2 = (String) key2;
long index1 = JSRuntime.propertyNameToArrayIndex(str1);
long index2 = JSRuntime.propertyNameToArrayIndex(str2);
boolean isIndex1 = isArrayIndex(index1);
boolean isIndex2 = isArrayIndex(index2);
if (isIndex1 && isIndex2) {
return Long.compare(index1, index2);
} else if (isIndex1) {
return -1;
} else if (isIndex2) {
return 1;
} else {
return 0;
}
} else if (isString1) {
return -1;
} else if (isString2) {
return 1;
} else {
return 0;
}
}
public static String getConstructorName(DynamicObject receiver) {
Object toStringTag = getDataProperty(receiver, Symbol.SYMBOL_TO_STRING_TAG);
if (JSRuntime.isString(toStringTag)) {
return JSRuntime.javaToString(toStringTag);
}
if (!isProxy(receiver)) {
DynamicObject prototype = JSObject.getPrototype(receiver);
if (prototype != Null.instance) {
Object constructor = getDataProperty(prototype, JSObject.CONSTRUCTOR);
if (JSFunction.isJSFunction(constructor)) {
return JSFunction.getName((DynamicObject) constructor);
}
}
}
return JSObject.getClassName(receiver);
}
public static Object getDataProperty(DynamicObject thisObj, Object key) {
assert JSRuntime.isPropertyKey(key);
DynamicObject current = thisObj;
while (current != Null.instance && current != null && !isProxy(current)) {
PropertyDescriptor desc = JSObject.getOwnProperty(current, key);
if (desc != null) {
if (desc.isDataDescriptor()) {
return desc.getValue();
} else {
break;
}
}
current = JSObject.getPrototype(current);
}
return null;
}
private static boolean isProxy(DynamicObject receiver) {
return JSProxy.isJSProxy(receiver) || JSAdapter.isJSAdapter(receiver);
}
public static boolean isJSRootNode(RootNode rootNode) {
return rootNode instanceof JavaScriptRootNode;
}
public static boolean isJSFunctionRootNode(RootNode rootNode) {
return rootNode instanceof JavaScriptRootNode && ((JavaScriptRootNode) rootNode).isFunction();
}
public static boolean isSafeInteger(double value) {
return value >= JSRuntime.MIN_SAFE_INTEGER && value <= JSRuntime.MAX_SAFE_INTEGER;
}
public static boolean isSafeInteger(long value) {
return value >= JSRuntime.MIN_SAFE_INTEGER_LONG && value <= JSRuntime.MAX_SAFE_INTEGER_LONG;
}
public static JSRealm getFunctionRealm(Object obj, JSContext context) {
JSRealm currentRealm = context.getRealm();
return getFunctionRealm(obj, currentRealm);
}
@TruffleBoundary
public static JSRealm getFunctionRealm(Object obj, JSRealm currentRealm) {
if (JSDynamicObject.isJSDynamicObject(obj)) {
DynamicObject dynObj = (DynamicObject) obj;
if (JSFunction.isJSFunction(dynObj)) {
if (JSFunction.isBoundFunction(dynObj)) {
return getFunctionRealm(JSFunction.getBoundTargetFunction(dynObj), currentRealm);
} else {
return JSFunction.getRealm(dynObj);
}
} else if (JSProxy.isJSProxy(dynObj)) {
if (JSProxy.getHandler(dynObj) == Null.instance) {
throw Errors.createTypeErrorProxyRevoked();
}
return getFunctionRealm(JSProxy.getTarget(dynObj), currentRealm);
}
}
return currentRealm;
}
public static boolean isConstructor(Object constrObj) {
if (JSFunction.isConstructor(constrObj)) {
return true;
} else if (JSProxy.isJSProxy(constrObj)) {
return isConstructorProxy((DynamicObject) constrObj);
} else if (constrObj instanceof TruffleObject) {
return isConstructorForeign(constrObj);
}
return false;
}
@TruffleBoundary
public static boolean isConstructorForeign(Object value) {
if (isForeignObject(value)) {
return InteropLibrary.getFactory().getUncached().isInstantiable(value);
}
return false;
}
@TruffleBoundary
public static boolean isConstructorProxy(DynamicObject constrObj) {
assert JSProxy.isJSProxy(constrObj);
return isConstructor(JSProxy.getTarget(constrObj));
}
public static boolean isGenerator(Object genObj) {
if (JSFunction.isJSFunction(genObj) && JSFunction.isGenerator((DynamicObject) genObj)) {
return true;
} else if (JSProxy.isJSProxy(genObj)) {
return isGeneratorProxy((DynamicObject) genObj);
}
return false;
}
@TruffleBoundary
public static boolean isGeneratorProxy(DynamicObject genObj) {
assert JSProxy.isJSProxy(genObj);
return isGenerator(JSProxy.getTarget(genObj));
}
@TruffleBoundary
public static List<Object> createListFromArrayLikeAllowSymbolString(Object obj) {
if (!isObject(obj)) {
throw Errors.createTypeErrorNotAnObject(obj);
}
DynamicObject jsObj = (DynamicObject) obj;
long len = JSRuntime.toLength(JSObject.get(jsObj, JSAbstractArray.LENGTH));
if (len > Integer.MAX_VALUE) {
throw Errors.createRangeError("range exceeded");
}
List<Object> list = new ArrayList<>();
long index = 0;
while (index < len) {
Object next = JSObject.get(jsObj, index);
if (JSRuntime.isLazyString(next)) {
next = next.toString();
}
if (!(next instanceof String || next instanceof Symbol)) {
throw Errors.createTypeError("Symbol or String expected");
}
Boundaries.listAdd(list, next);
index++;
}
return list;
}
@TruffleBoundary
public static String quote(String value) {
int pos = 0;
while (pos < value.length()) {
char ch = value.charAt(pos);
if (ch < ' ' || ch == '\\' || ch == '"') {
break;
}
pos++;
}
StringBuilder builder = new StringBuilder(value.length() + 2);
builder.append('"');
builder.append(value, 0, pos);
for (int i = pos; i < value.length(); i++) {
char ch = value.charAt(i);
if (ch < ' ') {
if (ch == '\b') {
builder.append("\\b");
} else if (ch == '\f') {
builder.append("\\f");
} else if (ch == '\n') {
builder.append("\\n");
} else if (ch == '\r') {
builder.append("\\r");
} else if (ch == '\t') {
builder.append("\\t");
} else {
builder.append("\\u00");
builder.append(Character.forDigit((ch & 0xF0) >> 4, 16));
builder.append(Character.forDigit((ch & 0x0F), 16));
}
} else if (ch == '\\') {
builder.append("\\\\");
} else if (ch == '"') {
builder.append("\\\"");
} else {
builder.append(ch);
}
}
builder.append('"');
return builder.toString();
}
public static DynamicObject expectJSObject(Object to, BranchProfile errorBranch) {
if (!JSDynamicObject.isJSDynamicObject(to)) {
errorBranch.enter();
throw Errors.createTypeErrorJSObjectExpected();
}
return (DynamicObject) to;
}
@TruffleBoundary
public static Object exportValue(Object value) {
if (JSRuntime.isLazyString(value)) {
return value.toString();
} else if (value instanceof SafeInteger) {
return ((SafeInteger) value).doubleValue();
} else if (value instanceof TruffleObject) {
return value;
} else if (JSRuntime.isJSPrimitive(value)) {
return value;
}
TruffleLanguage.Env env = JavaScriptLanguage.getCurrentEnv();
return env.asGuestValue(value);
}
@TruffleBoundary
public static Object[] exportValueArray(Object[] arr) {
Object[] newArr = new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
newArr[i] = exportValue(arr[i]);
}
return newArr;
}
@TruffleBoundary
public static Object importValue(Object value) {
if (value == null) {
return Null.instance;
} else if (value instanceof Integer || value instanceof Double || value instanceof String || value instanceof Boolean || value instanceof TruffleObject) {
return value;
} else if (value instanceof Character) {
return String.valueOf(value);
} else if (value instanceof Long) {
long longValue = (long) value;
if (longIsRepresentableAsInt(longValue)) {
return (int) longValue;
} else {
return longValue;
}
} else if (value instanceof Byte || value instanceof Short) {
return ((Number) value).intValue();
} else if (value instanceof Float) {
return ((Number) value).doubleValue();
} else {
throw Errors.createTypeErrorUnsupportedInteropType(value);
}
}
public static boolean intIsRepresentableAsFloat(int value) {
return (MIN_SAFE_INTEGER_IN_FLOAT <= value && value <= MAX_SAFE_INTEGER_IN_FLOAT);
}
public static boolean isJavaPrimitive(Object value) {
return value != null &&
value instanceof Boolean ||
value instanceof Byte ||
value instanceof Short ||
value instanceof Integer ||
value instanceof Long ||
value instanceof Float ||
value instanceof Double ||
value instanceof Character;
}
@SuppressWarnings("unchecked")
public static <E extends Throwable> RuntimeException rethrow(Throwable ex) throws E {
throw (E) ex;
}
public static boolean isTypedArrayBigIntFactory(TypedArrayFactory factory) {
return factory == TypedArrayFactory.BigInt64Array || factory == TypedArrayFactory.BigUint64Array;
}
public static GraalJSException getException(Object errorObject) {
if (JSError.isJSError(errorObject)) {
return JSError.getException((DynamicObject) errorObject);
} else {
return UserScriptException.create(errorObject);
}
}
}