package com.oracle.truffle.js.nodes.function;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
import com.oracle.truffle.js.nodes.function.DefineMethodNodeFactory.FunctionCreateNodeGen;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSFunctionFactory;
public class DefineMethodNode extends JavaScriptBaseNode {
private final JSFunctionData functionData;
@Child private FunctionCreateNode functionCreateNode;
@Child private PropertySetNode makeMethodNode;
protected DefineMethodNode(JSContext context, JSFunctionData functionData) {
this.functionData = functionData;
this.functionCreateNode = FunctionCreateNode.create(context, functionData);
this.makeMethodNode = PropertySetNode.createSetHidden(JSFunction.HOME_OBJECT_ID, context);
}
public static DefineMethodNode create(JSContext context, JSFunctionExpressionNode functionExpressionNode) {
return new DefineMethodNode(context, functionExpressionNode.functionData);
}
public JSFunctionData getFunctionData() {
return functionData;
}
public DynamicObject execute(VirtualFrame frame, DynamicObject homeObject, DynamicObject functionPrototype) {
assert JSRuntime.isObject(functionPrototype);
assert JSRuntime.isObject(homeObject);
DynamicObject closure = functionCreateNode.executeWithPrototype(frame, functionPrototype);
makeMethodNode.setValue(closure, homeObject);
return closure;
}
protected abstract static class FunctionCreateNode extends JavaScriptBaseNode {
private final JSFunctionData functionData;
@Child private InitFunctionNode initFunctionNode;
protected FunctionCreateNode(JSContext context, JSFunctionData functionData) {
assert context == functionData.getContext();
this.functionData = functionData;
this.initFunctionNode = InitFunctionNode.create(functionData);
}
public static FunctionCreateNode create(JSContext context, JSFunctionData functionData) {
return FunctionCreateNodeGen.create(context, functionData);
}
public abstract DynamicObject executeWithPrototype(VirtualFrame frame, Object prototype);
@SuppressWarnings("unused")
@Specialization(guards = {"!getContext().isMultiContext()", "prototype == cachedPrototype", "isJSObject(cachedPrototype)"}, limit = "getContext().getPropertyCacheLimit()")
protected final DynamicObject doCached(VirtualFrame frame, DynamicObject prototype,
@Cached("prototype") DynamicObject cachedPrototype,
@Cached("makeFactory(prototype)") JSFunctionFactory factory) {
return makeFunction(frame, factory, cachedPrototype);
}
@Specialization(guards = {"!getContext().isMultiContext()", "isJSObject(prototype)"}, replaces = "doCached")
protected final DynamicObject doUncached(VirtualFrame frame, DynamicObject prototype) {
JSFunctionFactory factory = makeFactory(prototype);
return makeFunction(frame, factory, prototype);
}
@Specialization(guards = {"getContext().isMultiContext()", "isJSObject(prototype)"})
protected final DynamicObject doMultiContext(VirtualFrame frame, DynamicObject prototype,
@Cached("makeFactoryMultiContext()") JSFunctionFactory factory) {
return makeFunction(frame, factory, prototype);
}
@TruffleBoundary
protected final JSFunctionFactory makeFactory(DynamicObject prototype) {
return JSFunctionFactory.create(getContext(), prototype);
}
protected final JSFunctionFactory makeFactoryMultiContext() {
return makeFactory(null);
}
protected final DynamicObject makeFunction(VirtualFrame frame, JSFunctionFactory factory, DynamicObject prototype) {
MaterializedFrame enclosingFrame = functionData.needsParentFrame() ? frame.materialize() : JSFrameUtil.NULL_MATERIALIZED_FRAME;
JSRealm realm = getContext().getRealm();
DynamicObject function = factory.createWithPrototype(functionData, enclosingFrame, JSFunction.CLASS_PROTOTYPE_PLACEHOLDER, realm, prototype);
initFunctionNode.execute(function);
return function;
}
final JSContext getContext() {
return functionData.getContext();
}
@Specialization(guards = "!isJSObject(prototype)")
protected final DynamicObject doNonObject(@SuppressWarnings("unused") Object prototype) {
throw Errors.createTypeError("functionPrototype not an object", this);
}
}
}