feat: implement symmetric coroutines with fiber.call and fiber.run distinction
Add new `fiber.call` method that remembers the calling fiber for symmetric transfer, while `fiber.run` now performs tail-call style switching without preserving the caller. Introduce re-entrancy guards preventing a fiber from being called multiple times, update all test fixtures to use the new API, and add comprehensive test coverage for call semantics including direct/indirect re-entrancy, value passing, and finished fiber errors.
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
fiber.call // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
fiber.call
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
}
|
||||
|
||||
var result = fiber.call // expect: fiber
|
||||
IO.print(result) // expect: null
|
||||
@@ -0,0 +1,7 @@
|
||||
var fiber = new Fiber {
|
||||
IO.print("fiber")
|
||||
return "result"
|
||||
}
|
||||
|
||||
var result = fiber.call // expect: fiber
|
||||
IO.print(result) // expect: result
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
var fiber
|
||||
|
||||
fiber = new Fiber {
|
||||
fiber.call(2) // expect runtime error: Fiber has already been called.
|
||||
}
|
||||
|
||||
fiber.call(1)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
@@ -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