[ccan] [PATCH] RFC: generator: Generators for C

David Gibson david at gibson.dropbear.id.au
Fri Jul 11 23:19:56 EST 2014


Yeah, yeah, I should finish one module before making another.  What
can I say, I like my Python generators and I was intrigued to see if I
could do it in C.

This draft is very limited - generators can't take parameters and
always return ints.  I have some ideas on how to fix that, but for now
wanted to send this out for comment.  Frankly, I'm kind of stoked it
worked even this much.

Generators are a limited for of co-routine, which people may be familiar
with from Python.  This module adds an implementation of generators for C.

Signed-off-by: David Gibson <david at gibson.dropbear.id.au>
---
 ccan/generator/LICENSE     |  1 +
 ccan/generator/_info       | 56 +++++++++++++++++++++++++++++++++++++++++++
 ccan/generator/generator.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++
 ccan/generator/generator.h | 47 ++++++++++++++++++++++++++++++++++++
 ccan/generator/test/run.c  | 37 ++++++++++++++++++++++++++++
 5 files changed, 201 insertions(+)
 create mode 120000 ccan/generator/LICENSE
 create mode 100644 ccan/generator/_info
 create mode 100644 ccan/generator/generator.c
 create mode 100644 ccan/generator/generator.h
 create mode 100644 ccan/generator/test/run.c

diff --git a/ccan/generator/LICENSE b/ccan/generator/LICENSE
new file mode 120000
index 0000000..dc314ec
--- /dev/null
+++ b/ccan/generator/LICENSE
@@ -0,0 +1 @@
+../../licenses/LGPL-2.1
\ No newline at end of file
diff --git a/ccan/generator/_info b/ccan/generator/_info
new file mode 100644
index 0000000..0d9bc6b
--- /dev/null
+++ b/ccan/generator/_info
@@ -0,0 +1,56 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * generator - generators for C
+ *
+ * Generators are a limited form of coroutines, which provide a useful
+ * way of expressing certain problems, while being much simpler to
+ * understand than general coroutines.
+ *
+ * Instead of returning a single value, a generator can "yield" a
+ * value at various points during its execution.  Whenever it yields,
+ * the "calling" function resumes and obtains the newly yielded value
+ * to work with.  When the caller asks for the next value from the
+ * generator, the generator resumes execution from the last yield and
+ * continues onto the next.
+ *
+ * Example:
+ *	#include <stdio.h>
+ *	#include <ccan/generator/generator.h>
+ *
+ *	generator_def(simple_gen)
+ *	{
+ *      	yield(1);
+ *		yield(3);
+ *		yield(17);
+ *	}
+ *
+ *	int main(int argc, char *argv[])
+ *	{
+ *		struct generator_state *gen = simple_gen();
+ *		int val;
+ *
+ *		while(generator_getnext(gen, val)) {
+ *			printf("Generator returned %d\n", val);
+ *		}
+ *
+ *		return 0;
+ *	}
+ *
+ * License: LGPL (v2.1 or any later version)
+ * Author: David Gibson <david at gibsond.dropbear.id.au>
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		return 0;
+	}
+
+	return 1;
+}
diff --git a/ccan/generator/generator.c b/ccan/generator/generator.c
new file mode 100644
index 0000000..0201de0
--- /dev/null
+++ b/ccan/generator/generator.c
@@ -0,0 +1,60 @@
+/* Licensed under LGPLv2.1+ - see LICENSE file for details */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <ccan/generator/generator.h>
+
+#define STATE_SIZE 8192
+#define STACK_SIZE (STATE_SIZE - sizeof(struct generator_state))
+
+static void *stack(void *p)
+{
+	return (void *)((ptrdiff_t)p & ~(STATE_SIZE - 1));
+}
+
+static struct generator_state *state(void *p)
+{
+	return (struct generator_state *)((char *)stack(p) + STACK_SIZE);
+}
+
+static void wrapper(void)
+{
+	struct generator_state *s;
+
+	s = state(&s);
+	s->fn(s);
+	s->complete = true;
+	setcontext(&s->caller);
+}
+
+struct generator_state *__generator_start(void (*fn)(struct generator_state *))
+{
+	void *ss;
+	struct generator_state *s;
+
+	if (posix_memalign(&ss, STATE_SIZE, STATE_SIZE) < 0)
+		abort();
+
+	s = state(ss);
+
+	s->complete = false;
+	s->fn = fn;
+
+	getcontext(&s->gen);
+
+	s->gen.uc_stack.ss_sp = ss;
+	s->gen.uc_stack.ss_size = STACK_SIZE;
+
+	makecontext(&s->gen, wrapper, 0);
+
+	return s;
+}
+
+void generator_cleanup(struct generator_state *s)
+{
+	void *stack = (char *)s - STACK_SIZE;
+
+	free(stack);
+}
diff --git a/ccan/generator/generator.h b/ccan/generator/generator.h
new file mode 100644
index 0000000..a152980
--- /dev/null
+++ b/ccan/generator/generator.h
@@ -0,0 +1,47 @@
+/* Licensed under LGPLv2.1+ - see LICENSE file for details */
+#ifndef CCAN_GENERATOR_H
+#define CCAN_GENERATOR_H
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <ucontext.h>
+
+struct generator_state {
+	bool complete;
+	void (*fn)(struct generator_state *);
+	ucontext_t gen;
+	ucontext_t caller;
+	int ret;
+};
+
+struct generator_state *__generator_start(void (*)(struct generator_state *));
+
+#define generator_def(name) \
+	static void __generator__##name(struct generator_state *__generator__state__); \
+	static struct generator_state *name(void) \
+	{ \
+		return __generator_start(__generator__##name); \
+	} \
+	static void __generator__##name(struct generator_state *__generator__state__)
+
+#define yield(val) \
+	do { \
+		__generator__state__->ret = (val); \
+		if (swapcontext(&__generator__state__->gen, \
+				&__generator__state__->caller) < 0)	\
+			abort(); \
+	} while (0)
+
+#define generator_getnext(s, v)			\
+	({ \
+		if (!(s)->complete  && swapcontext(&(s)->caller, \
+						   &(s)->gen) < 0)	\
+			abort(); \
+		(v) = (s)->ret; \
+		(s)->complete; \
+	})
+
+void generator_cleanup(struct generator_state *s);
+
+#endif /* CCAN_GENERATOR_H */
diff --git a/ccan/generator/test/run.c b/ccan/generator/test/run.c
new file mode 100644
index 0000000..ca8a80b
--- /dev/null
+++ b/ccan/generator/test/run.c
@@ -0,0 +1,37 @@
+#include <ccan/generator/generator.h>
+#include <ccan/tap/tap.h>
+
+#include <ccan/generator/generator.c>
+
+generator_def(gen1)
+{
+	yield(1);
+	yield(3);
+	yield(17);
+}
+
+static void test1(void)
+{
+	struct generator_state *state1 = gen1();
+	int val;
+
+	ok1(!generator_getnext(state1, val));
+	ok1(val == 1);
+	ok1(!generator_getnext(state1, val));
+	ok1(val == 3);
+	ok1(!generator_getnext(state1, val));
+	ok1(val == 17);
+	ok1(generator_getnext(state1, val));
+	generator_cleanup(state1);
+}
+
+int main(void)
+{
+	/* This is how many tests you plan to run */
+	plan_tests(7);
+
+	test1();
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}
-- 
1.9.3



More information about the ccan mailing list