Browse Source

add yield tests from https://github.com/nadako/haxe-coroutines/pull/6

Simon Krajewski 1 year ago
parent
commit
9f0238fee0

+ 4 - 4
src/coro/coroFromTexpr.ml

@@ -3,10 +3,6 @@ open Type
 open CoroTypes
 open CoroTypes
 open CoroFunctions
 open CoroFunctions
 
 
-let terminate cb kind t p =
-	if cb.cb_next.next_kind = NextUnknown then
-		cb.cb_next <- {next_kind = kind; next_type = t; next_pos = p}
-
 let e_no_value = Texpr.Builder.make_null t_dynamic null_pos
 let e_no_value = Texpr.Builder.make_null t_dynamic null_pos
 
 
 type coro_ret =
 type coro_ret =
@@ -35,6 +31,10 @@ let expr_to_coro ctx (vresult,verror) cb_root e =
 		if cb.cb_next.next_kind = NextUnknown && e != e_no_value && cb != ctx.cb_unreachable then
 		if cb.cb_next.next_kind = NextUnknown && e != e_no_value && cb != ctx.cb_unreachable then
 			DynArray.add cb.cb_el e
 			DynArray.add cb.cb_el e
 	in
 	in
+	let terminate cb kind t p =
+		if cb.cb_next.next_kind = NextUnknown && cb != ctx.cb_unreachable then
+			cb.cb_next <- {next_kind = kind; next_type = t; next_pos = p}
+	in
 	let fall_through cb_from cb_to =
 	let fall_through cb_from cb_to =
 		terminate cb_from (NextFallThrough cb_to) t_dynamic null_pos
 		terminate cb_from (NextFallThrough cb_to) t_dynamic null_pos
 	in
 	in

+ 1 - 1
src/coro/coroToTexpr.ml

@@ -132,7 +132,7 @@ let block_to_texpr_coroutine ctx cb vcontinuation vresult verror p =
 					cb.cb_id
 					cb.cb_id
 			in
 			in
 			if not (DynArray.empty cb.cb_el) then
 			if not (DynArray.empty cb.cb_el) then
-				add_state (Some cb_next.cb_id) []
+				add_state (Some (skip_loop cb_next)) []
 			else
 			else
 				skip_loop cb
 				skip_loop cb
 		| NextReturnVoid | NextReturn _ as r ->
 		| NextReturnVoid | NextReturn _ as r ->

+ 0 - 1
src/filters/filters.ml

@@ -732,7 +732,6 @@ let run tctx main before_destruction =
 		"add_final_return",if com.config.pf_add_final_return then add_final_return else (fun e -> e);
 		"add_final_return",if com.config.pf_add_final_return then add_final_return else (fun e -> e);
 		"RenameVars",(match com.platform with
 		"RenameVars",(match com.platform with
 		| Eval -> (fun e -> e)
 		| Eval -> (fun e -> e)
-		| Jvm -> (fun e -> e)
 		| _ -> (fun e -> RenameVars.run tctx.c.curclass.cl_path locals e));
 		| _ -> (fun e -> RenameVars.run tctx.c.curclass.cl_path locals e));
 		"mark_switch_break_loops",mark_switch_break_loops;
 		"mark_switch_break_loops",mark_switch_break_loops;
 	] in
 	] in

+ 19 - 0
tests/misc/coroutines/src/BaseCase.hx

@@ -0,0 +1,19 @@
+@:keepSub
+@:keep
+class BaseCase implements utest.ITest {
+	var dummy:String = '';
+
+	public function new() {}
+
+	public function setup() {
+		dummy = '';
+	}
+
+	function assert<T>(expected:Array<T>, generator:Iterator<T>, ?p:haxe.PosInfos) {
+		dummy = '';
+		for (it in generator) {
+			Assert.equals(expected.shift(), it, p);
+		}
+		Assert.equals(0, expected.length, p);
+	}
+}

+ 9 - 0
tests/misc/coroutines/src/Main.hx

@@ -1,3 +1,5 @@
+import yield.*;
+
 function main() {
 function main() {
 	utest.UTest.run([
 	utest.UTest.run([
 		new TestBasic(),
 		new TestBasic(),
@@ -7,5 +9,12 @@ function main() {
 		#if js
 		#if js
 		new TestJsPromise(),
 		new TestJsPromise(),
 		#end
 		#end
+		new TestYieldBasic(),
+		new TestYieldIf(),
+		new TestYieldFor(),
+		new TestYieldClosure(),
+		new TestYieldSwitch(),
+		new TestYieldTryCatch(),
+		new TestYieldWhile(),
 	]);
 	]);
 }
 }

+ 1 - 1
tests/misc/coroutines/src/TestControlFlow.hx

@@ -118,7 +118,7 @@ class TestControlFlow extends utest.Test {
 }
 }
 
 
 @:coroutine
 @:coroutine
-private function mapCalls<TArg,TRet>(args:Array<TArg>, f:haxe.coro.Coroutine<TArg->TRet>):Array<TRet> {
+private function mapCalls<TArg,TRet>(args:Array<TArg>, f:Coroutine<TArg->TRet>):Array<TRet> {
 	return [for (arg in args) f(arg)];
 	return [for (arg in args) f(arg)];
 }
 }
 
 

+ 1 - 35
tests/misc/coroutines/src/TestGenerator.hx

@@ -1,4 +1,4 @@
-import haxe.coro.Coroutine;
+import yield.Yield;
 
 
 class TestGenerator extends utest.Test {
 class TestGenerator extends utest.Test {
 	function testSimple() {
 	function testSimple() {
@@ -38,40 +38,6 @@ class TestGenerator extends utest.Test {
 	}
 	}
 }
 }
 
 
-private typedef Yield<T> = Coroutine<T->Void>;
-
-private function sequence<T>(f:Coroutine<Yield<T>->Void>):Iterator<T> {
-	var finished = false;
-	var nextValue:T = null;
-
-	var nextStep = null;
-
-	function finish(_, _) {
-		finished = true;
-	}
-
-	@:coroutine function yield(value:T) {
-		nextValue = value;
-		Coroutine.suspend(cont -> nextStep = cont);
-	}
-
-	function hasNext():Bool {
-		if (nextStep == null) {
-			nextStep = f.create(yield, finish);
-			nextStep(null, null);
-		}
-		return !finished;
-	}
-
-	function next():T {
-		var value = nextValue;
-		nextStep(null, null);
-		return value;
-	}
-
-	return {hasNext: hasNext, next: next};
-}
-
 private typedef Tree<T> = {
 private typedef Tree<T> = {
 	var leaf:T;
 	var leaf:T;
 	var ?left:Tree<T>;
 	var ?left:Tree<T>;

+ 0 - 1
tests/misc/coroutines/src/TestJsPromise.hx

@@ -1,6 +1,5 @@
 import js.lib.Error;
 import js.lib.Error;
 import js.lib.Promise;
 import js.lib.Promise;
-import haxe.coro.Coroutine;
 
 
 @:coroutine
 @:coroutine
 private function await<T>(p:Promise<T>):T {
 private function await<T>(p:Promise<T>):T {

+ 1 - 0
tests/misc/coroutines/src/import.hx

@@ -1,2 +1,3 @@
 import utest.Assert;
 import utest.Assert;
 import utest.Async;
 import utest.Async;
+import haxe.coro.Coroutine;

+ 126 - 0
tests/misc/coroutines/src/yield/TestYieldBasic.hx

@@ -0,0 +1,126 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldBasic extends BaseCase {
+	public function testBasicYieldReturn() {
+		assert([10, 20], basicYieldReturn());
+		Assert.equals('123', dummy);
+	}
+
+	@:yield function basicYieldReturn():Iterator<Int> {
+		dummy += '1';
+		@:yield return 10;
+		dummy += '2';
+		@:yield return 20;
+		dummy += '3';
+	}
+
+	#if broken
+
+	public function testBasicYieldReturn_multipleIterations() {
+		var generator = basicYieldReturn();
+		assert([10, 20], generator);
+		Assert.equals('123', dummy);
+		assert([10, 20], generator);
+		Assert.equals('123', dummy);
+	}
+
+	#end
+
+	public function testBasicYieldBreak() {
+		assert([10], basicYieldBreak());
+		Assert.equals('12', dummy);
+	}
+
+	@:yield function basicYieldBreak() {
+		dummy += '1';
+		@:yield return 10;
+		dummy += '2';
+		return;
+		dummy += '3';
+		@:yield return 20;
+		dummy += '4';
+	}
+
+	public function testLocalVars() {
+		assert([10, 25, 40, 19, 30], localVars(10, 20, 30));
+	}
+
+	@:yield function localVars(a:Int, b:Int, a1:Int) {
+		var q = b;
+		@:yield return a;
+		var a = 5;
+		@:yield return a + q;
+		var q = q * 2;
+		@:yield return q;
+		for (a in 1...2) {
+			q = a * 10;
+		}
+		for (c in 1...2) {
+			q += 5;
+		}
+		for (i in 0...2) {
+			for (j in 0...2) {
+				q += i + j;
+			}
+		}
+		@:yield return q;
+		@:yield return a1;
+	}
+
+	public function testLocalVars_sameVarNameInTwoChildScopes() {
+		assert([10], localVars_sameVarNameInTwoChildScopes(true));
+		assert([20], localVars_sameVarNameInTwoChildScopes(false));
+	}
+
+	@:yield function localVars_sameVarNameInTwoChildScopes(condition:Bool) {
+		if (condition) {
+			var v = 10;
+			@:yield return v;
+		} else {
+			var v = 'ab';
+			@:yield return v.length * 10;
+		}
+	}
+
+	public function testLocalFunction() {
+		assert([10, 20, 30], localFunction());
+	}
+
+	@:yield function localFunction() {
+		inline function local1()
+			return 20;
+		function local2() {
+			return 30;
+		}
+		@:yield return 10;
+		@:yield return local1();
+		var value = local2();
+		@:yield return value;
+	}
+
+	public function testInheritance() {
+		var result = [for (it in descendantsOfParent()) it];
+		Assert.equals(2, result.length);
+	}
+
+	@:yield function descendantsOfParent():Iterator<Parent> {
+		@:yield return new Child1();
+		@:yield return new Child2();
+	}
+}
+
+private class Parent {
+	public function new() {}
+}
+
+private class Child1 extends Parent {}
+private class Child2 extends Parent {}
+
+function main() {
+	utest.UTest.run([
+		new TestYieldBasic()
+	]);
+}

+ 88 - 0
tests/misc/coroutines/src/yield/TestYieldClosure.hx

@@ -0,0 +1,88 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldClosure extends BaseCase {
+	// @:yield function closure(arg) {
+	// 	var fn = @:yield function(arg2) {
+	// 	}
+	// 	@:yield function another(arg2) {
+	// 		trace({arg2:arg2});
+	// 	}
+	// }
+
+	var anchor:Dynamic;
+
+	public function testClosure() {
+		assert([20, 40, 60, 80, 20, 40, 60, 80, 100], closure(2));
+		Assert.equals('1234512345', dummy);
+	}
+
+	@:yield function closure(arg) {
+		var a:Dynamic = arg;
+		anchor = a;
+		var fn = @:yield function(arg2) {
+			var b:Dynamic = arg;
+			anchor = b;
+			dummy += '1';
+			@:yield return arg * 10;
+			dummy += '2';
+			@:yield return cast a * 20; // TODO: I had to insert these casts because this was errorring with Float should be Int
+			dummy += '3';
+			@:yield return cast b * 30;
+			dummy += '4';
+			@:yield return arg2 * 40;
+			dummy += '5';
+		}
+		for(i in fn(a)) {
+			@:yield return i;
+		}
+		@:yield function another(arg2) {
+			var b:Dynamic = arg;
+			anchor = b;
+			dummy += '1';
+			@:yield return arg * 10;
+			dummy += '2';
+			@:yield return cast a * 20;
+			dummy += '3';
+			@:yield return cast b * 30;
+			dummy += '4';
+			@:yield return arg2 * 40;
+			dummy += '5';
+			for(i in (@:yield function() @:yield return arg2 * 50)()) {
+				@:yield return i;
+			}
+		}
+		for(i in another(a)) {
+			@:yield return i;
+		}
+	}
+
+
+	public function testClosure_nested() {
+		assert([100], closure_nested(10));
+	}
+
+	@:yield function closure_nested(arg) {
+		@:yield function another(arg2) {
+			var fn = @:yield function() @:yield return arg2 * 10;
+			for(i in fn()) @:yield return i;
+		}
+		for(i in another(arg)) {
+			@:yield return i;
+		}
+	}
+
+
+	public function testClosure_withoutYield() {
+		assert([0, 10], closure_withoutYield(1));
+	}
+
+	@:yield function closure_withoutYield(arg:Int) {
+		var fn = function() return arg * 10;
+		for(i in 0...2) {
+			@:yield return fn() * i;
+		}
+	}
+}

+ 86 - 0
tests/misc/coroutines/src/yield/TestYieldFor.hx

@@ -0,0 +1,86 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldFor extends BaseCase {
+
+	public function testFor_basicYieldReturn() {
+		assert([11, 21, 31], for_basicYieldReturn(1));
+		Assert.equals('01122334', dummy);
+	}
+
+	@:yield function for_basicYieldReturn(arg:Int) {
+		dummy += '0';
+		for(i in 1...4) {
+			dummy += i;
+			@:yield return i * 10 + arg;
+			dummy += i;
+		}
+		dummy += '4';
+	}
+
+	public function testFor_basicYieldBreak() {
+		assert([10], for_basicYieldBreak());
+		Assert.equals('012', dummy);
+	}
+
+	@:yield function for_basicYieldBreak() {
+		dummy += '0';
+		@:yield return 10;
+		dummy += '1';
+		for(i in 2...100) {
+			dummy += i;
+			return;
+			dummy += i;
+		}
+		dummy += '101';
+	}
+
+	public function testFor_nested() {
+		assert([0, 1, 10, 11], for_nested());
+		Assert.equals('0[><><][><><]2', dummy);
+	}
+
+	@:yield function for_nested() {
+		dummy += '0';
+		for(i in 0...2) {
+			dummy += '[';
+			for(j in 0...2) {
+				dummy += '>';
+				@:yield return i * 10 + j;
+				dummy += '<';
+			}
+			dummy += ']';
+		}
+		dummy += '2';
+	}
+
+
+	public function testFor_breakContinue() {
+		assert([0, -1, 2], for_breakContinue());
+		Assert.equals('12356789235235670', dummy);
+	}
+
+	@:yield function for_breakContinue() {
+		dummy += '1';
+		for(i in 0...10) {
+			dummy += '2';
+			while(true) {
+				dummy += '3';
+				break;
+				dummy += '4';
+			}
+			dummy += '5';
+			if(i == 1) continue;
+			dummy += '6';
+			@:yield return i;
+			dummy += '7';
+			if(i == 2) break;
+			dummy += '8';
+			@:yield return -1;
+			dummy += '9';
+		}
+		dummy += '0';
+	}
+}

+ 97 - 0
tests/misc/coroutines/src/yield/TestYieldIf.hx

@@ -0,0 +1,97 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldIf extends BaseCase {
+
+	public function testIf_withoutElse() {
+		assert([10, 20, 30, 40], ifWithoutElse(true));
+		Assert.equals('1234567', dummy);
+		assert([10, 30], ifWithoutElse(false));
+		Assert.equals('12567', dummy);
+	}
+
+	@:yield function ifWithoutElse(condition:Bool) {
+		dummy += '1';
+		@:yield return 10;
+		dummy += '2';
+		if(condition) {
+			dummy += '3';
+			@:yield return 20;
+			dummy += '4';
+		}
+		dummy += '5';
+		@:yield return 30;
+		dummy += '6';
+		if(condition) @:yield return 40;
+		dummy += '7';
+	}
+
+
+	public function testIfElse() {
+		assert([10], ifElse(true));
+		Assert.equals('123678', dummy);
+		assert([20, 30], ifElse(false));
+		Assert.equals('14568', dummy);
+	}
+
+	@:yield function ifElse(condition:Bool) {
+		dummy += '1';
+		if(condition) {
+			dummy += '2';
+			@:yield return 10;
+			dummy += '3';
+		} else {
+			dummy += '4';
+			@:yield return 20;
+			dummy += '5';
+		}
+		dummy += '6';
+		if(condition) {
+			dummy += '7';
+		} else @:yield return 30;
+		dummy += '8';
+	}
+
+	#if broken
+	public function testIfElse_withoutYield_runInSingleState() {
+		assert([10], ifElseNoYield(true));
+		assert([10], ifElseNoYield(false));
+	}
+
+	@:yield function ifElseNoYield(condition:Bool) {
+		var state = __ctx__.state; //__ctx__ is generated by build macros
+		if(condition) {
+			Assert.equals(state, __ctx__.state);
+		} else {
+			Assert.equals(state, __ctx__.state);
+		}
+		Assert.equals(state, __ctx__.state);
+
+		@:yield return 10;
+	}
+	#end
+
+
+	public function testIfElse_nestedIfs() {
+		assert([10], nestedIfs(true));
+		Assert.equals('123456', dummy);
+		assert([], nestedIfs(false));
+		Assert.equals('16', dummy);
+	}
+
+	@:yield function nestedIfs(condition:Bool) {
+		dummy += '1';
+		if(condition) {
+			dummy += '2';
+			if(condition) {
+				dummy += '3';
+				@:yield return 10;
+				dummy += '4';
+			}
+			dummy += '5';
+		}
+		dummy += '6';
+	}
+}

+ 105 - 0
tests/misc/coroutines/src/yield/TestYieldSwitch.hx

@@ -0,0 +1,105 @@
+package yield;
+
+import yield.Yield;
+
+private enum Example {
+	One;
+	Two(v:Int);
+	Three(v:String);
+	Four;
+}
+
+@:build(yield.YieldMacro.build())
+class TestYieldSwitch extends BaseCase {
+
+	public function testSwitch() {
+		assert([10, 30], basicSwitch(One));
+		Assert.equals('1230-', dummy);
+		assert([20, 30], basicSwitch(Two(20)));
+		Assert.equals('1450-', dummy);
+		assert([5, 30], basicSwitch(Three('hello')));
+		Assert.equals('1670-', dummy);
+		assert([30], basicSwitch(Three('h')));
+		Assert.equals('1h0-', dummy);
+		assert([], basicSwitch(Four));
+		Assert.equals('18', dummy);
+	}
+
+	@:yield function basicSwitch(arg) {
+		dummy += '1';
+		switch(arg) {
+			case One:
+				dummy += '2';
+				@:yield return 10;
+				dummy += '3';
+			case Two(v):
+				dummy += '4';
+				@:yield return v;
+				dummy += '5';
+			case Three(v) if(v.length > 1):
+				dummy += '6';
+				@:yield return v.length;
+				dummy += '7';
+			case Three(v):
+				dummy += v;
+			default:
+				dummy += '8';
+				return;
+				dummy += '9';
+		}
+		dummy += '0';
+		@:yield return 30;
+		dummy += '-';
+	}
+
+	#if broken
+	public function testSwitch_withoutYield() {
+		assert([30], switch_withoutYield(One));
+		assert([30], switch_withoutYield(Two(10)));
+		assert([30], switch_withoutYield(Three('hello')));
+		assert([30], switch_withoutYield(Four));
+	}
+
+	@:yield function switch_withoutYield(arg) {
+		var state = __ctx__.state;
+		switch(arg) {
+			case One: Assert.equals(state, __ctx__.state);
+			case Two(v): Assert.equals(state, __ctx__.state);
+			case Three(v): Assert.equals(state, __ctx__.state);
+			case _: Assert.equals(state, __ctx__.state);
+		}
+		Assert.equals(state, __ctx__.state);
+		@:yield return 30;
+	}
+	#end
+
+	public function testSwitch_multipleSwitch() {
+		assert([20, 30, 40], switch_multipleSwitch(One));
+		assert([10, 20, 40], switch_multipleSwitch(Two(999)));
+	}
+
+	@:yield function switch_multipleSwitch(arg) {
+		switch(arg) {
+			case Two(_): @:yield return 10;
+			case _:
+		}
+		@:yield return 20;
+		switch(arg) {
+			case One: @:yield return 30;
+			case _:
+		}
+		@:yield return 40;
+	}
+
+	public function testNoYieldSwitchAsArgument() {
+		assert([10], noYieldSwitchAsArgument(10));
+	}
+
+	@:yield function noYieldSwitchAsArgument(arg:Int) {
+		var fn = function(v:Int) return v;
+		var result = fn(switch(arg) {
+			case _: arg;
+		});
+		@:yield return result;
+	}
+}

+ 171 - 0
tests/misc/coroutines/src/yield/TestYieldTryCatch.hx

@@ -0,0 +1,171 @@
+package yield;
+
+import utest.Assert;
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldTryCatch extends BaseCase {
+
+	public function testTryCatch_noCatch() {
+		assert([10], tryCatch_noCatch());
+		Assert.equals('1235', dummy);
+	}
+
+	@:yield function tryCatch_noCatch() {
+		dummy += '1';
+		try {
+			dummy += '2';
+			@:yield return 10;
+			dummy += '3';
+		}
+		catch(e:Dynamic) {
+			dummy += '4';
+		}
+		dummy += '5';
+	}
+
+
+	public function testTryCatch_oneCatch() {
+		assert([10], tryCatch_oneCatch());
+		Assert.equals('12456', dummy);
+	}
+
+	@:yield function tryCatch_oneCatch() {
+		dummy += '1';
+		try {
+			dummy += '2';
+			throw 'Error!';
+			dummy += '3';
+		}
+		catch(e:Dynamic) {
+			dummy += '4';
+			@:yield return 10;
+			dummy += '5';
+		}
+		dummy += '6';
+	}
+
+	#if broken
+
+	public function testTryCatch_multiCatch() {
+		assert([10], tryCatch_multiCatch('Error'));
+		Assert.equals('12458', dummy);
+		assert([20], tryCatch_multiCatch(123));
+		Assert.equals('12678', dummy);
+	}
+
+	@:yield function tryCatch_multiCatch(throwValue:Dynamic) {
+		dummy += '1';
+		try {
+			dummy += '2';
+			throw throwValue;
+			dummy += '3';
+		}
+		catch(e:String) {
+			dummy += '4';
+			@:yield return 10;
+			dummy += '5';
+		}
+		catch(e:Dynamic) {
+			dummy += '6';
+			@:yield return 20;
+			dummy += '7';
+		}
+		dummy += '8';
+	}
+
+	public function testTryCatch_nested() {
+		assert([10], tryCatch_nested(1));
+		Assert.equals('124569', dummy);
+		assert([20], tryCatch_nested('Error!'));
+		Assert.equals('12789', dummy);
+	}
+
+	@:yield function tryCatch_nested(throwValue:Dynamic) {
+		dummy += '1';
+		try {
+			try {
+				dummy += '2';
+				throw throwValue;
+				dummy += '3';
+			}
+			catch(e:Int) {
+				dummy += '4';
+				@:yield return 10;
+				dummy += '5';
+			}
+			dummy += '6';
+		}
+		catch(e:Dynamic) {
+			dummy += '7';
+			@:yield return 20;
+			dummy += '8';
+		}
+		dummy += '9';
+	}
+
+	public function testTryCatch_withoutYield_runInSingleState() {
+		assert([10], tryCatchNoYield(true));
+	}
+
+	@:yield function tryCatchNoYield(condition:Bool) {
+		var state = __ctx__.state; //__ctx__ is generated by build macros
+		try {
+			Assert.equals(state, __ctx__.state);
+		}
+		catch(e:Dynamic){
+			Assert.equals(state, __ctx__.state);
+		}
+		Assert.equals(state, __ctx__.state);
+
+		@:yield return 10;
+	}
+
+	public function testTryCatch_exceptionNotCaught_thrownOutOfYieldContext() {
+		try {
+			assert([], tryCatchNotCaught());
+			Assert.fail();
+		}
+		catch(e:String) {
+			Assert.equals('Error!', e);
+			Assert.equals('12', dummy);
+		}
+	}
+
+	@:yield function tryCatchNotCaught() {
+		dummy += '1';
+		try {
+			dummy += '2';
+			throw "Error!";
+			dummy += '3';
+			@:yield return 10;
+			dummy += '4';
+		}
+		catch(e:Int){
+			dummy += '5';
+		}
+		dummy += '6';
+	}
+
+	#end
+
+	public function testTryCatch_captureVariable() {
+		assert([10], tryCatch_captureVariable());
+		Assert.equals('12456', dummy);
+	}
+
+	@:yield function tryCatch_captureVariable() {
+		dummy += '1';
+		try {
+			dummy += '2';
+			throw 10;
+			dummy += '3';
+		}
+		catch(e:Int) {
+			dummy += '4';
+			@:yield return e;
+			dummy += 5;
+		}
+		dummy += '6';
+	}
+}

+ 98 - 0
tests/misc/coroutines/src/yield/TestYieldWhile.hx

@@ -0,0 +1,98 @@
+package yield;
+
+import yield.Yield;
+
+@:build(yield.YieldMacro.build())
+class TestYieldWhile extends BaseCase {
+
+	public function testWhile_basicYieldReturn() {
+		assert([11, 21, 31], while_basicYieldReturn(1));
+		Assert.equals('01122334', dummy);
+	}
+
+	@:yield function while_basicYieldReturn(arg:Int) {
+		dummy += '0';
+		var i = 1;
+		while(i < 4) {
+			dummy += i;
+			@:yield return i * 10 + arg;
+			dummy += i;
+			i++;
+		}
+		dummy += '4';
+	}
+
+
+	public function testWhile_basicYieldBreak() {
+		assert([10], while_basicYieldBreak());
+		Assert.equals('012', dummy);
+	}
+
+	@:yield function while_basicYieldBreak() {
+		dummy += '0';
+		@:yield return 10;
+		dummy += '1';
+		var i = 2;
+		while(i < 100) {
+			dummy += i;
+			return;
+			dummy += i;
+			i++;
+		}
+		dummy += '101';
+	}
+
+
+	public function testWhile_nested() {
+		assert([0, 1, 10, 11], while_nested());
+		Assert.equals('0[><><][><><]2', dummy);
+	}
+
+	@:yield function while_nested() {
+		dummy += '0';
+		var i = 0;
+		while(i < 2) {
+			dummy += '[';
+			var j = 0;
+			while(j < 2) {
+				dummy += '>';
+				@:yield return i * 10 + j;
+				dummy += '<';
+				j++;
+			}
+			dummy += ']';
+			i++;
+		}
+		dummy += '2';
+	}
+
+
+	public function testWhile_breakContinue() {
+		assert([0, -1, 2], while_breakContinue());
+		Assert.equals('12356789235235670', dummy);
+	}
+
+	@:yield function while_breakContinue() {
+		dummy += '1';
+		var i = -1;
+		while(i < 10) {
+			i++;
+			dummy += '2';
+			while(true) {
+				dummy += '3';
+				break;
+				dummy += '4';
+			}
+			dummy += '5';
+			if(i == 1) continue;
+			dummy += '6';
+			@:yield return i;
+			dummy += '7';
+			if(i == 2) break;
+			dummy += '8';
+			@:yield return -1;
+			dummy += '9';
+		}
+		dummy += '0';
+	}
+}

+ 36 - 0
tests/misc/coroutines/src/yield/Yield.hx

@@ -0,0 +1,36 @@
+package yield;
+import haxe.coro.Coroutine;
+
+typedef Yield<T> = Coroutine<T->Void>;
+
+function sequence<T>(f:Coroutine<Yield<T>->Void>):Iterator<T> {
+	var finished = false;
+	var nextValue:T = null;
+
+	var nextStep = null;
+
+	function finish(_, _) {
+		finished = true;
+	}
+
+	@:coroutine function yield(value:T) {
+		nextValue = value;
+		Coroutine.suspend(cont -> nextStep = cont);
+	}
+
+	function hasNext():Bool {
+		if (nextStep == null) {
+			nextStep = f.create(yield, finish);
+			nextStep(null, null);
+		}
+		return !finished;
+	}
+
+	function next():T {
+		var value = nextValue;
+		nextStep(null, null);
+		return value;
+	}
+
+	return {hasNext: hasNext, next: next};
+}

+ 61 - 0
tests/misc/coroutines/src/yield/YieldMacro.hx

@@ -0,0 +1,61 @@
+package yield;
+
+import haxe.macro.Context;
+import haxe.macro.Expr;
+import haxe.macro.Printer;
+using Lambda;
+using haxe.macro.Tools;
+
+class YieldMacro {
+	macro static public function build():Array<Field> {
+		var yieldFunctions = [];
+		var otherFunctions = [];
+		var inputFields = Context.getBuildFields();
+		for (field in inputFields) {
+			if (field.meta.exists(meta -> meta.name == ":yield")) {
+				var f = switch (field.kind) {
+					case FFun(f):
+						f;
+					case _:
+						Context.error("@:yield fields should be functions, found " + field.kind, field.pos);
+				}
+				transformYieldFunction(f, field.pos);
+				yieldFunctions.push(field);
+			}
+		}
+		return inputFields;
+	}
+
+	static function transformYieldFunction(f:Function, p:Position) {
+		if (f.expr == null) {
+			Context.error('@:yield function has no expression', p);
+		}
+		var ret = switch (f.ret) {
+			case macro :Iterator<$ct>:
+				macro : Coroutine<$ct -> Void>;
+			case _:
+				null;
+		}
+		function mapYield(e:Expr) {
+			return switch (e) {
+				case macro @:yield return $e:
+					e = mapYield(e);
+					macro @:pos(e.pos) yield($e);
+				case macro @:yield $e:
+					switch (e.expr) {
+						case EFunction(kind, f):
+							transformYieldFunction(f, e.pos);
+							e;
+						case _:
+							e.map(mapYield);
+					}
+				case _:
+					e.map(mapYield);
+			}
+		}
+		var e = mapYield(f.expr);
+		e = macro return sequence((yield : $ret) -> $e);
+		// trace(new Printer().printExpr(e));
+		f.expr = e;
+	}
+}