package com.oracle.truffle.js.nodes.unary;
import java.util.Set;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.js.nodes.JSGuards;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode.JSConstantNullNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode.JSConstantUndefinedNode;
import com.oracle.truffle.js.nodes.binary.JSEqualNode;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.BinaryOperationTag;
import com.oracle.truffle.js.runtime.BigInt;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.SafeInteger;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.objects.JSLazyString;
@ImportStatic({JSConfig.class})
public abstract class JSIsNullOrUndefinedNode extends JSUnaryNode {
protected static final int MAX_CLASSES = 3;
private final boolean isLeft;
private final boolean isUndefined;
protected JSIsNullOrUndefinedNode(JavaScriptNode operand, boolean isUndefined, boolean isLeft) {
super(operand);
this.isUndefined = isUndefined;
this.isLeft = isLeft;
}
public abstract boolean executeBoolean(Object input);
@Override
public boolean hasTag(Class<? extends Tag> tag) {
if (tag == BinaryOperationTag.class) {
return true;
} else {
return super.hasTag(tag);
}
}
@Override
public InstrumentableNode materializeInstrumentableNodes(Set<Class<? extends Tag>> materializedTags) {
if (materializedTags.contains(BinaryOperationTag.class)) {
JSConstantNode constantNode = isUndefined ? JSConstantNode.createUndefined() : JSConstantNode.createNull();
JavaScriptNode newOperand = cloneUninitialized(getOperand(), materializedTags);
JavaScriptNode left = isLeft ? constantNode : newOperand;
JavaScriptNode right = isLeft ? newOperand : constantNode;
JavaScriptNode materialized = JSEqualNode.createUnoptimized(left, right);
transferSourceSectionAddExpressionTag(this, constantNode);
transferSourceSectionAndTags(this, materialized);
return materialized;
} else {
return this;
}
}
@Specialization(guards = "isJSNull(operand)")
protected static boolean doNull(@SuppressWarnings("unused") Object operand) {
return true;
}
@Specialization(guards = "isUndefined(operand)")
protected static boolean doUndefined(@SuppressWarnings("unused") Object operand) {
return true;
}
@Specialization
protected static boolean doSymbol(@SuppressWarnings("unused") Symbol operand) {
return false;
}
@Specialization
protected static boolean doLazyString(@SuppressWarnings("unused") JSLazyString operand) {
return false;
}
@Specialization
protected static boolean doSafeInteger(@SuppressWarnings("unused") SafeInteger operand) {
return false;
}
@Specialization
protected static boolean doBigInt(@SuppressWarnings("unused") BigInt operand) {
return false;
}
@SuppressWarnings("unused")
@Specialization(guards = {"cachedClass != null", "cachedClass.isInstance(object)"}, limit = "1")
protected static boolean doJSObjectCached(Object object,
@Cached("getClassIfJSObject(object)") Class<?> cachedClass) {
assert !JSGuards.isNullOrUndefined(object);
return false;
}
@Specialization(guards = {"isJSObject(object)"}, replaces = {"doJSObjectCached"})
protected static boolean doJSObject(Object object) {
assert !JSGuards.isNullOrUndefined(object);
return false;
}
@SuppressWarnings("unused")
@Specialization(guards = {"operand != null", "cachedClass != null", "cachedClass == operand.getClass()"}, limit = "MAX_CLASSES")
protected static boolean doJSValueCached(Object operand,
@Cached("getNonTruffleObjectClass(operand)") Class<?> cachedClass) {
return false;
}
@Specialization(guards = {"!isTruffleObject(operand)"}, replaces = {"doJSValueCached"})
protected static boolean doJSValue(@SuppressWarnings("unused") Object operand) {
return false;
}
@Specialization(guards = "isForeignObject(operand)", limit = "InteropLibraryLimit")
protected boolean doForeign(Object operand,
@CachedLibrary("operand") InteropLibrary interop) {
return interop.isNull(operand);
}
public static JSIsNullOrUndefinedNode createFromEquals(JavaScriptNode left, JavaScriptNode right) {
assert isNullOrUndefined(left) || isNullOrUndefined(right);
boolean isLeft = isNullOrUndefined(left);
JavaScriptNode operand = isLeft ? right : left;
JavaScriptNode constant = isLeft ? left : right;
boolean isUndefined = constant instanceof JSConstantUndefinedNode;
return JSIsNullOrUndefinedNodeGen.create(operand, isUndefined, isLeft);
}
public static JSIsNullOrUndefinedNode create(JavaScriptNode value) {
return JSIsNullOrUndefinedNodeGen.create(value, false, false);
}
public static JSIsNullOrUndefinedNode create() {
return JSIsNullOrUndefinedNodeGen.create(null, true, true);
}
private static boolean isNullOrUndefined(JavaScriptNode node) {
return node instanceof JSConstantUndefinedNode || node instanceof JSConstantNullNode;
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return JSIsNullOrUndefinedNodeGen.create(cloneUninitialized(getOperand(), materializedTags), isUndefined, isLeft);
}
}