{"id":23503531,"url":"https://github.com/realyuniquename/coro","last_synced_at":"2025-04-15T21:50:57.840Z","repository":{"id":151066519,"uuid":"148179739","full_name":"RealyUniqueName/Coro","owner":"RealyUniqueName","description":"Async/await, generators, and arbitrary coroutines for Haxe","archived":false,"fork":false,"pushed_at":"2019-03-05T08:29:27.000Z","size":38,"stargazers_count":57,"open_issues_count":1,"forks_count":3,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-29T01:51:42.419Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RealyUniqueName.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-09-10T15:45:54.000Z","updated_at":"2023-10-07T01:06:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"14e4a793-e0b4-4431-a5e0-61f3f5260e1c","html_url":"https://github.com/RealyUniqueName/Coro","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FCoro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FCoro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FCoro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FCoro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RealyUniqueName","download_url":"https://codeload.github.com/RealyUniqueName/Coro/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249161104,"owners_count":21222468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-25T08:30:05.956Z","updated_at":"2025-04-15T21:50:57.832Z","avatar_url":"https://github.com/RealyUniqueName.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Coro\n\nThis library is the Haxe compiler plugin which provides generic coroutines implementation.\n\nThe library also includes async/await and generators implementations on top of the plugin.\n\n# Installation\n\nTo use this plugin you will need to setup Haxe for development (see [Building Haxe from source](https://haxe.org/documentation/introduction/building-haxe.html))\nAnd then:\n```\n$ git clone https://github.com/RealyUniqueName/coro.git\n$ cd coro\n$ haxelib dev coro .\n$ cd path/to/dev/haxe\n$ make PLUGIN=path/to/coro/src/ml/coro_plugin plugin\n```\nNow to use Coro all you need to do is to add `-lib coro` to your compilation flags.\n\n# Generator\n\n```haxe\nimport coro.Generator;\n\nclass Test {\n\tstatic public function main() {\n\t\tfor(n in fibonacci(5)) {\n\t\t\ttrace(n); // 1, 1, 2, 3, 5\n\t\t}\n\t}\n\n\tstatic function fibonacci(iterations:Int) return new Generator\u003cInt\u003e(yield -\u003e {\n\t\tvar current = 1;\n\t\tvar previous = 0;\n\t\twhile(iterations-- \u003e 0) {\n\t\t\tyield(current);\n\t\t\tvar next = current + previous;\n\t\t\tprevious = current;\n\t\t\tcurrent = next;\n\t\t}\n\t});\n}\n```\n\n# Async/await\n\n`coro.Async` mimics the `js.Promise` api.\n\nAdditionally it has `Async.wrap()` and `Async.wrapVoid` methods which can be used to transition from the 3rd-party\nasynchronous API to the `coro.Async`\n\n```haxe\nimport coro.Async;\n\nclass Test {\n\tstatic public function main() {\n\t\tmd5WebPage('http://example.com').start()\n\t\t\t.then(md5 -\u003e trace(md5)) \t\t//print the calculated hash in case of success\n\t\t\t.catchError(msg -\u003e trace(msg)); //print error message in case of failure\n\t}\n\n\tstatic function md5WebPage(url:String) return new AsyncValue(() -\u003e {\n\t\t//Wait a second. Just because I can.\n\t\tAsync.delay(1000).await();\n\t\ttry {\n\t\t\tvar contents = request(url).await();\n\t\t\treturn haxe.crypto.Md5.encode(contents);\n\t\t} catch(error:String) {\n\t\t\ttrace(error); //Log error message\n\t\t\tthrow error; //rethrow\n\t\t}\n\t});\n\n\tstatic function request(url:String) return Async.wrap((resolve, reject) -\u003e {\n\t\tvar http = new haxe.Http(url);\n\t\thttp.onData = resolve;\n\t\thttp.onError = error -\u003e reject(error);\n\t\thttp.request();\n\t});\n}\n```\n\n### Awaiting ES6 promises\n\n```haxe\nusing coro.Async;\n\nfunction greet(promise:js.Promise\u003cString\u003e) return new Async(() -\u003e {\n\tvar name = promise.await();\n\ttrace('Hello, $name!');\n});\n```\n\n### Converting Async to ES6 promise\n\n```haxe\nusing coro.Async;\n\nfunction delayGreet(name:String):Promise\u003cString\u003e {\n\tvar async = new AsyncValue\u003cString\u003e(() -\u003e {\n\t\tAsync.delay(1000).await();\n\t\treturn name;\n\t});\n\n\t//convert coroutine to promise\n\treturn async.promise();\n}\n```\n\n\n# Pipe\n\nPipe is the coroutine, which allows two-way communication between the coroutine caller and the coroutine itself.\n\n```haxe\nimport coro.Pipe;\n\nclass Test {\n\tstatic public function main() {\n\t\tvar repl = getRepl();\n\t\twhile(true) {\n\t\t\tSys.println('Type a command: ');\n\t\t\tvar cmd = input();\n\t\t\ttry {\n\t\t\t\t//send the command to REPL and get the result back\n\t\t\t\tvar result = repl.send(cmd);\n\t\t\t\tSys.println('Result: $result');\n\t\t\t} catch(e:ClosedPipeException) {\n\t\t\t\t//Pipe closed with the \"exit\" command\n\t\t\t\tbreak;\n\t\t\t} catch(e:Dynamic) {\n\t\t\t\tSys.println('Error: $e');\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t//Get the value returned with `return value` expression in the Pipe\n\t\tvar cmdCount = repl.getResult();\n\t\tSys.println('Total commands executed: $cmdCount');\n\t}\n\n\tstatic function getRepl() return new Pipe\u003cInt,String,Int\u003e(yield -\u003e {\n\t\tvar parser = ~/^(\\d+)\\s*\\+\\s*(\\d+)$/; //Allows \"123 + 456\"\n\t\tvar counter = 0;\n\t\tvar command = yield(0);\n\t\twhile(command != 'exit') {\n\t\t\t++counter;\n\t\t\tif(parser.match(command)) {\n\t\t\t\tvar eval = Std.parseInt(parser.matched(1)) + Std.parseInt(parser.matched(2));\n\t\t\t\t//Send the evaluated value to the caller and wait for the next command\n\t\t\t\tcommand = yield(eval);\n\t\t\t} else {\n\t\t\t\tthrow 'Invalid command: $command';\n\t\t\t}\n\t\t}\n\t\treturn counter; //this value can be accessed with the `Pipe.getResult()` method\n\t});\n\n\tstatic function input():String {\n\t\tvar result = '';\n\t\tvar c = Sys.getChar(true);\n\t\twhile(c != 13) {\n\t\t\tresult += String.fromCharCode(c);\n\t\t\tc = Sys.getChar(true);\n\t\t}\n\t\tSys.print('\\n');\n\t\treturn result;\n\t}\n}\n```\n\n# Arbitrary coroutine example\n\nEvery call in this example suspends execution of the `greet()`:\n```haxe\nfunction greet() return new Dialog(() -\u003e {\n\tspeak(\"What's your name?\");\n\tvar name = listen();\n\tspeak('Nice to meet you, $name!');\n\twait(1000);\n\texplode();\n});\n```\n\nSee https://github.com/RealyUniqueName/Coro/blob/master/tests/cases/TestDialogExample.hx#L10\n\n# Implementation details\n\nThe plugin operates on the Typed Syntax Tree. It is executed after typing step of the compiler and before the optimization step.\n\nThe plugin transforms local function declarations to state machines if passed to the argument\nof `coro.Coroutine` type followed by the optional argument of `coro.Coroutine.Generated` type.\nTransformed function is then passed to the argument of `Generated` type while the original function\nis replaced with `null`.\nFunction arguments list gets appended with the one or two new arguments: `sm` or `sm, resumeValue`\n\nE.g. if the signature of `generator` function is\n```haxe\n\tfunction generator(\n\t\tuserFunction:Coroutine\u003cyield:YieldType-\u003eReturnType\u003e,\n\t\t?genFunction:Generated\u003c(yield:YieldType, sm:StateMachineType, resumeValue:ResumeType)-\u003eVoid\u003e\n\t)\n```\nthen this expression\n```haxe\n\tgenerator(yield -\u003e {/* body */});\n```\nis transformed to\n```haxe\n\tgenerator(null, (yield, sm, resumeValue) -\u003e {/* transformed body */});\n```\n\nSuch approach was chosen to stay in the boundaries of the Haxe type system.\n\nThe benefits are\n\n* Full compiler-based completion support;\n* No macros;\n* No auto generated types or fields;\n\nThe downside is impossibility to invoke super methods in a coroutine.\n\nThe coroutine body is split into states by suspending calls.\nA function is considered suspending if the signature of a callee is `coro.Suspend\u003cT:Function\u003e` (e.g. `coro.Suspend\u003c()-\u003eVoid\u003e`)\nor if the callee is a method with `@:suspend` meta applied to it.\n\nNow for example the fibonacci generator mentioned above is transformed to the following state machine:\n```haxe\nstatic function fibonacci(iterations:Int) {\n\tvar previous;\n\tvar current;\n\treturn new coro.Generator(null, function(yield:coro.Suspend\u003c(Int)-\u003eVoid\u003e, sm:coro.Generator\u003cInt\u003e) {\n\t\tvar state = sm.state;\n\t\t//if an exception will raise, the state machine will be left in `Interrupted` state\n\t\t//which is `-2`. See `coro.Coroutine.StateExitCode` enum.\n\t\tsm.state = -2;\n\t\twhile (true)\n\t\t\tif (state == 0) {\n\t\t\t\tcurrent = 1;\n\t\t\t\tprevious = 0;\n\t\t\t\tstate = 1;\n\t\t\t} else if (state == 1) {\n\t\t\t\tif (iterations-- \u003e 0) {\n\t\t\t\t\tsm.nextState = 2;\n\t\t\t\t\tyield(current);\n\t\t\t\t\t//\"yield\" is the suspending function, so the `return` is generated to suspend execution\n\t\t\t\t\treturn sm.state = sm.nextState;\n\t\t\t\t} else\n\t\t\t\t\tstate = 3;\n\t\t\t} else if (state == 2) {\n\t\t\t\tvar next = current + previous;\n\t\t\t\tprevious = current;\n\t\t\t\tcurrent = next;\n\t\t\t\tstate = 1;\n\t\t\t} else if (state == 3)\n\t\t\t\treturn sm.state = sm.nextState = -1\n\t\t\telse\n\t\t\t\tthrow new coro.CoroutineStateException(state);\n\t});\n}\n```\n\n# Tests\n\nTo run test:\n\nFor eval: `$ haxe tests.hxml --interp`\n\nFor js: `$ haxe tests.hxml -js bin/test.js \u0026\u0026 node bin/test.js`\n\nFor java: `$ haxe tests.hxml -java bin/java \u0026\u0026 java -jar bin/java/Tests-Debug.jar`\n\netc.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealyuniquename%2Fcoro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealyuniquename%2Fcoro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealyuniquename%2Fcoro/lists"}