Last active
January 19, 2025 17:27
-
-
Save Enichan/5f01140530ff0133fde19c9549a2a973 to your computer and use it in GitHub Desktop.
Coroutines for C using Duff's Device based on Simon Tatham's coroutines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// coroutines for C adapted from Simon Tatham's coroutines | |
// https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html | |
// | |
// this implementation utilizes __VA_ARGS__ and __COUNTER__ | |
// to avoid a begin/end pair of macros. | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <stdbool.h> | |
// modify this if you don't like the `self->name` format | |
#define COSELF self | |
#define COVAR __co | |
#define costate(...) typedef struct __VA_ARGS__ CoState; | |
#define yieldBreak \ | |
COVAR->Co.state = -1; \ | |
if (COVAR->Co.locals) { \ | |
free(COVAR->Co.locals); \ | |
COVAR->Co.locals = NULL; \ | |
} \ | |
return false; | |
#define coroutine(...) \ | |
if (COVAR->Co.locals == NULL) { \ | |
COVAR->Co.locals = calloc(1, sizeof(CoState)); \ | |
if (COVAR->Co.locals == NULL) { \ | |
COVAR->Co.state = -1; \ | |
return false; \ | |
} \ | |
} \ | |
CoState* COSELF = (CoState*)COVAR->Co.locals; \ | |
switch (COVAR->Co.state) { \ | |
case 0:; \ | |
__VA_ARGS__ \ | |
default: \ | |
yieldBreak; \ | |
} | |
// do while from https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html: | |
// note the use of do ... while(0) to ensure that crReturn does not need braces | |
// around it when it comes directly between if and else | |
#define yield(v) _yield(v, __COUNTER__) | |
#define _yield(v, l) \ | |
do { \ | |
COVAR->Co.state = l; COVAR->Result = v; return true; \ | |
case l:; \ | |
} while (0) | |
#define yieldVoid _yieldVoid(__COUNTER__) | |
#define _yieldVoid(l) \ | |
do { \ | |
COVAR->Co.state = l; return true; \ | |
case l:; \ | |
} while (0) | |
// No empty structs allowed in C :( | |
#define costateEmpty typedef struct { char __codummy__; } CoState; | |
// increment __COUNTER__ past 0 so we don't get double case errors | |
#define CONCAT_IMPL(x, y) x##y | |
#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) | |
#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__) | |
__INC_COUNTER; | |
typedef struct { | |
int state; | |
void* locals; | |
} Coroutine; | |
void* CoCreate(Coroutine* co, size_t size) { | |
if (co->locals != NULL) { | |
free(co->locals); | |
} | |
co->state = 0; | |
co->locals = calloc(1, size); | |
return co->locals; | |
} | |
void CoBreak(Coroutine* co) { | |
co->state = -1; | |
if (co->locals != NULL) { | |
free(co->locals); | |
co->locals = NULL; | |
} | |
} | |
typedef struct { | |
Coroutine Co; | |
} CoVoid; | |
typedef struct { | |
int Result; | |
Coroutine Co; | |
} CoInt; | |
bool CountToTen(CoInt* COVAR) { | |
costate({ | |
int i; | |
} | |
); | |
coroutine({ | |
for (self->i = 0; self->i < 10; self->i++) { | |
yield(self->i); | |
if (self->i > 5) { | |
yieldBreak; | |
} | |
} | |
} | |
); | |
} | |
bool PrintThings(CoVoid* COVAR) { | |
costateEmpty; | |
coroutine({ | |
printf("i"); | |
yieldVoid; | |
printf("am"); | |
yieldVoid; | |
printf("printing"); | |
yieldVoid; | |
printf("some"); | |
yieldVoid; | |
printf("things\n"); | |
} | |
); | |
} | |
typedef struct ComplexState { | |
int A; | |
int B; | |
int i; | |
} ComplexState; | |
bool ComplexCo(CoInt* COVAR) { | |
costate(ComplexState); | |
coroutine({ | |
for (self->i = 0; self->i < 10; self->i++) { | |
yield((self->i + self->A) * self->B); | |
} | |
} | |
); | |
} | |
// look it just makes things prettier okay? | |
#define empty { 0 } | |
int main() { | |
CoInt myCo = empty; | |
while (CountToTen(&myCo)) { | |
printf("%d\n", myCo.Result); | |
// use CoBreak to dispose of an unfinished coroutine from the outside | |
// this is safe to call on an already disposed coroutine | |
//CoBreak(&myCo.Co); | |
} | |
CoVoid voidCo = empty; | |
while (PrintThings(&voidCo)) { | |
printf(" "); | |
} | |
CoInt complex = empty; | |
ComplexState* complexState = CoCreate(&complex.Co, sizeof(ComplexState)); | |
complexState->A = 10; | |
complexState->B = 2; | |
while (ComplexCo(&complex)) { | |
printf("%d\n", complex.Result); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment