package com.oracle.truffle.js.nodes.access;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Executed;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.nodes.unary.IsCallableNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.IteratorRecord;
import com.oracle.truffle.js.runtime.util.SimpleArrayList;
import java.util.Set;
@ImportStatic(JSInteropUtil.class)
public abstract class GetIteratorNode extends JavaScriptNode {
@Child @Executed protected JavaScriptNode objectNode;
@Child private GetMethodNode getIteratorMethodNode;
@Child protected PropertyGetNode getNextMethodNode;
private final BranchProfile errorBranch = BranchProfile.create();
protected final JSContext context;
protected GetIteratorNode(JSContext context, JavaScriptNode objectNode) {
this.context = context;
this.objectNode = objectNode;
this.getNextMethodNode = PropertyGetNode.create(JSRuntime.NEXT, context);
}
public static GetIteratorNode create(JSContext context) {
return create(context, null);
}
public static GetIteratorNode create(JSContext context, JavaScriptNode iteratedObject) {
return GetIteratorNodeGen.create(context, iteratedObject);
}
public static GetIteratorNode createAsync(JSContext context, JavaScriptNode iteratedObject) {
return GetAsyncIteratorNodeGen.create(context, iteratedObject);
}
protected JSContext getContext() {
return context;
}
@Specialization(guards = {"!isForeignObject(iteratedObject)"})
protected IteratorRecord doGetIterator(Object iteratedObject,
@Cached("create()") IsCallableNode isCallableNode,
@Cached("createCall()") JSFunctionCallNode methodCallNode,
@Cached("create()") IsJSObjectNode isObjectNode) {
Object method = getIteratorMethodNode().executeWithTarget(iteratedObject);
return getIterator(iteratedObject, method, isCallableNode, methodCallNode, isObjectNode);
}
protected final IteratorRecord getIterator(Object iteratedObject, Object method, IsCallableNode isCallableNode, JSFunctionCallNode methodCallNode, IsJSObjectNode isObjectNode) {
if (!isCallableNode.executeBoolean(method)) {
errorBranch.enter();
throw Errors.createTypeErrorNotIterable(iteratedObject, this);
}
return getIterator(iteratedObject, method, methodCallNode, isObjectNode, getNextMethodNode, this);
}
public static IteratorRecord getIterator(Object iteratedObject, Object method, JSFunctionCallNode methodCallNode, IsJSObjectNode isObjectNode, PropertyGetNode getNextMethodNode,
JavaScriptBaseNode origin) {
Object iterator = methodCallNode.executeCall(JSArguments.createZeroArg(iteratedObject, method));
if (isObjectNode.executeBoolean(iterator)) {
return IteratorRecord.create((DynamicObject) iterator, getNextMethodNode.getValue(iterator), false);
} else {
throw Errors.createTypeErrorNotAnObject(iterator, origin);
}
}
@Specialization(guards = {"isForeignObject(iteratedObject)"})
protected IteratorRecord doForeignIterable(Object iteratedObject,
@Cached("createEnumerateValues()") EnumerateNode enumerateNode) {
DynamicObject iterator = enumerateNode.execute(iteratedObject);
return IteratorRecord.create(iterator, getNextMethodNode.getValue(iterator), false);
}
protected EnumerateNode createEnumerateValues() {
return EnumerateNode.create(getContext(), true, true);
}
@Override
public abstract IteratorRecord execute(VirtualFrame frame);
public abstract IteratorRecord execute(Object iteratedObject);
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return GetIteratorNodeGen.create(getContext(), cloneUninitialized(objectNode, materializedTags));
}
protected GetMethodNode getIteratorMethodNode() {
if (getIteratorMethodNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getIteratorMethodNode = insert(GetMethodNode.create(context, null, Symbol.SYMBOL_ITERATOR));
}
return getIteratorMethodNode;
}
public static SimpleArrayList<Object> iterableToList(Object object, Object usingIterator, JSFunctionCallNode iteratorCallNode, IsJSObjectNode isObjectNode,
IteratorStepNode iteratorStepNode, IteratorValueNode getIteratorValueNode, PropertyGetNode getNextMethodNode, JavaScriptBaseNode origin, BranchProfile growProfile) {
SimpleArrayList<Object> values = new SimpleArrayList<>();
IteratorRecord iterator = GetIteratorNode.getIterator(object, usingIterator, iteratorCallNode, isObjectNode, getNextMethodNode, origin);
while (true) {
Object next = iteratorStepNode.execute(iterator);
if (next == Boolean.FALSE) {
break;
}
Object nextValue = getIteratorValueNode.execute((DynamicObject) next);
values.add(nextValue, growProfile);
}
return values;
}
}