Add Fiber.try().

This commit is contained in:
Bob Nystrom
2014-10-15 06:36:42 -07:00
parent 24967ed4eb
commit f6cbb6ad75
13 changed files with 162 additions and 37 deletions

View File

@ -17,7 +17,7 @@ This creates a class named `Unicorn` with no methods or fields.
## Methods
Once we've made a unicorn, to let it do stuff, we need to give it methods.
To let our unicorn do stuff, we need to give it methods.
:::dart
class Unicorn {
@ -35,7 +35,7 @@ This defines a `prance` method that takes no arguments. To support parameters, a
}
}
Unlike most other dynamically-typed languages, in Wren you can have multiple methods in a class with the same name, as long as they take a different number of parameters. In technical terms, you can overload by *arity*. So this class is fine:
Unlike most other dynamically-typed languages, in Wren you can have multiple methods in a class with the same name, as long as they take a different number of parameters. In technical terms, you can *overload by arity*. So this class is fine:
:::dart
class Unicorn {
@ -118,7 +118,6 @@ Operator overloading is really useful for types like vectors and complex numbers
## Setters
[Assignment](variables.html) *cannot* be overloaded. It isn't an operator, and its semantics are built right into the language.
**TODO: ...**
@ -143,8 +142,29 @@ This declares a new class `Pegasus` that inherits from `Unicorn`.
The metaclass hierarchy does *not* parallel the regular class hierarchy. So, if `Pegasus` inherits from `Unicorn`, `Pegasus`'s metaclass will not inherit from `Unicorn`'s metaclass. In more prosaic terms, this means that static methods are not inherited.
:::dart
class Unicorn {
// Unicorns cannot fly. :(
static canFly { false }
}
class Pegasus is Unicorn {}
Pegasus.canFly // ERROR: Static methods are not inherited.
Constructors, however, initialize the instance *after* it has been created. They are defined as instance methods on the class and not on the metaclass. That means that constructors *are* inherited.
:::dart
class Unicorn {
new(name) {
IO.print("My name is " + name + ".")
}
}
class Pegasus is Unicorn {}
new Pegasus("Fred") // Prints "My name is Fred.".
## Superclass method calls
**TODO**

View File

@ -269,10 +269,17 @@ DEF_NATIVE(fiber_call1)
return PRIM_RUN_FIBER;
}
DEF_NATIVE(fiber_error)
{
ObjFiber* runFiber = AS_FIBER(args[0]);
if (runFiber->error == NULL) RETURN_NULL;
RETURN_OBJ(runFiber->error);
}
DEF_NATIVE(fiber_isDone)
{
ObjFiber* runFiber = AS_FIBER(args[0]);
RETURN_BOOL(runFiber->numFrames == 0);
RETURN_BOOL(runFiber->numFrames == 0 || runFiber->error != NULL);
}
DEF_NATIVE(fiber_run)
@ -320,12 +327,33 @@ DEF_NATIVE(fiber_run1)
return PRIM_RUN_FIBER;
}
DEF_NATIVE(fiber_try)
{
ObjFiber* runFiber = AS_FIBER(args[0]);
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot try a finished fiber.");
if (runFiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
// Remember who ran it.
runFiber->caller = fiber;
runFiber->callerIsTrying = true;
// If the fiber was yielded, make the yield call return null.
if (runFiber->stackSize > 0)
{
runFiber->stack[runFiber->stackSize - 1] = NULL_VAL;
}
return PRIM_RUN_FIBER;
}
DEF_NATIVE(fiber_yield)
{
if (fiber->caller == NULL) RETURN_ERROR("No fiber to yield to.");
ObjFiber* caller = fiber->caller;
fiber->caller = NULL;
fiber->callerIsTrying = false;
// Make the caller's run method return null.
caller->stack[caller->stackSize - 1] = NULL_VAL;
@ -341,6 +369,7 @@ DEF_NATIVE(fiber_yield1)
ObjFiber* caller = fiber->caller;
fiber->caller = NULL;
fiber->callerIsTrying = false;
// Make the caller's run method return the argument passed to yield.
caller->stack[caller->stackSize - 1] = args[1];
@ -1027,9 +1056,11 @@ void wrenInitializeCore(WrenVM* vm)
NATIVE(vm->fiberClass->metaclass, "yield ", fiber_yield1);
NATIVE(vm->fiberClass, "call", fiber_call);
NATIVE(vm->fiberClass, "call ", fiber_call1);
NATIVE(vm->fiberClass, "error", fiber_error);
NATIVE(vm->fiberClass, "isDone", fiber_isDone);
NATIVE(vm->fiberClass, "run", fiber_run);
NATIVE(vm->fiberClass, "run ", fiber_run1);
NATIVE(vm->fiberClass, "try", fiber_try);
vm->fnClass = defineClass(vm, "Fn");

View File

@ -4,7 +4,7 @@
void wrenDebugPrintStackTrace(WrenVM* vm, ObjFiber* fiber)
{
fprintf(stderr, "%s\n", fiber->error);
fprintf(stderr, "%s\n", fiber->error->value);
for (int i = fiber->numFrames - 1; i >= 0; i--)
{

View File

@ -140,6 +140,7 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
fiber->openUpvalues = NULL;
fiber->caller = NULL;
fiber->error = NULL;
fiber->callerIsTrying = false;
CallFrame* frame = &fiber->frames[0];
frame->fn = fn;
@ -512,6 +513,8 @@ static void markFiber(WrenVM* vm, ObjFiber* fiber)
// The caller.
if (fiber->caller != NULL) markFiber(vm, fiber->caller);
if (fiber->error != NULL) markString(vm, fiber->error);
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjFiber);
// TODO: Count size of error message buffer.
@ -586,13 +589,6 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
wrenMethodBufferClear(vm, &((ObjClass*)obj)->methods);
break;
case OBJ_FIBER:
{
ObjFiber* fiber = ((ObjFiber*)obj);
if (fiber->error != NULL) wrenReallocate(vm, fiber->error, 0, 0);
break;
}
case OBJ_FN:
{
ObjFn* fn = (ObjFn*)obj;
@ -613,6 +609,7 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
break;
case OBJ_CLOSURE:
case OBJ_FIBER:
case OBJ_INSTANCE:
case OBJ_RANGE:
case OBJ_UPVALUE:

View File

@ -94,6 +94,14 @@ typedef struct
DECLARE_BUFFER(Value, Value);
typedef struct
{
Obj obj;
char* value;
// TODO: Flexible array.
} ObjString;
// The dynamically allocated data structure for a variable that has been used
// by a closure. Whenever a function accesses a variable declared in an
// enclosing function, it will get to it through this.
@ -160,7 +168,12 @@ typedef struct sObjFiber
// If the fiber failed because of a runtime error, this will contain the
// error message. Otherwise, it will be NULL.
char* error;
ObjString* error;
// This will be true if the caller that called this fiber did so using "try".
// In that case, if this fiber fails with an error, the error will be given
// to the caller.
bool callerIsTrying;
} ObjFiber;
typedef enum
@ -179,14 +192,6 @@ typedef enum
} PrimitiveResult;
typedef struct
{
Obj obj;
char* value;
// TODO: Flexible array.
} ObjString;
typedef PrimitiveResult (*Primitive)(WrenVM* vm, ObjFiber* fiber, Value* args);
// TODO: See if it's actually a perf improvement to have this in a separate

View File

@ -303,19 +303,30 @@ static void callForeign(WrenVM* vm, ObjFiber* fiber,
}
// Puts [fiber] into a runtime failed state because of [error].
static void runtimeError(WrenVM* vm, ObjFiber* fiber, const char* error)
//
// Returns the fiber that should receive the error or `NULL` if no fiber
// caught it.
static ObjFiber* runtimeError(WrenVM* vm, ObjFiber* fiber, const char* error)
{
// Copy the error onto the heap.
size_t length = strlen(error) + 1;
char* heapError = wrenReallocate(vm, NULL, 0, length);
strncpy(heapError, error, length);
ASSERT(fiber->error == NULL, "Can only fail once.");
fiber->error = heapError;
// TODO: If the calling fiber is going to handle the error, we shouldn't dump
// a stack trace.
// Store the error in the fiber so it can be accessed later.
fiber->error = AS_STRING(wrenNewString(vm, error, strlen(error)));
// If the caller ran this fiber using "try", give it the error.
if (fiber->callerIsTrying)
{
ObjFiber* caller = fiber->caller;
// Make the caller's try method return the error message.
caller->stack[caller->stackSize - 1] = OBJ_VAL(fiber->error);
return caller;
}
// If we got here, nothing caught the error, so show the stack trace.
wrenDebugPrintStackTrace(vm, fiber);
return NULL;
}
// Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs]
@ -384,11 +395,16 @@ static bool runInterpreter(WrenVM* vm)
upvalues = ((ObjClosure*)frame->fn)->upvalues; \
}
// Terminates the current fiber with error string [error]. If another calling
// fiber is willing to catch the error, transfers control to it, otherwise
// exits the interpreter.
#define RUNTIME_ERROR(error) \
do { \
STORE_FRAME(); \
runtimeError(vm, fiber, error); \
return false; \
fiber = runtimeError(vm, fiber, error); \
if (fiber == NULL) return false; \
LOAD_FRAME(); \
DISPATCH(); \
} \
while (false)
@ -953,6 +969,11 @@ static bool runInterpreter(WrenVM* vm)
ObjClass* classObj = wrenNewClass(vm, superclass, numFields, name);
// Don't pop the superclass and name off the stack until the subclass is
// done being created, to make sure it doesn't get collected.
DROP();
DROP();
// Now that we know the total number of fields, make sure we don't
// overflow.
if (superclass->numFields + numFields > MAX_FIELDS)
@ -964,11 +985,6 @@ static bool runInterpreter(WrenVM* vm)
RUNTIME_ERROR(message);
}
// Don't pop the superclass and name off the stack until the subclass is
// done being created, to make sure it doesn't get collected.
DROP();
DROP();
PUSH(OBJ_VAL(classObj));
DISPATCH();
}

7
test/fiber/error.wren Normal file
View File

@ -0,0 +1,7 @@
var fiber = new Fiber {
"s".unknown
}
IO.print(fiber.error) // expect: null
IO.print(fiber.try) // expect: String does not implement method 'unknown'.
IO.print(fiber.error) // expect: String does not implement method 'unknown'.

View File

@ -0,0 +1,6 @@
var fiber = new Fiber {
"s".unknown
}
fiber.try
IO.print(fiber.isDone) // expect: true

10
test/fiber/try.wren Normal file
View File

@ -0,0 +1,10 @@
var fiber = new Fiber {
IO.print("before")
true.unknownMethod
IO.print("after")
}
IO.print(fiber.try)
// expect: before
// expect: Bool does not implement method 'unknownMethod'.
IO.print("after try") // expect: after try

View File

@ -0,0 +1,7 @@
var fiber
fiber = new Fiber {
fiber.try // expect runtime error: Fiber has already been called.
}
fiber.call

View File

@ -0,0 +1,12 @@
var a
var b
a = new Fiber {
b.try // expect runtime error: Fiber has already been called.
}
b = new Fiber {
a.call
}
b.call

View File

@ -0,0 +1,6 @@
var fiber = new Fiber {
IO.print("try")
}
fiber.try // expect: try
fiber.try // expect runtime error: Cannot try a finished fiber.

View File

@ -0,0 +1,8 @@
var fiber = new Fiber {
IO.print("fiber")
}
IO.print("before") // expect: before
IO.print(fiber.try) // expect: fiber
// expect: null
IO.print("after") // expect: after