Skip to content

Instantly share code, notes, and snippets.

@Enichan
Last active August 28, 2023 10:07
Show Gist options
  • Save Enichan/5f01140530ff0133fde19c9549a2a973 to your computer and use it in GitHub Desktop.
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
// 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