Get rid of the special "call" primitive result type.

Instead, Fn.call(...) is a special *method* type that has the same
special sauce. The goal is eventually to get rid of the primitive
result type entirely.
This commit is contained in:
Bob Nystrom
2015-10-03 19:04:11 -07:00
parent e41d9eac7c
commit 812bc15380
4 changed files with 94 additions and 87 deletions

View File

@ -68,7 +68,7 @@ DEF_PRIMITIVE(fiber_new)
DEF_PRIMITIVE(fiber_abort)
{
vm->fiber->error = args[1];
// If the error is explicitly null, it's not really an abort.
return IS_NULL(args[1]) ? PRIM_VALUE : PRIM_FIBER;
}
@ -117,7 +117,7 @@ static PrimitiveResult runFiber(WrenVM* vm, ObjFiber* fiber, Value* args,
}
vm->fiber = fiber;
return PRIM_FIBER;
}
@ -133,7 +133,7 @@ DEF_PRIMITIVE(fiber_call1)
DEF_PRIMITIVE(fiber_current)
{
RETURN_OBJ(vm->fiber);
RETURN_OBJ(vm->fiber);
}
DEF_PRIMITIVE(fiber_error)
@ -168,22 +168,22 @@ DEF_PRIMITIVE(fiber_transferError)
{
PrimitiveResult result = runFiber(vm, AS_FIBER(args[0]), args, false, true);
if (result == PRIM_FIBER) vm->fiber->error = args[1];
return result;
}
DEF_PRIMITIVE(fiber_try)
{
ObjFiber* current = vm->fiber;
ObjFiber* current = vm->fiber;
ObjFiber* tried = AS_FIBER(args[0]);
// TODO: Use runFiber().
if (tried->numFrames == 0) RETURN_ERROR("Cannot try a finished fiber.");
if (tried->caller != NULL) RETURN_ERROR("Fiber has already been called.");
vm->fiber = tried;
// Remember who ran it.
vm->fiber->caller = current;
vm->fiber->caller = current;
vm->fiber->callerIsTrying = true;
// If the fiber was yielded, make the yield call return null.
@ -191,36 +191,36 @@ DEF_PRIMITIVE(fiber_try)
{
vm->fiber->stackTop[-1] = NULL_VAL;
}
return PRIM_FIBER;
}
DEF_PRIMITIVE(fiber_yield)
{
ObjFiber* current = vm->fiber;
vm->fiber = current->caller;
ObjFiber* current = vm->fiber;
vm->fiber = current->caller;
// Unhook this fiber from the one that called it.
current->caller = NULL;
current->callerIsTrying = false;
current->caller = NULL;
current->callerIsTrying = false;
if (vm->fiber != NULL)
{
// Make the caller's run method return null.
vm->fiber->stackTop[-1] = NULL_VAL;
}
return PRIM_FIBER;
}
DEF_PRIMITIVE(fiber_yield1)
{
ObjFiber* current = vm->fiber;
vm->fiber = current->caller;
ObjFiber* current = vm->fiber;
vm->fiber = current->caller;
// Unhook this fiber from the one that called it.
current->caller = NULL;
current->callerIsTrying = false;
current->caller = NULL;
current->callerIsTrying = false;
if (vm->fiber != NULL)
{
@ -231,7 +231,7 @@ DEF_PRIMITIVE(fiber_yield1)
// call in its stack. Since Fiber.yield(value) has two arguments (the Fiber
// class and the value) and we only need one slot for the result, discard
// the other slot now.
current->stackTop--;
current->stackTop--;
}
return PRIM_FIBER;
@ -250,41 +250,6 @@ DEF_PRIMITIVE(fn_arity)
RETURN_NUM(AS_FN(args[0])->arity);
}
static PrimitiveResult callFn(WrenVM* vm, Value* args, int numArgs)
{
ObjFn* fn;
if (IS_CLOSURE(args[0]))
{
fn = AS_CLOSURE(args[0])->fn;
}
else
{
fn = AS_FN(args[0]);
}
if (numArgs < fn->arity) RETURN_ERROR("Function expects more arguments.");
return PRIM_CALL;
}
DEF_PRIMITIVE(fn_call0) { return callFn(vm, args, 0); }
DEF_PRIMITIVE(fn_call1) { return callFn(vm, args, 1); }
DEF_PRIMITIVE(fn_call2) { return callFn(vm, args, 2); }
DEF_PRIMITIVE(fn_call3) { return callFn(vm, args, 3); }
DEF_PRIMITIVE(fn_call4) { return callFn(vm, args, 4); }
DEF_PRIMITIVE(fn_call5) { return callFn(vm, args, 5); }
DEF_PRIMITIVE(fn_call6) { return callFn(vm, args, 6); }
DEF_PRIMITIVE(fn_call7) { return callFn(vm, args, 7); }
DEF_PRIMITIVE(fn_call8) { return callFn(vm, args, 8); }
DEF_PRIMITIVE(fn_call9) { return callFn(vm, args, 9); }
DEF_PRIMITIVE(fn_call10) { return callFn(vm, args, 10); }
DEF_PRIMITIVE(fn_call11) { return callFn(vm, args, 11); }
DEF_PRIMITIVE(fn_call12) { return callFn(vm, args, 12); }
DEF_PRIMITIVE(fn_call13) { return callFn(vm, args, 13); }
DEF_PRIMITIVE(fn_call14) { return callFn(vm, args, 14); }
DEF_PRIMITIVE(fn_call15) { return callFn(vm, args, 15); }
DEF_PRIMITIVE(fn_call16) { return callFn(vm, args, 16); }
DEF_PRIMITIVE(fn_toString)
{
RETURN_VAL(CONST_STRING(vm, "<fn>"));
@ -1069,6 +1034,19 @@ static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name)
return classObj;
}
// Defines one of the overloads of the special "call(...)" method on Fn.
//
// These methods have their own unique method type to handle pushing the
// function onto the callstack and checking its arity.
static void fnCall(WrenVM* vm, const char* signature)
{
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, signature,
strlen(signature));
Method method;
method.type = METHOD_FN_CALL;
wrenBindMethod(vm, vm->fnClass, symbol, method);
}
void wrenInitializeCore(WrenVM* vm)
{
ObjModule* coreModule = wrenGetCoreModule(vm);
@ -1153,23 +1131,23 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new);
PRIMITIVE(vm->fnClass, "arity", fn_arity);
PRIMITIVE(vm->fnClass, "call()", fn_call0);
PRIMITIVE(vm->fnClass, "call(_)", fn_call1);
PRIMITIVE(vm->fnClass, "call(_,_)", fn_call2);
PRIMITIVE(vm->fnClass, "call(_,_,_)", fn_call3);
PRIMITIVE(vm->fnClass, "call(_,_,_,_)", fn_call4);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_)", fn_call5);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_)", fn_call6);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_)", fn_call7);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_)", fn_call8);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_)", fn_call9);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_)", fn_call10);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_)", fn_call11);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_)", fn_call12);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call13);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call14);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call15);
PRIMITIVE(vm->fnClass, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)", fn_call16);
fnCall(vm, "call()");
fnCall(vm, "call(_)");
fnCall(vm, "call(_,_)");
fnCall(vm, "call(_,_,_)");
fnCall(vm, "call(_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)");
fnCall(vm, "call(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)");
PRIMITIVE(vm->fnClass, "toString", fn_toString);
vm->nullClass = AS_CLASS(wrenFindVariable(vm, coreModule, "Null"));
@ -1274,7 +1252,7 @@ void wrenInitializeCore(WrenVM* vm)
ObjClass* systemClass = AS_CLASS(wrenFindVariable(vm, coreModule, "System"));
PRIMITIVE(systemClass->obj.classObj, "clock", system_clock);
PRIMITIVE(systemClass->obj.classObj, "writeString_(_)", system_writeString);
// While bootstrapping the core types and running the core library, a number
// of string objects have been created, many of which were instantiated
// before stringClass was stored in the VM. Some of them *must* be created

View File

@ -250,17 +250,14 @@ typedef enum
{
// A normal value has been returned.
PRIM_VALUE,
// A new callframe has been pushed.
PRIM_CALL,
// A fiber is being switched to. Also used for runtime errors (which also
// change which fiber is being executed).
PRIM_FIBER
} PrimitiveResult;
typedef PrimitiveResult (*Primitive)(WrenVM* vm, Value* args);
typedef PrimitiveResult (*Primitive)(WrenVM* vm, Value* args);
// TODO: See if it's actually a perf improvement to have this in a separate
// struct instead of in ObjFn.
@ -352,6 +349,9 @@ typedef enum
// A normal user-defined method.
METHOD_BLOCK,
// The special "call(...)" methods on function.
METHOD_FN_CALL,
// No method for the given symbol.
METHOD_NONE
@ -367,7 +367,7 @@ typedef struct
{
Primitive primitive;
WrenForeignMethodFn foreign;
// May be a [ObjFn] or [ObjClosure].
Obj* obj;
} fn;

View File

@ -387,6 +387,33 @@ static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol)
OBJ_VAL(classObj->name), vm->methodNames.data[symbol].buffer);
}
// Checks that [value], which must be a function or closure, does not require
// more parameters than are provided by [numArgs].
//
// If there are not enough arguments, aborts the current fiber and returns
// `false`.
static bool checkArity(WrenVM* vm, Value value, int numArgs)
{
ObjFn* fn;
if (IS_CLOSURE(value))
{
fn = AS_CLOSURE(value)->fn;
}
else
{
ASSERT(IS_FN(value), "Receiver must be a function or closure.");
fn = AS_FN(value);
}
// We only care about missing arguments, not extras. The "- 1" is because
// numArgs includes the receiver, the function itself, which we don't want to
// count.
if (numArgs - 1 >= fn->arity) return true;
vm->fiber->error = CONST_STRING(vm, "Function expects more arguments.");
return false;
}
// Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already.
// [function] can be an `ObjFn` or `ObjClosure`.
@ -897,7 +924,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
case METHOD_PRIMITIVE:
{
// After calling this, the result will be in the first arg slot.
switch (method->fn.primitive(vm, args))
switch (method->fn.primitive(vm, args))
{
case PRIM_VALUE:
// The result is now in the first arg slot. Discard the other
@ -905,12 +932,6 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
fiber->stackTop -= numArgs - 1;
break;
case PRIM_CALL:
STORE_FRAME();
callFunction(vm, fiber, AS_OBJ(args[0]), numArgs);
LOAD_FRAME();
break;
case PRIM_FIBER:
STORE_FRAME();
@ -929,6 +950,14 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
case METHOD_FOREIGN:
callForeign(vm, fiber, method->fn.foreign, numArgs);
break;
case METHOD_FN_CALL:
if (!checkArity(vm, args[0], numArgs)) RUNTIME_ERROR();
STORE_FRAME();
callFunction(vm, fiber, AS_OBJ(args[0]), numArgs);
LOAD_FRAME();
break;
case METHOD_BLOCK:
STORE_FRAME();

View File

@ -1,2 +1,2 @@
var f2 = Fn.new {|a, b| System.print(a, b) }
var f2 = Fn.new {|a, b| System.print(a + b) }
f2.call("a") // expect runtime error: Function expects more arguments.