summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTyge Løvset <[email protected]>2023-02-22 14:35:56 +0100
committerTyge Løvset <[email protected]>2023-02-22 15:05:27 +0100
commitb63df794ee2bda2c33856a646ca6e1386f5ab782 (patch)
tree93ae40aca24ed5a71adfec252e2b4255db6f617b
parenta8fc8ac4e8c1481300e0d46bbd376f32ebeb4635 (diff)
downloadSTC-modified-b63df794ee2bda2c33856a646ca6e1386f5ab782.tar.gz
STC-modified-b63df794ee2bda2c33856a646ca6e1386f5ab782.zip
Added coroutines, based upon Simon Tatham's famous implementation. This version is much improved, but uses the same basic mechanisms.
-rw-r--r--include/stc/algo/cco.h136
-rw-r--r--misc/examples/cofib.c40
-rw-r--r--misc/examples/coread.c41
3 files changed, 217 insertions, 0 deletions
diff --git a/include/stc/algo/cco.h b/include/stc/algo/cco.h
new file mode 100644
index 00000000..f7ed7ff1
--- /dev/null
+++ b/include/stc/algo/cco.h
@@ -0,0 +1,136 @@
+/*
+ * coroutine.h is copyright 1995, 2000 Simon Tatham.
+ * Modernized/improved 2023 Tyge Løvset.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+/*
+ * Coroutine mechanics, implemented on top of standard ANSI C. See
+ * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html for
+ * a full discussion of the theory behind this.
+ *
+ * To use these macros to define a coroutine, you need to write a
+ * function that looks something like this:
+ *
+ * [Re-entrant version using an explicit context structure]
+ *
+ * int ascending(ccontext* ctx) {
+ * cco_context(ctx,
+ * int idx;
+ * );
+ *
+ * cco_routine(c,
+ * for (c->idx = 0; c->idx < 10; c->idx += 1)
+ * cco_yield(c->idx);
+ *
+ * cco_finish:; // add this to support cco_stop!
+ * );
+ * return -1;
+ * }
+ *
+ * In the re-entrant version, you need to declare your persistent
+ * variables by the `cco_context' macro. This macro takes the base
+ * ccontext as first parameter.
+ *
+ * Note that the context variable is set back to zero when the
+ * coroutine terminates (by cco_stop, or by control reaching
+ * finish. This can make the re-entrant coroutines more useful
+ * than the static ones, because you can tell when they have
+ * finished.
+ *
+ * This mechanism could have been better implemented using GNU C
+ * and its ability to store pointers to labels, but sadly this is
+ * not part of the ANSI C standard and so the mechanism is done by
+ * case statements instead. That's why you can't put a cco_yield()
+ * inside a switch-statement.
+ *
+ * The re-entrant macros will malloc() the state structure on first
+ * call, and free() it when end is reached. If you want to
+ * abort in the middle, you can use, the caller should call the
+ * coroutine with `cco_stop(&ctx)' as parameter instead of &ctx alone.
+ *
+ * Ground rules:
+ * - never put `c_yield' within an explicit `switch'.
+ * - never put two `c_yield' statements on the same source line.
+ * - add `cco_finish:' label at the end of the coroutine, before
+ * any cleanup code. Required to support cco_stop(ctx) argument.
+ *
+ * The caller of a re-entrant coroutine must provide a context
+ * variable:
+ *
+ * void main(void) {
+ * ccontext z = 0;
+ * for (;;) {
+ * int x = ascending(&z);
+ * if (!z) break;
+ * printf("got number %d\n", x);
+ *
+ * // stop if x == 5:
+ * if (x == 5) ascending(cco_stop(&z));
+ * }
+ * }
+ */
+
+#ifndef COROUTINE_H
+#define COROUTINE_H
+
+#include <stdlib.h>
+#include <assert.h>
+/*
+ * `c_' macros for re-entrant coroutines.
+ */
+typedef struct {
+ int _ccoline;
+} *ccontext;
+
+#define cco_context(cref, ...) \
+ struct ccoContext { \
+ int _ccoline; \
+ __VA_ARGS__ \
+ } **_ccoparam = (struct ccoContext **)cref
+
+#define cco_routine(ctx, ...) \
+ do { \
+ if (!*_ccoparam) { \
+ *_ccoparam = malloc(sizeof **_ccoparam); \
+ (*_ccoparam)->_ccoline = 0; \
+ } \
+ struct ccoContext *ctx = *_ccoparam; \
+ switch (ctx->_ccoline) { \
+ case 0: __VA_ARGS__ break; \
+ default: assert(!"cco_finish: missing"); \
+ } \
+ free(ctx), *_ccoparam = 0; \
+ } while (0)
+
+#define cco_yield(ret) \
+ do { \
+ (*_ccoparam)->_ccoline = __LINE__; return ret; \
+ case __LINE__:; \
+ } while (0)
+
+#define cco_finish case -1
+
+#define cco_stop(ctx) \
+ ((*(ctx))->_ccoline = -1, (ctx))
+
+#endif /* COROUTINE_H */
diff --git a/misc/examples/cofib.c b/misc/examples/cofib.c
new file mode 100644
index 00000000..d0e4ac2a
--- /dev/null
+++ b/misc/examples/cofib.c
@@ -0,0 +1,40 @@
+#include <stc/algo/cco.h>
+#include <stdio.h>
+#include <stdint.h>
+
+// Use coroutine to create a fibonacci sequence generator:
+
+typedef long long llong;
+
+llong fibonacci_sequence(ccontext* ctx, unsigned n) {
+ assert (n < 95);
+
+ cco_context(ctx,
+ llong a, b, idx;
+ );
+
+ cco_routine(u,
+ u->a = 0;
+ u->b = 1;
+ for (u->idx = 2; u->idx < n; u->idx++) {
+ llong sum = u->a + u->b;
+ cco_yield(sum);
+ u->a = u->b;
+ u->b = sum;
+ }
+ cco_finish:
+ );
+ return 0;
+}
+
+
+int main(void) {
+ ccontext z = 0;
+ printf("Fibonacci numbers:\n");
+ for (;;) {
+ llong x = fibonacci_sequence(&z, 30);
+ if (!z) break;
+ printf(" %lld", x);
+ }
+ puts("");
+}
diff --git a/misc/examples/coread.c b/misc/examples/coread.c
new file mode 100644
index 00000000..9181401d
--- /dev/null
+++ b/misc/examples/coread.c
@@ -0,0 +1,41 @@
+#include <stc/cstr.h>
+#include <stc/algo/cco.h>
+#include <errno.h>
+
+// Read file line by line using coroutines:
+
+cstr file_nextline(ccontext* ccx, const char* name)
+{
+ cco_context(ccx,
+ FILE* fp;
+ cstr line;
+ );
+
+ cco_routine(U,
+ U->fp = fopen(name, "r");
+ U->line = cstr_NULL;
+
+ while (cstr_getline(&U->line, U->fp))
+ cco_yield (cstr_clone(U->line));
+
+ cco_finish: // cco_finish is needed to support cco_stop.
+ printf("finish\n");
+ cstr_drop(&U->line);
+ fclose(U->fp);
+ );
+ return cstr_NULL;
+}
+
+
+int main(void) {
+ ccontext z = 0;
+ int n = 0;
+ do {
+ c_with (cstr line = file_nextline(&z, __FILE__), z, cstr_drop(&line)) {
+ printf("%3d %s\n", ++n, cstr_str(&line));
+
+ // stop after 10 lines:
+ if (n == 10) file_nextline(cco_stop(&z), __FILE__);
+ }
+ } while (z);
+}