mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
Add Fiber.try().
This commit is contained in:
@ -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**
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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--)
|
||||
{
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
7
test/fiber/error.wren
Normal 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'.
|
||||
6
test/fiber/is_done_after_error.wren
Normal file
6
test/fiber/is_done_after_error.wren
Normal 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
10
test/fiber/try.wren
Normal 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
|
||||
7
test/fiber/try_direct_reenter.wren
Normal file
7
test/fiber/try_direct_reenter.wren
Normal file
@ -0,0 +1,7 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
fiber.try // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
fiber.call
|
||||
12
test/fiber/try_indirect_reenter.wren
Normal file
12
test/fiber/try_indirect_reenter.wren
Normal 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
|
||||
6
test/fiber/try_when_done.wren
Normal file
6
test/fiber/try_when_done.wren
Normal 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.
|
||||
8
test/fiber/try_without_error.wren
Normal file
8
test/fiber/try_without_error.wren
Normal 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
|
||||
Reference in New Issue
Block a user