Skip to content

Commit 5509501

Browse files
committed
add SyncGenerator
1 parent 6d2f9c0 commit 5509501

File tree

3 files changed

+104
-48
lines changed

3 files changed

+104
-48
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package hxcoro.generators;
2+
3+
import haxe.Exception;
4+
import haxe.coro.Coroutine;
5+
import haxe.coro.IContinuation;
6+
import haxe.coro.context.Context;
7+
import haxe.coro.dispatchers.Dispatcher;
8+
import haxe.coro.dispatchers.IDispatchObject;
9+
import haxe.exceptions.CoroutineException;
10+
import hxcoro.Coro.*;
11+
12+
@:coroutine.restrictedSuspension
13+
typedef Yield<T> = Coroutine<T -> Void>;
14+
15+
private class GeneratorImpl<T> extends Dispatcher implements IContinuation<Any> {
16+
public var context(get, null):Context;
17+
18+
final f:Coroutine<Yield<T> -> Void>;
19+
var nextValue:Null<T>;
20+
var nextStep:Null<IContinuation<T>>;
21+
var resumed:Bool;
22+
23+
public function new(f:Coroutine<Yield<T> -> Void>) {
24+
this.context = Context.create(this);
25+
this.f = f;
26+
resumed = true;
27+
}
28+
29+
function get_context() {
30+
return context;
31+
}
32+
33+
function get_scheduler() {
34+
return throw new CoroutineException('Cannot access scheduler on Generator contexts');
35+
}
36+
37+
public function hasNext() {
38+
if (nextStep == null) {
39+
f(this, yield);
40+
} else if (!resumed) {
41+
nextStep.resume(null, null);
42+
}
43+
return !resumed;
44+
}
45+
46+
public function next() {
47+
return nextValue;
48+
}
49+
50+
public function resume(result:Null<Any>, error:Null<Exception>) {
51+
resumed = true;
52+
if (error != null) {
53+
throw error;
54+
}
55+
}
56+
57+
public function dispatch(obj:IDispatchObject) {
58+
obj.onDispatch();
59+
}
60+
61+
@:coroutine function yield(value:T) {
62+
resumed = false;
63+
nextValue = value;
64+
suspend(cont -> {
65+
nextStep = cont;
66+
});
67+
}
68+
}
69+
70+
/**
71+
A synchronous generator that can be used as an `Iterator`.
72+
**/
73+
abstract SyncGenerator<T>(GeneratorImpl<T>) {
74+
/**
75+
@see `Iterator.hasNext`
76+
**/
77+
public inline function hasNext() {
78+
return this.hasNext();
79+
}
80+
81+
/**
82+
@see `Iterator.next`
83+
**/
84+
public inline function next() {
85+
return this.next();
86+
}
87+
88+
/**
89+
Creates a new generator that produces values by calling and resuming `f`.
90+
91+
The coroutine `f` is executed in a restricted suspension scope, which means
92+
that it cannot call arbitrary coroutines that might suspend.
93+
**/
94+
static public function create<T>(f:Coroutine<Yield<T> -> Void>) {
95+
return new GeneratorImpl(f);
96+
}
97+
}

src/hxcoro/task/ICoroNode.hx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package hxcoro.task;
22

3-
import haxe.exceptions.CancellationException;
43
import haxe.coro.context.Context;
5-
import haxe.coro.context.Key;
64
import haxe.coro.context.IElement;
5+
import haxe.coro.context.Key;
6+
import haxe.exceptions.CancellationException;
77
import hxcoro.task.ICoroTask;
88

99
interface ICoroNodeWith {
@@ -14,6 +14,7 @@ interface ICoroNodeWith {
1414
function without(...keys:Key<Any>):ICoroNodeWith;
1515
}
1616

17+
@:coroutine.scope
1718
interface ICoroNode extends ICoroNodeWith {
1819
var id(get, never):Int;
1920
@:coroutine function awaitChildren():Void;

tests/src/TestGenerator.hx

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,12 @@
11
import hxcoro.dispatchers.TrampolineDispatcher;
22
import hxcoro.task.CoroTask;
33
import haxe.coro.context.Context;
4+
import hxcoro.generators.SyncGenerator;
45
import haxe.Exception;
56

6-
private typedef Yield<T> = Coroutine<T->Void>;
7-
8-
private function sequence<T>(f:Coroutine<Yield<T>->Void>):Iterator<T> {
9-
var hasValue = false;
10-
var nextValue:T = null;
11-
var exception:Null<Exception> = null;
12-
13-
var nextStep = null;
14-
final scope = new CoroTask(Context.create(new TrampolineDispatcher()), CoroTask.CoroScopeStrategy);
15-
16-
@:coroutine function yield(value:T) {
17-
nextValue = value;
18-
hasValue = true;
19-
suspend(cont -> {
20-
nextStep = () -> {
21-
hasValue = false;
22-
cont.resume(null, null);
23-
if (!scope.isActive()) {
24-
exception = scope.getError();
25-
}
26-
}
27-
});
28-
}
29-
30-
nextStep = () -> {
31-
f(scope, yield);
32-
scope.start();
33-
}
34-
35-
function hasNext() {
36-
nextStep();
37-
if (exception != null) {
38-
throw exception;
39-
}
40-
return hasValue;
41-
}
42-
function next() {
43-
return nextValue;
44-
}
45-
46-
return {hasNext: hasNext, next: next};
47-
}
48-
497
class TestGenerator extends utest.Test {
508
function testSimple() {
51-
var iter = sequence(yield -> {
9+
var iter = SyncGenerator.create(yield -> {
5210
yield(1);
5311
yield(2);
5412
yield(3);
@@ -64,7 +22,7 @@ class TestGenerator extends utest.Test {
6422
}
6523

6624
function iterTree<T>(tree:Tree<T>):Iterator<T> {
67-
return sequence(yield -> iterTreeRec(yield, tree));
25+
return SyncGenerator.create(yield -> iterTreeRec(yield, tree));
6826
}
6927

7028
var tree:Tree<Int> = {
@@ -86,7 +44,7 @@ class TestGenerator extends utest.Test {
8644
function testException() {
8745
final result = [];
8846
Assert.raises(() -> {
89-
for (i in sequence(yield -> {
47+
for (i in SyncGenerator.create(yield -> {
9048
yield(1);
9149
yield(2);
9250
throw "oh no";

0 commit comments

Comments
 (0)