diff --git a/script/test.py b/script/test.py index 8261f45c..0a14e99a 100755 --- a/script/test.py +++ b/script/test.py @@ -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') diff --git a/src/wren_core.c b/src/wren_core.c index c9472135..a1215ecb 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -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"); diff --git a/test/fiber/call.wren b/test/fiber/call.wren new file mode 100644 index 00000000..4acd00b4 --- /dev/null +++ b/test/fiber/call.wren @@ -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 diff --git a/test/fiber/call_direct_reenter.wren b/test/fiber/call_direct_reenter.wren new file mode 100644 index 00000000..a4d27515 --- /dev/null +++ b/test/fiber/call_direct_reenter.wren @@ -0,0 +1,7 @@ +var fiber + +fiber = new Fiber { + fiber.call // expect runtime error: Fiber has already been called. +} + +fiber.call diff --git a/test/fiber/call_indirect_reenter.wren b/test/fiber/call_indirect_reenter.wren new file mode 100644 index 00000000..da889522 --- /dev/null +++ b/test/fiber/call_indirect_reenter.wren @@ -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 diff --git a/test/fiber/call_return_implicit_null.wren b/test/fiber/call_return_implicit_null.wren new file mode 100644 index 00000000..bdf9ac34 --- /dev/null +++ b/test/fiber/call_return_implicit_null.wren @@ -0,0 +1,6 @@ +var fiber = new Fiber { + IO.print("fiber") +} + +var result = fiber.call // expect: fiber +IO.print(result) // expect: null diff --git a/test/fiber/call_return_value.wren b/test/fiber/call_return_value.wren new file mode 100644 index 00000000..fa871c97 --- /dev/null +++ b/test/fiber/call_return_value.wren @@ -0,0 +1,7 @@ +var fiber = new Fiber { + IO.print("fiber") + return "result" +} + +var result = fiber.call // expect: fiber +IO.print(result) // expect: result diff --git a/test/fiber/call_when_done.wren b/test/fiber/call_when_done.wren new file mode 100644 index 00000000..33f1705d --- /dev/null +++ b/test/fiber/call_when_done.wren @@ -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. diff --git a/test/fiber/call_with_value.wren b/test/fiber/call_with_value.wren new file mode 100644 index 00000000..16d78fe0 --- /dev/null +++ b/test/fiber/call_with_value.wren @@ -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 diff --git a/test/fiber/call_with_value_direct_reenter.wren b/test/fiber/call_with_value_direct_reenter.wren new file mode 100644 index 00000000..05804b08 --- /dev/null +++ b/test/fiber/call_with_value_direct_reenter.wren @@ -0,0 +1,7 @@ +var fiber + +fiber = new Fiber { + fiber.call(2) // expect runtime error: Fiber has already been called. +} + +fiber.call(1) diff --git a/test/fiber/call_with_value_indirect_reenter.wren b/test/fiber/call_with_value_indirect_reenter.wren new file mode 100644 index 00000000..d31c5a61 --- /dev/null +++ b/test/fiber/call_with_value_indirect_reenter.wren @@ -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) diff --git a/test/fiber/call_with_value_when_done.wren b/test/fiber/call_with_value_when_done.wren new file mode 100644 index 00000000..c3fc2f7d --- /dev/null +++ b/test/fiber/call_with_value_when_done.wren @@ -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. diff --git a/test/fiber/closure.wren b/test/fiber/closure.wren index 1d949b92..3dcfea07 100644 --- a/test/fiber/closure.wren +++ b/test/fiber/closure.wren @@ -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 diff --git a/test/fiber/is_done.wren b/test/fiber/is_done.wren index 945f0c28..31489b90 100644 --- a/test/fiber/is_done.wren +++ b/test/fiber/is_done.wren @@ -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 diff --git a/test/fiber/resume_caller.wren b/test/fiber/resume_caller.wren index 14765943..1bdd88fc 100644 --- a/test/fiber/resume_caller.wren +++ b/test/fiber/resume_caller.wren @@ -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 diff --git a/test/fiber/run.wren b/test/fiber/run.wren index 9c9baca6..c37ace17 100644 --- a/test/fiber/run.wren +++ b/test/fiber/run.wren @@ -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") diff --git a/test/fiber/run_direct_reenter.wren b/test/fiber/run_direct_reenter.wren new file mode 100644 index 00000000..a56a7555 --- /dev/null +++ b/test/fiber/run_direct_reenter.wren @@ -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 \ No newline at end of file diff --git a/test/fiber/run_indirect_reenter.wren b/test/fiber/run_indirect_reenter.wren new file mode 100644 index 00000000..5bfc185c --- /dev/null +++ b/test/fiber/run_indirect_reenter.wren @@ -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 diff --git a/test/fiber/run_return_implicit_null.wren b/test/fiber/run_return_implicit_null.wren deleted file mode 100644 index 4e406b1a..00000000 --- a/test/fiber/run_return_implicit_null.wren +++ /dev/null @@ -1,6 +0,0 @@ -var fiber = new Fiber { - IO.print("fiber") -} - -var result = fiber.run // expect: fiber -IO.print(result) // expect: null diff --git a/test/fiber/run_return_value.wren b/test/fiber/run_return_value.wren deleted file mode 100644 index c8adc517..00000000 --- a/test/fiber/run_return_value.wren +++ /dev/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 diff --git a/test/fiber/run_when_done.wren b/test/fiber/run_when_done.wren index 24231fb1..7d680e3c 100644 --- a/test/fiber/run_when_done.wren +++ b/test/fiber/run_when_done.wren @@ -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. diff --git a/test/fiber/run_with_value.wren b/test/fiber/run_with_value.wren index 9a201518..e3cef529 100644 --- a/test/fiber/run_with_value.wren +++ b/test/fiber/run_with_value.wren @@ -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") diff --git a/test/fiber/run_with_value_direct_reenter.wren b/test/fiber/run_with_value_direct_reenter.wren new file mode 100644 index 00000000..dead5b28 --- /dev/null +++ b/test/fiber/run_with_value_direct_reenter.wren @@ -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 \ No newline at end of file diff --git a/test/fiber/run_with_value_indirect_reenter.wren b/test/fiber/run_with_value_indirect_reenter.wren new file mode 100644 index 00000000..2387ce3e --- /dev/null +++ b/test/fiber/run_with_value_indirect_reenter.wren @@ -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 diff --git a/test/fiber/run_with_value_when_done.wren b/test/fiber/run_with_value_when_done.wren deleted file mode 100644 index fbcd1d8c..00000000 --- a/test/fiber/run_with_value_when_done.wren +++ /dev/null @@ -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. diff --git a/test/fiber/yield.wren b/test/fiber/yield.wren index 5f2c0992..c262a39e 100644 --- a/test/fiber/yield.wren +++ b/test/fiber/yield.wren @@ -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 diff --git a/test/fiber/yield_return_value.wren b/test/fiber/yield_return_call_value.wren similarity index 72% rename from test/fiber/yield_return_value.wren rename to test/fiber/yield_return_call_value.wren index 3314a31e..ce6b0bf1 100644 --- a/test/fiber/yield_return_value.wren +++ b/test/fiber/yield_return_call_value.wren @@ -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 diff --git a/test/fiber/yield_return_run_value.wren b/test/fiber/yield_return_run_value.wren new file mode 100644 index 00000000..3c951da7 --- /dev/null +++ b/test/fiber/yield_return_run_value.wren @@ -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") diff --git a/test/fiber/yield_with_no_caller.wren b/test/fiber/yield_with_no_caller.wren new file mode 100644 index 00000000..ccbeeae6 --- /dev/null +++ b/test/fiber/yield_with_no_caller.wren @@ -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 diff --git a/test/fiber/yield_with_value.wren b/test/fiber/yield_with_value.wren index 9ed579c0..e7d8d9a1 100644 --- a/test/fiber/yield_with_value.wren +++ b/test/fiber/yield_with_value.wren @@ -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 diff --git a/test/fiber/yield_with_value_with_no_caller.wren b/test/fiber/yield_with_value_with_no_caller.wren new file mode 100644 index 00000000..591eef64 --- /dev/null +++ b/test/fiber/yield_with_value_with_no_caller.wren @@ -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