package com.oracle.truffle.js.nodes;
import java.util.Set;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.NodeLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.nodes.function.BlockScopeNode;
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.SafeInteger;
import com.oracle.truffle.js.runtime.interop.ScopeVariables;
@GenerateWrapper
@ExportLibrary(NodeLibrary.class)
public abstract class JavaScriptNode extends JavaScriptBaseNode implements InstrumentableNode {
private Object source;
private int charIndex;
private int charLength;
private static final int STATEMENT_TAG_BIT = 1 << 31;
private static final int CALL_TAG_BIT = 1 << 30;
private static final int CHAR_LENGTH_MASK = ~(STATEMENT_TAG_BIT | CALL_TAG_BIT);
private static final int ROOT_BODY_TAG_BIT = 1 << 31;
private static final int EXPRESSION_TAG_BIT = 1 << 30;
private static final int CHAR_INDEX_MASK = ~(ROOT_BODY_TAG_BIT | EXPRESSION_TAG_BIT);
protected static final String INTERMEDIATE_VALUE = "(intermediate value)";
protected JavaScriptNode() {
}
protected JavaScriptNode(SourceSection sourceSection) {
setSourceSection(sourceSection);
}
@Override
public boolean isInstrumentable() {
return hasSourceSection();
}
@Override
public WrapperNode createWrapper(ProbeNode probe) {
return new JavaScriptNodeWrapper(this, probe);
}
public abstract Object execute(VirtualFrame frame);
public int executeInt(VirtualFrame frame) throws UnexpectedResultException {
Object o = execute(frame);
if (o instanceof Integer) {
return (int) o;
} else {
throw new UnexpectedResultException(o);
}
}
public double executeDouble(VirtualFrame frame) throws UnexpectedResultException {
Object o = execute(frame);
if (o instanceof Double) {
return (double) o;
} else {
throw new UnexpectedResultException(o);
}
}
public boolean executeBoolean(VirtualFrame frame) throws UnexpectedResultException {
Object o = execute(frame);
if (o instanceof Boolean) {
return (boolean) o;
} else {
throw new UnexpectedResultException(o);
}
}
public String executeString(VirtualFrame frame) throws UnexpectedResultException {
return JSTypesGen.expectString(execute(frame));
}
public long executeLong(VirtualFrame frame) throws UnexpectedResultException {
return JSTypesGen.expectLong(execute(frame));
}
public SafeInteger executeSafeInteger(VirtualFrame frame) throws UnexpectedResultException {
return JSTypesGen.expectSafeInteger(execute(frame));
}
public void executeVoid(VirtualFrame frame) {
execute(frame);
}
@Override
public JavaScriptNode copy() {
CompilerAsserts.neverPartOfCompilation("cannot call JavaScriptNode.copy() in compiled code");
return (JavaScriptNode) super.copy();
}
@Override
public String toString() {
CompilerAsserts.neverPartOfCompilation("cannot call JavaScriptNode.toString() in compiled code");
String simpleName = getClass().getName().substring(getClass().getName().lastIndexOf('.') + 1);
StringBuilder sb = new StringBuilder(simpleName);
sb.append('@').append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" ").append(JSNodeUtil.formatSourceSection(this));
String tagsString = JSNodeUtil.formatTags(this);
if (!tagsString.isEmpty()) {
sb.append("[").append(tagsString).append("]");
}
RootNode rootNode = getRootNode();
if (rootNode != null) {
sb.append(" '").append(JSNodeUtil.resolveName(rootNode)).append("'");
}
String expressionString = expressionToString();
if (expressionString != null) {
sb.append(" (").append(expressionString).append(")");
}
return sb.toString();
}
@Override
protected void onReplace(Node newNode, CharSequence reason) {
super.onReplace(newNode, reason);
transferSourceSectionAndTags(this, (JavaScriptNode) newNode);
}
public static void transferSourceSectionAndTags(JavaScriptNode fromNode, JavaScriptNode toNode) {
if (!toNode.hasSourceSection() && fromNode.hasSourceSection()) {
toNode.source = fromNode.source;
toNode.charIndex = fromNode.charIndex | (toNode.charIndex & ~CHAR_INDEX_MASK);
toNode.charLength = fromNode.charLength | (toNode.charLength & ~CHAR_LENGTH_MASK);
}
}
public static void transferSourceSectionAddExpressionTag(JavaScriptNode fromNode, JavaScriptNode toNode) {
if (!toNode.hasSourceSection() && fromNode.hasSourceSection()) {
toNode.source = fromNode.source;
toNode.charIndex = fromNode.charIndex & CHAR_INDEX_MASK;
toNode.charLength = fromNode.charLength & CHAR_LENGTH_MASK;
toNode.addExpressionTag();
}
}
public static void transferSourceSection(JavaScriptNode fromNode, JavaScriptNode toNode) {
if (!toNode.hasSourceSection() && fromNode.hasSourceSection()) {
toNode.source = fromNode.source;
toNode.charIndex = fromNode.charIndex & CHAR_INDEX_MASK;
toNode.charLength = fromNode.charLength & CHAR_LENGTH_MASK;
}
}
public final boolean hasSourceSection() {
return source != null;
}
@Override
public final SourceSection getSourceSection() {
if (hasSourceSection()) {
Object src = source;
if (src instanceof SourceSection) {
return (SourceSection) src;
} else {
SourceSection section = ((Source) src).createSection(charIndex & CHAR_INDEX_MASK, charLength & CHAR_LENGTH_MASK);
source = section;
return section;
}
}
return null;
}
public final void setSourceSection(SourceSection section) {
CompilerAsserts.neverPartOfCompilation();
if (hasSourceSection()) {
checkSameSourceSection(section);
}
this.source = section;
}
public final void setSourceSection(Source source, int charIndex, int charLength) {
CompilerAsserts.neverPartOfCompilation();
checkValidSourceSection(source, charIndex, charLength);
if (hasSourceSection()) {
checkSameSourceSection(source.createSection(charIndex, charLength));
}
assert charIndex <= CHAR_INDEX_MASK && charLength <= CHAR_LENGTH_MASK;
this.charIndex = charIndex | (this.charIndex & ~CHAR_INDEX_MASK);
this.charLength = charLength | (this.charLength & ~CHAR_LENGTH_MASK);
this.source = source;
}
private static void checkValidSourceSection(Source source, int charIndex, int charLength) {
if (charIndex < 0) {
throw new IllegalArgumentException("charIndex < 0");
} else if (charLength < 0) {
throw new IllegalArgumentException("length < 0");
}
assert charIndex + charLength <= source.getCharacters().length();
}
private void checkSameSourceSection(SourceSection newSection) {
SourceSection sourceSection = getSourceSection();
if (sourceSection != null && !sourceSection.equals(newSection)) {
throw new IllegalStateException(String.format("Source section is already assigned. Old: %s, new: %s", sourceSection, newSection));
}
}
public boolean isResultAlwaysOfType(@SuppressWarnings("unused") Class<?> clazz) {
return false;
}
@Override
public boolean hasTag(Class<? extends Tag> tag) {
if (tag == StandardTags.StatementTag.class) {
return (charLength & STATEMENT_TAG_BIT) != 0;
} else if (tag == StandardTags.CallTag.class) {
return (charLength & CALL_TAG_BIT) != 0;
} else if (tag == StandardTags.RootBodyTag.class) {
return (charIndex & ROOT_BODY_TAG_BIT) != 0;
} else if (tag == StandardTags.ExpressionTag.class) {
return (charIndex & EXPRESSION_TAG_BIT) != 0;
} else {
return false;
}
}
public final void addStatementTag() {
charLength |= STATEMENT_TAG_BIT;
}
public final void addCallTag() {
charLength |= CALL_TAG_BIT;
}
public final void addRootBodyTag() {
charIndex |= ROOT_BODY_TAG_BIT;
}
public final void addExpressionTag() {
charIndex |= EXPRESSION_TAG_BIT;
}
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
if (this instanceof WrapperNode) {
WrapperNode wrapperNode = (WrapperNode) this;
return cloneUninitialized((JavaScriptNode) wrapperNode.getDelegateNode(), materializedTags);
}
throw Errors.notImplemented(getClass().getSimpleName() + ".copyUninitialized()");
}
@SuppressWarnings("unchecked")
public static <T extends JavaScriptNode> T cloneUninitialized(T node, Set<Class<? extends Tag>> materializedTags) {
if (node == null) {
return null;
} else {
T copy = node;
if (materializedTags != null && node.isInstrumentable()) {
copy = (T) node.materializeInstrumentableNodes(materializedTags);
}
if (node == copy) {
copy = (T) node.copyUninitialized(materializedTags);
assert copy.getClass() == node.getClass() || node instanceof JSBuiltinNode || node instanceof WrapperNode : node.getClass() + " => " + copy.getClass();
transferSourceSectionAndTags(node, copy);
}
return copy;
}
}
public static <T extends JavaScriptNode> T[] cloneUninitialized(T[] nodeArray, Set<Class<? extends Tag>> materializedTags) {
if (nodeArray == null) {
return null;
} else {
T[] copy = nodeArray.clone();
for (int i = 0; i < copy.length; i++) {
copy[i] = cloneUninitialized(copy[i], materializedTags);
}
return copy;
}
}
public void removeSourceSection() {
this.source = null;
}
public String expressionToString() {
return null;
}
@ExportMessage
boolean accepts(@Cached(value = "this", adopt = false) JavaScriptNode cachedNode) {
return this == cachedNode;
}
@ExportMessage
final boolean hasScope(@SuppressWarnings("unused") Frame frame) {
return this.getParent() != null;
}
@ExportMessage
final Object getScope(Frame frame, boolean nodeEnter,
@Cached(value = "findBlockScopeNode(this)", allowUncached = true, adopt = false) Node block) throws UnsupportedMessageException {
if (hasScope(frame)) {
return new ScopeVariables(frame, nodeEnter, block);
} else {
throw UnsupportedMessageException.create();
}
}
@ExportMessage
@SuppressWarnings("static-method")
final boolean hasReceiverMember(Frame frame) {
return frame != null;
}
@ExportMessage
@SuppressWarnings("static-method")
final Object getReceiverMember(Frame frame) throws UnsupportedMessageException {
if (frame == null) {
throw UnsupportedMessageException.create();
}
return ScopeVariables.RECEIVER_MEMBER;
}
@ExportMessage
@SuppressWarnings("static-method")
boolean hasRootInstance(Frame frame) {
return frame != null;
}
@ExportMessage
@SuppressWarnings("static-method")
Object getRootInstance(Frame frame) throws UnsupportedMessageException {
if (frame == null) {
throw UnsupportedMessageException.create();
}
return JSFrameUtil.getFunctionObject(frame);
}
@TruffleBoundary
public static Node findBlockScopeNode(Node node) {
if (node == null) {
return null;
}
Node parent = node;
for (Node n = parent; n != null; parent = n, n = n.getParent()) {
if (n instanceof BlockScopeNode) {
return n;
}
}
assert parent instanceof RootNode : "Node " + node + " is not adopted.";
return parent;
}
}