{"id":26600765,"url":"https://github.com/nim-works/cps","last_synced_at":"2025-04-06T20:08:37.129Z","repository":{"id":39658488,"uuid":"281553292","full_name":"nim-works/cps","owner":"nim-works","description":"Continuation-Passing Style for Nim 🔗","archived":false,"fork":false,"pushed_at":"2025-03-28T12:01:56.000Z","size":1983,"stargazers_count":204,"open_issues_count":22,"forks_count":17,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-03T13:49:37.763Z","etag":null,"topics":["async","await","concurrency","continuation","coroutines","cps","fibers","io","nim","parallel","passing","style","threads"],"latest_commit_sha":null,"homepage":"","language":"Nim","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/nim-works.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":"2020-07-22T02:25:08.000Z","updated_at":"2025-03-30T22:45:53.000Z","dependencies_parsed_at":"2023-02-17T05:31:17.304Z","dependency_job_id":"a58b5570-de7d-45bc-9b01-501caebca00f","html_url":"https://github.com/nim-works/cps","commit_stats":null,"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nim-works%2Fcps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nim-works%2Fcps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nim-works%2Fcps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nim-works%2Fcps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nim-works","download_url":"https://codeload.github.com/nim-works/cps/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543589,"owners_count":20955865,"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":["async","await","concurrency","continuation","coroutines","cps","fibers","io","nim","parallel","passing","style","threads"],"created_at":"2025-03-23T18:34:57.518Z","updated_at":"2025-04-06T20:08:37.099Z","avatar_url":"https://github.com/nim-works.png","language":"Nim","readme":"# Continuation-Passing Style\n\n[![Test Matrix](https://github.com/nim-works/cps/workflows/CI/badge.svg)](https://github.com/nim-works/cps/actions?query=workflow%3ACI)\n[![GitHub release (latest by date)](https://img.shields.io/github/v/release/nim-works/cps?style=flat)](https://github.com/nim-works/cps/releases/latest)\n![Minimum supported Nim version](https://img.shields.io/badge/nim-1.9.3-informational?style=flat\u0026logo=nim)\n![Recommended Nim version](https://img.shields.io/badge/nim-1.9.3-informational?style=flat\u0026logo=nim)\n![Maximum supported Nim version](https://img.shields.io/badge/nim-2.1.1-informational?style=flat\u0026logo=nim)\n[![License](https://img.shields.io/github/license/nim-works/cps?style=flat)](#license)\n[![Matrix](https://img.shields.io/matrix/cps:matrix.org?style=flat\u0026logo=matrix)](https://matrix.to/#/#cps:matrix.org)\n[![IRC](https://img.shields.io/badge/chat-%23cps%20on%20libera.chat-brightgreen?style=flat)](https://web.libera.chat/#cps)\n\n## TL;DR\n\nIt's a `cps` pragma you annotate a procedure declaration with in order to\n_automagically_ rewrite it as a type which holds 1) all the procedure's locals,\nand 2) a pointer to run the continuation.\n\nYou instantiate continuations by calling your function with arguments, or via\ntyped continuation callbacks. You may extend the Continuation type to add\ncustom data and functionality.\n\nThe macro provides comfort for automatic chaining, stack tracing, drying up\nfunction color, and handling exceptions. Hooks enable precisely-scoped control\nof much of the machinery, including memory management.\n\n## What Is CPS?\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick here for an awesome infographic explaining what CPS is and how our transform works.\n  \u003c/summary\u003e\n  \u003cimg alt=\"The Nim CPS Transform\" src=\"/docs/cps.svg\"/\u003e\n\u003c/details\u003e\n\nA `cps` proc macro enables enjoyment of the CPS abstraction for free, hiding all\nthe verbosity and spaghetti code paths of hand-written continuation passing.  It\ntakes your standard procedure and rewrites it at compile-time into a continuation\nform.\n\nThe macro performs only the control-flow rewrite. You may define your own\ncontinuation types. To manage them, you can choose from available dispatchers\nor customize your own.\n\nAs CPS is essentially a safe transformation of Nim to equivalent Nim, you can\nuse it anywhere you can use Nim, _or more accurately_, C, C++, or JavaScript.\n\n## Why Do I Want It?\n\nThe continuations produced by this macro are _tiny_ -- as little as 32 bytes\neach -- and run at the speed of any native function call; they…\n\n- compose efficient and idiomatic asynchronous code\n- are over a thousand times lighter than threads\n- are leak-free under Nim's modern memory management\n- may be extended with custom inheritance-based types\n- may be managed using custom dispatchers (executors)\n- may be moved between threads to parallelize execution\n- have no backend or runtime library requirements\n- exploit no unsafe features of the language (`cast`, `ptr`, `addr`, `emit`)\n\n## Why Haven't I Heard of It?\n\nThe project isn't dead; it simply hasn't needed much development. We have a few\nsmall improvements we want to make before a 1.0 major release, but the general\nconsensus is that the current implementation accomplishes our original goals\nquite well.\n\nMajor future development will revolve around implementing CPS directly in [the\nNimskull compiler](https://github.com/nim-works/nimskull).\n\n## How Does It Work?\n\nA substantial effort to demystify this style of programming, and what it may\nenable, lives [in the docs/ subdirectory](/docs).\n\nWe also have [a tutorial to help new users on their way to get starting with\nCPS](/tutorial/README.md).\n\nFor a description of the origins of our approach, see [the included\npapers](/papers) and\nhttps://github.com/nim-lang/RFCs/issues/295, where we write in more depth about\nwhy the implementation exists, goals for future development, etc.\n\n### Architecture\n\nThe implementation is comprised of two moving parts:\n\n1. a *continuation* is a bespoke type made to carry all locals in a procedure\n-- the _environment_ -- plus a function pointer with which the continuation\nis poised to continue, or _resume_:\n\n```nim\ntype\n  Continuation = ref object   # all continuations inherit from this\n    next: proc(c: Continuation): Continuation\n```\n\n1. a *dispatcher* is a procedure which resumes a continuation by invoking\nthe continuation's function pointer with the continuation itself as input.\nIt follows that to _suspend_, the function simply returns control to the\ndispatcher.\n\n```nim\nc = c.next(c)           # we often replace the continuation with its result\n```\n\nA *trampoline* is common form of dispatcher which resumes a continuation in a\nloop; it bounces control up to the continuation, which returns back down to the\ntrampoline when it's ready to suspend.\n\n```nim\nwhile true:\n  if c.isNil:           # 'dismissed': a nil continuation result\n    break\n  if not c.next.isNil:  # 'finished': a nil continuation function\n    break\n  c = c.next(c)         # resuming a 'running' (suspended) continuation\n```\n\n### Application\n\nWe use a procedure definition to define the continuation. The type we want to\nbase the continuation upon is supplied as the only argument to our `cps` pragma\nmacro.  You can simply use the `Continuation` type itself, if you prefer.\n\n```nim\nproc zoom() {.cps: Continuation.} =\n  ## a very fast continuation\n  discard\n```\n\nCalling the procedure (with arguments, if required) runs the continuation to\ncompletion.\n\n```nim\nzoom()\necho \"that was fast!\"\n```\n\nYou can extend the `Continuation` type to carry state during the execution of\nyour continuation.\n\n```nim\ntype\n  Count = ref object of Continuation\n    labels: Table[string, ContinuationFn]\n```\n\nHere we've introduced a table that maps strings to a continuation \"leg\", or\nslice of control-flow that is executed as a unit.\n\nNow we'll introduce two magical procedures for manipulating the table.\n\n```nim\nproc label(c: Count; name: string): Count {.cpsMagic.} =\n  ## Record a label by `name` which we can later goto().\n  c.labels[name] = c.fn\n  return c  # control resumes in the continuation\n\nproc goto(c: Count; name: string): Count {.cpsMagic.} =\n  ## Resume execution at the `name` label in the code.\n  c.fn = c.labels[name]\n  return c  # control resumes in the continuation\n```\n\nThese pragma'd procedures act as continuation legs and we can use them in our\ncontinuations without supplying the initial `Count` continuation argument.\n\nSome people call this \"colorless\" syntax, since calls look the same whether\nmade inside or outside of a continuation.\n\n```nim\nproc count(upto: int): int {.cps: Count.} =\n  ## deploy the Count to make counting fun again;\n  ## this continuation returns the number of trips through the goto\n  var number = 0\n  label: \"again!\"\n  inc number\n  echo number, \"!\"\n  echo number, \" loops, ah ah ah!\"\n  if number \u003c upto:\n    goto \"again!\"\n  echo \"whew!\"\n  return number\n\nconst many = 1_000_000_000\nassert many + 1 == count(many)  # (this might take awhile to finish)\n```\n\nSometimes you don't want to do a lot of counting right away, but, y'know, maybe\na bit later, after your nap. In that case, you can use `whelp` to instantiate\nyour continuation with arguments, without actually invoking it.\n\nWhen you're ready, the `trampoline` will run your continuation to completion\nand bounce it back to you.\n\n```nim\nvar later = whelp count(1_000_000)\nsleep 30*60*1000\necho \"it's later!  time to count!\"\nlater = trampoline later\n```\n\nContinuations have a simple `state(c: Continuation): State` enum that is helped\ninto `running()`, `finished()`, and `dismissed()` boolean predicates.\n\n```nim\nassert later.finished, \"counting incomplete!\"\n```\n\nContinuations can themselves be called in order to retrieve their result.\n\n```nim\necho \"i counted \", later(), \" trips through the goto\"\n```\n\nSuch a call will run the continuation on your behalf if it has not already run\nto completion.\n\n```nim\nvar later = whelp count(1_000_000)\necho \"we counted \", later(), \" trips through the goto\"\n```\n## Installation\n\n```sh\n$ nimble install https://github.com/nim-works/cps\n```\n\n## Documentation\n\n### Ready For More?\n\n[examples/coroutine.nim](/examples/coroutine.nim)\n ([walkthrough](/docs/coroutines.md)): A simple coroutine implementation of\n coroutines on top of CPS communicating with each other.\n\n### Complete API Documentation\n\nSee [the documentation for the cps module](https://nim-works.github.io/cps/cps.html) as generated directly from the source.\n\n### Extensive Test Suite\n\nAt last count, there are no less than [211 unique tests of the cps\nlibrary](/tests) which confirm successful handling of myriad semantic forms\nand complex continuation composition. The tests are arguably the most valuable\npiece of code in the project and, as per usual, serve as the most complete\ndocumentation of the specification.\n\n## Dispatchers\n\nAn example dispatcher with support for yields, I/O, and timers was included in\nthe past, but demonstrating dispatch conflated the roles of _continuation_ and\n_dispatcher_, confusing newcomers.\n\nFor a robust dispatcher implementation targeting broad OS support and modern\nasync features, take a look at https://github.com/alaviss/nim-sys.\n\n## Examples\n\nA small collection of examples provides good demonstration of multiple patterns\nof CPS composition. Each example runs independently, with no other requirements,\nyet demonstrates different exploits of `cps`.\n\n| Example | Description |\n|     --: | :--         |\n|[Channels](/examples/channels.nim)|A channel connects sender and receiver continuations|\n|[Goto](/examples/goto.nim)|Implementation of `label` and `goto` statements using CPS|\n|[Iterator](/examples/iterator.nim)|A simple demonstration of a CPS-based iterator|\n|[Coroutines](/examples/coroutine.nim)|A pair of continuations communicate as coroutines. [Walkthrough](/docs/coroutines.md).|\n|[Lazy](/examples/lazy.nim)|Lazy streams are composed by continuations in a functional style|\n|[Pipes](/examples/pipes.nim)|Coroutines compose streams which connect arbitrarily|\n|[TryCatch](/examples/trycatch.nim)|Exception handling is reimplemented using only CPS|\n|[CpsCps](/examples/cpscps.nim)|Continuations can efficiently call other continuations|\n|[Work](/examples/work.nim)|Implementation of a simple continuation scheduler|\n|[LuaCoroutines](/examples/lua_coroutines.nim)|Coroutines implemented in the style of Lua|\n|[ThreadPool](/examples/threadpool.nim)|1,000,000 continuations run across all your CPU cores|\n\n## Demonstration Projects\n\nHere are a few projects which demonstrate CPS library integration.\n\n#### Smaller\n\n| Project | Description |\n|     --: | :--         |\n|[WebServer](https://github.com/zevv/cpstest)|Zevv's \"Real World Test\" WebServer And More|\n|[Background](https://github.com/disruptek/background)|Run any function on a background thread|\n|[Passenger](https://github.com/disruptek/passenger)|Compose graph visualizations of CPS control-flow|\n|[HttpLeast](https://github.com/disruptek/httpleast)|A simple web-server for benchmarking CPS|\n|[solo5_dispatcher](https://git.sr.ht/~ehmry/solo5_dispatcher)|A simple CPS scheduler for Solo5 unikernels|\n\n#### Larger\n\n| Project | Description |\n|     --: | :--         |\n|[Balls](https://github.com/disruptek/balls)|A threaded test runner based upon CPS|\n|[Sys](https://github.com/alaviss/nim-sys)|Next-generation operating system service abstractions|\n|[Actors](https://github.com/zevv/actors)|Zevv's experimental project to create a threaded, share-nothing actor based framework on top of CPS|\n|[InsideOut](https://github.com/disruptek/insideout)|Another experimental concurrency library which supports CPS over threads|\n|[Taps](https://git.sr.ht/~ehmry/nim_taps)|A portable, asynchronous, network abstraction library|\n|[Syndicate](https://git.syndicate-lang.org/ehmry/syndicate-nim)|The Syndicated Actor Model for Nim|\n\n## Debugging\n\n### Expectations\n\nSee [this list of open Nim issues surfaced by CPS\ndevelopment](https://github.com/nim-lang/Nim/issues?q=is%3Aopen+is%3Aissue+label%3ACPS); some repercussions include the following:\n\n- Exceptions are evaluated differently under `panics:on` and `panics:off`, so\n  you may need to use `panics:on` in order to produce reliably correct code.\n\n- Expressions are evaluated differently under `gc:[ao]rc`, so you may need to\n  use those memory managers in order to produce reliably correct code.\n\n- The `cpp` backend often doesn't work, particularly due to faulty codegen but\n  also, perhaps, due to `exceptions:goto` assumptions that we rely upon.\n\n- `var`/`openArray`/`varargs` parameters to procedures with the `cps` pragma\n  are not supported.\n\n- Nim's `for` loops work, but you cannot perform any CPS control-flow inside of\n  them; if in doubt, use a `while` loop instead.\n\n### Call Syntax\n\nUse `--define:cpsNoCallOperator` to explicitly disable the `()` operator.\n\n### Performance\n\nIf you are not running with `define:danger` and `gc:arc` and `panics:on` then\nperformance considerations really aren't your primary consideration, right?\n\n### Tracing\n\nThere are two tracing systems that are enabled by default via Nim's built-in\n`stackTrace:on` compiler option, which defaults to `on` outside of `release`\nand `danger` builds.\n\n#### Stack Frames\n\nStack semantics are implemented by a single `TraceFrame` object stored on each\ncontinuation and these serve to capture the progress of control-flow through\nmultiple levels of CPS calls just as they do so for normal stack-based code.\n\nThe `renderStackFrames()` and `writeStackFrames()` procedures return a sequence\nof strings or write them to the standard message stream, respectively. You can\nrun these procedures without arguments in any continuation.\n\nYou can extend the `Stack` hook to alter its behavior.\n\nForce-enable the stack frame support with `--define:cpsStackFrames=on`.\n\n#### Trace Deque\n\nThe trace deque system stores the last _N_ hooks executed by the continuation,\nwhere _N_ defaults to `4096` (see below). A single continuation has multiple\nhooks executed during its lifecycle, so these traces can be very detailed.\n\nThe `renderTraceDeque()` and `writeTraceDeque()` procedures return a sequence\nof strings or write them to the standard message stream, respectively. You can\nrun these procedures without arguments in any continuation.\n\nYou can extend the `Trace` hook to alter its behavior.\n\nForce-enable the trace deque support with `--define:cpsTraceDeque=on` and\nalter the size of the deque with e.g. `--define:traceDequeSize=100`.\n\n### Using `cpsDebug`\n\nAdd `--define:cpsDebug=SomePass` where `SomePass` matches one of the CPS\ntransformation passes; this will output Nim codegen corresponding to the\nrewrite phase. Interesting places to start include the following:\n\n- `cpsTransform`\n- `cpsResolver`\n- `cpsJump`\n- `cpsContinuationJump`\n- `cpsManageException`\n- `cpsTryFinally`\n- etc.\n\nNote that only one `--define:cpsDebug=...` can be enabled at a time - multiple defines will use only the final one.\n\n### Using `trace`\n\nImplement `trace` and it will be called at each continuation leg; [see the documentation for details](https://nim-works.github.io/cps/cps.html#trace.m%2Cstatic%5BHook%5D%2Ctyped%2Ctyped%2Cstring%2CLineInfo%2Ctyped).\n\nUse `--define:cpsNoTrace` to disable the `trace` code generation.\n\n## License\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnim-works%2Fcps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnim-works%2Fcps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnim-works%2Fcps/lists"}