From 9f0238fee0b61909ec50334ed2fd8e69c055b379 Mon Sep 17 00:00:00 2001 From: Simon Krajewski Date: Thu, 15 Feb 2024 12:15:15 +0100 Subject: [PATCH] add yield tests from https://github.com/nadako/haxe-coroutines/pull/6 --- src/coro/coroFromTexpr.ml | 8 +- src/coro/coroToTexpr.ml | 2 +- src/filters/filters.ml | 1 - tests/misc/coroutines/src/BaseCase.hx | 19 ++ tests/misc/coroutines/src/Main.hx | 9 + tests/misc/coroutines/src/TestControlFlow.hx | 2 +- tests/misc/coroutines/src/TestGenerator.hx | 36 +--- tests/misc/coroutines/src/TestJsPromise.hx | 1 - tests/misc/coroutines/src/import.hx | 1 + .../coroutines/src/yield/TestYieldBasic.hx | 126 +++++++++++++ .../coroutines/src/yield/TestYieldClosure.hx | 88 +++++++++ .../misc/coroutines/src/yield/TestYieldFor.hx | 86 +++++++++ .../misc/coroutines/src/yield/TestYieldIf.hx | 97 ++++++++++ .../coroutines/src/yield/TestYieldSwitch.hx | 105 +++++++++++ .../coroutines/src/yield/TestYieldTryCatch.hx | 171 ++++++++++++++++++ .../coroutines/src/yield/TestYieldWhile.hx | 98 ++++++++++ tests/misc/coroutines/src/yield/Yield.hx | 36 ++++ tests/misc/coroutines/src/yield/YieldMacro.hx | 61 +++++++ 18 files changed, 904 insertions(+), 43 deletions(-) create mode 100644 tests/misc/coroutines/src/BaseCase.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldBasic.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldClosure.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldFor.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldIf.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldSwitch.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldTryCatch.hx create mode 100644 tests/misc/coroutines/src/yield/TestYieldWhile.hx create mode 100644 tests/misc/coroutines/src/yield/Yield.hx create mode 100644 tests/misc/coroutines/src/yield/YieldMacro.hx diff --git a/src/coro/coroFromTexpr.ml b/src/coro/coroFromTexpr.ml index 7b89ccd8a4f..3a85b9862c4 100644 --- a/src/coro/coroFromTexpr.ml +++ b/src/coro/coroFromTexpr.ml @@ -3,10 +3,6 @@ open Type open CoroTypes 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 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 DynArray.add cb.cb_el e 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 = terminate cb_from (NextFallThrough cb_to) t_dynamic null_pos in diff --git a/src/coro/coroToTexpr.ml b/src/coro/coroToTexpr.ml index b0868fa06b0..eb2685bb4ae 100644 --- a/src/coro/coroToTexpr.ml +++ b/src/coro/coroToTexpr.ml @@ -132,7 +132,7 @@ let block_to_texpr_coroutine ctx cb vcontinuation vresult verror p = cb.cb_id in if not (DynArray.empty cb.cb_el) then - add_state (Some cb_next.cb_id) [] + add_state (Some (skip_loop cb_next)) [] else skip_loop cb | NextReturnVoid | NextReturn _ as r -> diff --git a/src/filters/filters.ml b/src/filters/filters.ml index d9a1077fe60..8c19cc1dabf 100644 --- a/src/filters/filters.ml +++ b/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); "RenameVars",(match com.platform with | Eval -> (fun e -> e) - | Jvm -> (fun e -> e) | _ -> (fun e -> RenameVars.run tctx.c.curclass.cl_path locals e)); "mark_switch_break_loops",mark_switch_break_loops; ] in diff --git a/tests/misc/coroutines/src/BaseCase.hx b/tests/misc/coroutines/src/BaseCase.hx new file mode 100644 index 00000000000..0a447a46b23 --- /dev/null +++ b/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(expected:Array, generator:Iterator, ?p:haxe.PosInfos) { + dummy = ''; + for (it in generator) { + Assert.equals(expected.shift(), it, p); + } + Assert.equals(0, expected.length, p); + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/Main.hx b/tests/misc/coroutines/src/Main.hx index afd702d1be7..de85212d723 100644 --- a/tests/misc/coroutines/src/Main.hx +++ b/tests/misc/coroutines/src/Main.hx @@ -1,3 +1,5 @@ +import yield.*; + function main() { utest.UTest.run([ new TestBasic(), @@ -7,5 +9,12 @@ function main() { #if js new TestJsPromise(), #end + new TestYieldBasic(), + new TestYieldIf(), + new TestYieldFor(), + new TestYieldClosure(), + new TestYieldSwitch(), + new TestYieldTryCatch(), + new TestYieldWhile(), ]); } \ No newline at end of file diff --git a/tests/misc/coroutines/src/TestControlFlow.hx b/tests/misc/coroutines/src/TestControlFlow.hx index 9853c6a6c36..9fb2b33388f 100644 --- a/tests/misc/coroutines/src/TestControlFlow.hx +++ b/tests/misc/coroutines/src/TestControlFlow.hx @@ -118,7 +118,7 @@ class TestControlFlow extends utest.Test { } @:coroutine -private function mapCalls(args:Array, f:haxe.coro.CoroutineTRet>):Array { +private function mapCalls(args:Array, f:CoroutineTRet>):Array { return [for (arg in args) f(arg)]; } diff --git a/tests/misc/coroutines/src/TestGenerator.hx b/tests/misc/coroutines/src/TestGenerator.hx index b756905fc64..e224128001d 100644 --- a/tests/misc/coroutines/src/TestGenerator.hx +++ b/tests/misc/coroutines/src/TestGenerator.hx @@ -1,4 +1,4 @@ -import haxe.coro.Coroutine; +import yield.Yield; class TestGenerator extends utest.Test { function testSimple() { @@ -38,40 +38,6 @@ class TestGenerator extends utest.Test { } } -private typedef Yield = CoroutineVoid>; - -private function sequence(f:Coroutine->Void>):Iterator { - 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 = { var leaf:T; var ?left:Tree; diff --git a/tests/misc/coroutines/src/TestJsPromise.hx b/tests/misc/coroutines/src/TestJsPromise.hx index eaee2b331ac..9a6b9d57bf2 100644 --- a/tests/misc/coroutines/src/TestJsPromise.hx +++ b/tests/misc/coroutines/src/TestJsPromise.hx @@ -1,6 +1,5 @@ import js.lib.Error; import js.lib.Promise; -import haxe.coro.Coroutine; @:coroutine private function await(p:Promise):T { diff --git a/tests/misc/coroutines/src/import.hx b/tests/misc/coroutines/src/import.hx index 4a8d34165e8..a7e1d0bcaa3 100644 --- a/tests/misc/coroutines/src/import.hx +++ b/tests/misc/coroutines/src/import.hx @@ -1,2 +1,3 @@ import utest.Assert; import utest.Async; +import haxe.coro.Coroutine; \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldBasic.hx b/tests/misc/coroutines/src/yield/TestYieldBasic.hx new file mode 100644 index 00000000000..7b62bd50186 --- /dev/null +++ b/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 { + 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 { + @: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() + ]); +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldClosure.hx b/tests/misc/coroutines/src/yield/TestYieldClosure.hx new file mode 100644 index 00000000000..56183ea8f2d --- /dev/null +++ b/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; + } + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldFor.hx b/tests/misc/coroutines/src/yield/TestYieldFor.hx new file mode 100644 index 00000000000..3f1511c4108 --- /dev/null +++ b/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'; + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldIf.hx b/tests/misc/coroutines/src/yield/TestYieldIf.hx new file mode 100644 index 00000000000..45dcf6be1a5 --- /dev/null +++ b/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'; + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldSwitch.hx b/tests/misc/coroutines/src/yield/TestYieldSwitch.hx new file mode 100644 index 00000000000..efb51ce0690 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldTryCatch.hx b/tests/misc/coroutines/src/yield/TestYieldTryCatch.hx new file mode 100644 index 00000000000..d03b5794b81 --- /dev/null +++ b/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'; + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/TestYieldWhile.hx b/tests/misc/coroutines/src/yield/TestYieldWhile.hx new file mode 100644 index 00000000000..f1561956a40 --- /dev/null +++ b/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'; + } +} \ No newline at end of file diff --git a/tests/misc/coroutines/src/yield/Yield.hx b/tests/misc/coroutines/src/yield/Yield.hx new file mode 100644 index 00000000000..3b63d40edd7 --- /dev/null +++ b/tests/misc/coroutines/src/yield/Yield.hx @@ -0,0 +1,36 @@ +package yield; +import haxe.coro.Coroutine; + +typedef Yield = CoroutineVoid>; + +function sequence(f:Coroutine->Void>):Iterator { + 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}; +} diff --git a/tests/misc/coroutines/src/yield/YieldMacro.hx b/tests/misc/coroutines/src/yield/YieldMacro.hx new file mode 100644 index 00000000000..4e86d53d65b --- /dev/null +++ b/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 { + 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; + } +} \ No newline at end of file