mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
Symmetric coroutines!
This commit is contained in:
@ -17,7 +17,7 @@ EXPECT_ERROR_PATTERN = re.compile(r'// expect error')
|
||||
EXPECT_ERROR_LINE_PATTERN = re.compile(r'// expect error line (\d+)')
|
||||
EXPECT_RUNTIME_ERROR_PATTERN = re.compile(r'// expect runtime error: (.+)')
|
||||
ERROR_PATTERN = re.compile(r'\[.* line (\d+)\] Error')
|
||||
STACK_TRACE_PATTERN = re.compile(r'\[.* line (\d+)\] in \(script\)')
|
||||
STACK_TRACE_PATTERN = re.compile(r'\[.* line (\d+)\] in')
|
||||
SKIP_PATTERN = re.compile(r'// skip: (.*)')
|
||||
NONTEST_PATTERN = re.compile(r'// nontest')
|
||||
|
||||
|
||||
@ -224,18 +224,13 @@ DEF_NATIVE(fiber_new)
|
||||
RETURN_OBJ(newFiber);
|
||||
}
|
||||
|
||||
DEF_NATIVE(fiber_isDone)
|
||||
{
|
||||
ObjFiber* runFiber = AS_FIBER(args[0]);
|
||||
RETURN_BOOL(runFiber->numFrames == 0);
|
||||
}
|
||||
|
||||
DEF_NATIVE(fiber_run)
|
||||
DEF_NATIVE(fiber_call)
|
||||
{
|
||||
ObjFiber* runFiber = AS_FIBER(args[0]);
|
||||
|
||||
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot run a finished fiber.");
|
||||
|
||||
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot call a finished fiber.");
|
||||
if (runFiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
|
||||
|
||||
// Remember who ran it.
|
||||
runFiber->caller = fiber;
|
||||
|
||||
@ -248,11 +243,12 @@ DEF_NATIVE(fiber_run)
|
||||
return PRIM_RUN_FIBER;
|
||||
}
|
||||
|
||||
DEF_NATIVE(fiber_run1)
|
||||
DEF_NATIVE(fiber_call1)
|
||||
{
|
||||
ObjFiber* runFiber = AS_FIBER(args[0]);
|
||||
|
||||
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot run a finished fiber.");
|
||||
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot call a finished fiber.");
|
||||
if (runFiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
|
||||
|
||||
// Remember who ran it.
|
||||
runFiber->caller = fiber;
|
||||
@ -273,15 +269,69 @@ DEF_NATIVE(fiber_run1)
|
||||
return PRIM_RUN_FIBER;
|
||||
}
|
||||
|
||||
DEF_NATIVE(fiber_isDone)
|
||||
{
|
||||
ObjFiber* runFiber = AS_FIBER(args[0]);
|
||||
RETURN_BOOL(runFiber->numFrames == 0);
|
||||
}
|
||||
|
||||
DEF_NATIVE(fiber_run)
|
||||
{
|
||||
ObjFiber* runFiber = AS_FIBER(args[0]);
|
||||
|
||||
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot run a finished fiber.");
|
||||
|
||||
// If the fiber was yielded, make the yield call return null.
|
||||
if (runFiber->caller == NULL && runFiber->stackSize > 0)
|
||||
{
|
||||
runFiber->stack[runFiber->stackSize - 1] = NULL_VAL;
|
||||
}
|
||||
|
||||
// Unlike run, this does not remember the calling fiber. Instead, it
|
||||
// remember's *that* fiber's caller. You can think of it like tail call
|
||||
// elimination. The switched-from fiber is discarded and when the switched
|
||||
// to fiber completes or yields, control passes to the switched-from fiber's
|
||||
// caller.
|
||||
runFiber->caller = fiber->caller;
|
||||
|
||||
return PRIM_RUN_FIBER;
|
||||
}
|
||||
|
||||
DEF_NATIVE(fiber_run1)
|
||||
{
|
||||
ObjFiber* runFiber = AS_FIBER(args[0]);
|
||||
|
||||
if (runFiber->numFrames == 0) RETURN_ERROR("Cannot run a finished fiber.");
|
||||
|
||||
// If the fiber was yielded, make the yield call return the value passed to
|
||||
// run.
|
||||
if (runFiber->caller == NULL && runFiber->stackSize > 0)
|
||||
{
|
||||
runFiber->stack[runFiber->stackSize - 1] = args[1];
|
||||
}
|
||||
|
||||
// Unlike run, this does not remember the calling fiber. Instead, it
|
||||
// remember's *that* fiber's caller. You can think of it like tail call
|
||||
// elimination. The switched-from fiber is discarded and when the switched
|
||||
// to fiber completes or yields, control passes to the switched-from fiber's
|
||||
// caller.
|
||||
runFiber->caller = fiber->caller;
|
||||
|
||||
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;
|
||||
|
||||
// Make the caller's run method return null.
|
||||
fiber->caller->stack[fiber->caller->stackSize - 1] = NULL_VAL;
|
||||
caller->stack[caller->stackSize - 1] = NULL_VAL;
|
||||
|
||||
// Return the fiber to resume.
|
||||
args[0] = OBJ_VAL(fiber->caller);
|
||||
args[0] = OBJ_VAL(caller);
|
||||
return PRIM_RUN_FIBER;
|
||||
}
|
||||
|
||||
@ -289,8 +339,11 @@ DEF_NATIVE(fiber_yield1)
|
||||
{
|
||||
if (fiber->caller == NULL) RETURN_ERROR("No fiber to yield to.");
|
||||
|
||||
ObjFiber* caller = fiber->caller;
|
||||
fiber->caller = NULL;
|
||||
|
||||
// Make the caller's run method return the argument passed to yield.
|
||||
fiber->caller->stack[fiber->caller->stackSize - 1] = args[1];
|
||||
caller->stack[caller->stackSize - 1] = args[1];
|
||||
|
||||
// When the yielding fiber resumes, we'll store the result of the yield call
|
||||
// in its stack. Since Fiber.yield(value) has two arguments (the Fiber class
|
||||
@ -299,7 +352,7 @@ DEF_NATIVE(fiber_yield1)
|
||||
fiber->stackSize--;
|
||||
|
||||
// Return the fiber to resume.
|
||||
args[0] = OBJ_VAL(fiber->caller);
|
||||
args[0] = OBJ_VAL(caller);
|
||||
return PRIM_RUN_FIBER;
|
||||
}
|
||||
|
||||
@ -966,16 +1019,17 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
NATIVE(vm->boolClass, "toString", bool_toString);
|
||||
NATIVE(vm->boolClass, "!", bool_not);
|
||||
|
||||
// TODO: Make fibers inherit Sequence and be iterable.
|
||||
vm->fiberClass = defineClass(vm, "Fiber");
|
||||
NATIVE(vm->fiberClass->metaclass, " instantiate", fiber_instantiate);
|
||||
NATIVE(vm->fiberClass->metaclass, "new ", fiber_new);
|
||||
NATIVE(vm->fiberClass->metaclass, "yield", fiber_yield);
|
||||
NATIVE(vm->fiberClass->metaclass, "yield ", fiber_yield1);
|
||||
NATIVE(vm->fiberClass, "call", fiber_call);
|
||||
NATIVE(vm->fiberClass, "call ", fiber_call1);
|
||||
NATIVE(vm->fiberClass, "isDone", fiber_isDone);
|
||||
NATIVE(vm->fiberClass, "run", fiber_run);
|
||||
NATIVE(vm->fiberClass, "run ", fiber_run1);
|
||||
// TODO: Primitives for switching to a fiber without setting the caller.
|
||||
// (I.e. symmetric coroutines.)
|
||||
|
||||
vm->fnClass = defineClass(vm, "Fn");
|
||||
|
||||
|
||||
7
test/fiber/call.wren
Normal file
7
test/fiber/call.wren
Normal file
@ -0,0 +1,7 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
}
|
||||
|
||||
IO.print("before") // expect: before
|
||||
fiber.call // expect: fiber
|
||||
IO.print("after") // expect: after
|
||||
7
test/fiber/call_direct_reenter.wren
Normal file
7
test/fiber/call_direct_reenter.wren
Normal file
@ -0,0 +1,7 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
fiber.call // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
fiber.call
|
||||
12
test/fiber/call_indirect_reenter.wren
Normal file
12
test/fiber/call_indirect_reenter.wren
Normal file
@ -0,0 +1,12 @@
|
||||
var a
|
||||
var b
|
||||
|
||||
a = new Fiber {
|
||||
b.call // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
b = new Fiber {
|
||||
a.call
|
||||
}
|
||||
|
||||
b.call
|
||||
6
test/fiber/call_return_implicit_null.wren
Normal file
6
test/fiber/call_return_implicit_null.wren
Normal file
@ -0,0 +1,6 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
}
|
||||
|
||||
var result = fiber.call // expect: fiber
|
||||
IO.print(result) // expect: null
|
||||
7
test/fiber/call_return_value.wren
Normal file
7
test/fiber/call_return_value.wren
Normal file
@ -0,0 +1,7 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
return "result"
|
||||
}
|
||||
|
||||
var result = fiber.call // expect: fiber
|
||||
IO.print(result) // expect: result
|
||||
6
test/fiber/call_when_done.wren
Normal file
6
test/fiber/call_when_done.wren
Normal file
@ -0,0 +1,6 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("call")
|
||||
}
|
||||
|
||||
fiber.call // expect: call
|
||||
fiber.call // expect runtime error: Cannot call a finished fiber.
|
||||
9
test/fiber/call_with_value.wren
Normal file
9
test/fiber/call_with_value.wren
Normal file
@ -0,0 +1,9 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
}
|
||||
|
||||
// The first value passed to the fiber is ignored, since there's no yield call
|
||||
// to return it.
|
||||
IO.print("before") // expect: before
|
||||
fiber.call("ignored") // expect: fiber
|
||||
IO.print("after") // expect: after
|
||||
7
test/fiber/call_with_value_direct_reenter.wren
Normal file
7
test/fiber/call_with_value_direct_reenter.wren
Normal file
@ -0,0 +1,7 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
fiber.call(2) // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
fiber.call(1)
|
||||
12
test/fiber/call_with_value_indirect_reenter.wren
Normal file
12
test/fiber/call_with_value_indirect_reenter.wren
Normal file
@ -0,0 +1,12 @@
|
||||
var a
|
||||
var b
|
||||
|
||||
a = new Fiber {
|
||||
b.call(3) // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
b = new Fiber {
|
||||
a.call(2)
|
||||
}
|
||||
|
||||
b.call(1)
|
||||
6
test/fiber/call_with_value_when_done.wren
Normal file
6
test/fiber/call_with_value_when_done.wren
Normal file
@ -0,0 +1,6 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("call")
|
||||
}
|
||||
|
||||
fiber.call(1) // expect: call
|
||||
fiber.call(2) // expect runtime error: Cannot call a finished fiber.
|
||||
@ -17,9 +17,9 @@ var closure
|
||||
}
|
||||
}
|
||||
|
||||
fiber.run // expect: before
|
||||
fiber.call // expect: before
|
||||
closure.call // expect: before
|
||||
fiber.run
|
||||
fiber.call
|
||||
closure.call // expect: after
|
||||
fiber.run // expect: after
|
||||
fiber.call // expect: after
|
||||
closure.call // expect: final
|
||||
|
||||
@ -5,7 +5,7 @@ var fiber = new Fiber {
|
||||
}
|
||||
|
||||
IO.print(fiber.isDone) // expect: false
|
||||
fiber.run // expect: 1
|
||||
fiber.call // expect: 1
|
||||
IO.print(fiber.isDone) // expect: false
|
||||
fiber.run // expect: 2
|
||||
fiber.call // expect: 2
|
||||
IO.print(fiber.isDone) // expect: true
|
||||
|
||||
@ -4,12 +4,12 @@ var b = new Fiber {
|
||||
|
||||
var a = new Fiber {
|
||||
IO.print("begin fiber a")
|
||||
b.run
|
||||
b.call
|
||||
IO.print("end fiber a")
|
||||
}
|
||||
|
||||
IO.print("begin main")
|
||||
a.run
|
||||
a.call
|
||||
IO.print("end main")
|
||||
|
||||
// expect: begin main
|
||||
|
||||
@ -4,6 +4,6 @@ var fiber = new Fiber {
|
||||
|
||||
IO.print("before") // expect: before
|
||||
fiber.run // expect: fiber
|
||||
IO.print("after") // expect: after
|
||||
|
||||
// TODO: Test handles error if fiber tries to run itself.
|
||||
// This does not get run since we exit when the run fiber completes.
|
||||
IO.print("nope")
|
||||
|
||||
10
test/fiber/run_direct_reenter.wren
Normal file
10
test/fiber/run_direct_reenter.wren
Normal file
@ -0,0 +1,10 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
IO.print(1) // expect: 1
|
||||
fiber.run
|
||||
IO.print(2) // expect: 2
|
||||
}
|
||||
|
||||
fiber.call
|
||||
IO.print(3) // expect: 3
|
||||
20
test/fiber/run_indirect_reenter.wren
Normal file
20
test/fiber/run_indirect_reenter.wren
Normal file
@ -0,0 +1,20 @@
|
||||
var a
|
||||
var b
|
||||
|
||||
a = new Fiber {
|
||||
IO.print(2)
|
||||
b.run
|
||||
IO.print("nope")
|
||||
}
|
||||
|
||||
b = new Fiber {
|
||||
IO.print(1)
|
||||
a.run
|
||||
IO.print(3)
|
||||
}
|
||||
|
||||
b.call
|
||||
// expect: 1
|
||||
// expect: 2
|
||||
// expect: 3
|
||||
IO.print(4) // expect: 4
|
||||
@ -1,6 +0,0 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
}
|
||||
|
||||
var result = fiber.run // expect: fiber
|
||||
IO.print(result) // expect: null
|
||||
@ -1,7 +0,0 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
return "result"
|
||||
}
|
||||
|
||||
var result = fiber.run // expect: fiber
|
||||
IO.print(result) // expect: result
|
||||
@ -1,6 +1,13 @@
|
||||
var fiber = new Fiber {
|
||||
var a = new Fiber {
|
||||
IO.print("run")
|
||||
}
|
||||
|
||||
fiber.run // expect: run
|
||||
fiber.run // expect runtime error: Cannot run a finished fiber.
|
||||
// Run a through an intermediate fiber since it will get discarded and we need
|
||||
// to return to the main one after a completes.
|
||||
var b = new Fiber {
|
||||
a.run
|
||||
IO.print("nope")
|
||||
}
|
||||
|
||||
b.call // expect: run
|
||||
a.run // expect runtime error: Cannot run a finished fiber.
|
||||
|
||||
@ -4,6 +4,8 @@ var fiber = new Fiber {
|
||||
|
||||
// The first value passed to the fiber is ignored, since there's no yield call
|
||||
// to return it.
|
||||
IO.print("before") // expect: before
|
||||
IO.print("before") // expect: before
|
||||
fiber.run("ignored") // expect: fiber
|
||||
IO.print("after") // expect: after
|
||||
|
||||
// This does not get run since we exit when the run fiber completes.
|
||||
IO.print("nope")
|
||||
|
||||
10
test/fiber/run_with_value_direct_reenter.wren
Normal file
10
test/fiber/run_with_value_direct_reenter.wren
Normal file
@ -0,0 +1,10 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
IO.print(1) // expect: 1
|
||||
fiber.run("ignored")
|
||||
IO.print(2) // expect: 2
|
||||
}
|
||||
|
||||
fiber.call
|
||||
IO.print(3) // expect: 3
|
||||
20
test/fiber/run_with_value_indirect_reenter.wren
Normal file
20
test/fiber/run_with_value_indirect_reenter.wren
Normal file
@ -0,0 +1,20 @@
|
||||
var a
|
||||
var b
|
||||
|
||||
a = new Fiber {
|
||||
IO.print(2)
|
||||
b.run("ignored")
|
||||
IO.print("nope")
|
||||
}
|
||||
|
||||
b = new Fiber {
|
||||
IO.print(1)
|
||||
a.run("ignored")
|
||||
IO.print(3)
|
||||
}
|
||||
|
||||
b.call
|
||||
// expect: 1
|
||||
// expect: 2
|
||||
// expect: 3
|
||||
IO.print(4) // expect: 4
|
||||
@ -1,6 +0,0 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("run")
|
||||
}
|
||||
|
||||
fiber.run(1) // expect: run
|
||||
fiber.run(2) // expect runtime error: Cannot run a finished fiber.
|
||||
@ -6,9 +6,9 @@ var fiber = new Fiber {
|
||||
IO.print("fiber 3")
|
||||
}
|
||||
|
||||
var result = fiber.run // expect: fiber 1
|
||||
IO.print("main 1") // expect: main 1
|
||||
result = fiber.run // expect: fiber 2
|
||||
IO.print("main 2") // expect: main 2
|
||||
result = fiber.run // expect: fiber 3
|
||||
IO.print("main 3") // expect: main 3
|
||||
var result = fiber.call // expect: fiber 1
|
||||
IO.print("main 1") // expect: main 1
|
||||
result = fiber.call // expect: fiber 2
|
||||
IO.print("main 2") // expect: main 2
|
||||
result = fiber.call // expect: fiber 3
|
||||
IO.print("main 3") // expect: main 3
|
||||
|
||||
@ -6,9 +6,9 @@ var fiber = new Fiber {
|
||||
IO.print(result)
|
||||
}
|
||||
|
||||
fiber.run // expect: fiber 1
|
||||
fiber.call // expect: fiber 1
|
||||
IO.print("main 1") // expect: main 1
|
||||
fiber.run("run 1") // expect: run 1
|
||||
fiber.call("call 1") // expect: call 1
|
||||
IO.print("main 2") // expect: main 2
|
||||
fiber.run // expect: null
|
||||
fiber.call // expect: null
|
||||
IO.print("main 3") // expect: main 3
|
||||
12
test/fiber/yield_return_run_value.wren
Normal file
12
test/fiber/yield_return_run_value.wren
Normal file
@ -0,0 +1,12 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
var result = Fiber.yield
|
||||
IO.print(result)
|
||||
}
|
||||
|
||||
fiber.call // expect: fiber
|
||||
IO.print("main") // expect: main
|
||||
fiber.run("run") // expect: run
|
||||
|
||||
// This does not get run since we exit when the run fiber completes.
|
||||
IO.print("nope")
|
||||
9
test/fiber/yield_with_no_caller.wren
Normal file
9
test/fiber/yield_with_no_caller.wren
Normal file
@ -0,0 +1,9 @@
|
||||
var a = new Fiber {
|
||||
Fiber.yield // expect runtime error: No fiber to yield to.
|
||||
}
|
||||
|
||||
// Run a chain of fibers. Since none of them are called, they all get discarded
|
||||
// and there is no remaining caller.
|
||||
var b = new Fiber { a.run }
|
||||
var c = new Fiber { b.run }
|
||||
c.run
|
||||
@ -6,9 +6,9 @@ var fiber = new Fiber {
|
||||
IO.print("fiber 3")
|
||||
}
|
||||
|
||||
var result = fiber.run // expect: fiber 1
|
||||
var result = fiber.call // expect: fiber 1
|
||||
IO.print(result) // expect: yield 1
|
||||
result = fiber.run // expect: fiber 2
|
||||
result = fiber.call // expect: fiber 2
|
||||
IO.print(result) // expect: yield 2
|
||||
result = fiber.run // expect: fiber 3
|
||||
result = fiber.call // expect: fiber 3
|
||||
IO.print(result) // expect: null
|
||||
|
||||
9
test/fiber/yield_with_value_with_no_caller.wren
Normal file
9
test/fiber/yield_with_value_with_no_caller.wren
Normal file
@ -0,0 +1,9 @@
|
||||
var a = new Fiber {
|
||||
Fiber.yield(1) // expect runtime error: No fiber to yield to.
|
||||
}
|
||||
|
||||
// Run a chain of fibers. Since none of them are called, they all get discarded
|
||||
// and there is no remaining caller.
|
||||
var b = new Fiber { a.run }
|
||||
var c = new Fiber { b.run }
|
||||
c.run
|
||||
Reference in New Issue
Block a user