summaryrefslogtreecommitdiff
path: root/src/runtime/c/gu
diff options
context:
space:
mode:
authorkr.angelov <kr.angelov@gmail.com>2012-01-20 13:41:10 +0000
committerkr.angelov <kr.angelov@gmail.com>2012-01-20 13:41:10 +0000
commit2eee382a62a909d5a3f2f5eda94f30fe68fd5335 (patch)
treeb0b0d513535895f244214aebf6358e172b8dce6d /src/runtime/c/gu
parentb9728357126f8b9a6311cca17d9f0dcc2a7bfb9b (diff)
initial import of the C runtime
Diffstat (limited to 'src/runtime/c/gu')
-rw-r--r--src/runtime/c/gu/assert.c53
-rw-r--r--src/runtime/c/gu/assert.h61
-rw-r--r--src/runtime/c/gu/bits.c53
-rw-r--r--src/runtime/c/gu/bits.h151
-rw-r--r--src/runtime/c/gu/choice.c73
-rw-r--r--src/runtime/c/gu/choice.h37
-rw-r--r--src/runtime/c/gu/defs.c4
-rw-r--r--src/runtime/c/gu/defs.h217
-rw-r--r--src/runtime/c/gu/dump.c411
-rw-r--r--src/runtime/c/gu/dump.h34
-rw-r--r--src/runtime/c/gu/enum.c7
-rw-r--r--src/runtime/c/gu/enum.h35
-rw-r--r--src/runtime/c/gu/exn.c72
-rw-r--r--src/runtime/c/gu/exn.h195
-rw-r--r--src/runtime/c/gu/file.c73
-rw-r--r--src/runtime/c/gu/file.h14
-rw-r--r--src/runtime/c/gu/fun.c1
-rw-r--r--src/runtime/c/gu/fun.h65
-rw-r--r--src/runtime/c/gu/hash.c77
-rw-r--r--src/runtime/c/gu/hash.h40
-rw-r--r--src/runtime/c/gu/in.c421
-rw-r--r--src/runtime/c/gu/in.h146
-rw-r--r--src/runtime/c/gu/intern.c59
-rw-r--r--src/runtime/c/gu/intern.h24
-rw-r--r--src/runtime/c/gu/list.c59
-rw-r--r--src/runtime/c/gu/list.h140
-rw-r--r--src/runtime/c/gu/log.c79
-rw-r--r--src/runtime/c/gu/log.h65
-rw-r--r--src/runtime/c/gu/map.c353
-rw-r--r--src/runtime/c/gu/map.h121
-rw-r--r--src/runtime/c/gu/mem.c346
-rw-r--r--src/runtime/c/gu/mem.h276
-rw-r--r--src/runtime/c/gu/out.c302
-rw-r--r--src/runtime/c/gu/out.h166
-rw-r--r--src/runtime/c/gu/prime.c154
-rw-r--r--src/runtime/c/gu/prime.h16
-rw-r--r--src/runtime/c/gu/read.c15
-rw-r--r--src/runtime/c/gu/read.h31
-rw-r--r--src/runtime/c/gu/seq.c245
-rw-r--r--src/runtime/c/gu/seq.h198
-rw-r--r--src/runtime/c/gu/str.c85
-rw-r--r--src/runtime/c/gu/str.h29
-rw-r--r--src/runtime/c/gu/string.c270
-rw-r--r--src/runtime/c/gu/string.h125
-rw-r--r--src/runtime/c/gu/sysdeps.h30
-rw-r--r--src/runtime/c/gu/type.c229
-rw-r--r--src/runtime/c/gu/type.h454
-rw-r--r--src/runtime/c/gu/ucs.c135
-rw-r--r--src/runtime/c/gu/ucs.h53
-rw-r--r--src/runtime/c/gu/utf8.c220
-rw-r--r--src/runtime/c/gu/utf8.h67
-rw-r--r--src/runtime/c/gu/variant.c100
-rw-r--r--src/runtime/c/gu/variant.h167
-rw-r--r--src/runtime/c/gu/write.c174
-rw-r--r--src/runtime/c/gu/write.h64
-rw-r--r--src/runtime/c/gu/yaml.c339
-rw-r--r--src/runtime/c/gu/yaml.h38
57 files changed, 7468 insertions, 0 deletions
diff --git a/src/runtime/c/gu/assert.c b/src/runtime/c/gu/assert.c
new file mode 100644
index 000000000..e111912d3
--- /dev/null
+++ b/src/runtime/c/gu/assert.c
@@ -0,0 +1,53 @@
+#include <gu/assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+const char*
+gu_assert_mode_descs[] = {
+ [GU_ASSERT_PRECOND] = "precondition failed",
+ [GU_ASSERT_POSTCOND] = "postcondition failed",
+ [GU_ASSERT_ASSERTION] = "assertion failed",
+ [GU_ASSERT_NEVER] = "control should not reach here",
+};
+
+void
+gu_abort_v_(GuAssertMode mode,
+ const char* file, const char* func, int line,
+ const char* msg_fmt, va_list args)
+{
+ const char* desc = gu_assert_mode_descs[mode];
+ (void) fprintf(stderr, "%s (%s:%d): %s\n", func, file, line, desc);
+ if (msg_fmt != NULL) {
+ (void) fputc('\t', stderr);
+ (void) vfprintf(stderr, msg_fmt, args);
+ (void) fputc('\n', stderr);
+ }
+ abort();
+}
+
+void
+gu_abort_(GuAssertMode mode,
+ const char* file, const char* func, int line,
+ const char* msg_fmt, ...)
+{
+ va_list args;
+ va_start(args, msg_fmt);
+ gu_abort_v_(mode, file, func, line, msg_fmt, args);
+ va_end(args);
+}
+
+
+void
+gu_fatal(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ fputs("Fatal error", stderr);
+ if (fmt) {
+ fputs(": ", stderr);
+ (void) vfprintf(stderr, fmt, args);
+ }
+ fputc('\n', stderr);
+ abort();
+}
diff --git a/src/runtime/c/gu/assert.h b/src/runtime/c/gu/assert.h
new file mode 100644
index 000000000..9d7ecc15f
--- /dev/null
+++ b/src/runtime/c/gu/assert.h
@@ -0,0 +1,61 @@
+#ifndef GU_ASSERT_H_
+#define GU_ASSERT_H_
+
+#include <gu/defs.h>
+
+typedef enum {
+ GU_ASSERT_PRECOND,
+ GU_ASSERT_ASSERTION,
+ GU_ASSERT_POSTCOND,
+ GU_ASSERT_NEVER
+} GuAssertMode;
+
+void
+gu_abort_v_(GuAssertMode mode,
+ const char* file, const char* func, int line,
+ const char* msg_fmt, va_list args);
+
+void
+gu_abort_(GuAssertMode mode,
+ const char* file, const char* func, int line,
+ const char* msg_fmt, ...);
+
+#ifndef NDEBUG
+#define gu_assertion_(mode_, expr_, ...) \
+ GU_BEGIN \
+ if (!(expr_)) { \
+ gu_abort_(mode_, __FILE__, __func__, __LINE__, __VA_ARGS__); \
+ } \
+ GU_END
+#else
+// this should prevent unused variable warnings when a variable is only used
+// in an assertion
+#define gu_assertion_(mode_, expr_, ...) \
+ GU_BEGIN \
+ (void) (sizeof (expr_)); \
+ GU_END
+#endif
+
+
+#define gu_require(expr) \
+ gu_assertion_(GU_ASSERT_PRECOND, expr, "%s", #expr)
+
+#define gu_assert_msg(expr, ...) \
+ gu_assertion_(GU_ASSERT_ASSERTION, expr, __VA_ARGS__)
+
+#define gu_assert(expr) \
+ gu_assert_msg(expr, "%s", #expr)
+
+#define gu_ensure(expr) \
+ gu_assertion_(GU_ASSERT_POSTCOND, expr, "%s", #expr)
+
+#define gu_impossible_msg(...) \
+ gu_assertion_(GU_ASSERT_ASSERTION, false, __VA_ARGS__)
+
+#define gu_impossible() \
+ gu_impossible_msg(NULL)
+
+void
+gu_fatal(const char* fmt, ...);
+
+#endif /* GU_ASSERT_H_ */
diff --git a/src/runtime/c/gu/bits.c b/src/runtime/c/gu/bits.c
new file mode 100644
index 000000000..9126b0448
--- /dev/null
+++ b/src/runtime/c/gu/bits.c
@@ -0,0 +1,53 @@
+#include <gu/bits.h>
+
+#include <limits.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+unsigned gu_ceil2e(unsigned u)
+{
+ u--;
+ u |= u >> 1;
+ u |= u >> 2;
+ u |= u >> 4;
+ u |= u >> 8;
+#if UINT_MAX > UINT16_MAX
+ u |= u >> 16;
+#endif
+#if UINT_MAX > UINT32_MAX
+ u |= u >> 32;
+#endif
+ u++;
+ return u;
+}
+
+GU_DEFINE_TYPE(GuIntDecodeExn, abstract, _);
+
+double
+gu_decode_double(uint64_t u)
+{
+ bool sign = u >> 63;
+ unsigned rawexp = u >> 52 & 0x7ff;
+ uint64_t mantissa = u & 0xfffffffffffff;
+ double ret;
+
+ if (rawexp == 0x7ff) {
+ if (mantissa == 0) {
+ ret = INFINITY;
+ } else {
+ // At least glibc supports specifying the
+ // mantissa like this.
+ int len = snprintf(NULL, 0, "0x%" PRIx64, mantissa);
+ char buf[len + 1];
+ snprintf(buf, len + 1, "0x%" PRIx64, mantissa);
+ ret = nan(buf);
+ }
+ } else {
+ uint64_t m = rawexp ? 1ULL << 52 | mantissa : mantissa << 1;
+ ret = ldexp((double) m, rawexp - 1075);
+ }
+ return sign ? copysign(ret, -1.0) : ret;
+}
diff --git a/src/runtime/c/gu/bits.h b/src/runtime/c/gu/bits.h
new file mode 100644
index 000000000..9ba1b0c8e
--- /dev/null
+++ b/src/runtime/c/gu/bits.h
@@ -0,0 +1,151 @@
+#ifndef GU_BITS_H_
+#define GU_BITS_H_
+
+#include <gu/defs.h>
+#include <gu/assert.h>
+
+
+#define GU_WORD_BITS (sizeof(GuWord) * CHAR_BIT)
+
+
+/*
+ * Based on the Bit Twiddling Hacks collection by Sean Eron Anderson
+ * <http://graphics.stanford.edu/~seander/bithacks.html>
+ */
+
+unsigned gu_ceil2e(unsigned i);
+
+static inline int
+gu_sign(int i) {
+ return (i > 0) - (i < 0);
+}
+
+static inline size_t
+gu_ceildiv(size_t size, size_t div)
+{
+ return (size + div - 1) / div;
+}
+
+static inline bool
+gu_aligned(uintptr_t addr, size_t alignment)
+{
+ gu_require(alignment == gu_ceil2e(alignment));
+ return (addr & (alignment - 1)) == 0;
+}
+
+static inline uintptr_t
+gu_align_forward(uintptr_t addr, size_t alignment) {
+ gu_require(alignment == gu_ceil2e(alignment));
+ uintptr_t mask = alignment - 1;
+ return (addr + mask) & ~mask;
+}
+
+static inline uintptr_t
+gu_align_backward(uintptr_t addr, size_t alignment) {
+ gu_require(alignment == gu_ceil2e(alignment));
+ return addr & ~(alignment - 1);
+}
+
+static inline bool
+gu_bits_test(const GuWord* bitmap, int idx) {
+ return !!(bitmap[idx / GU_WORD_BITS] & 1 << (idx % GU_WORD_BITS));
+}
+
+static inline void
+gu_bits_set(GuWord* bitmap, int idx) {
+ bitmap[idx / GU_WORD_BITS] |= ((GuWord) 1) << (idx % GU_WORD_BITS);
+}
+
+static inline void
+gu_bits_clear(GuWord* bitmap, int idx) {
+ bitmap[idx / GU_WORD_BITS] &= ~(((GuWord) 1) << (idx % GU_WORD_BITS));
+}
+
+static inline size_t
+gu_bits_size(size_t n_bits) {
+ return gu_ceildiv(n_bits, GU_WORD_BITS) * sizeof(GuWord);
+}
+
+static inline void*
+gu_word_ptr(GuWord w)
+{
+ return (void*) w;
+}
+
+static inline GuWord
+gu_ptr_word(void* p)
+{
+ return (GuWord) p;
+}
+
+#define GuOpaque() struct { GuWord w_; }
+
+typedef GuWord GuTagged;
+
+#define GU_TAG_MAX (sizeof(GuWord) - 1)
+
+static inline size_t
+gu_tagged_tag(GuTagged t) {
+ return (int) (t & (sizeof(GuWord) - 1));
+}
+
+static inline void*
+gu_tagged_ptr(GuTagged w) {
+ return (void*) gu_align_backward(w, sizeof(GuWord));
+}
+
+static inline GuTagged
+gu_tagged(void* ptr, size_t tag) {
+ gu_require(tag < sizeof(GuWord));
+ uintptr_t u = (uintptr_t) ptr;
+ gu_require(gu_align_backward(u, sizeof(GuWord)) == u);
+ return (GuWord) { u | tag };
+}
+
+#include <gu/exn.h>
+#include <gu/type.h>
+
+extern GU_DECLARE_TYPE(GuIntDecodeExn, abstract);
+
+#define GU_DECODE_2C_(u_, t_, umax_, posmax_, tmin_, err_) \
+ (((u_) <= (posmax_)) \
+ ? (t_) (u_) \
+ : (tmin_) + ((t_) ((umax_) - (u_))) < 0 \
+ ? -1 - ((t_) ((umax_) - (u_))) \
+ : (gu_raise(err_, GuIntDecodeExn), -1))
+
+
+static inline int8_t
+gu_decode_2c8(uint8_t u, GuExn* err)
+{
+ return GU_DECODE_2C_(u, int8_t, UINT8_C(0xff),
+ INT8_C(0x7f), INT8_MIN, err);
+}
+
+static inline int16_t
+gu_decode_2c16(uint16_t u, GuExn* err)
+{
+ return GU_DECODE_2C_(u, int16_t, UINT16_C(0xffff),
+ INT16_C(0x7fff), INT16_MIN, err);
+}
+
+static inline int32_t
+gu_decode_2c32(uint32_t u, GuExn* err)
+{
+ return GU_DECODE_2C_(u, int32_t, UINT32_C(0xffffffff),
+ INT32_C(0x7fffffff), INT32_MIN, err);
+}
+
+static inline int64_t
+gu_decode_2c64(uint64_t u, GuExn* err)
+{
+ return GU_DECODE_2C_(u, int64_t, UINT64_C(0xffffffffffffffff),
+ INT64_C(0x7fffffffffffffff), INT64_MIN, err);
+}
+
+double
+gu_decode_double(uint64_t u);
+
+
+
+#endif // GU_BITS_H_
diff --git a/src/runtime/c/gu/choice.c b/src/runtime/c/gu/choice.c
new file mode 100644
index 000000000..b1e4a3a5e
--- /dev/null
+++ b/src/runtime/c/gu/choice.c
@@ -0,0 +1,73 @@
+#include <gu/choice.h>
+#include <gu/seq.h>
+#include <gu/assert.h>
+#include <gu/log.h>
+
+struct GuChoice {
+ GuBuf* path;
+ size_t path_idx;
+};
+
+GuChoice*
+gu_new_choice(GuPool* pool)
+{
+ GuChoice* ch = gu_new(GuChoice, pool);
+ ch->path = gu_new_buf(uint8_t, pool);
+ ch->path_idx = 0;
+ return ch;
+}
+
+GuChoiceMark
+gu_choice_mark(GuChoice* ch)
+{
+ gu_assert(ch->path_idx <= gu_buf_length(ch->path));
+ gu_debug("%p@%d: mark", ch, ch->path_idx);
+ return (GuChoiceMark){ch->path_idx};
+}
+
+void
+gu_choice_reset(GuChoice* ch, GuChoiceMark mark)
+{
+ gu_assert(ch->path_idx <= gu_buf_length(ch->path));
+ gu_debug("%p@%d: reset %d", ch, ch->path_idx, mark.path_idx);
+ gu_require(mark.path_idx <= ch->path_idx );
+ ch->path_idx = mark.path_idx;
+}
+
+int
+gu_choice_next(GuChoice* ch, int n_choices)
+{
+ gu_assert(n_choices >= 0);
+ gu_require(n_choices <= UINT8_MAX);
+ gu_assert(ch->path_idx <= gu_buf_length(ch->path));
+ if (n_choices == 0) {
+ return -1;
+ }
+ int i = 0;
+ if (gu_buf_length(ch->path) > ch->path_idx) {
+ i = (int) gu_buf_get(ch->path, uint8_t, ch->path_idx);
+ gu_assert(i <= n_choices);
+ } else {
+ gu_buf_push(ch->path, uint8_t, n_choices);
+ i = n_choices;
+ }
+ int ret = (i == 0) ? -1 : n_choices - i;
+ gu_debug("%p@%d: %d", ch, ch->path_idx, ret);
+ ch->path_idx++;
+ return ret;
+}
+
+bool
+gu_choice_advance(GuChoice* ch)
+{
+ gu_assert(ch->path_idx <= gu_buf_length(ch->path));
+
+ while (gu_buf_length(ch->path) > ch->path_idx) {
+ uint8_t last = gu_buf_pop(ch->path, uint8_t);
+ if (last > 1) {
+ gu_buf_push(ch->path, uint8_t, last-1);
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/runtime/c/gu/choice.h b/src/runtime/c/gu/choice.h
new file mode 100644
index 000000000..aea76dde8
--- /dev/null
+++ b/src/runtime/c/gu/choice.h
@@ -0,0 +1,37 @@
+#ifndef GU_CHOICE_H_
+#define GU_CHOICE_H_
+
+#include <gu/mem.h>
+
+typedef struct GuChoice GuChoice;
+
+typedef struct GuChoiceMark GuChoiceMark;
+
+GuChoice*
+gu_new_choice(GuPool* pool);
+
+int
+gu_choice_next(GuChoice* ch, int n_choices);
+
+GuChoiceMark
+gu_choice_mark(GuChoice* ch);
+
+void
+gu_choice_reset(GuChoice* ch, GuChoiceMark mark);
+
+bool
+gu_choice_advance(GuChoice* ch);
+
+
+// private
+
+struct GuChoiceMark {
+ size_t path_idx;
+};
+
+
+
+
+
+
+#endif // GU_CHOICE_H_
diff --git a/src/runtime/c/gu/defs.c b/src/runtime/c/gu/defs.c
new file mode 100644
index 000000000..2c3772b7c
--- /dev/null
+++ b/src/runtime/c/gu/defs.c
@@ -0,0 +1,4 @@
+#include <gu/defs.h>
+
+void* const gu_null = NULL;
+GuStruct* const gu_null_struct = NULL;
diff --git a/src/runtime/c/gu/defs.h b/src/runtime/c/gu/defs.h
new file mode 100644
index 000000000..9fa62b39b
--- /dev/null
+++ b/src/runtime/c/gu/defs.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file
+ *
+ * Miscellaneous macros.
+ */
+
+#ifndef GU_DEFS_H_
+#define GU_DEFS_H_
+
+#include <stddef.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <gu/sysdeps.h>
+
+#define gu_container(mem_p, container_type, member) \
+ ((container_type*)(((uint8_t*) (mem_p)) - offsetof(container_type, member)))
+/**< Find the address of a containing structure.
+ *
+ * If @c s has type @c t*, where @c t is a struct or union type with a
+ * member @m, then <tt>GU_CONTAINER_P(&s->m, t, m) == s</tt>.
+ *
+ * @param mem_p Pointer to the member of a structure.
+ * @param container_type The type of the containing structure.
+ * @param member The name of the member of @a container_type
+ * @return The address of the containing structure.
+ *
+ * @hideinitializer */
+
+
+#define gu_member_p(struct_p_, offset_) \
+ ((void*)&((uint8_t*)(struct_p_))[offset_])
+
+#define gu_member(t_, struct_p_, offset_) \
+ (*(t_*)gu_member_p(struct_p_, offset_))
+
+#ifdef GU_ALIGNOF
+# define gu_alignof GU_ALIGNOF
+# define GU_ALIGNOF_WORKS_ON_FAM_STRUCTS
+#else
+# define gu_alignof(t_) \
+ ((size_t)(offsetof(struct { char c_; t_ e_; }, e_)))
+# ifdef GU_CAN_HAVE_FAM_IN_MEMBER
+# define GU_ALIGNOF_WORKS_ON_FAM_STRUCTS
+# endif
+#endif
+
+#define GU_PLIT(type, expr) \
+ ((type[1]){ expr })
+
+#define GU_LVALUE(type, expr) \
+ (*((type[1]){ expr }))
+
+#define GU_COMMA ,
+
+#define GU_ARRAY_LEN(t,a) (sizeof((const t[])a) / sizeof(t))
+
+#define GU_ID(...) __VA_ARGS__
+
+// This trick is by Laurent Deniau <laurent.deniau@cern.ch>
+#define GU_N_ARGS(...) \
+ GU_N_ARGS_(__VA_ARGS__, \
+ 31,30,29,28,27,26,25,24, \
+ 23,22,21,20,19,18,17,16, \
+ 15,14,13,12,11,10,9,8, \
+ 7,6,5,4,3,2,1,0)
+#define GU_N_ARGS_(...) GU_N_ARGS__(__VA_ARGS__)
+#define GU_N_ARGS__(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p, \
+ q,r,s,t,u,v,w,x,y,z,aa,ab,ac,ad,ae,N,...) \
+ N
+
+#define GU_ARG1(a1, ...) a1
+#define GU_ARG2(a1, a2, ...) a2
+
+#define GU_BEGIN do {
+#define GU_END } while (false)
+
+#define GU_NOP GU_BEGIN (void) 0; GU_END
+
+/**< @hideinitializer */
+
+//
+// Assert
+//
+
+#define GU_MAX(a_, b_) ((a_) > (b_) ? (a_) : (b_))
+#define GU_MIN(a_, b_) ((a_) < (b_) ? (a_) : (b_))
+
+static inline int
+gu_max(int a, int b) {
+ return GU_MAX(a, b);
+}
+
+static inline int
+gu_min(int a, int b) {
+ return GU_MIN(a, b);
+}
+
+#ifdef GU_ALIGNOF_WORKS_ON_FAM_STRUCTS
+#define gu_flex_alignof gu_alignof
+#else
+#define gu_flex_alignof(t) 0
+#endif
+
+static inline size_t
+gu_flex_size(size_t ssize, size_t offset, int n_elems, size_t e_size)
+{
+ return GU_MAX(ssize, offset + n_elems * e_size);
+}
+
+#define GU_FLEX_SIZE(type, flex_member, n_elems) \
+ gu_flex_size(sizeof(type), offsetof(type, flex_member), \
+ n_elems, sizeof(((type*)NULL)->flex_member[0]))
+
+
+// The following are directly from gmacros.h in GLib
+
+#define GU_PASTE_ARGS(id1_,id2_) \
+ id1_ ## id2_
+
+#define GU_PASTE(id1_, id2_) \
+ GU_PASTE_ARGS(id1_, id2_)
+
+#define GU_STATIC_ASSERT(expr_) \
+ typedef struct { \
+ char static_assert[(expr_) ? 1 : -1]; \
+ } GU_PASTE(GuStaticAssert_, __LINE__)
+
+
+#define GU_ENSURE_TYPE(T, EXPR) \
+ ((void)(sizeof(*(T*)NULL=(EXPR))),(EXPR))
+
+#define GU_END_DECLS \
+ extern void gu_dummy_(void)
+
+extern void* const gu_null;
+
+// Dummy struct used for generic struct pointers
+typedef struct GuStruct GuStruct;
+
+extern GuStruct* const gu_null_struct;
+
+typedef uintptr_t GuWord;
+
+#define GU_WORD_MAX UINTPTR_MAX
+
+// TODO: use max_align_t once C1X is supported
+typedef union {
+ char c;
+ short s;
+ int i;
+ long l;
+ long long ll;
+ intmax_t im;
+ float f;
+ double d;
+ long double ld;
+ void* p;
+ void (*fp)();
+} GuMaxAlign;
+
+#define gu_alloca(N) \
+ (((union { GuMaxAlign align_; uint8_t buf_[N]; }){{0}}).buf_)
+
+
+// For Doxygen
+#define GU_PRIVATE /** @private */
+
+#ifdef GU_GNUC
+# define GU_LIKELY(EXPR) __builtin_expect(EXPR, 1)
+# define GU_UNLIKELY(EXPR) __builtin_expect(EXPR, 0)
+# define GU_IS_CONSTANT(EXPR) __builtin_constant_p(EXPR)
+#else
+# define GU_LIKELY(EXPR) (EXPR)
+# define GU_UNLIKELY(EXPR) (EXPR)
+# ifdef GU_OPTIMIZE_SIZE
+# define GU_IS_CONSTANT(EXPR) false
+# else
+# define GU_IS_CONSTANT(EXPR) true
+# endif
+#endif
+
+// Splint annotations
+#define GU_ONLY GU_SPLINT(only)
+#define GU_NULL GU_SPLINT(null)
+#define GU_NOTNULL GU_SPLINT(notnull)
+#define GU_RETURNED GU_SPLINT(returned)
+#define GU_ABSTRACT GU_SPLINT(abstract)
+#define GU_IMMUTABLE GU_SPLINT(immutable)
+#define GU_NOTREACHED GU_SPLINT(notreached)
+#define GU_UNUSED GU_SPLINT(unused) GU_GNUC_ATTR(unused)
+#define GU_OUT GU_SPLINT(out)
+#define GU_IN GU_SPLINT(in)
+#define GU_NORETURN GU_SPLINT(noreturn) GU_GNUC_ATTR(noreturn)
+#define GU_MODIFIES(x) GU_SPLINT(modifies x)
+
+#endif // GU_DEFS_H_
diff --git a/src/runtime/c/gu/dump.c b/src/runtime/c/gu/dump.c
new file mode 100644
index 000000000..0329f5932
--- /dev/null
+++ b/src/runtime/c/gu/dump.c
@@ -0,0 +1,411 @@
+#include <gu/dump.h>
+#include <gu/list.h>
+#include <gu/variant.h>
+#include <gu/seq.h>
+#include <gu/assert.h>
+#include <gu/str.h>
+#include <gu/file.h>
+
+GuDump*
+gu_new_dump(GuWriter* wtr, GuTypeTable* dumpers, GuExn* err, GuPool* pool)
+{
+ GuDump* ctx = gu_new(GuDump, pool);
+ ctx->pool = pool;
+ if (dumpers == NULL) {
+ dumpers = &gu_dump_table;
+ }
+ ctx->dumpers = gu_new_type_map(dumpers, pool);
+ ctx->yaml = gu_new_yaml(wtr, err, pool);
+ ctx->data = gu_new_addr_map(void, void*, &gu_null, pool);
+ ctx->print_address = false;
+ return ctx;
+}
+
+void
+gu_dump(GuType* type, const void* value, GuDump* ctx)
+{
+ GuDumpFn* dumper = gu_type_map_get(ctx->dumpers, type);
+ if (ctx->print_address) {
+ GuPool* pool = gu_new_pool();
+ GuString s = gu_format_string(pool, "%p", value);
+ gu_yaml_comment(ctx->yaml, s);
+ gu_pool_free(pool);
+ }
+ (*dumper)(dumper, type, value, ctx);
+}
+
+void
+gu_dump_stderr(GuType* type, const void* value, GuExn* err)
+{
+ GuPool* pool = gu_new_pool();
+ GuOut* out = gu_file_out(stderr, pool);
+#if 0
+ GuWriter* wtr = gu_locale_writer(out, pool);
+#else
+ GuWriter* wtr = gu_new_utf8_writer(out, pool);
+#endif
+ GuDump* ctx = gu_new_dump(wtr, NULL, err, pool);
+ gu_dump(type, value, ctx);
+ gu_pool_free(pool);
+}
+
+static void
+gu_dump_scalar(GuDump* ctx, const char* fmt, ...)
+{
+ GuPool* tmp_pool = gu_local_pool();
+ va_list args;
+ va_start(args, fmt);
+ GuString s = gu_format_string_v(fmt, args, tmp_pool);
+ va_end(args);
+ gu_yaml_scalar(ctx->yaml, s);
+ gu_pool_free(tmp_pool);
+}
+
+static void
+gu_dump_str_scalar(GuDump* ctx, const char* str)
+{
+ GuPool* tmp_pool = gu_local_pool();
+ GuString s = gu_str_string(str, tmp_pool);
+ gu_yaml_scalar(ctx->yaml, s);
+ gu_pool_free(tmp_pool);
+}
+
+static void
+gu_dump_null(GuDump* ctx)
+{
+ gu_yaml_tag_secondary(ctx->yaml, "null");
+ gu_yaml_scalar(ctx->yaml, gu_empty_string);
+}
+
+static void
+gu_dump_int(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ const int* ip = p;
+ gu_dump_scalar(ctx, "%d", *ip);
+}
+
+static void
+gu_dump_uint16(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ const uint16_t* ip = p;
+ gu_dump_scalar(ctx, "%" PRIu16, *ip);
+}
+
+static void
+gu_dump_size(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) (dumper && type);
+ const size_t* zp = p;
+ gu_dump_scalar(ctx, "%zu", *zp);
+}
+
+
+
+static void
+gu_dump_double(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ const double* dp = p;
+ gu_dump_scalar(ctx, "%lf", *dp);
+}
+
+static const char gu_dump_length_key[] = "gu_dump_length_key";
+
+static void
+gu_dump_length(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ const GuLength* ip = p;
+ gu_dump_scalar(ctx, "%d", *ip);
+ GuLength* lenp = gu_map_get(ctx->data, gu_dump_length_key, void*);
+ if (lenp != NULL) {
+ *lenp = *ip;
+ }
+}
+
+static void
+gu_dump_str(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ const GuStr* sp = p;
+ gu_dump_str_scalar(ctx, *sp);
+}
+
+static void
+gu_dump_string(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ const GuString* sp = p;
+ gu_yaml_scalar(ctx->yaml, *sp);
+}
+
+
+// For _non-shared_ pointers.
+static void
+gu_dump_pointer(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuPointerType* ptype = (GuPointerType*) type;
+ void* const* pp = p;
+ if (*pp == NULL) {
+ gu_dump_null(ctx);
+ } else {
+ gu_dump(ptype->pointed_type, *pp, ctx);
+ }
+}
+
+typedef struct {
+ GuMapItor itor;
+ GuMapType* mtype;
+ GuDump* ctx;
+} GuDumpMapFn;
+
+static void
+gu_dump_map_itor(GuMapItor* self, const void* key, void* value, GuExn* err)
+{
+ (void) err;
+ GuDumpMapFn* clo = (GuDumpMapFn*) self;
+ gu_dump(clo->mtype->key_type, key, clo->ctx);
+ gu_dump(clo->mtype->value_type, value, clo->ctx);
+}
+
+static void
+gu_dump_map(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuMapType* mtype = (GuMapType*) type;
+ GuMap* map = (GuMap*) p;
+ gu_yaml_begin_mapping(ctx->yaml);
+ GuDumpMapFn clo = { { gu_dump_map_itor }, mtype, ctx };
+ gu_map_iter(map, &clo.itor, NULL);
+ gu_yaml_end(ctx->yaml);
+}
+
+
+static void
+gu_dump_struct(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuStructRepr* srepr = (GuStructRepr*) type;
+ gu_yaml_begin_mapping(ctx->yaml);
+ const uint8_t* data = p;
+ GuLength* old_lenp = gu_map_get(ctx->data, gu_dump_length_key, void*);
+ GuLength len = (GuLength)-1;
+ gu_map_put(ctx->data, gu_dump_length_key, void*, &len);
+
+ for (int i = 0; i < srepr->members.len; i++) {
+ const GuMember* member = &srepr->members.elems[i];
+ gu_dump_str_scalar(ctx, member->name);
+ const uint8_t* memp = &data[member->offset];
+ if (member->is_flex) {
+ // Flexible array member
+ gu_assert(len != (GuLength)-1);
+ size_t mem_s = gu_type_size(member->type);
+ gu_yaml_begin_sequence(ctx->yaml);
+ for (GuLength i = 0; i < len; i++) {
+ gu_dump(member->type, &memp[i * mem_s], ctx);
+ }
+ gu_yaml_end(ctx->yaml);
+ } else {
+ gu_dump(member->type, memp, ctx);
+ }
+ }
+ gu_yaml_end(ctx->yaml);
+ if (old_lenp) {
+ gu_map_set(ctx->data, gu_dump_length_key, void*, old_lenp);
+ }
+}
+
+static void
+gu_dump_alias(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuTypeAlias* alias = gu_type_cast(type, alias);
+
+ gu_dump(alias->type, p, ctx);
+}
+
+static const char gu_dump_reference_key[] = "reference";
+
+static bool
+gu_dump_anchor(GuDump* ctx, const void* p)
+{
+ GuMap* map = gu_map_get(ctx->data, gu_dump_reference_key, void*);
+ if (map == NULL) {
+ map = gu_new_addr_map(void, GuYamlAnchor,
+ &gu_yaml_null_anchor, ctx->pool);
+ gu_map_put(ctx->data, gu_dump_reference_key, void*, map);
+ }
+ GuYamlAnchor a = gu_map_get(map, p, GuYamlAnchor);
+ if (a == gu_yaml_null_anchor) {
+ a = gu_yaml_anchor(ctx->yaml);
+ gu_map_put(map, p, GuYamlAnchor, a);
+ return true;
+ } else {
+ gu_yaml_alias(ctx->yaml, a);
+ return false;
+ }
+}
+
+static void
+gu_dump_referenced(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuTypeAlias* alias = gu_type_cast(type, alias);
+ bool created = gu_dump_anchor(ctx, p);
+ if (created) {
+ gu_dump(alias->type, p, ctx);
+ } else {
+ // gu_assert(false);
+ }
+}
+
+static void
+gu_dump_reference(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ (void) type;
+ void* const* pp = p;
+ bool created = gu_dump_anchor(ctx, *pp);
+ if (created) {
+ // gu_assert(false);
+ GuPointerType* ptype = (GuPointerType*) type;
+ gu_dump(ptype->pointed_type, *pp, ctx);
+ }
+}
+
+static void
+gu_dump_shared(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ void* const* pp = p;
+ if (*pp == NULL) {
+ gu_dump_null(ctx);
+ } else {
+ bool created = gu_dump_anchor(ctx, *pp);
+ if (created) {
+ GuPointerType* ptype = (GuPointerType*) type;
+ gu_dump(ptype->pointed_type, *pp, ctx);
+ }
+ }
+}
+
+static void
+gu_dump_list(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuListType* ltype = (GuListType*) type;
+ const uint8_t* up = p;
+ int len = * (const int*) p;
+ size_t elem_size = gu_type_size(ltype->elem_type);
+ gu_yaml_begin_sequence(ctx->yaml);
+ for (int i = 0; i < len; i++) {
+ ptrdiff_t offset = ltype->elems_offset + i * elem_size;
+ gu_dump(ltype->elem_type, &up[offset], ctx);
+ }
+ gu_yaml_end(ctx->yaml);
+}
+
+static void
+gu_dump_variant(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuVariantType* vtype = gu_type_cast(type, GuVariant);
+ const GuVariant* vp = p;
+ int tag = gu_variant_tag(*vp);
+ for (int i = 0; i < vtype->ctors.len; i++) {
+ GuConstructor* ctor = &vtype->ctors.elems[i];
+ if (ctor->c_tag == tag) {
+ gu_yaml_begin_mapping(ctx->yaml);
+ gu_dump_str_scalar(ctx, ctor->c_name);
+ void* data = gu_variant_data(*vp);
+ gu_dump(ctor->type, data, ctx);
+ gu_yaml_end(ctx->yaml);
+ return;
+ }
+ }
+ gu_assert(false);
+}
+
+
+static void
+gu_dump_enum(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuEnumType* etype = gu_type_cast(type, enum);
+ GuEnumConstant* cp = gu_enum_value(etype, p);
+ gu_assert(cp != NULL);
+ gu_dump_str_scalar(ctx, cp->name);
+}
+
+static void
+gu_dump_seq(GuDumpFn* dumper, GuType* type, const void* p,
+ GuDump* ctx)
+{
+ (void) dumper;
+ GuSeqType* dtype = gu_type_cast(type, GuSeq);
+ size_t elem_size = gu_type_size(dtype->elem_type);
+ const GuSeq* seqp = p;
+ GuSeq seq = *seqp;
+ if (gu_seq_is_null(seq)) {
+ gu_dump_null(ctx);
+ return;
+ }
+ size_t len = gu_seq_length(seq);
+ const uint8_t* data = gu_seq_data(seq);
+ gu_yaml_begin_sequence(ctx->yaml);
+ for (size_t i = 0; i < len; i++) {
+ const void* elemp = &data[i * elem_size];
+ gu_dump(dtype->elem_type, elemp, ctx);
+ }
+ gu_yaml_end(ctx->yaml);
+}
+
+
+GuTypeTable
+gu_dump_table = GU_TYPETABLE(
+ GU_SLIST_0,
+ { gu_kind(int), gu_fn(gu_dump_int) },
+ { gu_kind(uint16_t), gu_fn(gu_dump_uint16) },
+ { gu_kind(size_t), gu_fn(gu_dump_size) },
+ { gu_kind(GuStr), gu_fn(gu_dump_str) },
+ { gu_kind(GuString), gu_fn(gu_dump_string) },
+ { gu_kind(struct), gu_fn(gu_dump_struct) },
+ { gu_kind(pointer), gu_fn(gu_dump_pointer) },
+ { gu_kind(GuMap), gu_fn(gu_dump_map) },
+ { gu_kind(alias), gu_fn(gu_dump_alias) },
+ { gu_kind(reference), gu_fn(gu_dump_reference) },
+ { gu_kind(referenced), gu_fn(gu_dump_referenced) },
+ { gu_kind(shared), gu_fn(gu_dump_shared) },
+ { gu_kind(GuList), gu_fn(gu_dump_list) },
+ { gu_kind(GuSeq), gu_fn(gu_dump_seq) },
+ { gu_kind(GuLength), gu_fn(gu_dump_length) },
+ { gu_kind(GuVariant), gu_fn(gu_dump_variant) },
+ { gu_kind(double), gu_fn(gu_dump_double) },
+ { gu_kind(enum), gu_fn(gu_dump_enum) },
+ );
diff --git a/src/runtime/c/gu/dump.h b/src/runtime/c/gu/dump.h
new file mode 100644
index 000000000..7d6f10a77
--- /dev/null
+++ b/src/runtime/c/gu/dump.h
@@ -0,0 +1,34 @@
+#ifndef GU_DUMP_H_
+#define GU_DUMP_H_
+
+#include <gu/defs.h>
+#include <gu/yaml.h>
+#include <gu/type.h>
+#include <gu/map.h>
+
+typedef struct GuDump GuDump;
+
+struct GuDump {
+ GuPool* pool;
+ GuYaml* yaml;
+ GuMap* data;
+ GuTypeMap* dumpers;
+ bool print_address;
+};
+
+typedef void (*GuDumpFn)(GuFn* self, GuType* type, const void* value, GuDump* ctx);
+
+GuDump*
+gu_new_dump(GuWriter* wtr, GuTypeTable* dumpers, GuExn* err, GuPool* pool);
+
+void
+gu_dump(GuType* type, const void* value, GuDump* ctx);
+
+void
+gu_dump_stderr(GuType* type, const void* value, GuExn* err);
+
+extern GuTypeTable
+gu_dump_table;
+
+
+#endif // GU_DUMP_H_
diff --git a/src/runtime/c/gu/enum.c b/src/runtime/c/gu/enum.c
new file mode 100644
index 000000000..fa8595a55
--- /dev/null
+++ b/src/runtime/c/gu/enum.c
@@ -0,0 +1,7 @@
+#include <gu/enum.h>
+
+void
+gu_enum_next(GuEnum* en, void* to, GuPool* pool)
+{
+ en->next(en, to, pool);
+}
diff --git a/src/runtime/c/gu/enum.h b/src/runtime/c/gu/enum.h
new file mode 100644
index 000000000..88cd5ba6a
--- /dev/null
+++ b/src/runtime/c/gu/enum.h
@@ -0,0 +1,35 @@
+#ifndef GU_ENUM_H_
+#define GU_ENUM_H_
+
+#include <gu/mem.h>
+
+typedef struct GuEnum GuEnum;
+
+struct GuEnum {
+ void (*next)(GuEnum* self, void* to, GuPool* pool);
+};
+
+void
+gu_enum_next(GuEnum* en, void* to, GuPool* pool);
+
+#ifdef GU_GNUC
+
+#define gu_next(ENUM, T, POOL) \
+ ({ \
+ T gu_next_tmp_; \
+ gu_enum_next((ENUM), &gu_next_tmp_, (POOL)); \
+ gu_next_tmp_; \
+ })
+#else
+static inline void*
+gu_enum_next_(GuEnum* en, void* to, GuPool* pool)
+{
+ gu_enum_next(en, to, pool);
+ return to;
+}
+#define gu_next(ENUM, T, POOL) \
+ (*(T*)gu_enum_next_((ENUM), &(T){0}, (POOL)))
+
+#endif
+
+#endif /* GU_ENUM_H_ */
diff --git a/src/runtime/c/gu/exn.c b/src/runtime/c/gu/exn.c
new file mode 100644
index 000000000..7bcfeb088
--- /dev/null
+++ b/src/runtime/c/gu/exn.c
@@ -0,0 +1,72 @@
+#include <gu/exn.h>
+#include <gu/assert.h>
+
+
+GuExn*
+gu_new_exn(GuExn* parent, GuKind* catch, GuPool* pool)
+{
+ return gu_new_s(pool, GuExn,
+ .state = GU_EXN_OK,
+ .parent = parent,
+ .catch = catch,
+ .caught = NULL,
+ .data.pool = pool,
+ .data.data = NULL);
+}
+
+void
+gu_exn_block(GuExn* err)
+{
+ if (err && err->state == GU_EXN_RAISED) {
+ err->state = GU_EXN_BLOCKED;
+ }
+}
+
+void
+gu_exn_unblock(GuExn* err)
+{
+ if (err && err->state == GU_EXN_BLOCKED) {
+ err->state = GU_EXN_RAISED;
+ }
+}
+
+GuExnData*
+gu_exn_raise_debug_(GuExn* base, GuType* type,
+ const char* filename, const char* func, int lineno)
+{
+ gu_require(type);
+
+ // TODO: log the error, once there's a system for dumping
+ // error objects.
+
+ GuExn* err = base;
+
+ while (err && !(err->catch && gu_type_has_kind(type, err->catch))) {
+ err->state = GU_EXN_RAISED;
+ err = err->parent;
+ }
+ if (!err) {
+ gu_abort_(GU_ASSERT_ASSERTION, filename, func, lineno,
+ "Unexpected error raised");
+ }
+ GuExnState old_state = err->state;
+ err->state = GU_EXN_RAISED;
+ if (old_state == GU_EXN_OK) {
+ err->caught = type;
+ if (err->data.pool) {
+ return &err->data;
+ }
+ }
+ // Exceptian had already been raised, possibly blocked, or no
+ // exception value is required.
+ return NULL;
+}
+
+GuExnData*
+gu_exn_raise_(GuExn* base, GuType* type)
+{
+ return gu_exn_raise_debug_(base, type, NULL, NULL, -1);
+}
+
+
+GU_DEFINE_TYPE(GuErrno, signed, _);
diff --git a/src/runtime/c/gu/exn.h b/src/runtime/c/gu/exn.h
new file mode 100644
index 000000000..66ca107e3
--- /dev/null
+++ b/src/runtime/c/gu/exn.h
@@ -0,0 +1,195 @@
+#ifndef GU_EXN_H_
+#define GU_EXN_H_
+
+#include <gu/mem.h>
+#include <gu/type.h>
+
+/** @file
+ *
+ * @defgroup GuExn Exceptions
+ * Defined in <gu/exn.h>.
+ * @{
+ */
+
+/// An exception frame.
+typedef struct GuExn GuExn;
+
+/// @private
+typedef enum {
+ GU_EXN_RAISED,
+ GU_EXN_OK,
+ GU_EXN_BLOCKED
+} GuExnState;
+
+typedef struct GuExnData GuExnData;
+
+/// A structure for storing exception values.
+struct GuExnData
+/**
+ * When an exception is raised, if there is an associated value, it
+ * must be allocated from a pool that still exists when control
+ * returns to the handler of that exception. This structure is used to
+ * communicate the exception from the raiser to the handler: the
+ * handler sets #pool when setting up the exception frame, and the
+ * raiser uses that pool to allocate the value and stores that in
+ * #data. When control returns to the handler, it reads the value from
+ * there.
+ */
+{
+
+ /// The pool that the exception value should be allocated from.
+ GuPool* const pool;
+
+ /// The exception value.
+ const void* data;
+};
+
+struct GuExn {
+ /// @privatesection
+ GuExnState state;
+ GuExn* parent;
+ GuKind* catch;
+ GuType* caught;
+ GuExnData data;
+};
+
+
+/// @name Creating exception frames
+//@{
+
+
+/// Allocate a new local exception frame.
+#define gu_exn(parent_, catch_, pool_) &(GuExn){ \
+ .state = GU_EXN_OK, \
+ .parent = parent_, \
+ .catch = gu_kind(catch_), \
+ .caught = NULL, \
+ .data.pool = pool_, \
+ .data.data = NULL \
+}
+
+
+/// Allocate a new exception frame.
+GuExn*
+gu_new_exn(GuExn* parent, GuKind* catch_kind, GuPool* pool);
+
+
+
+static inline bool
+gu_exn_is_raised(GuExn* err) {
+ return err && (err->state == GU_EXN_RAISED);
+}
+
+static inline void
+gu_exn_clear(GuExn* err) {
+ err->caught = NULL;
+ err->state = GU_EXN_OK;
+}
+
+
+
+GuType*
+gu_exn_caught(GuExn* err);
+
+const void*
+gu_exn_caught_data(GuExn* err);
+
+/// Temporarily block a raised exception.
+void
+gu_exn_block(GuExn* err);
+
+/// Show again a blocked exception.
+void
+gu_exn_unblock(GuExn* err);
+
+//@private
+GuExnData*
+gu_exn_raise_(GuExn* err, GuType* type);
+
+//@private
+GuExnData*
+gu_exn_raise_debug_(GuExn* err, GuType* type,
+ const char* filename, const char* func, int lineno);
+
+#ifdef NDEBUG
+#define gu_exn_raise(err_, type_) \
+ gu_exn_raise_(err_, type_)
+#else
+#define gu_exn_raise(err_, type_) \
+ gu_exn_raise_debug_(err_, type_, \
+ __FILE__, __func__, __LINE__)
+#endif
+
+/// Raise an exception.
+#define gu_raise(exn, T) \
+ gu_exn_raise(exn, gu_type(T))
+/**<
+ * @param exn The current exception frame.
+ *
+ * @param T The C type of the exception to raise.
+ *
+ * @return A #GuExnData object that can be used to store the exception value, or
+ * \c NULL if no value is required.
+ *
+ * @note The associated #GuType object for type \p T must be visible.
+ */
+
+#define gu_raise_new(error_, t_, pool_, expr_) \
+ GU_BEGIN \
+ GuExnData* gu_raise_err_ = gu_raise(error_, t_); \
+ if (gu_raise_err_) { \
+ GuPool* pool_ = gu_raise_err_->pool; \
+ gu_raise_err_->data = expr_; \
+ } \
+ GU_END
+
+#define gu_raise_i(error_, t_, ...) \
+ gu_raise_new(error_, t_, gu_raise_pool_, gu_new_i(gu_raise_pool_, t_, __VA_ARGS__))
+
+
+/// Check the status of the current exception frame
+static inline bool
+gu_ok(GuExn* exn) {
+ return !GU_UNLIKELY(gu_exn_is_raised(exn));
+}
+/**<
+ * @return \c false if an exception has been raised in the frame \p exn
+ * and it has not been blocked, \c true otherwise.
+ */
+
+
+/// Return from current function if an exception has been raised.
+#define gu_return_on_exn(exn_, retval_) \
+ GU_BEGIN \
+ if (gu_exn_is_raised(exn_)) return retval_; \
+ GU_END
+/**<
+ * @showinitializer
+ */
+
+
+#include <errno.h>
+
+typedef int GuErrno;
+
+extern GU_DECLARE_TYPE(GuErrno, signed);
+
+
+
+#define gu_raise_errno(error_) \
+ gu_raise_i(error_, GuErrno, errno)
+
+#if 0
+
+typedef void (*GuExnPrintFn)(GuFn* clo, void* err, FILE* out);
+
+extern GuTypeTable gu_exn_default_printer;
+
+void
+gu_exn_print(GuExn* err, FILE* out, GuTypeMap printer_map);
+
+#endif
+
+/** @} */
+
+#endif // GU_EXN_H_
diff --git a/src/runtime/c/gu/file.c b/src/runtime/c/gu/file.c
new file mode 100644
index 000000000..ed1956537
--- /dev/null
+++ b/src/runtime/c/gu/file.c
@@ -0,0 +1,73 @@
+#include <gu/file.h>
+
+typedef struct GuFileOutStream GuFileOutStream;
+
+struct GuFileOutStream {
+ GuOutStream stream;
+ FILE* file;
+};
+
+static size_t
+gu_file_output(GuOutStream* stream, const uint8_t* buf, size_t len, GuExn* err)
+{
+ GuFileOutStream* fos = gu_container(stream, GuFileOutStream, stream);
+ errno = 0;
+ size_t wrote = fwrite(buf, 1, len, fos->file);
+ if (wrote < len) {
+ if (ferror(fos->file)) {
+ gu_raise_errno(err);
+ }
+ }
+ return wrote;
+}
+
+static void
+gu_file_flush(GuOutStream* stream, GuExn* err)
+{
+ GuFileOutStream* fos = gu_container(stream, GuFileOutStream, stream);
+ errno = 0;
+ if (fflush(fos->file) != 0) {
+ gu_raise_errno(err);
+ }
+}
+
+GuOut*
+gu_file_out(FILE* file, GuPool* pool)
+{
+ GuFileOutStream* fos = gu_new_i(pool, GuFileOutStream,
+ .stream.output = gu_file_output,
+ .stream.flush = gu_file_flush,
+ .file = file);
+ return gu_new_out(&fos->stream, pool);
+}
+
+
+typedef struct GuFileInStream GuFileInStream;
+
+struct GuFileInStream {
+ GuInStream stream;
+ FILE* file;
+};
+
+static size_t
+gu_file_input(GuInStream* stream, uint8_t* buf, size_t sz, GuExn* err)
+{
+ GuFileInStream* fis = gu_container(stream, GuFileInStream, stream);
+ errno = 0;
+ size_t got = fread(buf, 1, sz, fis->file);
+ if (got == 0) {
+ if (ferror(fis->file)) {
+ gu_raise_errno(err);
+ }
+ }
+ return got;
+}
+
+GuIn*
+gu_file_in(FILE* file, GuPool* pool)
+{
+ GuFileInStream* fis = gu_new_s(pool, GuFileInStream,
+ .stream.input = gu_file_input,
+ .file = file);
+ return gu_new_in(&fis->stream, pool);
+}
diff --git a/src/runtime/c/gu/file.h b/src/runtime/c/gu/file.h
new file mode 100644
index 000000000..1bb3fb00a
--- /dev/null
+++ b/src/runtime/c/gu/file.h
@@ -0,0 +1,14 @@
+#ifndef GU_FILE_H_
+#define GU_FILE_H_
+
+#include <gu/in.h>
+#include <gu/out.h>
+#include <stdio.h>
+
+GuOut*
+gu_file_out(FILE* file, GuPool* pool);
+
+GuIn*
+gu_file_in(FILE* file, GuPool* pool);
+
+#endif // GU_FILE_H_
diff --git a/src/runtime/c/gu/fun.c b/src/runtime/c/gu/fun.c
new file mode 100644
index 000000000..ca777c490
--- /dev/null
+++ b/src/runtime/c/gu/fun.c
@@ -0,0 +1 @@
+#include <gu/fun.h>
diff --git a/src/runtime/c/gu/fun.h b/src/runtime/c/gu/fun.h
new file mode 100644
index 000000000..0004e9923
--- /dev/null
+++ b/src/runtime/c/gu/fun.h
@@ -0,0 +1,65 @@
+#ifndef GU_FUN_H_
+#define GU_FUN_H_
+
+#include <gu/defs.h>
+
+typedef void (*GuFn)();
+typedef void (*GuFn0)(GuFn* clo);
+typedef void (*GuFn1)(GuFn* clo, void* arg1);
+typedef void (*GuFn2)(GuFn* clo, void* arg1, void* arg2);
+
+#define gu_fn(fn_) (&(GuFn){ fn_ })
+
+static inline void
+gu_apply0(GuFn* fn) {
+ (*fn)(fn);
+}
+
+static inline void
+gu_apply1(GuFn* fn, void* arg1) {
+ (*fn)(fn, arg1);
+}
+
+static inline void
+gu_apply2(GuFn* fn, void* arg1, void* arg2) {
+ (*fn)(fn, arg1, arg2);
+}
+
+#define gu_apply(fn_, ...) \
+ ((fn_)->fn((fn_), __VA_ARGS__))
+
+typedef struct GuClo0 GuClo0;
+
+struct GuClo0 {
+ GuFn fn;
+};
+
+typedef struct GuClo1 GuClo1;
+
+struct GuClo1 {
+ GuFn fn;
+ void *env1;
+};
+
+typedef struct GuClo2 GuClo2;
+struct GuClo2 {
+ GuFn fn;
+ void *env1;
+ void *env2;
+};
+
+typedef struct GuClo3 GuClo3;
+struct GuClo3 {
+ GuFn fn;
+ void *env1;
+ void *env2;
+ void *env3;
+};
+
+typedef const struct GuEquality GuEquality;
+
+struct GuEquality {
+ bool (*is_equal)(GuEquality* self, const void* a, const void* b);
+};
+
+#endif // GU_FUN_H_
diff --git a/src/runtime/c/gu/hash.c b/src/runtime/c/gu/hash.c
new file mode 100644
index 000000000..1666263e6
--- /dev/null
+++ b/src/runtime/c/gu/hash.c
@@ -0,0 +1,77 @@
+#include <gu/hash.h>
+
+GuHash
+gu_hash_bytes(GuHash h, const uint8_t* buf, size_t len)
+{
+ for (size_t n = 0; n < len; n++) {
+ h = gu_hash_byte(h, buf[n]);
+ }
+ return h;
+}
+
+static bool
+gu_int_eq_fn(GuEquality* self, const void* p1, const void* p2)
+{
+ (void) self;
+ const int* ip1 = p1;
+ const int* ip2 = p2;
+ return *ip1 == *ip2;
+}
+
+static GuHash
+gu_int_hash_fn(GuHasher* self, const void* p)
+{
+ (void) self;
+ return (GuHash) *(const int*) p;
+}
+
+GuHasher gu_int_hasher[1] = {
+ {
+ { gu_int_eq_fn },
+ gu_int_hash_fn
+ }
+};
+
+static bool
+gu_addr_eq_fn(GuEquality* self, const void* p1, const void* p2)
+{
+ (void) self;
+ return (p1 == p2);
+}
+
+static GuHash
+gu_addr_hash_fn(GuHasher* self, const void* p)
+{
+ (void) self;
+ return (GuHash) (uintptr_t) p;
+}
+
+GuHasher gu_addr_hasher[1] = {
+ {
+ { gu_addr_eq_fn },
+ gu_addr_hash_fn
+ }
+};
+
+static bool
+gu_word_eq_fn(GuEquality* self, const void* p1, const void* p2)
+{
+ (void) self;
+ const GuWord* wp1 = p1;
+ const GuWord* wp2 = p2;
+ return (*wp1 == *wp2);
+}
+
+static GuHash
+gu_word_hash_fn(GuHasher* self, const void* p)
+{
+ (void) self;
+ return (GuHash) (uintptr_t) p;
+}
+
+GuHasher gu_word_hasher[1] = {
+ {
+ { gu_word_eq_fn },
+ gu_word_hash_fn
+ }
+};
diff --git a/src/runtime/c/gu/hash.h b/src/runtime/c/gu/hash.h
new file mode 100644
index 000000000..e16c2f454
--- /dev/null
+++ b/src/runtime/c/gu/hash.h
@@ -0,0 +1,40 @@
+#ifndef GU_HASH_H_
+#define GU_HASH_H_
+
+#include <gu/fun.h>
+
+typedef GuWord GuHash;
+
+static inline GuHash
+gu_hash_ptr(void* ptr)
+{
+ return (GuHash) ptr;
+}
+
+
+static inline GuHash
+gu_hash_byte(GuHash h, uint8_t u)
+{
+ // Paul Larson's simple byte hash
+ return h * 101 + u;
+}
+
+
+GuHash
+gu_hash_bytes(GuHash h, const uint8_t* buf, size_t len);
+
+typedef const struct GuHasher GuHasher;
+
+struct GuHasher {
+ GuEquality eq;
+ GuHash (*hash)(GuHasher* self, const void* p);
+};
+
+
+extern GuHasher gu_int_hasher[1];
+
+extern GuHasher gu_addr_hasher[1];
+
+extern GuHasher gu_word_hasher[1];
+
+#endif // GU_HASH_H_
diff --git a/src/runtime/c/gu/in.c b/src/runtime/c/gu/in.c
new file mode 100644
index 000000000..4238475f8
--- /dev/null
+++ b/src/runtime/c/gu/in.c
@@ -0,0 +1,421 @@
+#include <gu/in.h>
+#include <gu/bits.h>
+#include <math.h>
+
+GU_DEFINE_TYPE(GuEOF, abstract, _);
+
+
+static bool
+gu_in_is_buffering(GuIn* in)
+{
+ return (in->buf_end != NULL);
+}
+
+static void
+gu_in_end_buffering(GuIn* in, GuExn* err)
+{
+ if (!gu_in_is_buffering(in)) {
+ return;
+ }
+ if (in->stream->end_buffer) {
+ size_t len = ((ptrdiff_t) in->buf_size) + in->buf_curr;
+ in->stream->end_buffer(in->stream, len, err);
+ }
+ in->buf_curr = 0;
+ in->buf_size = 0;
+ in->buf_end = NULL;
+}
+
+static bool
+gu_in_begin_buffering(GuIn* in, GuExn* err)
+{
+ if (gu_in_is_buffering(in)) {
+ if (in->buf_curr < 0) {
+ return true;
+ } else {
+ gu_in_end_buffering(in, err);
+ if (!gu_ok(err)) return false;
+ }
+ }
+ if (!in->stream->begin_buffer) {
+ return false;
+ }
+ size_t sz = 0;
+ const uint8_t* new_buf =
+ in->stream->begin_buffer(in->stream, &sz, err);
+ if (new_buf) {
+ in->buf_end = &new_buf[sz];
+ in->buf_curr = -(ptrdiff_t) sz;
+ in->buf_size = sz;
+ return true;
+ }
+ return false;
+}
+
+static size_t
+gu_in_input(GuIn* in, uint8_t* dst, size_t sz, GuExn* err)
+{
+ if (sz == 0) {
+ return 0;
+ }
+ gu_in_end_buffering(in, err);
+ if (!gu_ok(err)) {
+ return 0;
+ }
+ GuInStream* stream = in->stream;
+ if (stream->input) {
+ return stream->input(stream, dst, sz, err);
+ }
+ gu_raise(err, GuEOF);
+ return 0;
+}
+
+size_t
+gu_in_some(GuIn* in, uint8_t* dst, size_t sz, GuExn* err)
+{
+ gu_require(sz <= PTRDIFF_MAX);
+ if (!gu_in_begin_buffering(in, err)) {
+ if (!gu_ok(err)) return 0;
+ return gu_in_input(in, dst, sz, err);
+ }
+ size_t real_sz = GU_MIN(sz, (size_t)(-in->buf_curr));
+ memcpy(dst, &in->buf_end[in->buf_curr], real_sz);
+ in->buf_curr += real_sz;
+ return real_sz;
+}
+
+void
+gu_in_bytes_(GuIn* in, uint8_t* dst, size_t sz, GuExn* err)
+{
+ size_t avail_sz = GU_MIN(sz, (size_t)(-in->buf_curr));
+ memcpy(dst, &in->buf_end[in->buf_curr], avail_sz);
+ in->buf_curr += avail_sz;
+ if (avail_sz < sz) {
+ gu_in_input(in, &dst[avail_sz], sz - avail_sz, err);
+ }
+}
+
+const uint8_t*
+gu_in_begin_span(GuIn* in, size_t *sz_out, GuExn* err)
+{
+ if (!gu_in_begin_buffering(in, err)) {
+ return NULL;
+ }
+ *sz_out = (size_t) -in->buf_curr;
+ return &in->buf_end[in->buf_curr];
+}
+
+void
+gu_in_end_span(GuIn* in, size_t consumed)
+{
+ gu_require(consumed <= (size_t) -in->buf_curr);
+ in->buf_curr += (ptrdiff_t) consumed;
+}
+
+uint8_t
+gu_in_u8_(GuIn* in, GuExn* err)
+{
+ if (gu_in_begin_buffering(in, err) && in->buf_curr < 0) {
+ return in->buf_end[in->buf_curr++];
+ }
+ uint8_t u = 0;
+ size_t r = gu_in_input(in, &u, 1, err);
+ if (r < 1) {
+ gu_raise(err, GuEOF);
+ return 0;
+ }
+ return u;
+}
+
+static uint64_t
+gu_in_be(GuIn* in, GuExn* err, int n)
+{
+ uint8_t buf[8];
+ gu_in_bytes(in, buf, n, err);
+ uint64_t u = 0;
+ for (int i = 0; i < n; i++) {
+ u = u << 8 | buf[i];
+ }
+ return u;
+}
+
+static uint64_t
+gu_in_le(GuIn* in, GuExn* err, int n)
+{
+ uint8_t buf[8];
+ gu_in_bytes(in, buf, n, err);
+ uint64_t u = 0;
+ for (int i = 0; i < n; i++) {
+ u = u << 8 | buf[i];
+ }
+ return u;
+}
+
+int8_t
+gu_in_s8(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c8(gu_in_u8(in, err), err);
+}
+
+
+uint16_t
+gu_in_u16le(GuIn* in, GuExn* err)
+{
+ return gu_in_le(in, err, 2);
+}
+
+int16_t
+gu_in_s16le(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c16(gu_in_u16le(in, err), err);
+}
+
+uint16_t
+gu_in_u16be(GuIn* in, GuExn* err)
+{
+ return gu_in_be(in, err, 2);
+}
+
+int16_t
+gu_in_s16be(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c16(gu_in_u16be(in, err), err);
+}
+
+
+uint32_t
+gu_in_u32le(GuIn* in, GuExn* err)
+{
+ return gu_in_le(in, err, 4);
+}
+
+int32_t
+gu_in_s32le(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c32(gu_in_u32le(in, err), err);
+}
+
+uint32_t
+gu_in_u32be(GuIn* in, GuExn* err)
+{
+ return gu_in_be(in, err, 4);
+}
+
+int32_t
+gu_in_s32be(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c32(gu_in_u32be(in, err), err);
+}
+
+
+uint64_t
+gu_in_u64le(GuIn* in, GuExn* err)
+{
+ return gu_in_le(in, err, 8);
+}
+
+int64_t
+gu_in_s64le(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c64(gu_in_u64le(in, err), err);
+}
+
+uint64_t
+gu_in_u64be(GuIn* in, GuExn* err)
+{
+ return gu_in_be(in, err, 8);
+}
+
+int64_t
+gu_in_s64be(GuIn* in, GuExn* err)
+{
+ return gu_decode_2c64(gu_in_u64be(in, err), err);
+}
+
+double
+gu_in_f64le(GuIn* in, GuExn* err)
+{
+ return gu_decode_double(gu_in_u64le(in, err));
+}
+
+double
+gu_in_f64be(GuIn* in, GuExn* err)
+{
+ return gu_decode_double(gu_in_u64le(in, err));
+}
+
+
+static void
+gu_in_fini(GuFinalizer* fin)
+{
+ GuIn* in = gu_container(fin, GuIn, fini);
+ GuPool* pool = gu_local_pool();
+ GuExn* err = gu_exn(NULL, type, pool);
+ gu_in_end_buffering(in, err);
+ gu_pool_free(pool);
+}
+
+GuIn
+gu_init_in(GuInStream* stream)
+{
+ return (GuIn) {
+ .buf_end = NULL,
+ .buf_curr = 0,
+ .buf_size = 0,
+ .stream = stream,
+ .fini.fn = gu_in_fini
+ };
+}
+
+GuIn*
+gu_new_in(GuInStream* stream, GuPool* pool)
+{
+ GuIn* in = gu_new(GuIn, pool);
+ *in = gu_init_in(stream);
+ return in;
+}
+
+
+typedef struct GuProxyInStream GuProxyInStream;
+
+struct GuProxyInStream {
+ GuInStream stream;
+ GuIn* real_in;
+};
+
+static const uint8_t*
+gu_proxy_in_begin_buffer(GuInStream* self, size_t* sz_out, GuExn* err)
+{
+ GuProxyInStream* pis = gu_container(self, GuProxyInStream, stream);
+ return gu_in_begin_span(pis->real_in, sz_out, err);
+}
+
+static void
+gu_proxy_in_end_buffer(GuInStream* self, size_t sz, GuExn* err)
+{
+ GuProxyInStream* pis = gu_container(self, GuProxyInStream, stream);
+ gu_in_end_span(pis->real_in, sz);
+}
+
+static size_t
+gu_proxy_in_input(GuInStream* self, uint8_t* dst, size_t sz, GuExn* err)
+{
+ GuProxyInStream* pis = gu_container(self, GuProxyInStream, stream);
+ return gu_in_some(pis->real_in, dst, sz, err);
+}
+
+GuInStream*
+gu_in_proxy_stream(GuIn* in, GuPool* pool)
+{
+ return &gu_new_s(
+ pool, GuProxyInStream,
+ .stream.begin_buffer = gu_proxy_in_begin_buffer,
+ .stream.end_buffer = gu_proxy_in_end_buffer,
+ .stream.input = gu_proxy_in_input,
+ .real_in = in)->stream;
+}
+
+enum {
+ GU_BUFFERED_IN_BUF_SIZE = 4096
+};
+
+typedef struct GuBufferedInStream GuBufferedInStream;
+
+struct GuBufferedInStream {
+ GuInStream stream;
+ size_t alloc;
+ size_t have;
+ size_t curr;
+ GuIn* in;
+ uint8_t buf[];
+};
+
+static const uint8_t*
+gu_buffered_in_begin_buffer(GuInStream* self, size_t* sz_out, GuExn* err)
+{
+ GuBufferedInStream* bis =
+ gu_container(self, GuBufferedInStream, stream);
+ if (bis->curr == bis->have) {
+ bis->curr = 0;
+ bis->have = gu_in_some(bis->in, bis->buf, bis->alloc, err);
+ if (!gu_ok(err)) return NULL;
+ }
+ *sz_out = bis->have - bis->curr;
+ return &bis->buf[bis->curr];
+}
+
+static void
+gu_buffered_in_end_buffer(GuInStream* self, size_t consumed, GuExn* err)
+{
+ GuBufferedInStream* bis =
+ gu_container(self, GuBufferedInStream, stream);
+ gu_require(consumed < bis->have - bis->curr);
+ bis->curr += consumed;
+}
+
+static size_t
+gu_buffered_in_input(GuInStream* self, uint8_t* dst, size_t sz, GuExn* err)
+{
+ GuBufferedInStream* bis =
+ gu_container(self, GuBufferedInStream, stream);
+ return gu_in_some(bis->in, dst, sz, err);
+}
+
+GuIn*
+gu_buffered_in(GuIn* in, size_t buf_sz, GuPool* pool)
+{
+ GuBufferedInStream* bis = gu_new_flex(pool, GuBufferedInStream,
+ buf, buf_sz);
+ bis->stream = (GuInStream) {
+ .begin_buffer = gu_buffered_in_begin_buffer,
+ .end_buffer = gu_buffered_in_end_buffer,
+ .input = gu_buffered_in_input
+ };
+ bis->have = bis->curr = 0;
+ bis->alloc = buf_sz;
+ return gu_new_in(&bis->stream, pool);
+}
+
+typedef struct GuDataIn GuDataIn;
+
+struct GuDataIn {
+ GuInStream stream;
+ const uint8_t* data;
+ size_t sz;
+};
+
+static const uint8_t*
+gu_data_in_begin_buffer(GuInStream* self, size_t* sz_out, GuExn* err)
+{
+ (void) err;
+ GuDataIn* di = gu_container(self, GuDataIn, stream);
+ const uint8_t* buf = di->data;
+ if (buf) {
+ *sz_out = di->sz;
+ di->data = NULL;
+ di->sz = 0;
+ }
+ return buf;
+}
+
+GuIn*
+gu_data_in(const uint8_t* data, size_t sz, GuPool* pool)
+{
+ GuDataIn* di = gu_new_s(pool, GuDataIn,
+ .stream.begin_buffer = gu_data_in_begin_buffer,
+ .data = data,
+ .sz = sz);
+ return gu_new_in(&di->stream, pool);
+}
+
+extern inline uint8_t
+gu_in_u8(GuIn* restrict in, GuExn* err);
+
+extern inline void
+gu_in_bytes(GuIn* in, uint8_t* buf, size_t sz, GuExn* err);
+
+extern inline int
+gu_in_peek_u8(GuIn* restrict in);
+
+extern inline void
+gu_in_consume(GuIn* restrict in, size_t sz);
diff --git a/src/runtime/c/gu/in.h b/src/runtime/c/gu/in.h
new file mode 100644
index 000000000..c85a6581b
--- /dev/null
+++ b/src/runtime/c/gu/in.h
@@ -0,0 +1,146 @@
+#ifndef GU_IN_H_
+#define GU_IN_H_
+
+#include <gu/defs.h>
+#include <gu/exn.h>
+#include <gu/assert.h>
+
+typedef struct GuInStream GuInStream;
+
+struct GuInStream {
+ const uint8_t* (*begin_buffer)(GuInStream* self, size_t* sz_out,
+ GuExn* err);
+ void (*end_buffer)(GuInStream* self, size_t consumed, GuExn* err);
+ size_t (*input)(GuInStream* self, uint8_t* buf, size_t max_sz,
+ GuExn* err);
+};
+
+typedef struct GuIn GuIn;
+
+struct GuIn {
+ const uint8_t* restrict buf_end;
+ ptrdiff_t buf_curr;
+ size_t buf_size;
+ GuInStream* stream;
+ GuFinalizer fini;
+};
+
+
+GuIn
+gu_init_in(GuInStream* stream);
+
+GuIn*
+gu_new_in(GuInStream* stream, GuPool* pool);
+
+GuInStream*
+gu_in_proxy_stream(GuIn* in, GuPool* pool);
+
+const uint8_t*
+gu_in_begin_span(GuIn* in, size_t *sz_out, GuExn* err);
+
+void
+gu_in_end_span(GuIn* in, size_t consumed);
+
+size_t
+gu_in_some(GuIn* in, uint8_t* buf, size_t max_len, GuExn* err);
+
+inline void
+gu_in_bytes(GuIn* in, uint8_t* buf, size_t sz, GuExn* err)
+{
+ gu_require(sz < PTRDIFF_MAX);
+ ptrdiff_t curr = in->buf_curr;
+ ptrdiff_t new_curr = curr + (ptrdiff_t) sz;
+ if (GU_UNLIKELY(new_curr > 0)) {
+ extern void gu_in_bytes_(GuIn* in, uint8_t* buf, size_t sz,
+ GuExn* err);
+ gu_in_bytes_(in, buf, sz, err);
+ return;
+ }
+ memcpy(buf, &in->buf_end[curr], sz);
+ in->buf_curr = new_curr;
+}
+
+inline int
+gu_in_peek_u8(GuIn* restrict in)
+{
+ if (GU_UNLIKELY(in->buf_curr == 0)) {
+ return -1;
+ }
+ return in->buf_end[in->buf_curr];
+}
+
+inline void
+gu_in_consume(GuIn* restrict in, size_t sz)
+{
+ gu_require((ptrdiff_t) sz + in->buf_curr <= 0);
+ in->buf_curr += sz;
+}
+
+
+inline uint8_t
+gu_in_u8(GuIn* restrict in, GuExn* err)
+{
+ if (GU_UNLIKELY(in->buf_curr == 0)) {
+ extern uint8_t gu_in_u8_(GuIn* restrict in, GuExn* err);
+ return gu_in_u8_(in, err);
+ }
+ return in->buf_end[in->buf_curr++];
+}
+
+int8_t
+gu_in_s8(GuIn* in, GuExn* err);
+
+uint16_t
+gu_in_u16le(GuIn* in, GuExn* err);
+
+uint16_t
+gu_in_u16be(GuIn* in, GuExn* err);
+
+int16_t
+gu_in_s16le(GuIn* in, GuExn* err);
+
+int16_t
+gu_in_s16be(GuIn* in, GuExn* err);
+
+uint32_t
+gu_in_u32le(GuIn* in, GuExn* err);
+
+uint32_t
+gu_in_u32be(GuIn* in, GuExn* err);
+
+int32_t
+gu_in_s32le(GuIn* in, GuExn* err);
+
+int32_t
+gu_in_s32be(GuIn* in, GuExn* err);
+
+uint64_t
+gu_in_u64le(GuIn* in, GuExn* err);
+
+uint64_t
+gu_in_u64be(GuIn* in, GuExn* err);
+
+int64_t
+gu_in_s64le(GuIn* in, GuExn* err);
+
+int64_t
+gu_in_s64be(GuIn* in, GuExn* err);
+
+double
+gu_in_f64le(GuIn* in, GuExn* err);
+
+double
+gu_in_f64be(GuIn* in, GuExn* err);
+
+GuIn*
+gu_buffered_in(GuIn* in, size_t sz, GuPool* pool);
+
+GuIn*
+gu_data_in(const uint8_t* buf, size_t size, GuPool* pool);
+
+
+extern GU_DECLARE_TYPE(GuEOF, abstract);
+
+#include <gu/type.h>
+
+#endif // GU_IN_H_
diff --git a/src/runtime/c/gu/intern.c b/src/runtime/c/gu/intern.c
new file mode 100644
index 000000000..35eaa1c5f
--- /dev/null
+++ b/src/runtime/c/gu/intern.c
@@ -0,0 +1,59 @@
+#include "intern.h"
+
+struct GuIntern {
+ GuPool* str_pool;
+ GuMap* map;
+};
+
+GuIntern*
+gu_new_intern(GuPool* str_pool, GuPool* pool)
+{
+ GuIntern* intern = gu_new(GuIntern, pool);
+ intern->str_pool = str_pool;
+ intern->map = gu_new_set(const char*, gu_str_hasher, pool);
+ return intern;
+}
+
+const char*
+gu_intern_str(GuIntern* intern, const char* cstr)
+{
+ const char* const* strp = gu_map_find_key(intern->map, &cstr);
+ if (strp) {
+ return *strp;
+ }
+ const char* str = gu_strdup(cstr, intern->str_pool);
+ gu_map_insert(intern->map, &str);
+ return str;
+}
+
+
+
+
+struct GuSymTable {
+ GuPool* sym_pool;
+ GuMap* map;
+};
+
+GuSymTable*
+gu_new_symtable(GuPool* sym_pool, GuPool* pool)
+{
+ GuSymTable* tab = gu_new(GuSymTable, pool);
+ tab->sym_pool = sym_pool;
+ tab->map = gu_new_set(GuSymbol, gu_string_hasher, pool);
+ return tab;
+}
+
+GuSymbol
+gu_symtable_intern(GuSymTable* tab, GuString string)
+{
+ if (gu_string_is_stable(string)) {
+ return string;
+ }
+ const GuSymbol* symp = gu_map_find_key(tab->map, &string);
+ if (symp) {
+ return *symp;
+ }
+ GuSymbol sym = gu_string_copy(string, tab->sym_pool);
+ gu_map_insert(tab->map, &sym);
+ return sym;
+}
diff --git a/src/runtime/c/gu/intern.h b/src/runtime/c/gu/intern.h
new file mode 100644
index 000000000..bf9e9b321
--- /dev/null
+++ b/src/runtime/c/gu/intern.h
@@ -0,0 +1,24 @@
+#ifndef GU_INTERN_H_
+#define GU_INTERN_H_
+
+#include <gu/map.h>
+#include <gu/str.h>
+#include <gu/string.h>
+
+typedef struct GuIntern GuIntern;
+
+GuIntern* gu_new_intern(GuPool* str_pool, GuPool* pool);
+const char* gu_intern_str(GuIntern* intern, const char* cstr);
+
+
+typedef struct GuSymTable GuSymTable;
+
+typedef GuString GuSymbol;
+
+GuSymTable*
+gu_new_symtable(GuPool* sym_pool, GuPool* pool);
+
+GuSymbol
+gu_symtable_intern(GuSymTable* symtab, GuString string);
+
+#endif /* GU_INTERN_H_ */
diff --git a/src/runtime/c/gu/list.c b/src/runtime/c/gu/list.c
new file mode 100644
index 000000000..d98a42e41
--- /dev/null
+++ b/src/runtime/c/gu/list.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gu/list.h>
+#include <gu/assert.h>
+#include <string.h>
+
+static const int gu_list_empty = 0;
+
+void* gu_list_alloc(GuPool* pool, size_t base_size, size_t elem_size,
+ int n_elems, size_t alignment)
+{
+ gu_assert(n_elems >= 0);
+ if (n_elems == 0) {
+ return (void*) &gu_list_empty;
+ }
+ // XXX: use gu_flex_size, use offset of elems
+ void* p = gu_malloc_aligned(pool, base_size + elem_size * n_elems,
+ alignment);
+ *(int*) p = n_elems;
+ return p;
+}
+
+
+GU_DEFINE_KIND(GuList, abstract);
+
+// GU_DEFINE_TYPE(GuStrs, GuList, gu_type(GuStr));
+// GU_DEFINE_TYPE(GuStrsP, pointer, gu_type(GuStrs));
+
+void*
+gu_list_type_alloc(GuListType* ltype, int n_elems, GuPool* pool)
+{
+ return gu_list_alloc(pool, ltype->size,
+ gu_type_size(ltype->elem_type),
+ n_elems, ltype->align);
+}
+
+void*
+gu_list_type_index(GuListType* ltype, void* list, int i)
+{
+ uint8_t* p = list;
+ return &p[ltype->elems_offset + i * gu_type_size(ltype->elem_type)];
+}
diff --git a/src/runtime/c/gu/list.h b/src/runtime/c/gu/list.h
new file mode 100644
index 000000000..f2add157f
--- /dev/null
+++ b/src/runtime/c/gu/list.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file
+ *
+ * Lists.
+ */
+
+#ifndef GU_LIST_H_
+#define GU_LIST_H_
+
+#include <gu/mem.h>
+
+
+#define GuList(t) \
+ struct { \
+ const int len; \
+ t elems[]; \
+ }
+
+void* gu_list_alloc(GuPool* pool, size_t base_size, size_t elem_size,
+ int n_elems, size_t alignment);
+
+#define gu_new_list(t, pool, n) \
+ ((t*) gu_list_alloc(pool, \
+ sizeof(t), \
+ sizeof(((t*)NULL)->elems[0]), \
+ (n), \
+ gu_flex_alignof(t)))
+
+static inline int
+gu_list_length(const void* list)
+{
+ return *(const int*) list;
+}
+
+#define gu_list_elems(lst) \
+ ((lst)->elems)
+
+#define gu_list_index(lst, i) \
+ (gu_list_elems(lst)[i])
+
+typedef GuList(void*) GuPointers;
+//typedef GuList(uint8_t) GuBytes;
+
+typedef GuList(int) GuInts;
+
+
+#define GuListN(t_, len_) \
+ struct { \
+ int len; \
+ t elems[len_]; \
+ }
+
+#define gu_list_(qual_, t_, ...) \
+ ((qual_ GuList(t_) *) \
+ ((qual_ GuListN(t_, (sizeof((t_[]){__VA_ARGS__}) / sizeof(t_)))[]){ \
+ __VA_ARGS__ \
+ }))
+
+#define gu_list(t_, ...) \
+ gu_list_(, t_, __VA_ARGS__)
+
+#define gu_clist(t_, ...) \
+ gu_list_(const, t_, __VA_ARGS__)
+
+#define GuSList(t) \
+ const struct { \
+ int len; \
+ t* elems; \
+ }
+
+#define GU_SLIST_0 { .len = 0, .elems = NULL }
+
+#define GU_SLIST(t, ...) \
+ { \
+ .len = GU_ARRAY_LEN(t,GU_ID({__VA_ARGS__})), \
+ .elems = ((t[]){__VA_ARGS__}) \
+ }
+
+
+#include <gu/type.h>
+
+//
+// list
+//
+
+typedef const struct GuListType GuListType, GuType_GuList;
+
+struct GuListType {
+ GuType_abstract abstract_base;
+ size_t size;
+ size_t align;
+ GuType* elem_type;
+ ptrdiff_t elems_offset;
+};
+
+#define GU_TYPE_INIT_GuList(k_, t_, elem_type_) { \
+ .abstract_base = GU_TYPE_INIT_abstract(k_, t_, _), \
+ .size = sizeof(t_), \
+ .align = gu_alignof(t_), \
+ .elem_type = elem_type_, \
+ .elems_offset = offsetof(t_, elems) \
+}
+
+extern GU_DECLARE_KIND(GuList);
+
+void*
+gu_list_type_alloc(GuListType* ltype, int n_elems, GuPool* pool);
+
+void*
+gu_list_type_index(GuListType* ltype, void* list, int i);
+
+#include <gu/str.h>
+
+
+typedef GuList(GuStr) GuStrs;
+typedef GuStrs* GuStrsP;
+
+extern GU_DECLARE_TYPE(GuStrs, GuList);
+extern GU_DECLARE_TYPE(GuStrsP, pointer);
+
+
+#endif // GU_LIST_H_
diff --git a/src/runtime/c/gu/log.c b/src/runtime/c/gu/log.c
new file mode 100644
index 000000000..399646c50
--- /dev/null
+++ b/src/runtime/c/gu/log.c
@@ -0,0 +1,79 @@
+#include <gu/defs.h>
+#include <gu/log.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+static int gu_log_depth = 0;
+
+static bool
+gu_log_match(const char* pat, size_t patlen, const char* str)
+{
+ if (patlen > 0 && pat[patlen-1] == '*') {
+ return strncmp(pat, str, patlen-1) == 0;
+ } else if (strlen(str) == patlen) {
+ return strncmp(pat, str, patlen) == 0;
+ }
+ return false;
+}
+
+static bool
+gu_log_enabled(const char* func, const char* file)
+{
+ const char* cfg = getenv("GU_LOG");
+ if (cfg == NULL) {
+ return false;
+ }
+ const char* p = cfg;
+ while (true) {
+ size_t len = strcspn(p, ",");
+ if (gu_log_match(p, len, func)) {
+ return true;
+ }
+ if (gu_log_match(p, len, file)) {
+ return true;
+ }
+ if (p[len] == '\0') {
+ break;
+ }
+ p = &p[len + 1];
+ }
+ return false;
+}
+
+
+void
+gu_log_full_v(GuLogKind kind, const char* func, const char* file, int line,
+ const char* fmt, va_list args)
+{
+ (void) (kind && line);
+ if (!gu_log_enabled(func, file)) {
+ return;
+ }
+ if (kind == GU_LOG_KIND_EXIT) {
+ gu_log_depth--;
+ }
+ if (fmt) {
+ int indent = gu_min(32 + gu_log_depth, 48);
+ fprintf(stderr, "%-*s: ", indent, func);
+ vfprintf(stderr, fmt, args);
+ fputc('\n', stderr);
+ fflush(stderr);
+ }
+ if (kind == GU_LOG_KIND_ENTER) {
+ gu_log_depth++;
+ }
+}
+
+void
+gu_log_full(GuLogKind kind, const char* func, const char* file, int line,
+ const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gu_log_full_v(kind, func, file, line, fmt, args);
+ va_end(args);
+}
+
diff --git a/src/runtime/c/gu/log.h b/src/runtime/c/gu/log.h
new file mode 100644
index 000000000..ec9ecdf75
--- /dev/null
+++ b/src/runtime/c/gu/log.h
@@ -0,0 +1,65 @@
+#ifndef GU_LOG_H_
+#define GU_LOG_H_
+
+#include <stdarg.h>
+
+typedef enum GuLogKind {
+ GU_LOG_KIND_ENTER,
+ GU_LOG_KIND_EXIT,
+ GU_LOG_KIND_DEBUG,
+ GU_LOG_KIND_ERROR
+} GuLogKind;
+
+void
+gu_log_full(GuLogKind kind, const char* func, const char* file, int line,
+ const char* fmt, ...);
+
+
+void
+gu_log_full_v(GuLogKind kind, const char* func, const char* file, int line,
+ const char* fmt, va_list args);
+
+
+#ifndef NDEBUG
+
+#define gu_logv(kind_, fmt_, args_) \
+ gu_log_full_v(kind_, __func__, __FILE__, __LINE__, fmt_, args_)
+
+#define gu_log(kind_, ...) \
+ gu_log_full(kind_, __func__, __FILE__, __LINE__, __VA_ARGS__)
+
+#else
+
+static inline void
+gu_logv(GuLogKind kind, const char* fmt, va_list args)
+{
+ (void) kind;
+ (void) fmt;
+ (void) args;
+}
+
+static inline void
+gu_log(GuLogKind kind, const char* fmt, ...)
+{
+ (void) kind;
+ (void) fmt;
+}
+
+#endif
+
+
+
+
+#define gu_enter(...) \
+ gu_log(GU_LOG_KIND_ENTER, __VA_ARGS__)
+
+#define gu_exit(...) \
+ gu_log(GU_LOG_KIND_EXIT, __VA_ARGS__)
+
+#define gu_debug(...) \
+ gu_log(GU_LOG_KIND_DEBUG, __VA_ARGS__)
+
+#define gu_debugv(kind_, fmt_, args_) \
+ gu_logv(GU_LOG_KIND_DEBUG, fmt_, args_)
+
+#endif // GU_LOG_H_
diff --git a/src/runtime/c/gu/map.c b/src/runtime/c/gu/map.c
new file mode 100644
index 000000000..2ee77bb23
--- /dev/null
+++ b/src/runtime/c/gu/map.c
@@ -0,0 +1,353 @@
+#include <gu/defs.h>
+#include <gu/mem.h>
+#include <gu/type.h>
+#include <gu/map.h>
+#include <gu/assert.h>
+#include <gu/prime.h>
+#include <gu/log.h>
+
+typedef enum {
+ GU_MAP_GENERIC,
+ GU_MAP_ADDR,
+ GU_MAP_WORD
+} GuMapKind;
+
+typedef struct GuMapData GuMapData;
+
+struct GuMapData {
+ uint8_t* keys;
+ uint8_t* values;
+ size_t n_occupied;
+ size_t n_entries;
+ size_t zero_idx;
+};
+
+struct GuMap {
+ GuMapKind const kind;
+ GuHasher* const hasher;
+ size_t const key_size;
+ size_t const value_size;
+ const void* default_value;
+ GuMapData data;
+
+ GuFinalizer fin;
+};
+
+static void
+gu_map_finalize(GuFinalizer* fin)
+{
+ GuMap* map = gu_container(fin, GuMap, fin);
+ gu_mem_buf_free(map->data.keys);
+ if (map->value_size) {
+ gu_mem_buf_free(map->data.values);
+ }
+}
+
+static const GuWord gu_map_empty_key = 0;
+
+static bool
+gu_map_buf_is_zero(const uint8_t* p, size_t sz) {
+ while (sz >= sizeof(GuWord)) {
+ sz -= sizeof(GuWord);
+ if (memcmp(&p[sz], &gu_map_empty_key, sizeof(GuWord)) != 0) {
+ return false;
+ }
+ }
+ return (memcmp(p, &gu_map_empty_key, sz) == 0);
+}
+
+static bool
+gu_map_entry_is_free(GuMap* map, GuMapData* data, size_t idx)
+{
+ if (idx == data->zero_idx) {
+ return false;
+ } else if (map->kind == GU_MAP_ADDR) {
+ const void* key = ((const void**)data->keys)[idx];
+ return key == NULL;
+ } else if (map->kind == GU_MAP_WORD) {
+ GuWord key = ((GuWord*)data->keys)[idx];
+ return key == 0;
+ }
+ gu_assert(map->kind == GU_MAP_GENERIC);
+ const void* key = &data->keys[idx * map->key_size];
+ return gu_map_buf_is_zero(key, map->key_size);
+}
+
+static bool
+gu_map_lookup(GuMap* map, const void* key, size_t* idx_out)
+{
+ size_t n = map->data.n_entries;
+ switch (map->kind) {
+ case GU_MAP_GENERIC: {
+ GuHasher* hasher = map->hasher;
+ GuEquality* eq = (GuEquality*) hasher;
+ GuHash hash = hasher->hash(hasher, key);
+ size_t idx = hash % n;
+ size_t offset = (hash % (n - 2)) + 1;
+ size_t key_size = map->key_size;
+ while (true) {
+ void* entry_key = &map->data.keys[idx * key_size];
+ if (gu_map_buf_is_zero(entry_key, key_size) &&
+ map->data.zero_idx != idx) {
+ *idx_out = idx;
+ return false;
+ } else if (eq->is_equal(eq, key, entry_key)) {
+ *idx_out = idx;
+ return true;
+ }
+ idx = (idx + offset) % n;
+ }
+ gu_impossible();
+ break;
+ }
+ case GU_MAP_ADDR: {
+ GuHash hash = (GuHash) key;
+ size_t idx = hash % n;
+ size_t offset = (hash % (n - 2)) + 1;
+ while (true) {
+ const void* entry_key =
+ ((const void**)map->data.keys)[idx];
+ if (entry_key == NULL && map->data.zero_idx != idx) {
+ *idx_out = idx;
+ return false;
+ } else if (entry_key == key) {
+ *idx_out = idx;
+ return true;
+ }
+ idx = (idx + offset) % n;
+ }
+ gu_impossible();
+ break;
+ }
+ case GU_MAP_WORD: {
+ GuWord w = *(const GuWord*)key;
+ GuHash hash = (GuHash) w;
+ size_t idx = hash % n;
+ size_t offset = (hash % (n - 2)) + 1;
+ while (true) {
+ GuWord entry_key = ((GuWord*)map->data.keys)[idx];
+ if (entry_key == 0 && map->data.zero_idx != idx) {
+ *idx_out = idx;
+ return false;
+ } else if (entry_key == w) {
+ *idx_out = idx;
+ return true;
+ }
+ idx = (idx + offset) % n;
+ }
+ gu_impossible();
+ break;
+ }
+ default:
+ gu_impossible();
+ }
+ gu_impossible();
+ return false;
+}
+
+
+static void
+gu_map_resize(GuMap* map)
+{
+ GuMapData* data = &map->data;
+ GuMapData old_data = *data;
+ size_t req_entries =
+ gu_twin_prime_sup(GU_MAX(11, map->data.n_occupied * 4 / 3 + 1));
+
+ size_t key_size = map->key_size;
+ size_t key_alloc = 0;
+ data->keys = gu_mem_buf_alloc(req_entries * key_size, &key_alloc);
+
+ size_t value_size = map->value_size;
+ size_t value_alloc = 0;
+ if (value_size) {
+ data->values = gu_mem_buf_alloc(req_entries * value_size,
+ &value_alloc);
+ memset(data->values, 0, value_alloc);
+ }
+
+ data->n_entries = gu_twin_prime_inf(value_size ?
+ GU_MIN(key_alloc / key_size,
+ value_alloc / value_size)
+ : key_alloc / key_size);
+ switch (map->kind) {
+ case GU_MAP_GENERIC:
+ case GU_MAP_WORD:
+ memset(data->keys, 0, key_alloc);
+ break;
+ case GU_MAP_ADDR:
+ for (size_t i = 0; i < data->n_entries; i++) {
+ ((const void**)data->keys)[i] = NULL;
+ }
+ break;
+ default:
+ gu_impossible();
+ }
+
+ gu_assert(data->n_entries > data->n_occupied);
+ gu_debug("Resized to %d entries", data->n_entries);
+
+ data->n_occupied = 0;
+ data->zero_idx = SIZE_MAX;
+
+ for (size_t i = 0; i < old_data.n_entries; i++) {
+ if (gu_map_entry_is_free(map, &old_data, i)) {
+ continue;
+ }
+ void* old_key = &old_data.keys[i * key_size];
+ if (map->kind == GU_MAP_ADDR) {
+ old_key = *(void**)old_key;
+ }
+ void* old_value = &old_data.values[i * value_size];
+
+ memcpy(gu_map_insert(map, old_key),
+ old_value, map->value_size);
+ }
+
+ gu_mem_buf_free(old_data.keys);
+ if (value_size) {
+ gu_mem_buf_free(old_data.values);
+ }
+}
+
+
+static bool
+gu_map_maybe_resize(GuMap* map)
+{
+ if (map->data.n_entries <=
+ map->data.n_occupied + (map->data.n_occupied / 4)) {
+ gu_map_resize(map);
+ return true;
+ }
+ return false;
+}
+
+void*
+gu_map_find(GuMap* map, const void* key)
+{
+ size_t idx;
+ bool found = gu_map_lookup(map, key, &idx);
+ if (found) {
+ return &map->data.values[idx * map->value_size];
+ }
+ return NULL;
+}
+
+const void*
+gu_map_find_default(GuMap* map, const void* key)
+{
+ void* p = gu_map_find(map, key);
+ return p ? p : map->default_value;
+}
+
+const void*
+gu_map_find_key(GuMap* map, const void* key)
+{
+ size_t idx;
+ bool found = gu_map_lookup(map, key, &idx);
+ if (found) {
+ return &map->data.keys[idx * map->key_size];
+ }
+ return NULL;
+}
+
+
+void*
+gu_map_insert(GuMap* map, const void* key)
+{
+ size_t idx;
+ bool found = gu_map_lookup(map, key, &idx);
+ if (!found) {
+ if (gu_map_maybe_resize(map)) {
+ found = gu_map_lookup(map, key, &idx);
+ gu_assert(!found);
+ }
+ if (map->kind == GU_MAP_ADDR) {
+ ((const void**)map->data.keys)[idx] = key;
+ } else {
+ memcpy(&map->data.keys[idx * map->key_size],
+ key, map->key_size);
+ }
+ if (map->default_value) {
+ memcpy(&map->data.values[idx * map->value_size],
+ map->default_value, map->value_size);
+ }
+ if (gu_map_entry_is_free(map, &map->data, idx)) {
+ gu_assert(map->data.zero_idx == SIZE_MAX);
+ map->data.zero_idx = idx;
+ }
+ map->data.n_occupied++;
+ }
+ return &map->data.values[idx * map->value_size];
+}
+
+void
+gu_map_iter(GuMap* map, GuMapItor* itor, GuExn* err)
+{
+ for (size_t i = 0; i < map->data.n_entries && gu_ok(err); i++) {
+ if (gu_map_entry_is_free(map, &map->data, i)) {
+ continue;
+ }
+ const void* key = &map->data.keys[i * map->key_size];
+ void* value = &map->data.values[i * map->value_size];
+ if (map->kind == GU_MAP_ADDR) {
+ key = *(const void* const*) key;
+ }
+ itor->fn(itor, key, value, err);
+ }
+}
+
+static const uint8_t gu_map_no_values[1] = { 0 };
+
+GuMap*
+gu_make_map(size_t key_size, GuHasher* hasher,
+ size_t value_size, const void* default_value,
+ GuPool* pool)
+{
+ GuMapKind kind =
+ ((!hasher || hasher == gu_addr_hasher)
+ ? GU_MAP_ADDR
+ : (key_size == sizeof(GuWord) && hasher == gu_word_hasher)
+ ? GU_MAP_WORD
+ : GU_MAP_GENERIC);
+ if (kind == GU_MAP_ADDR) {
+ key_size = sizeof(GuWord);
+ }
+ GuMapData data = {
+ .n_occupied = 0,
+ .n_entries = 0,
+ .keys = NULL,
+ .values = value_size ? NULL : (uint8_t*) gu_map_no_values,
+ .zero_idx = SIZE_MAX
+ };
+ GuMap* map = gu_new_i(
+ pool, GuMap,
+ .default_value = default_value,
+ .hasher = hasher,
+ .data = data,
+ .key_size = key_size,
+ .value_size = value_size,
+ .fin.fn = gu_map_finalize,
+ .kind = kind
+ );
+ gu_pool_finally(pool, &map->fin);
+ gu_map_resize(map);
+ return map;
+}
+
+GuMap*
+gu_map_type_make(GuMapType* mtype, GuPool* pool)
+{
+ size_t key_size = 0;
+ if (mtype->hasher && mtype->hasher != gu_addr_hasher) {
+ key_size = gu_type_size(mtype->key_type);
+ }
+ size_t value_size = gu_type_size(mtype->value_type);
+ return gu_make_map(key_size, mtype->hasher,
+ value_size, mtype->default_value, pool);
+}
+
+GU_DEFINE_KIND(GuMap, abstract);
+// GU_DEFINE_KIND(GuIntMap, GuMap);
+
+
diff --git a/src/runtime/c/gu/map.h b/src/runtime/c/gu/map.h
new file mode 100644
index 000000000..6523a8057
--- /dev/null
+++ b/src/runtime/c/gu/map.h
@@ -0,0 +1,121 @@
+#ifndef GU_MAP_H_
+#define GU_MAP_H_
+
+#include <gu/hash.h>
+#include <gu/mem.h>
+#include <gu/exn.h>
+
+typedef const struct GuMapItor GuMapItor;
+
+struct GuMapItor {
+ void (*fn)(GuMapItor* self, const void* key, void* value,
+ GuExn *err);
+};
+
+typedef struct GuMap GuMap;
+
+GuMap*
+gu_make_map(size_t key_size, GuHasher* hasher,
+ size_t value_size, const void* default_value,
+ GuPool* pool);
+
+#define gu_new_map(K, HASHER, V, DV, POOL) \
+ (gu_make_map(sizeof(K), (HASHER), sizeof(V), (DV), (POOL)))
+
+#define gu_new_set(K, HASHER, POOL) \
+ (gu_make_map(sizeof(K), (HASHER), 0, NULL, (POOL)))
+
+#define gu_new_addr_map(K, V, DV, POOL) \
+ (gu_make_map(0, NULL, sizeof(V), (DV), (POOL)))
+
+size_t
+gu_map_count(GuMap* map);
+
+void*
+gu_map_find_full(GuMap* ht, void* key_inout);
+
+const void*
+gu_map_find_default(GuMap* ht, const void* key);
+
+#define gu_map_get(MAP, KEYP, V) \
+ (*(V*)gu_map_find_default((MAP), (KEYP)))
+
+void*
+gu_map_find(GuMap* ht, const void* key);
+
+#define gu_map_set(MAP, KEYP, V, VAL) \
+ GU_BEGIN \
+ V* gu_map_set_p_ = gu_map_find((MAP), (KEYP)); \
+ *gu_map_set_p_ = (VAL); \
+ GU_END
+
+const void*
+gu_map_find_key(GuMap* ht, const void* key);
+
+static inline bool
+gu_map_has(GuMap* ht, const void* key)
+{
+ return gu_map_find_key(ht, key) != NULL;
+}
+
+
+void*
+gu_map_insert(GuMap* ht, const void* key);
+
+#define gu_map_put(MAP, KEYP, V, VAL) \
+ GU_BEGIN \
+ V* gu_map_put_p_ = gu_map_insert((MAP), (KEYP)); \
+ *gu_map_put_p_ = (VAL); \
+ GU_END
+
+void
+gu_map_iter(GuMap* ht, GuMapItor* itor, GuExn* err);
+
+
+typedef GuMap GuIntMap;
+
+#define gu_new_int_map(VAL_T, DEFAULT, POOL) \
+ gu_new_map(int, gu_int_hasher, VAL_T, DEFAULT, POOL)
+
+
+#if defined(GU_TYPE_H_) && !defined(GU_MAP_H_TYPE_)
+#define GU_MAP_H_TYPE_
+
+extern GU_DECLARE_KIND(GuMap);
+
+typedef const struct GuMapType GuMapType, GuType_GuMap;
+
+struct GuMapType {
+ GuType_abstract abstract_base;
+ GuHasher* hasher;
+ GuType* key_type;
+ GuType* value_type;
+ const void* default_value;
+};
+
+GuMap*
+gu_map_type_make(GuMapType* mtype, GuPool* pool);
+
+#define gu_map_type_new(MAP_T, POOL) \
+ gu_map_type_make(gu_type_cast(gu_type(MAP_T), GuMap), (POOL))
+
+#define GU_TYPE_INIT_GuMap(k_, t_, kt_, h_, vt_, dv_) \
+ { \
+ .abstract_base = GU_TYPE_INIT_abstract(k_, t_, _), \
+ .hasher = h_, \
+ .key_type = kt_, \
+ .value_type = vt_, \
+ .default_value = dv_ \
+ }
+
+#define gu_type__GuIntMap gu_type__GuMap
+
+typedef GuType_GuMap GuType_GuIntMap;
+
+#define GU_TYPE_INIT_GuIntMap(KIND, MAP_T, VAL_T, DEFAULT) \
+ GU_TYPE_INIT_GuMap(KIND, MAP_T, gu_type(int), gu_int_hasher, \
+ VAL_T, DEFAULT)
+
+#endif
+
+#endif // GU_MAP_H_
diff --git a/src/runtime/c/gu/mem.c b/src/runtime/c/gu/mem.c
new file mode 100644
index 000000000..649105a6a
--- /dev/null
+++ b/src/runtime/c/gu/mem.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gu/mem.h>
+#include <gu/fun.h>
+#include <gu/bits.h>
+#include <gu/assert.h>
+#include <gu/log.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef USE_VALGRIND
+#include <valgrind/valgrind.h>
+#define VG(X) X
+#else
+#define VG(X) GU_NOP
+#endif
+
+static const size_t
+// Maximum request size for a chunk. The actual maximum chunk size
+// may be somewhat larger.
+gu_mem_chunk_max_size = 1024 * sizeof(void*),
+
+// number of bytes to allocate in the pool when it is created
+ gu_mem_pool_initial_size = 24 * sizeof(void*),
+
+// Pool allocations larger than this will get their own chunk if
+// there's no room in the current one. Allocations smaller than this may trigger
+// the creation of a new chunk, in which case the remaining space in
+// the current chunk is left unused (internal fragmentation).
+ gu_mem_max_shared_alloc = 64 * sizeof(void*),
+
+// Should not be smaller than the granularity for malloc
+ gu_mem_unit_size = 2 * sizeof(void*),
+
+/* Malloc tuning: the additional memory used by malloc next to the
+ allocated object */
+ gu_malloc_overhead = sizeof(size_t);
+
+static void*
+gu_mem_realloc(void* p, size_t size)
+{
+ void* buf = realloc(p, size);
+ if (size != 0 && buf == NULL) {
+ gu_fatal("Memory allocation failed");
+ }
+ gu_debug("%p %zu -> %p", p, size, buf); // strictly illegal
+ return buf;
+}
+
+static void*
+gu_mem_alloc(size_t size)
+{
+ void* buf = malloc(size);
+ if (buf == NULL) {
+ gu_fatal("Memory allocation failed");
+ }
+ gu_debug("%zu -> %p", size, buf);
+ return buf;
+}
+
+static void
+gu_mem_free(void* p)
+{
+ gu_debug("%p", p);
+ free(p);
+}
+
+static size_t
+gu_mem_padovan(size_t min)
+{
+ // This could in principle be done faster with Q-matrices for
+ // Padovan numbers, but not really worth it for our commonly
+ // small numbers.
+ if (min <= 5) {
+ return min;
+ }
+ size_t a = 7, b = 9, c = 12;
+ while (min > a) {
+ if (b < a) {
+ // overflow
+ return SIZE_MAX;
+ }
+ size_t tmp = a + b;
+ a = b;
+ b = c;
+ c = tmp;
+ }
+ return a;
+}
+
+void*
+gu_mem_buf_realloc(void* old_buf, size_t min_size, size_t* real_size_out)
+{
+ size_t min_blocks = ((min_size + gu_malloc_overhead - 1) /
+ gu_mem_unit_size) + 1;
+ size_t blocks = gu_mem_padovan(min_blocks);
+ size_t size = blocks * gu_mem_unit_size - gu_malloc_overhead;
+ void* buf = gu_mem_realloc(old_buf, size);
+ *real_size_out = buf ? size : 0;
+ return buf;
+}
+void*
+gu_mem_buf_alloc(size_t min_size, size_t* real_size_out)
+{
+ return gu_mem_buf_realloc(NULL, min_size, real_size_out);
+}
+
+void
+gu_mem_buf_free(void* buf)
+{
+ gu_mem_free(buf);
+}
+
+
+typedef struct GuMemChunk GuMemChunk;
+
+struct GuMemChunk {
+ GuMemChunk* next;
+ uint8_t data[];
+};
+
+typedef struct GuFinalizerNode GuFinalizerNode;
+
+struct GuFinalizerNode {
+ GuFinalizerNode* next;
+ GuFinalizer* fin;
+};
+
+enum GuPoolFlags {
+ GU_POOL_LOCAL = 1 << 0
+};
+
+struct GuPool {
+ uint8_t* curr_buf; // actually GuMemChunk*
+ GuMemChunk* chunks;
+ GuFinalizerNode* finalizers;
+ uint16_t flags;
+ uint16_t left_edge;
+ uint16_t right_edge;
+ uint16_t curr_size;
+ uint8_t init_buf[];
+};
+
+static GuPool*
+gu_init_pool(uint8_t* buf, size_t sz)
+{
+ gu_require(gu_aligned((uintptr_t) (void*) buf, gu_alignof(GuPool)));
+ gu_require(sz >= sizeof(GuPool));
+ GuPool* pool = (GuPool*) buf;
+ pool->flags = 0;
+ pool->curr_size = sz;
+ pool->curr_buf = (uint8_t*) pool;
+ pool->chunks = NULL;
+ pool->finalizers = NULL;
+ pool->left_edge = offsetof(GuPool, init_buf);
+ pool->right_edge = sz;
+ VG(VALGRIND_CREATE_MEMPOOL(pool, 0, false));
+ return pool;
+}
+
+GuPool*
+gu_local_pool_(uint8_t* buf, size_t sz)
+{
+ GuPool* pool = gu_init_pool(buf, sz);
+ pool->flags |= GU_POOL_LOCAL;
+ gu_debug("%p", pool);
+ return pool;
+}
+
+GuPool*
+gu_new_pool(void)
+{
+ size_t sz = GU_FLEX_SIZE(GuPool, init_buf, gu_mem_pool_initial_size);
+ uint8_t* buf = gu_mem_buf_alloc(sz, &sz);
+ GuPool* pool = gu_init_pool(buf, sz);
+ gu_debug("%p", pool);
+ return pool;
+}
+
+static void
+gu_pool_expand(GuPool* pool, size_t req)
+{
+ size_t real_req = GU_MAX(req, GU_MIN(((size_t)pool->curr_size) + 1,
+ gu_mem_chunk_max_size));
+ gu_assert(real_req >= sizeof(GuMemChunk));
+ size_t size = 0;
+ GuMemChunk* chunk = gu_mem_buf_alloc(real_req, &size);
+ chunk->next = pool->chunks;
+ pool->chunks = chunk;
+ pool->curr_buf = (uint8_t*) chunk;
+ pool->left_edge = offsetof(GuMemChunk, data);
+ pool->right_edge = pool->curr_size = size;
+ // size should always fit in uint16_t
+ gu_assert((size_t) pool->right_edge == size);
+}
+
+static size_t
+gu_mem_advance(size_t old_pos, size_t pre_align, size_t pre_size,
+ size_t align, size_t size)
+{
+ size_t p = gu_align_forward(old_pos, pre_align);
+ p += pre_size;
+ p = gu_align_forward(p, align);
+ p += size;
+ return p;
+}
+
+static void*
+gu_pool_malloc_aligned(GuPool* pool, size_t pre_align, size_t pre_size,
+ size_t align, size_t size)
+{
+ gu_require(size <= gu_mem_max_shared_alloc);
+ size_t pos = gu_mem_advance(pool->left_edge, pre_align, pre_size,
+ align, size);
+ if (pos > (size_t) pool->right_edge) {
+ pos = gu_mem_advance(offsetof(GuMemChunk, data),
+ pre_align, pre_size, align, size);
+ gu_pool_expand(pool, pos);
+ gu_assert(pos <= pool->right_edge);
+ }
+ pool->left_edge = pos;
+ uint8_t* addr = &pool->curr_buf[pos - size];
+ VG(VALGRIND_MEMPOOL_ALLOC(pool, addr - pre_size, size + pre_size ));
+ return addr;
+}
+
+static size_t
+gu_pool_avail(GuPool* pool)
+{
+ return (size_t) pool->right_edge - (size_t) pool->left_edge;
+}
+
+void*
+gu_pool_malloc_unaligned(GuPool* pool, size_t size)
+{
+ if (size > gu_pool_avail(pool)) {
+ gu_pool_expand(pool, offsetof(GuMemChunk, data) + size);
+ gu_assert(size <= gu_pool_avail(pool));
+ }
+ pool->right_edge -= size;
+ void* addr = &pool->curr_buf[pool->right_edge];
+ VG(VALGRIND_MEMPOOL_ALLOC(pool, addr, size));
+ return addr;
+}
+
+void*
+gu_malloc_prefixed(GuPool* pool, size_t pre_align, size_t pre_size,
+ size_t align, size_t size)
+{
+ gu_enter("-> %p %zu %zu %zu %zu",
+ pool, pre_align, pre_size, align, size);
+ void* ret = NULL;
+ if (pre_align == 0) {
+ pre_align = gu_alignof(GuMaxAlign);
+ }
+ if (align == 0) {
+ align = gu_alignof(GuMaxAlign);
+ }
+ size_t full_size = gu_mem_advance(offsetof(GuMemChunk, data),
+ pre_align, pre_size, align, size);
+ if (full_size > gu_mem_max_shared_alloc) {
+ GuMemChunk* chunk = gu_mem_alloc(full_size);
+ chunk->next = pool->chunks;
+ pool->chunks = chunk;
+ uint8_t* addr = &chunk->data[full_size - size
+ - offsetof(GuMemChunk, data)];
+ VG(VALGRIND_MEMPOOL_ALLOC(pool, addr - pre_size,
+ pre_size + size));
+ ret = addr;
+ } else if (pre_align == 1 && align == 1) {
+ uint8_t* buf = gu_pool_malloc_unaligned(pool, pre_size + size);
+ ret = &buf[pre_size];
+ } else {
+ ret = gu_pool_malloc_aligned(pool, pre_align, pre_size,
+ align, size);
+ }
+ gu_exit("<- %p", ret);
+ return ret;
+}
+
+void*
+gu_malloc_aligned(GuPool* pool, size_t size, size_t align)
+{
+ if (align == 0) {
+ align = GU_MIN(size, gu_alignof(GuMaxAlign));
+ }
+ void* ret = gu_malloc_prefixed(pool, 1, 0, align, size);
+ return ret;
+}
+
+
+void
+gu_pool_finally(GuPool* pool, GuFinalizer* finalizer)
+{
+ GuFinalizerNode* node = gu_new(GuFinalizerNode, pool);
+ node->next = pool->finalizers;
+ node->fin = finalizer;
+ pool->finalizers = node;
+}
+
+void
+gu_pool_free(GuPool* pool)
+{
+ gu_debug("%p", pool);
+ GuFinalizerNode* node = pool->finalizers;
+ while (node) {
+ GuFinalizerNode* next = node->next;
+ node->fin->fn(node->fin);
+ node = next;
+ }
+ GuMemChunk* chunk = pool->chunks;
+ while (chunk) {
+ GuMemChunk* next = chunk->next;
+ gu_mem_buf_free(chunk);
+ chunk = next;
+ }
+ VG(VALGRIND_DESTROY_MEMPOOL(pool));
+ if (!pool->flags & GU_POOL_LOCAL) {
+ gu_mem_buf_free(pool);
+ }
+}
+
+
+extern inline void* gu_malloc(GuPool* pool, size_t size);
+
+extern inline void*
+gu_malloc_init_aligned(GuPool* pool, size_t size, size_t alignment,
+ const void* init);
+
diff --git a/src/runtime/c/gu/mem.h b/src/runtime/c/gu/mem.h
new file mode 100644
index 000000000..4369f4036
--- /dev/null
+++ b/src/runtime/c/gu/mem.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file
+ *
+ * Memory allocation tools.
+ */
+
+#ifndef GU_MEM_H_
+#define GU_MEM_H_
+
+#include <gu/defs.h>
+#include <gu/fun.h>
+
+/** @defgroup GuPool Memory pools */
+//@{
+
+
+/// A memory pool.
+typedef struct GuPool GuPool;
+
+/// @name Creating a pool
+//@{
+
+
+/// Create a new memory pool.
+GU_ONLY GuPool*
+gu_new_pool(void);
+
+/**<
+ * @return A new memory pool.
+ */
+
+
+//@private
+GuPool*
+gu_local_pool_(uint8_t* init_buf, size_t sz);
+
+//@private
+#define GU_LOCAL_POOL_INIT_SIZE (16 * sizeof(GuWord))
+
+
+/// Create a stack-allocated memory pool.
+#define gu_local_pool() \
+ gu_local_pool_(gu_alloca(GU_LOCAL_POOL_INIT_SIZE), \
+ GU_LOCAL_POOL_INIT_SIZE)
+/**<
+ * @return A memory pool whose first chunk is allocated directly from
+ * the stack. This makes its creation faster, and more suitable for
+ * functions that usually allocate only a little memory from the pool
+ * until it is freed.
+ *
+ * @note The pool created with #gu_local_pool \e must be freed with
+ * #gu_pool_free before the end of the block where #gu_local_pool was
+ * called.
+ *
+ * @note Because #gu_local_pool uses relatively much stack space, it
+ * should not be used in the bodies of recursive functions.
+ */
+
+
+//@}
+/// @name Destroying a pool
+//@{
+
+
+/// Free a memory pool and all objects allocated from it.
+void
+gu_pool_free(GU_ONLY GuPool* pool);
+/**<
+ * When the pool is freed, all finalizers registered by
+ * #gu_pool_finally on \p pool are invoked in reverse order of
+ * registration.
+ *
+ * @note After the pool is freed, all objects allocated from it become
+ * invalid and may no longer be used. */
+
+//@}
+/// @name Allocating from a pool
+//@{
+
+
+/// Allocate memory with a specified alignment.
+void*
+gu_malloc_aligned(GuPool* pool, size_t size, size_t alignment);
+
+void*
+gu_malloc_prefixed(GuPool* pool, size_t pre_align, size_t pre_size,
+ size_t align, size_t size);
+
+/// Allocate memory from a pool.
+inline void*
+gu_malloc(GuPool* pool, size_t size) {
+ return gu_malloc_aligned(pool, size, 0);
+}
+
+#include <string.h>
+
+//@private
+static inline void*
+gu_malloc_init_aligned(GuPool* pool, size_t size, size_t alignment,
+ const void* init)
+{
+ void* p = gu_malloc_aligned(pool, size, alignment);
+ memcpy(p, init, size);
+ return p;
+}
+
+//@private
+static inline void*
+gu_malloc_init(GuPool* pool, size_t size, const void* init)
+{
+ return gu_malloc_init_aligned(pool, size, 0, init);
+}
+
+
+/** Allocate memory to store an array of objects of a given type. */
+
+#define gu_new_n(type, n, pool) \
+ ((type*)gu_malloc_aligned((pool), \
+ sizeof(type) * (n), \
+ gu_alignof(type)))
+/**<
+ * @param type The C type of the objects to allocate.
+ *
+ * @param n The number of objects to allocate.
+ *
+ * @param pool The memory pool to allocate from.
+ *
+ * @return A pointer to a heap-allocated array of \p n uninitialized
+ * objects of type \p type.
+ */
+
+
+/** Allocate memory to store an object of a given type. */
+
+#define gu_new(type, pool) \
+ gu_new_n(type, 1, pool)
+/**<
+ * @param type The C type of the object to allocate.
+ *
+ * @param pool The memory pool to allocate from.
+ *
+ * @return A pointer to a heap-allocated uninitialized object of type
+ * \p type.
+ */
+
+
+#define gu_new_prefixed(pre_type, type, pool) \
+ ((type*)(gu_malloc_prefixed((pool), \
+ gu_alignof(pre_type), sizeof(pre_type), \
+ gu_alignof(type), sizeof(type))))
+
+
+
+
+
+#ifdef GU_HAVE_STATEMENT_EXPRESSIONS
+#define gu_new_i(pool, type, ...) \
+ ({ \
+ type *gu_new_p_ = gu_new(type, pool); \
+ memcpy((void*) gu_new_p_, &(type){ __VA_ARGS__ }, \
+ sizeof(type)); \
+ gu_new_p_; \
+ })
+#else // GU_HAVE_STATEMENT_EXPRESSIONS
+#define gu_new_i(pool, type, ...) \
+ ((type*)gu_malloc_init_aligned((pool), sizeof(type), \
+ gu_alignof(type), \
+ &(type){ __VA_ARGS__ }))
+#endif // GU_HAVE_STATEMENT_EXPRESSIONS
+
+/** @def gu_new_i(pool, type, ...)
+ *
+ * Allocate and initialize an object.
+ *
+ * @param pool The pool to allocate from.
+ *
+ * @param type The C type of the object to allocate.
+ *
+ * @param ... An initializer list for the object to allocate.
+ */
+
+#define gu_new_s gu_new_i
+
+// Alas, there's no portable way to get the alignment of flex structs.
+#define gu_new_flex(pool_, type_, flex_member_, n_elems_) \
+ ((type_ *)gu_malloc_aligned( \
+ (pool_), \
+ GU_FLEX_SIZE(type_, flex_member_, n_elems_), \
+ gu_flex_alignof(type_)))
+
+
+//@}
+/// @name Finalizers
+//@{
+
+
+typedef struct GuFinalizer GuFinalizer;
+
+struct GuFinalizer {
+ void (*fn)(GuFinalizer* self);
+ ///< @param self A pointer to this finalizer.
+};
+
+/// Register a finalizer.
+void gu_pool_finally(GuPool* pool, GuFinalizer* fini);
+
+/**< Register \p fini to be called when \p pool is destroyed. The
+ * finalizers are called in reverse order of registration.
+ */
+
+
+//@}
+//@}
+
+/** @defgroup GuMemBuf Memory buffers
+ *
+ * Resizable blocks of heap-allocated memory. These operations differ
+ * from standard \c malloc, \c realloc and \c free -functions in that
+ * memory buffers are not allocated by exact size. Instead, a minimum
+ * size is requested, and the returned buffer may be larger. This
+ * gives the memory allocator more flexibility when the client code
+ * can make use of larger buffers than requested.
+ * */
+
+//@{
+
+
+/// Allocate a new memory buffer.
+GU_ONLY void*
+gu_mem_buf_alloc(size_t min_size, size_t* real_size);
+/**<
+ * @param min_size The minimum acceptable size for a returned memory block.
+ *
+ * @param[out] real_size The actual size of the returned memory
+ * block. This is never less than \p min_size.
+ *
+ * @return A pointer to the memory buffer.
+ */
+
+
+/// Allocate a new memory buffer to replace an old one.
+GU_ONLY void*
+gu_mem_buf_realloc(
+ GU_NULL GU_ONLY GU_RETURNED
+ void* buf,
+ size_t min_size,
+ size_t* real_size_out);
+
+
+/// Free a memory buffer.
+void
+gu_mem_buf_free(GU_ONLY void* buf);
+
+
+//@}
+
+
+#endif // GU_MEM_H_
diff --git a/src/runtime/c/gu/out.c b/src/runtime/c/gu/out.c
new file mode 100644
index 000000000..61df2c184
--- /dev/null
+++ b/src/runtime/c/gu/out.c
@@ -0,0 +1,302 @@
+#include <gu/seq.h>
+#include <gu/out.h>
+
+
+
+GuOut
+gu_init_out(GuOutStream* stream)
+{
+ gu_require(stream != NULL);
+ GuOut out = {
+ .buf_end = NULL,
+ .buf_curr = 0,
+ .stream = stream,
+ .fini.fn = NULL
+ };
+ return out;
+}
+
+static bool
+gu_out_is_buffering(GuOut* out)
+{
+ return !!out->buf_end;
+}
+
+
+static void
+gu_out_end_buf(GuOut* out, GuExn* err)
+{
+ if (!gu_out_is_buffering(out)) {
+ return;
+ }
+ GuOutStream* stream = out->stream;
+ size_t curr_len = ((ptrdiff_t)out->buf_size) + out->buf_curr;
+ stream->end_buf(stream, curr_len, err);
+ out->buf_end = NULL;
+ out->buf_size = out->buf_curr = 0;
+}
+
+static bool
+gu_out_begin_buf(GuOut* out, size_t req, GuExn* err)
+{
+ GuOutStream* stream = out->stream;
+ if (gu_out_is_buffering(out)) {
+ if (out->buf_curr < 0) {
+ return true;
+ } else {
+ gu_out_end_buf(out, err);
+ if (!gu_ok(err)) {
+ return false;
+ }
+ }
+ }
+ if (stream->begin_buf) {
+ size_t sz = 0;
+ uint8_t* buf = stream->begin_buf(stream, req, &sz, err);
+ gu_assert(sz <= PTRDIFF_MAX);
+ if (buf) {
+ out->buf_end = &buf[sz];
+ out->buf_curr = -(ptrdiff_t) sz;
+ out->buf_size = sz;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+static void
+gu_out_fini(GuFinalizer* self)
+{
+ GuOut* out = gu_container(self, GuOut, fini);
+ if (gu_out_is_buffering(out)) {
+ GuPool* pool = gu_local_pool();
+ GuExn* err = gu_new_exn(NULL, gu_kind(type), pool);
+ gu_out_end_buf(out, err);
+ gu_pool_free(pool);
+ }
+}
+
+GuOut*
+gu_new_out(GuOutStream* stream, GuPool* pool)
+{
+ GuOut* out = gu_new(GuOut, pool);
+ *out = gu_init_out(stream);
+ out->fini.fn = gu_out_fini;
+ gu_pool_finally(pool, &out->fini);
+ return out;
+}
+
+extern inline bool
+gu_out_try_buf_(GuOut* out, const uint8_t* src, size_t len);
+
+
+extern inline size_t
+gu_out_bytes(GuOut* out, const uint8_t* buf, size_t len, GuExn* err);
+
+static size_t
+gu_out_output(GuOut* out, const uint8_t* src, size_t len, GuExn* err)
+{
+ gu_out_end_buf(out, err);
+ if (!gu_ok(err)) {
+ return 0;
+ }
+ return out->stream->output(out->stream, src, len, err);
+}
+
+
+
+
+void
+gu_out_flush(GuOut* out, GuExn* err)
+{
+ GuOutStream* stream = out->stream;
+ if (out->buf_end) {
+ gu_out_end_buf(out, err);
+ if (!gu_ok(err)) {
+ return;
+ }
+ }
+ if (stream->flush) {
+ stream->flush(stream, err);
+ }
+}
+
+uint8_t*
+gu_out_begin_span(GuOut* out, size_t req, size_t* sz_out, GuExn* err)
+{
+ if (!out->buf_end && !gu_out_begin_buf(out, req, err)) {
+ return NULL;
+ }
+ *sz_out = -out->buf_curr;
+ return &out->buf_end[out->buf_curr];
+}
+
+void
+gu_out_end_span(GuOut* out, size_t sz)
+{
+ ptrdiff_t new_curr = (ptrdiff_t) sz + out->buf_curr;
+ gu_require(new_curr <= 0);
+ out->buf_curr = new_curr;
+}
+
+size_t
+gu_out_bytes_(GuOut* restrict out, const uint8_t* restrict src, size_t len,
+ GuExn* err)
+{
+ if (!gu_ok(err)) {
+ return 0;
+ } else if (gu_out_try_buf_(out, src, len)) {
+ return len;
+ }
+ if (gu_out_begin_buf(out, len, err)) {
+ if (gu_out_try_buf_(out, src, len)) {
+ return len;
+ }
+ }
+ return gu_out_output(out, src, len, err);
+}
+
+
+void gu_out_u8_(GuOut* restrict out, uint8_t u, GuExn* err)
+{
+ if (gu_out_begin_buf(out, 1, err)) {
+ if (gu_out_try_u8_(out, u)) {
+ return;
+ }
+ }
+ gu_out_output(out, &u, 1, err);
+}
+
+
+extern inline void
+gu_out_u8(GuOut* restrict out, uint8_t u, GuExn* err);
+
+extern inline void
+gu_out_s8(GuOut* restrict out, int8_t i, GuExn* err);
+
+extern inline bool
+gu_out_is_buffered(GuOut* out);
+
+extern inline bool
+gu_out_try_u8_(GuOut* restrict out, uint8_t u);
+
+
+
+
+
+
+
+
+
+typedef struct GuProxyOutStream GuProxyOutStream;
+
+struct GuProxyOutStream {
+ GuOutStream stream;
+ GuOut* real_out;
+};
+
+
+static uint8_t*
+gu_proxy_out_buf_begin(GuOutStream* self, size_t req, size_t* sz_out,
+ GuExn* err)
+{
+ GuProxyOutStream* pos =
+ gu_container(self, GuProxyOutStream, stream);
+ return gu_out_begin_span(pos->real_out, req, sz_out, err);
+}
+
+static void
+gu_proxy_out_buf_end(GuOutStream* self, size_t sz, GuExn* err)
+{
+ GuProxyOutStream* pos =
+ gu_container(self, GuProxyOutStream, stream);
+ gu_out_end_span(pos->real_out, sz);
+}
+
+static size_t
+gu_proxy_out_output(GuOutStream* self, const uint8_t* src, size_t sz,
+ GuExn* err)
+{
+ GuProxyOutStream* pos =
+ gu_container(self, GuProxyOutStream, stream);
+ return gu_out_bytes(pos->real_out, src, sz, err);
+}
+
+static void
+gu_proxy_out_flush(GuOutStream* self, GuExn* err)
+{
+ GuProxyOutStream* pos =
+ gu_container(self, GuProxyOutStream, stream);
+ gu_out_flush(pos->real_out, err);
+}
+
+
+GuOutStream*
+gu_out_proxy_stream(GuOut* out, GuPool* pool)
+{
+ return &gu_new_s(pool, GuProxyOutStream,
+ .stream.begin_buf = gu_proxy_out_buf_begin,
+ .stream.end_buf = gu_proxy_out_buf_end,
+ .stream.output = gu_proxy_out_output,
+ .stream.flush = gu_proxy_out_flush,
+ .real_out = out)->stream;
+}
+
+
+
+typedef struct GuBufferedOutStream GuBufferedOutStream;
+
+struct GuBufferedOutStream {
+ GuProxyOutStream pstream;
+ size_t sz;
+ uint8_t buf[];
+};
+
+static uint8_t*
+gu_buffered_out_buf_begin(GuOutStream* self, size_t req, size_t* sz_out,
+ GuExn* err)
+{
+ (void) (req && err);
+ GuBufferedOutStream* b =
+ gu_container(self, GuBufferedOutStream, pstream.stream);
+ *sz_out = b->sz;
+ return b->buf;
+}
+
+static void
+gu_buffered_out_buf_end(GuOutStream* self, size_t sz, GuExn* err)
+{
+ GuBufferedOutStream* b =
+ gu_container(self, GuBufferedOutStream, pstream.stream);
+ gu_require(sz <= b->sz);
+ gu_out_bytes(b->pstream.real_out, b->buf, sz, err);
+}
+
+GuOut*
+gu_new_buffered_out(GuOut* out, size_t sz, GuPool* pool)
+{
+ GuBufferedOutStream* b =
+ gu_new_flex(pool, GuBufferedOutStream, buf, sz);
+ b->pstream.stream = (GuOutStream) {
+ .begin_buf = gu_buffered_out_buf_begin,
+ .end_buf = gu_buffered_out_buf_end,
+ .output = gu_proxy_out_output,
+ .flush = gu_proxy_out_flush
+ };
+ b->pstream.real_out = out;
+ b->sz = sz;
+ return gu_new_out(&b->pstream.stream, pool);
+}
+
+GuOut*
+gu_out_buffered(GuOut* out, GuPool* pool)
+{
+ if (gu_out_is_buffered(out)) {
+ return out;
+ }
+ return gu_new_buffered_out(out, 4096, pool);
+}
+
+
diff --git a/src/runtime/c/gu/out.h b/src/runtime/c/gu/out.h
new file mode 100644
index 000000000..df2638c72
--- /dev/null
+++ b/src/runtime/c/gu/out.h
@@ -0,0 +1,166 @@
+#ifndef GU_OUT_H_
+#define GU_OUT_H_
+
+#include <gu/defs.h>
+#include <gu/assert.h>
+#include <gu/exn.h>
+
+typedef struct GuOut GuOut;
+
+typedef struct GuOutStream GuOutStream;
+
+struct GuOutStream {
+ uint8_t* (*begin_buf)(GuOutStream* self, size_t req, size_t* sz_out,
+ GuExn* err);
+ void (*end_buf)(GuOutStream* self, size_t span, GuExn* err);
+ size_t (*output)(GuOutStream* self, const uint8_t* buf, size_t size,
+ GuExn* err);
+ void (*flush)(GuOutStream* self, GuExn* err);
+};
+
+
+struct GuOut {
+ uint8_t* restrict buf_end;
+ ptrdiff_t buf_curr;
+ size_t buf_size;
+ GuOutStream* stream;
+ GuFinalizer fini;
+};
+
+
+GuOut
+gu_init_out(GuOutStream* stream);
+
+GuOut*
+gu_new_out(GuOutStream* stream, GuPool* pool);
+
+inline bool
+gu_out_is_buffered(GuOut* out)
+{
+ return !!out->stream->begin_buf;
+}
+
+GuOutStream*
+gu_out_proxy_stream(GuOut* out, GuPool* pool);
+
+GuOut*
+gu_new_buffered_out(GuOut* out, size_t buf_sz, GuPool* pool);
+
+GuOut*
+gu_out_buffered(GuOut* out, GuPool* pool);
+
+uint8_t*
+gu_out_begin_span(GuOut* out, size_t req, size_t* sz_out, GuExn* err);
+
+uint8_t*
+gu_out_force_span(GuOut* out, size_t min, size_t max, size_t* sz_out,
+ GuExn* err);
+
+void
+gu_out_end_span(GuOut* out, size_t sz);
+
+size_t
+gu_out_bytes_(GuOut* restrict out, const uint8_t* restrict src,
+ size_t len, GuExn* err);
+
+inline bool
+gu_out_try_buf_(GuOut* restrict out, const uint8_t* restrict src, size_t len)
+{
+ gu_require(len <= PTRDIFF_MAX);
+ ptrdiff_t curr = out->buf_curr;
+ ptrdiff_t new_curr = curr + (ptrdiff_t) len;
+ if (GU_UNLIKELY(new_curr > 0)) {
+ return false;
+ }
+ memcpy(&out->buf_end[curr], src, len);
+ out->buf_curr = new_curr;
+ return true;
+}
+
+inline size_t
+gu_out_bytes(GuOut* restrict out, const uint8_t* restrict src, size_t len,
+ GuExn* err)
+{
+ if (GU_LIKELY(gu_out_try_buf_(out, src, len))) {
+ return len;
+ }
+ return gu_out_bytes_(out, src, len, err);
+}
+
+void
+gu_out_flush(GuOut* out, GuExn* err);
+
+inline bool
+gu_out_try_u8_(GuOut* restrict out, uint8_t u)
+{
+ ptrdiff_t curr = out->buf_curr;
+ ptrdiff_t new_curr = curr + 1;
+ if (GU_UNLIKELY(new_curr > 0)) {
+ return false;
+ }
+ out->buf_end[curr] = u;
+ out->buf_curr = new_curr;
+ return true;
+}
+
+inline void
+gu_out_u8(GuOut* restrict out, uint8_t u, GuExn* err)
+{
+ if (GU_UNLIKELY(!gu_out_try_u8_(out, u))) {
+ extern void gu_out_u8_(GuOut* restrict out, uint8_t u,
+ GuExn* err);
+ gu_out_u8_(out, u, err);
+ }
+}
+
+inline void
+gu_out_s8(GuOut* restrict out, int8_t i, GuExn* err)
+{
+ gu_out_u8(out, (uint8_t) i, err);
+}
+
+
+
+void
+gu_out_u16le(GuOut* out, uint16_t u, GuExn* err);
+
+void
+gu_out_u16be(GuOut* out, uint16_t u, GuExn* err);
+
+void
+gu_out_s16le(GuOut* out, int16_t u, GuExn* err);
+
+void
+gu_out_s16be(GuOut* out, int16_t u, GuExn* err);
+
+void
+gu_out_u32le(GuOut* out, uint32_t u, GuExn* err);
+
+void
+gu_out_u32be(GuOut* out, uint32_t u, GuExn* err);
+
+void
+gu_out_s32le(GuOut* out, int32_t u, GuExn* err);
+
+void
+gu_out_s32be(GuOut* out, int32_t u, GuExn* err);
+
+void
+gu_out_u64le(GuOut* out, uint64_t u, GuExn* err);
+
+void
+gu_out_u64be(GuOut* out, uint64_t u, GuExn* err);
+
+void
+gu_out_s64le(GuOut* out, int64_t u, GuExn* err);
+
+void
+gu_out_s64be(GuOut* out, int64_t u, GuExn* err);
+
+void
+gu_out_f64le(GuOut* out, double d, GuExn* err);
+
+void
+gu_out_f64be(GuOut* out, double d, GuExn* err);
+
+#endif // GU_OUT_H_
diff --git a/src/runtime/c/gu/prime.c b/src/runtime/c/gu/prime.c
new file mode 100644
index 000000000..6452f8777
--- /dev/null
+++ b/src/runtime/c/gu/prime.c
@@ -0,0 +1,154 @@
+#include <gu/defs.h>
+#include <gu/assert.h>
+
+static const uint32_t gu_prime_wheel_mask = 0UL
+ | 1 << 1
+ | 1 << 7
+ | 1 << 11
+ | 1 << 13
+ | 1 << 17
+ | 1 << 19
+ | 1 << 23
+ | 1 << 29;
+
+static bool
+gu_prime_wheel(int i)
+{
+ gu_assert(i >= 0 && i < 30);
+ return !!(gu_prime_wheel_mask & (1 << i));
+}
+
+static const uint32_t gu_small_prime_mask = 0UL
+ | 1 << 2
+ | 1 << 3
+ | 1 << 5
+ | 1 << 7
+ | 1 << 11
+ | 1 << 13
+ | 1 << 17
+ | 1 << 19
+ | 1 << 23
+ | 1 << 29
+ | 1U << 31;
+
+static bool
+gu_is_wheel_prime(int u)
+{
+ gu_assert(u > 30 && u % 2 != 0 && u % 3 != 0 && u % 5 != 0);
+ int d = 0;
+ int i = 7;
+ goto start;
+ while (d * d <= u) {
+ for (i = 1; i <= 29; i+=2) {
+ start:
+ if (gu_prime_wheel(i) && u % (d + i) == 0) {
+ return false;
+ }
+ }
+ d += 30;
+ }
+ return true;
+}
+
+int
+gu_prime_inf(int i)
+{
+ if (i < 2) {
+ return 0;
+ } else if (i < 32) {
+ while (!(gu_small_prime_mask & (1 << i))) {
+ i--;
+ }
+ return i;
+ }
+
+ int d = (i - 1) | 1;
+ int r = d % 30;
+
+ while (!gu_prime_wheel(r) || !gu_is_wheel_prime(d)) {
+ d -= 2;
+ r -= 2;
+ if (r < 0) {
+ r += 30;
+ }
+ }
+ return d;
+}
+
+int
+gu_prime_sup(int i)
+{
+ if (i <= 2) {
+ return 2;
+ } else if (i < 32) {
+ while (!(gu_small_prime_mask & (1 << i))) {
+ i++;
+ }
+ return i;
+ }
+
+ int d = i | 1;
+ int r = d % 30;
+
+ while (!gu_prime_wheel(r) || !gu_is_wheel_prime(d)) {
+ d += 2;
+ r += 2;
+ if (r > 30) {
+ r -= 30;
+ }
+ }
+ return d;
+}
+
+bool
+gu_is_prime(int i)
+{
+ if (i < 2) {
+ return false;
+ } else if (i < 30) {
+ return !!(gu_small_prime_mask & (1 << i));
+ } else if (!gu_prime_wheel(i % 30)) {
+ return false;
+ } else {
+ return gu_is_wheel_prime(i);
+ }
+}
+
+bool
+gu_is_twin_prime(int i)
+{
+ return gu_is_prime(i) && gu_is_prime(i - 2);
+}
+
+int
+gu_twin_prime_inf(int i)
+{
+ while (true) {
+ i = gu_prime_inf(i);
+ if (i == 0) {
+ return 0;
+ } else if (gu_is_prime(i - 2)) {
+ return i;
+ }
+ i = i - 4;
+ }
+ return i;
+}
+
+int
+gu_twin_prime_sup(int i)
+{
+ if (i <= 5) {
+ return 5;
+ }
+ i = i - 2;
+ while (true) {
+ i = gu_prime_sup(i);
+ if (gu_is_prime(i + 2)) {
+ return i + 2;
+ }
+ i = i + 4;
+ }
+ return i;
+}
+
diff --git a/src/runtime/c/gu/prime.h b/src/runtime/c/gu/prime.h
new file mode 100644
index 000000000..2ae0617f8
--- /dev/null
+++ b/src/runtime/c/gu/prime.h
@@ -0,0 +1,16 @@
+#ifndef GU_PRIME_H_
+#define GU_PRIME_H_
+
+#include <gu/defs.h>
+
+bool gu_is_prime(int i);
+
+bool gu_is_twin_prime(int i);
+
+int gu_prime_inf(int i);
+int gu_twin_prime_inf(int i);
+
+int gu_prime_sup(int i);
+int gu_twin_prime_sup(int i);
+
+#endif // GU_PRIME_H_
diff --git a/src/runtime/c/gu/read.c b/src/runtime/c/gu/read.c
new file mode 100644
index 000000000..245c59f2c
--- /dev/null
+++ b/src/runtime/c/gu/read.c
@@ -0,0 +1,15 @@
+#include <gu/read.h>
+
+extern inline GuUCS
+gu_read_ucs(GuReader* rdr, GuExn* err);
+
+extern inline char
+gu_getc(GuReader* rdr, GuExn* err);
+
+GuReader*
+gu_new_utf8_reader(GuIn* utf8_in, GuPool* pool)
+{
+ GuReader* rdr = gu_new(GuReader, pool);
+ rdr->in_ = gu_init_in(gu_in_proxy_stream(utf8_in, pool));
+ return rdr;
+}
diff --git a/src/runtime/c/gu/read.h b/src/runtime/c/gu/read.h
new file mode 100644
index 000000000..f2496975d
--- /dev/null
+++ b/src/runtime/c/gu/read.h
@@ -0,0 +1,31 @@
+#ifndef GU_READ_H_
+#define GU_READ_H_
+
+#include <gu/in.h>
+#include <gu/ucs.h>
+#include <gu/utf8.h>
+
+typedef struct GuReader GuReader;
+
+struct GuReader {
+ GuIn in_;
+};
+
+inline GuUCS
+gu_read_ucs(GuReader* rdr, GuExn* err)
+{
+ return gu_in_utf8(&rdr->in_, err);
+}
+
+inline char
+gu_getc(GuReader* rdr, GuExn* err)
+{
+ return gu_in_utf8_char(&rdr->in_, err);
+}
+
+GuReader*
+gu_new_utf8_reader(GuIn* utf8_in, GuPool* pool);
+/**< @todo Implement. */
+
+
+#endif // GU_READ_H_
diff --git a/src/runtime/c/gu/seq.c b/src/runtime/c/gu/seq.c
new file mode 100644
index 000000000..660cf5af6
--- /dev/null
+++ b/src/runtime/c/gu/seq.c
@@ -0,0 +1,245 @@
+#include <gu/out.h>
+#include <gu/seq.h>
+#include <gu/fun.h>
+#include <gu/assert.h>
+#include <string.h>
+
+
+struct GuBuf {
+ uint8_t* data;
+ size_t elem_size;
+ size_t avail_len;
+ GuFinalizer fin;
+};
+
+GuBuf*
+gu_seq_buf(GuSeq seq)
+{
+ gu_require(gu_tagged_tag(seq.w_) == 0);
+ return gu_word_ptr(seq.w_);
+}
+
+GuSeq
+gu_buf_seq(GuBuf* buf)
+{
+ return (GuSeq) { .w_ = gu_ptr_word(buf) };
+}
+
+size_t
+gu_buf_length(GuBuf* dyn)
+{
+ return (size_t)(((GuWord*)(void*)dyn)[-1] >> 1);
+}
+
+size_t
+gu_buf_avail(GuBuf* buf)
+{
+ return buf->avail_len;
+}
+
+
+static void
+gu_buf_set_length(GuBuf* dyn, size_t new_len)
+{
+ ((GuWord*)(void*)dyn)[-1] = ((GuWord) new_len) << 1 | 0x1;
+}
+
+static void
+gu_buf_fini(GuFinalizer* fin)
+{
+ GuBuf* buf = gu_container(fin, GuBuf, fin);
+ gu_mem_buf_free(buf->data);
+}
+
+GuBuf*
+gu_make_buf(size_t elem_size, GuPool* pool)
+{
+ GuBuf* buf = gu_new_prefixed(unsigned, GuBuf, pool);
+ gu_buf_set_length(buf, 0);
+ buf->elem_size = elem_size;
+ buf->data = NULL;
+ buf->avail_len = 0;
+ buf->fin.fn = gu_buf_fini;
+ gu_pool_finally(pool, &buf->fin);
+ gu_buf_set_length(buf, 0);
+ return buf;
+}
+
+static const GuWord gu_empty_seq[2] = {0, 0};
+
+GuSeq
+gu_make_seq(size_t elem_size, size_t length, GuPool* pool)
+{
+ size_t size = elem_size * length;
+ if (0 < length && length <= GU_TAG_MAX) {
+ void* buf = gu_malloc(pool, size);
+ return (GuSeq) { gu_tagged(buf, length) };
+ } else if (size == 0) {
+ return (GuSeq) { gu_tagged((void*)&gu_empty_seq[1], 0) };
+ } else {
+ void* buf = gu_malloc_prefixed(pool,
+ gu_alignof(GuWord),
+ sizeof(GuWord),
+ 0, size);
+ ((GuWord*) buf)[-1] = ((GuWord) length) << 1;
+ return (GuSeq) { gu_tagged(buf, 0) };
+ }
+}
+
+static void
+gu_buf_require(GuBuf* buf, size_t req_len)
+{
+ if (req_len <= buf->avail_len) {
+ return;
+ }
+ size_t req_size = buf->elem_size * req_len;
+ size_t real_size;
+ buf->data = gu_mem_buf_realloc(buf->data, req_size,
+ &real_size);
+ buf->avail_len = real_size / buf->elem_size;
+}
+
+void*
+gu_buf_data(GuBuf* buf)
+{
+ return buf->data;
+}
+
+void*
+gu_buf_extend_n(GuBuf* buf, size_t n_elems)
+{
+ size_t len = gu_buf_length(buf);
+ size_t new_len = len + n_elems;
+ gu_buf_require(buf, new_len);
+ gu_buf_set_length(buf, new_len);
+ return &buf->data[buf->elem_size * len];
+}
+
+void*
+gu_buf_extend(GuBuf* buf)
+{
+ return gu_buf_extend_n(buf, 1);
+}
+
+void
+gu_buf_push_n(GuBuf* buf, const void* data, size_t n_elems)
+{
+
+ void* p = gu_buf_extend_n(buf, n_elems);
+ memcpy(p, data, buf->elem_size * n_elems);
+}
+
+const void*
+gu_buf_trim_n(GuBuf* buf, size_t n_elems)
+{
+ gu_require(n_elems <= gu_buf_length(buf));
+ size_t new_len = gu_buf_length(buf) - n_elems;
+ gu_buf_set_length(buf, new_len);
+ return &buf->data[buf->elem_size * new_len];
+}
+
+const void*
+gu_buf_trim(GuBuf* buf)
+{
+ return gu_buf_trim_n(buf, 1);
+}
+
+void
+gu_buf_pop_n(GuBuf* buf, size_t n_elems, void* data_out)
+{
+ const void* p = gu_buf_trim_n(buf, n_elems);
+ memcpy(data_out, p, buf->elem_size * n_elems);
+}
+
+GuSeq
+gu_buf_freeze(GuBuf* buf, GuPool* pool)
+{
+ size_t len = gu_buf_length(buf);
+ GuSeq seq = gu_make_seq(buf->elem_size, len, pool);
+ void* bufdata = gu_buf_data(buf);
+ void* seqdata = gu_seq_data(seq);
+ memcpy(seqdata, bufdata, buf->elem_size * len);
+ return seq;
+}
+
+typedef struct GuBufOut GuBufOut;
+struct GuBufOut
+{
+ GuOutStream stream;
+ GuBuf* buf;
+};
+
+static size_t
+gu_buf_out_output(GuOutStream* stream, const uint8_t* src, size_t sz,
+ GuExn* err)
+{
+ (void) err;
+ GuBufOut* bout = gu_container(stream, GuBufOut, stream);
+ GuBuf* buf = bout->buf;
+ gu_assert(sz % buf->elem_size == 0);
+ size_t len = sz / buf->elem_size;
+ gu_buf_push_n(bout->buf, src, len);
+ return len;
+}
+
+static uint8_t*
+gu_buf_outbuf_begin(GuOutStream* stream, size_t req, size_t* sz_out, GuExn* err)
+{
+ (void) req;
+ (void) err;
+ GuBufOut* bout = gu_container(stream, GuBufOut, stream);
+ GuBuf* buf = bout->buf;
+ size_t esz = buf->elem_size;
+ size_t len = gu_buf_length(buf);
+ gu_buf_require(buf, len + (req + esz - 1) / esz);
+ size_t avail = buf->avail_len;
+ gu_assert(len < avail);
+ *sz_out = esz * (avail - len);
+ return &buf->data[len * esz];
+}
+
+static void
+gu_buf_outbuf_end(GuOutStream* stream, size_t sz, GuExn* err)
+{
+ (void) err;
+ GuBufOut* bout = gu_container(stream, GuBufOut, stream);
+ GuBuf* buf = bout->buf;
+ size_t len = gu_buf_length(buf);
+ size_t elem_size = buf->elem_size;
+ gu_require(sz % elem_size == 0);
+ gu_require(sz < elem_size * (len - buf->avail_len));
+ gu_buf_set_length(buf, len + (sz / elem_size));
+}
+
+GuOut*
+gu_buf_out(GuBuf* buf, GuPool* pool)
+{
+ GuBufOut* bout = gu_new_i(pool, GuBufOut,
+ .stream.output = gu_buf_out_output,
+ .stream.begin_buf = gu_buf_outbuf_begin,
+ .stream.end_buf = gu_buf_outbuf_end,
+ .buf = buf);
+ return gu_new_out(&bout->stream, pool);
+}
+
+const GuSeq
+gu_null_seq = GU_NULL_SEQ;
+
+
+#include <gu/type.h>
+
+GU_DEFINE_KIND(GuSeq, GuOpaque);
+GU_DEFINE_KIND(GuBuf, abstract);
+
+GU_DEFINE_TYPE(GuChars, GuSeq, gu_type(char));
+GU_DEFINE_TYPE(GuBytes, GuSeq, gu_type(uint8_t));
+
+char*
+gu_chars_str(GuChars chars, GuPool* pool)
+{
+ size_t len = gu_seq_length(chars);
+ char* data = gu_seq_data(chars);
+ char* str = gu_new_str(len, pool);
+ memcpy(str, data, len);
+ return str;
+}
diff --git a/src/runtime/c/gu/seq.h b/src/runtime/c/gu/seq.h
new file mode 100644
index 000000000..257d71e5f
--- /dev/null
+++ b/src/runtime/c/gu/seq.h
@@ -0,0 +1,198 @@
+#ifndef GU_SEQ_H_
+#define GU_SEQ_H_
+
+#include <gu/mem.h>
+#include <gu/bits.h>
+
+
+typedef struct GuBuf GuBuf;
+
+typedef GuOpaque() GuSeq;
+
+GuSeq
+gu_make_seq(size_t elem_size, size_t len, GuPool* pool);
+
+#define gu_new_seq(T, N, POOL) \
+ gu_make_seq(sizeof(T), (N), (POOL))
+
+static inline size_t
+gu_seq_length(GuSeq seq)
+{
+ GuWord w = seq.w_;
+ size_t tag = gu_tagged_tag(w);
+ if (tag == 0) {
+ GuWord* p = gu_tagged_ptr(w);
+ return (size_t) (p[-1] >> 1);
+ }
+ return tag;
+}
+
+static inline void*
+gu_seq_data(GuSeq seq)
+{
+ GuWord w = seq.w_;
+ int tag = gu_tagged_tag(w);
+ void* ptr = gu_tagged_ptr(w);
+ if (tag == 0) {
+ GuWord* p = ptr;
+ if (p[-1] & 0x1) {
+ return *(uint8_t**) ptr;
+ }
+ }
+ return ptr;
+}
+
+static inline bool
+gu_seq_is_null(GuSeq seq)
+{
+ return (gu_tagged_ptr(seq.w_)) == NULL;
+}
+
+
+#define gu_seq_index(SEQ, T, I) \
+ (&((T*)gu_seq_data(SEQ))[I])
+
+#define gu_seq_get(SEQ, T, I) \
+ (*gu_seq_index(SEQ, T, I))
+
+#define gu_seq_set(SEQ, T, I, V) \
+ GU_BEGIN \
+ (*gu_seq_index(SEQ, T, I) = (V)); \
+ GU_END
+
+
+
+
+GuBuf*
+gu_seq_buf(GuSeq seq);
+
+GuSeq
+gu_buf_seq(GuBuf* buf);
+
+GuBuf*
+gu_make_buf(size_t elem_size, GuPool* pool);
+
+#define gu_new_buf(T, POOL) \
+ gu_make_buf(sizeof(T), (POOL))
+
+size_t
+gu_buf_length(GuBuf* buf);
+
+size_t
+gu_buf_avail(GuBuf* buf);
+
+void*
+gu_buf_data(GuBuf* buf);
+
+#define gu_buf_index(BUF, T, I) \
+ (&((T*)gu_buf_data(BUF))[I])
+
+#define gu_buf_get(BUF, T, I) \
+ (*gu_buf_index(BUF, T, I))
+
+#define gu_buf_set(BUF, T, I) \
+ GU_BEGIN \
+ (*gu_buf_index(BUF, T, I) = (V)); \
+ GU_END
+
+void
+gu_buf_push_n(GuBuf* buf, const void* elems, size_t n_elems);
+
+void*
+gu_buf_extend_n(GuBuf* buf, size_t n_elems);
+
+void*
+gu_buf_extend(GuBuf* buf);
+
+#define gu_buf_push(BUF, T, VAL) \
+ GU_BEGIN \
+ ((*(T*)gu_buf_extend(BUF)) = (VAL)); \
+ GU_END
+
+void
+gu_buf_pop_n(GuBuf* buf, size_t n_elems, void* data_out);
+
+const void*
+gu_buf_trim_n(GuBuf* buf, size_t n_elems);
+
+const void*
+gu_buf_trim(GuBuf* buf);
+
+#define gu_buf_pop(BUF, T) \
+ (*(T*)gu_buf_trim(BUF))
+
+void
+gu_seq_resize_tail(GuSeq seq, ptrdiff_t change);
+
+#if 0
+void
+gu_buf_resize_head(GuBuf* buf, ptrdiff_t change);
+
+void
+gu_buf_unshift(GuBuf* buf, const void* data, size_t size);
+
+void
+gu_buf_shift(GuBuf* buf, size_t size, void* data_out);
+#endif
+
+GuSeq
+gu_buf_freeze(GuBuf* buf, GuPool* pool);
+
+extern const GuSeq gu_null_seq;
+
+#define GU_NULL_SEQ { .w_ = (GuWord)(void*)NULL }
+
+typedef GuSeq GuChars;
+typedef GuSeq GuBytes;
+typedef GuBuf GuCharBuf;
+typedef GuBuf GuByteBuf;
+
+char*
+gu_chars_str(GuChars chars, GuPool* pool);
+
+#endif // GU_SEQ_H_
+
+#if defined(GU_OUT_H_) && !defined(GU_SEQ_H_OUT_)
+#define GU_SEQ_H_OUT_
+
+GuOut*
+gu_buf_out(GuBuf* buf, GuPool* pool);
+
+#endif
+
+
+#if defined(GU_TYPE_H_) && !defined(GU_SEQ_H_TYPE_)
+#define GU_SEQ_H_TYPE_
+
+extern GU_DECLARE_KIND(GuSeq);
+extern GU_DECLARE_KIND(GuBuf);
+
+struct GuSeqType {
+ GuType_GuOpaque opaque_base;
+ GuType* elem_type;
+};
+
+typedef const struct GuSeqType GuSeqType, GuType_GuSeq;
+
+#define GU_TYPE_INIT_GuSeq(k_, t_, elem_type_) { \
+ .opaque_base = GU_TYPE_INIT_GuOpaque(k_, t_, _), \
+ .elem_type = elem_type_, \
+}
+
+typedef struct GuBufType GuBufType, GuType_GuBuf;
+
+struct GuBufType {
+ GuType_abstract abstract_base;
+ GuType* elem_type;
+};
+
+#define GU_TYPE_INIT_GuBuf(KIND, BUF_T, ELEM_T) { \
+ .abstract_base = GU_TYPE_INIT_abstract(KIND, BUF_T, _), \
+ .elem_type = ELEM_T \
+}
+
+extern GU_DECLARE_TYPE(GuChars, GuSeq);
+extern GU_DECLARE_TYPE(GuBytes, GuSeq);
+
+#endif
+
diff --git a/src/runtime/c/gu/str.c b/src/runtime/c/gu/str.c
new file mode 100644
index 000000000..073781b23
--- /dev/null
+++ b/src/runtime/c/gu/str.c
@@ -0,0 +1,85 @@
+#include <gu/assert.h>
+#include <gu/str.h>
+#include <string.h>
+#include <wchar.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+const char gu_empty_str[] = "";
+const char* const gu_null_str = NULL;
+
+char*
+gu_new_str(size_t size, GuPool* pool)
+{
+ char* str = gu_new_n(char, size + 1, pool);
+ memset(str, '\0', size + 1);
+ return str;
+}
+
+char*
+gu_strdup(const char* cstr, GuPool* pool)
+{
+ int len = strlen(cstr);
+ char* str = gu_new_str(len, pool);
+ memcpy(str, cstr, len);
+ return str;
+}
+
+bool
+gu_str_eq(GuStr s1, GuStr s2)
+{
+ return (strcmp(s1, s2)) == 0;
+}
+
+static bool
+gu_str_is_equal(GuEquality* self, const void* p1, const void* p2)
+{
+ (void) self;
+ const GuStr* sp1 = p1;
+ const GuStr* sp2 = p2;
+ return gu_str_eq(*sp1, *sp2);
+}
+
+static GuHash
+gu_str_hasher_hash(GuHasher* self, const void* p)
+{
+ (void) self;
+ GuHash h = 0;
+ const GuStr* sp = p;
+ for (const char* s = *sp; *s != '\0'; s++) {
+ h = 101 * h + (unsigned char) *s;
+ }
+ return h;
+}
+
+GuHasher gu_str_hasher[1] = {
+ {
+ .eq = { .is_equal = gu_str_is_equal },
+ .hash = gu_str_hasher_hash
+ }
+};
+
+GU_DEFINE_TYPE(GuStr, repr, _);
+
+char*
+gu_vasprintf(const char* fmt, va_list args, GuPool* pool)
+{
+ va_list args2;
+ va_copy(args2, args);
+ int len = vsnprintf(NULL, 0, fmt, args2);
+ gu_assert_msg(len >= 0, "Invalid format string: \"%s\"", fmt);
+ va_end(args2);
+ char* str = gu_new_str(len, pool);
+ vsnprintf(str, len + 1, fmt, args);
+ return str;
+}
+
+char*
+gu_asprintf(GuPool* pool, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ char* str = gu_vasprintf(fmt, args, pool);
+ va_end(args);
+ return str;
+}
diff --git a/src/runtime/c/gu/str.h b/src/runtime/c/gu/str.h
new file mode 100644
index 000000000..d40f57b08
--- /dev/null
+++ b/src/runtime/c/gu/str.h
@@ -0,0 +1,29 @@
+#ifndef GU_STR_H_
+#define GU_STR_H_
+
+#include <gu/mem.h>
+#include <gu/hash.h>
+
+extern const char gu_empty_str[];
+extern const char* const gu_null_str;
+
+typedef const char* GuStr;
+
+char* gu_new_str(size_t size, GuPool* pool);
+
+char* gu_strdup(const char* str, GuPool* pool);
+
+bool
+gu_str_eq(GuStr s1, GuStr s2);
+
+extern GuHasher gu_str_hasher[1];
+
+#include <gu/type.h>
+
+extern GU_DECLARE_TYPE(GuStr, repr);
+
+char* gu_vasprintf(const char* fmt, va_list args, GuPool* pool);
+
+char* gu_asprintf(GuPool* pool, const char* fmt, ...);
+
+#endif // GU_STR_H_
diff --git a/src/runtime/c/gu/string.c b/src/runtime/c/gu/string.c
new file mode 100644
index 000000000..b24eeeb06
--- /dev/null
+++ b/src/runtime/c/gu/string.c
@@ -0,0 +1,270 @@
+#include <gu/type.h>
+#include <gu/out.h>
+#include <gu/seq.h>
+#include <gu/map.h>
+#include <gu/string.h>
+#include <gu/utf8.h>
+#include <gu/assert.h>
+#include "config.h"
+
+const GuString gu_empty_string = { 1 };
+
+struct GuStringBuf {
+ GuByteBuf* bbuf;
+ GuWriter* wtr;
+};
+
+GuStringBuf*
+gu_string_buf(GuPool* pool)
+{
+ GuBuf* buf = gu_new_buf(uint8_t, pool);
+ GuOut* out = gu_buf_out(buf, pool);
+ GuWriter* wtr = gu_new_utf8_writer(out, pool);
+ return gu_new_s(pool, GuStringBuf,
+ .bbuf = buf,
+ .wtr = wtr);
+}
+
+GuWriter*
+gu_string_buf_writer(GuStringBuf* sb)
+{
+ return sb->wtr;
+}
+
+static GuString
+gu_utf8_string(const uint8_t* buf, size_t sz, GuPool* pool)
+{
+ if (sz < GU_MIN(sizeof(GuWord), 128)) {
+ GuWord w = 0;
+ for (size_t n = 0; n < sz; n++) {
+ w = w << 8 | buf[n];
+ }
+ w = w << 8 | (sz << 1) | 1;
+ return (GuString) { w };
+ }
+ uint8_t* p = NULL;
+ if (sz < 256) {
+ p = gu_malloc_aligned(pool, 1 + sz, 2);
+ p[0] = (uint8_t) sz;
+ } else {
+ uint8_t* p =
+ gu_malloc_prefixed(pool, gu_alignof(size_t),
+ sizeof(size_t), 1, 1 + sizeof(sz));
+ ((size_t*) p)[-1] = sz;
+ p[0] = 0;
+ }
+ memcpy(&p[1], buf, sz);
+ return (GuString) { (GuWord) (void*) p };
+}
+
+
+
+GuString
+gu_string_buf_freeze(GuStringBuf* sb, GuPool* pool)
+{
+ gu_writer_flush(sb->wtr, NULL);
+ uint8_t* data = gu_buf_data(sb->bbuf);
+ size_t len = gu_buf_length(sb->bbuf);
+ return gu_utf8_string(data, len, pool);
+}
+
+GuReader*
+gu_string_reader(GuString s, GuPool* pool)
+{
+ GuWord w = s.w_;
+ uint8_t* buf = NULL;
+ size_t len = 0;
+ if (w & 1) {
+ len = (w & 0xff) >> 1;
+ buf = gu_new_n(uint8_t, len, pool);
+ for (int i = len - 1; i >= 0; i--) {
+ w >>= 8;
+ buf[i] = w & 0xff;
+ }
+ } else {
+ uint8_t* p = (void*) w;
+ len = (p[0] == 0) ? ((size_t*) p)[-1] : p[0];
+ buf = &p[1];
+ }
+ GuIn* in = gu_data_in(buf, len, pool);
+ GuReader* rdr = gu_new_utf8_reader(in, pool);
+ return rdr;
+}
+
+static bool
+gu_string_is_long(GuString s)
+{
+ return !(s.w_ & 1);
+}
+
+bool
+gu_string_is_stable(GuString s)
+{
+ return !gu_string_is_long(s);
+}
+
+static size_t
+gu_string_long_length(GuString s)
+{
+ gu_assert(gu_string_is_long(s));
+ uint8_t* p = (void*) s.w_;
+ uint8_t len = p[0];
+ if (len > 0) {
+ return len;
+ }
+ return ((size_t*) p)[-1];
+}
+
+size_t
+gu_string_length(GuString s)
+{
+ if (gu_string_is_long(s)) {
+ return gu_string_long_length(s);
+ }
+ return (s.w_ & 0xff) >> 1;
+}
+
+static uint8_t*
+gu_string_long_data(GuString s)
+{
+ gu_require(gu_string_is_long(s));
+ uint8_t* p = (void*) s.w_;
+ return &p[1];
+}
+
+GuString
+gu_string_copy(GuString string, GuPool* pool)
+{
+ if (gu_string_is_long(string)) {
+ uint8_t* data = gu_string_long_data(string);
+ size_t len = gu_string_long_length(string);
+ return gu_utf8_string(data, len, pool);
+ } else {
+ return string;
+ }
+}
+
+
+void
+gu_string_write(GuString s, GuWriter* wtr, GuExn* err)
+{
+ GuWord w = s.w_;
+ uint8_t buf[sizeof(GuWord)];
+ uint8_t* src;
+ size_t sz;
+ if (w & 1) {
+ sz = (w & 0xff) >> 1;
+ gu_assert(sz <= sizeof(GuWord));
+ size_t i = sz;
+ while (i > 0) {
+ w >>= 8;
+ buf[--i] = w & 0xff;
+ }
+ src = buf;
+ } else {
+ uint8_t* p = (void*) w;
+ sz = (p[0] == 0) ? ((size_t*) p)[-1] : p[0];
+ src = &p[1];
+ }
+ gu_utf8_write(src, sz, wtr, err);
+}
+
+GuString
+gu_format_string_v(const char* fmt, va_list args, GuPool* pool)
+{
+ GuPool* tmp_pool = gu_local_pool();
+ GuStringBuf* sb = gu_string_buf(tmp_pool);
+ GuWriter* wtr = gu_string_buf_writer(sb);
+ gu_vprintf(fmt, args, wtr, NULL);
+ gu_writer_flush(wtr, NULL);
+ GuString s = gu_string_buf_freeze(sb, pool);
+ gu_pool_free(tmp_pool);
+ return s;
+}
+
+GuString
+gu_format_string(GuPool* pool, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ GuString s = gu_format_string_v(fmt, args, pool);
+ va_end(args);
+ return s;
+}
+
+GuString
+gu_str_string(const char* str, GuPool* pool)
+{
+#ifdef GU_CHAR_ASCII
+ return gu_utf8_string((const uint8_t*) str, strlen(str), pool);
+#else
+ GuPool* tmp_pool = gu_local_pool();
+ GuStringBuf* sb = gu_string_buf(tmp_pool);
+ GuWriter* wtr = gu_string_buf_writer(sb);
+ gu_puts(str, wtr, NULL);
+ gu_writer_flush(wtr, NULL);
+ GuString s = gu_string_buf_freeze(sb, pool);
+ gu_pool_free(tmp_pool);
+ return s;
+#endif
+}
+
+GuWord
+gu_string_hash(GuString s)
+{
+ if (s.w_ & 1) {
+ return s.w_;
+ }
+ size_t len = gu_string_length(s);
+ uint8_t* data = gu_string_long_data(s);
+ return gu_hash_bytes(0, data, len);
+}
+
+bool
+gu_string_eq(GuString s1, GuString s2)
+{
+ if (s1.w_ == s2.w_) {
+ return true;
+ } else if (gu_string_is_long(s1) && gu_string_is_long(s2)) {
+ size_t len1 = gu_string_long_length(s1);
+ size_t len2 = gu_string_long_length(s2);
+ if (len1 != len2) {
+ return false;
+ }
+ uint8_t* data1 = gu_string_long_data(s1);
+ uint8_t* data2 = gu_string_long_data(s2);
+ return (memcmp(data1, data2, len1) == 0);
+ }
+ return false;
+
+}
+
+
+static GuHash
+gu_string_hasher_hash(GuHasher* self, const void* p)
+{
+ (void) self;
+ const GuString* sp = p;
+ return gu_string_hash(*sp);
+}
+
+static bool
+gu_string_eq_fn(GuEquality* self, const void* p1, const void* p2)
+{
+ (void) self;
+ const GuString* sp1 = p1;
+ const GuString* sp2 = p2;
+ return gu_string_eq(*sp1, *sp2);
+}
+
+GuHasher gu_string_hasher[1] = {
+ {
+ .eq = { gu_string_eq_fn },
+ .hash = gu_string_hasher_hash
+ }
+};
+
+
+GU_DEFINE_TYPE(GuString, GuOpaque, _);
+GU_DEFINE_TYPE(GuStrings, GuSeq, gu_type(GuString));
+GU_DEFINE_KIND(GuStringMap, GuMap);
diff --git a/src/runtime/c/gu/string.h b/src/runtime/c/gu/string.h
new file mode 100644
index 000000000..385b162ed
--- /dev/null
+++ b/src/runtime/c/gu/string.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GU_STRING_H_
+#define GU_STRING_H_
+
+#include <gu/bits.h>
+#include <gu/read.h>
+#include <gu/write.h>
+
+typedef GuOpaque() GuString;
+
+extern const GuString gu_empty_string;
+
+GuString
+gu_string_copy(GuString string, GuPool* pool);
+
+void
+gu_string_write(GuString string, GuWriter* wtr, GuExn* err);
+
+GuReader*
+gu_string_reader(GuString string, GuPool* pool);
+
+bool
+gu_string_is_stable(GuString string);
+
+GuString
+gu_ucs_string(const GuUCS* ubuf, size_t len, GuPool* pool);
+
+typedef struct GuStringBuf GuStringBuf;
+
+GuStringBuf*
+gu_string_buf(GuPool* pool);
+
+GuWriter*
+gu_string_buf_writer(GuStringBuf* sb);
+
+GuString
+gu_string_buf_freeze(GuStringBuf* sb, GuPool* pool);
+
+GuString
+gu_format_string_v(const char* fmt, va_list args, GuPool* pool);
+
+GuString
+gu_format_string(GuPool* pool, const char* fmt, ...);
+
+GuString
+gu_str_string(const char* str, GuPool* pool);
+
+#endif // GU_STRING_H_
+
+#if defined(GU_HASH_H_) && !defined(GU_STRING_H_HASH_)
+#define GU_STRING_H_HASH_
+
+uintptr_t
+gu_string_hash(GuString s);
+
+extern GuHasher gu_string_hasher[1];
+
+bool
+gu_string_eq(GuString s1, GuString s2);
+#endif
+
+#ifdef GU_TYPE_H_
+# ifndef GU_STRING_H_TYPE_
+# define GU_STRING_H_TYPE_
+
+extern GU_DECLARE_TYPE(GuString, GuOpaque);
+# endif
+
+# if defined(GU_SEQ_H_) && !defined(GU_STRING_H_SEQ_TYPE_)
+# define GU_STRING_H_SEQ_TYPE_
+extern GU_DECLARE_TYPE(GuStrings, GuSeq);
+# endif
+
+# if defined(GU_MAP_H_TYPE_) && !defined(GU_STRING_H_MAP_TYPE_)
+# define GU_STRING_H_MAP_TYPE_
+
+extern GU_DECLARE_KIND(GuStringMap);
+typedef GuType_GuMap GuType_GuStringMap;
+
+#define GU_TYPE_INIT_GuStringMap(KIND, MAP_T, VAL_T, DEFAULT) \
+ GU_TYPE_INIT_GuMap(KIND, MAP_T, \
+ gu_type(GuString), gu_string_hasher, \
+ VAL_T, DEFAULT)
+
+# endif
+#endif
+
+
+#if defined(GU_SEQ_H_) && !defined(GU_STRING_H_SEQ_)
+#define GU_STRING_H_SEQ_
+
+typedef GuSeq GuStrings;
+// typedef GuBuf GuStringBuf;
+
+#endif
+
+
+#if defined(GU_MAP_H_) && !defined(GU_STRING_H_MAP_)
+#define GU_STRING_H_MAP_
+
+typedef GuMap GuStringMap;
+
+#define gu_new_string_map(VAL_T, DEFAULT, POOL) \
+ gu_new_map(GuString, gu_string_hasher, (VAL_T), (DEFAULT), (POOL))
+
+#endif
+
diff --git a/src/runtime/c/gu/sysdeps.h b/src/runtime/c/gu/sysdeps.h
new file mode 100644
index 000000000..114e1c40d
--- /dev/null
+++ b/src/runtime/c/gu/sysdeps.h
@@ -0,0 +1,30 @@
+#ifndef GU_SYSDEPS_H_
+#define GU_SYSDEPS_H_
+
+#include <guconfig.h>
+
+#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
+# define GU_GNUC
+#endif
+
+#ifdef GU_GNUC
+# define GU_ALIGNOF __alignof
+# define GU_HAVE_STATEMENT_EXPRESSIONS
+# define GU_GNUC_ATTR(x) __attribute__(( x ))
+# if defined(__OPTIMIZE_SIZE__)
+# define GU_OPTIMIZE_SIZE
+# elif defined(__OPTIMIZE__)
+# define GU_OPTIMIZE_SPEED
+# endif
+#else
+# define GU_GNUC_ATTR(x)
+#endif
+
+#ifdef S_SPLINT_S
+# define GU_SPLINT(x) %{ x %}
+#else
+# define GU_SPLINT(x)
+#endif
+
+
+#endif // GU_SYSDEPS_H_
diff --git a/src/runtime/c/gu/type.c b/src/runtime/c/gu/type.c
new file mode 100644
index 000000000..13fbfa42d
--- /dev/null
+++ b/src/runtime/c/gu/type.c
@@ -0,0 +1,229 @@
+
+#include <gu/type.h>
+#include <gu/assert.h>
+#include <gu/map.h>
+
+GuKind GU_TYPE_IDENT(type)[1] = {{ .super = NULL }};
+
+GU_DEFINE_KIND(alias, type);
+GU_DEFINE_KIND(typedef, alias);
+GU_DEFINE_KIND(referenced, alias);
+
+GU_DEFINE_KIND(repr, type);
+GU_DEFINE_KIND(GuOpaque, repr);
+
+GU_DEFINE_KIND(abstract, type);
+
+GU_DEFINE_KIND(struct, repr);
+
+GU_DEFINE_KIND(pointer, repr);
+GU_DEFINE_KIND(reference, pointer);
+GU_DEFINE_KIND(shared, pointer);
+
+GU_DEFINE_KIND(primitive, repr);
+
+// sizeof(void) is illegal, so do this manually
+GuPrimType GU_TYPE_IDENT(void)[1] = {{
+ .repr_base = {
+ .type_base = {
+ .kind_base = {
+ .super = gu_kind(primitive),
+ },
+ },
+ .size = 0,
+ .align = 1,
+ },
+ .name = "void",
+}};
+
+GU_DEFINE_KIND(integer, primitive);
+GU_DEFINE_TYPE(char, integer, _);
+
+GU_DEFINE_KIND(signed, integer);
+GU_DEFINE_TYPE(int, signed, _);
+GU_DEFINE_TYPE(int8_t, signed, _);
+GU_DEFINE_TYPE(int16_t, signed, _);
+GU_DEFINE_TYPE(int32_t, signed, _);
+GU_DEFINE_TYPE(int64_t, signed, _);
+GU_DEFINE_TYPE(intptr_t, signed, _);
+GU_DEFINE_TYPE(intmax_t, signed, _);
+
+GU_DEFINE_KIND(unsigned, integer);
+GU_DEFINE_TYPE(uint8_t, unsigned, _);
+GU_DEFINE_TYPE(uint16_t, unsigned, _);
+GU_DEFINE_TYPE(uint32_t, unsigned, _);
+GU_DEFINE_TYPE(uint64_t, unsigned, _);
+GU_DEFINE_TYPE(uintmax_t, unsigned, _);
+GU_DEFINE_TYPE(size_t, unsigned, _);
+
+GU_DEFINE_TYPE(GuLength, unsigned, _);
+
+GU_DEFINE_KIND(GuFloating, primitive);
+GU_DEFINE_TYPE(float, GuFloating, _);
+GU_DEFINE_TYPE(double, GuFloating, _);
+GU_DEFINE_TYPE(GuLongDouble, GuFloating, _);
+
+
+GU_DEFINE_KIND(enum, repr);
+
+bool gu_type_has_kind(GuType* type, GuKind* kind)
+{
+ GuKind* k = (GuKind*)type;
+ while (k != NULL) {
+ if (k == kind) {
+ return true;
+ }
+ k = k->super;
+ }
+ return false;
+}
+
+
+struct GuTypeMap {
+ GuMap* map;
+};
+
+static void
+gu_type_map_init(GuTypeMap* tmap, GuTypeTable* table)
+{
+ for (int i = 0; i < table->parents.len; i++) {
+ gu_type_map_init(tmap, table->parents.elems[i]);
+ }
+ for (int i = 0; i < table->entries.len; i++) {
+ GuTypeTableEntry* e = &table->entries.elems[i];
+ gu_map_put(tmap->map, e->kind, void*, e->val);
+ }
+}
+
+GuTypeMap*
+gu_new_type_map(GuTypeTable* table, GuPool* pool)
+{
+ GuTypeMap* tmap =
+ gu_new_i(pool, GuTypeMap,
+ .map = gu_new_map(GuKind, NULL, void*, &gu_null, pool));
+ gu_type_map_init(tmap, table);
+ return tmap;
+}
+
+bool
+gu_struct_has_flex(GuStructRepr* srepr)
+{
+ for (int i = 0; i < srepr->members.len; i++) {
+ if (srepr->members.elems[i].is_flex) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void*
+gu_type_map_get(GuTypeMap* tmap, GuType* type)
+{
+ GuKind* kind = (GuKind*)type;
+ while (kind != NULL) {
+ void* val = gu_map_get(tmap->map, kind, void*);
+ if (val != NULL) {
+ return val;
+ }
+ kind = kind->super;
+ }
+ return NULL;
+}
+
+const void*
+gu_type_dyn_cast(GuType* type, GuKind* kind)
+{
+ if (gu_type_has_kind(type, kind)) {
+ return type;
+ }
+ return NULL;
+}
+
+
+const void*
+gu_type_check_cast(GuType* type, GuKind* kind)
+{
+ gu_assert(gu_type_has_kind(type, kind));
+ return type;
+}
+
+GuTypeRepr*
+gu_type_repr(GuType* type)
+{
+ GuTypeAlias* alias;
+ while ((alias = gu_type_try_cast(type, alias))) {
+ type = alias->type;
+ }
+ return gu_type_try_cast(type, repr);
+}
+
+size_t
+gu_type_size(GuType* type)
+{
+ GuTypeRepr* repr = gu_type_repr(type);
+ return repr ? repr->size : 0;
+}
+
+GuEnumConstant*
+gu_enum_value(GuEnumType* etype, const void* enump)
+{
+ size_t esize = etype->repr_base.size;
+#define CHECK_ENUM_TYPE(t_) do { \
+ if (esize == sizeof(t_)) { \
+ t_ c = *(const t_*)enump; \
+ for (int i = 0; i < etype->constants.len; i++) { \
+ GuEnumConstant* cp = &etype->constants.elems[i]; \
+ t_ d = *(const t_*)cp->enum_value; \
+ if (c == d) { \
+ return cp; \
+ } \
+ } \
+ return NULL; \
+ } \
+ } while (false)
+
+ CHECK_ENUM_TYPE(int);
+ CHECK_ENUM_TYPE(char);
+ CHECK_ENUM_TYPE(short);
+ CHECK_ENUM_TYPE(long);
+ CHECK_ENUM_TYPE(long long);
+
+ return NULL;
+}
+
+void*
+gu_type_malloc(GuType* type, GuPool* pool)
+{
+ GuTypeRepr* repr = gu_type_repr(type);
+ gu_assert(repr);
+ return gu_malloc_aligned(pool, repr->size, repr->align);
+}
+
+#if 0
+
+typedef const struct GuPtrConvFns GuPtrConvFns;
+
+struct GuPtrConvFns {
+ void* (*get)(const void* pp);
+ void (*set)(void** pp, void* p);
+};
+
+#define GU_TYPE_PTR_DEFINE_GETSET(name_, t_) \
+ static void* gu_type_##name_##_ptr_get(const void* pp) { \
+ return *(t_* const*) pp; \
+ } \
+ \
+ static void gu_type_##name_##_ptr_set(void* pp, void* p) { \
+ *(t_**) pp = p; \
+ } \
+ static GuPtrConvFns gu_ptr_conv_##name_ = { \
+ .get = gu_type_##name_##_ptr_get, \
+ .set = gu_type_##name_##_ptr_set \
+ }
+
+GU_TYPE_PTR_DEFINE_GETSET(void, void);
+GU_TYPE_PTR_DEFINE_GETSET(struct, GuStruct);
+GU_TYPE_PTR_DEFINE_GETSET(int, int);
+
+
+#endif
diff --git a/src/runtime/c/gu/type.h b/src/runtime/c/gu/type.h
new file mode 100644
index 000000000..777b2e1f9
--- /dev/null
+++ b/src/runtime/c/gu/type.h
@@ -0,0 +1,454 @@
+
+#ifndef GU_TYPE_H_
+#define GU_TYPE_H_
+
+#include <gu/defs.h>
+
+//
+// kind
+//
+
+typedef const struct GuKind GuKind;
+
+struct GuKind {
+ GuKind* super;
+};
+
+// Use GU_PASTE here so k_ can be preprocessor-expanded
+#define GU_TYPE_IDENT(k_) GU_PASTE(gu_type__,k_)
+
+#define gu_kind(k_) ((GuKind*)GU_TYPE_IDENT(k_))
+
+#define GU_DECLARE_KIND(k_) \
+ GuKind GU_TYPE_IDENT(k_)[1]
+
+extern GU_DECLARE_KIND(kind);
+
+#define GU_DEFINE_KIND(k_, super_k_) \
+ GuKind GU_TYPE_IDENT(k_)[1] = {{ .super = gu_kind(super_k_) }}
+
+//
+// type
+//
+
+typedef const struct GuType GuType;
+
+struct GuType {
+ GuKind kind_base;
+};
+
+typedef GuType GuType_type;
+
+extern GU_DECLARE_KIND(type);
+
+#define GU_TYPE_INIT_type(k_, t_, _) { .kind_base = { .super = gu_kind(k_) } }
+
+#define gu_type(t_) ((GuType*)gu_kind(t_))
+
+
+#define GU_KIND_TYPE(k_) GU_PASTE(GuType_,k_)
+
+// This cannot be used indirectly, since we don't want to pp-expand k_.
+// We must inline the body into other macros.
+#define GU_TYPE_INIT(k_, ...) \
+ GU_TYPE_INIT_##k_(k_, __VA_ARGS__)
+
+//#define GU_TYPE_LIT(k_, ...)
+// ((GuType*)(GuType_##k_[]){GU_TYPE_INIT(k_, __VA_ARGS__)})
+#define GU_TYPE_LIT(k_, ...) \
+ ((GuType*)&(GU_KIND_TYPE(k_)) GU_TYPE_INIT_##k_(k_, __VA_ARGS__))
+
+#define GU_DECLARE_TYPE(t_, k_) \
+ GU_KIND_TYPE(k_) GU_TYPE_IDENT(t_)[1]
+
+//#define GU_DEFINE_TYPE(t_, k_, ...)
+// GuType_##k_ GU_TYPE_IDENT(t_) = GU_TYPE_INIT(k_, t_, __VA_ARGS__)
+#define GU_DEFINE_TYPE(t_, k_, ...) \
+ GU_KIND_TYPE(k_) GU_TYPE_IDENT(t_)[1] = \
+ { GU_TYPE_INIT_##k_(k_, t_, __VA_ARGS__) }
+
+#define GU_DEFINE_TYPE_ALIAS(t1_, t2_) \
+ static GuType* const GU_TYPE_IDENT(t1_) = gu_type(t2_)
+
+
+//
+// abstract
+//
+
+typedef GuType GuType_abstract;
+
+#define GU_TYPE_INIT_abstract(k_, t_, _) \
+ GU_TYPE_INIT_type(k_, t_, _)
+
+extern GU_DECLARE_KIND(abstract);
+
+
+//
+// repr
+//
+
+typedef struct GuTypeRepr GuTypeRepr, GuType_repr;
+
+struct GuTypeRepr {
+ GuType type_base;
+ uint16_t size;
+ uint16_t align;
+};
+
+#define GU_TYPE_INIT_repr(k_, t_, _) { \
+ .type_base = GU_TYPE_INIT_type(k_, t_, _), \
+ .size = sizeof(t_), \
+ .align = gu_alignof(t_) \
+ }
+
+extern GU_DECLARE_KIND(repr);
+
+
+
+//
+// GuOpaque
+//
+
+typedef GuType_repr GuType_GuOpaque;
+
+#define GU_TYPE_INIT_GuOpaque GU_TYPE_INIT_repr
+
+extern GU_DECLARE_KIND(GuOpaque);
+
+//
+// pointer
+//
+
+typedef const struct GuPointerType GuPointerType, GuType_pointer;
+
+struct GuPointerType {
+ GuType_repr repr_base;
+ GuType* pointed_type;
+};
+
+#define GU_TYPE_INIT_pointer(k_, t_, pointed_) \
+ { \
+ .repr_base = GU_TYPE_INIT_repr(k_, t_, _), \
+ .pointed_type = pointed_ \
+}
+
+
+extern GU_DECLARE_KIND(pointer);
+
+#define gu_ptr_type(t_) \
+ GU_TYPE_LIT(pointer, t_*, gu_type(t_))
+
+
+
+
+
+
+
+//
+// alias
+//
+
+
+typedef const struct GuTypeAlias GuTypeAlias, GuType_alias;
+
+struct GuTypeAlias {
+ GuType type_base;
+ GuType* type;
+};
+
+#define GU_TYPE_INIT_alias(k_, t_, type_) { \
+ .type_base = GU_TYPE_INIT_type(k_, t_, _), \
+ .type = type_ \
+}
+
+extern GU_DECLARE_KIND(alias);
+
+//
+// typedef
+//
+
+typedef const struct GuTypeDef GuTypeDef, GuType_typedef;
+
+struct GuTypeDef {
+ GuType_alias alias_base;
+ const char* name;
+};
+
+#define GU_TYPE_INIT_typedef(k_, t_, type_) { \
+ .alias_base = GU_TYPE_INIT_alias(k_, t_, type_), \
+ .name = #t_, \
+}
+
+extern GU_DECLARE_KIND(typedef);
+
+#define GU_DEFINE_TYPEDEF_X(t_, dk_, k_, ...) \
+ GU_DEFINE_TYPE(t_, dk_, GU_TYPE_LIT(k_, t_, __VA_ARGS__))
+
+#define GU_DEFINE_TYPEDEF(t_, ...) \
+ GU_DEFINE_TYPEDEF_X(t_, typedef, __VA_ARGS__)
+
+
+
+//
+// referenced
+//
+
+extern GU_DECLARE_KIND(referenced);
+
+typedef GuType_alias GuType_referenced;
+
+#define GU_TYPE_INIT_referenced GU_TYPE_INIT_alias
+
+
+
+#include <gu/list.h>
+
+//
+// struct
+//
+
+typedef const struct GuStructRepr GuStructRepr, GuType_struct;
+
+typedef const struct GuMember GuMember;
+
+struct GuMember {
+ ptrdiff_t offset;
+ const char* name;
+ GuType* type;
+ bool is_flex;
+};
+
+struct GuStructRepr {
+ GuType_repr repr_base;
+ const char* name;
+ GuSList(GuMember) members;
+};
+
+extern GU_DECLARE_KIND(struct);
+
+#define GU_MEMBER_AUX_(struct_, member_, type_, is_flex_) \
+ { \
+ .offset = offsetof(struct_, member_), \
+ .name = #member_, \
+ .type = type_, \
+ .is_flex = is_flex_, \
+ }
+
+#define GU_MEMBER_V(struct_, member_, type_) \
+ GU_MEMBER_AUX_(struct_, member_, type_, false)
+
+#define GU_MEMBER(s_, m_, t_) \
+ GU_MEMBER_V(s_, m_, gu_type(t_))
+
+#define GU_MEMBER_P(s_, m_, t_) \
+ GU_MEMBER_V(s_, m_, gu_ptr_type(t_))
+
+#define GU_MEMBER_S(s_, m_, t_) \
+ GU_MEMBER_V(s_, m_, gu_shared_ptr_type(t_))
+
+#define GU_FLEX_MEMBER_V(struct_, member_, type_) \
+ GU_MEMBER_AUX_(struct_, member_, type_, true)
+
+#define GU_FLEX_MEMBER(s_, m_, t_) \
+ GU_FLEX_MEMBER_V(s_, m_, gu_type(t_))
+
+#define GU_FLEX_MEMBER_P(s_, m_, t_) \
+ GU_FLEX_MEMBER_V(s_, m_, gu_ptr_type(t_))
+
+
+#define GU_TYPE_INIT_struct(k_, t_, ...) { \
+ .repr_base = GU_TYPE_INIT_repr(k_, t_, _), \
+ .name = #t_, \
+ .members = GU_SLIST(GuMember, __VA_ARGS__) \
+}
+
+bool
+gu_struct_has_flex(GuStructRepr* srepr);
+
+
+//
+// reference
+//
+
+typedef GuType_pointer GuType_reference;
+
+#define GU_TYPE_INIT_reference GU_TYPE_INIT_pointer
+
+extern GU_DECLARE_KIND(reference);
+
+
+//
+// shared
+//
+
+typedef GuType_pointer GuType_shared;
+
+#define GU_TYPE_INIT_shared GU_TYPE_INIT_pointer
+
+extern GU_DECLARE_KIND(shared);
+
+#define gu_shared_ptr_type(t_) \
+ GU_TYPE_LIT(shared, t_*, gu_type(t_))
+
+//
+// primitives
+//
+
+typedef const struct GuPrimType GuPrimType, GuType_primitive;
+
+struct GuPrimType {
+ GuType_repr repr_base;
+ const char* name;
+};
+
+#define GU_TYPE_INIT_primitive(k_, t_, _) { \
+ .repr_base = GU_TYPE_INIT_repr(k_, t_, _), \
+ .name = #t_ \
+}
+
+extern GU_DECLARE_KIND(primitive);
+extern GU_DECLARE_TYPE(void, primitive);
+
+#define GU_TYPE_INIT_integer GU_TYPE_INIT_primitive
+typedef GuType_primitive GuType_integer;
+extern GU_DECLARE_KIND(integer);
+extern GU_DECLARE_TYPE(char, integer);
+
+#define GU_TYPE_INIT_signed GU_TYPE_INIT_integer
+typedef GuType_integer GuType_signed;
+extern GU_DECLARE_KIND(signed);
+extern GU_DECLARE_TYPE(int, signed);
+extern GU_DECLARE_TYPE(int8_t, signed);
+extern GU_DECLARE_TYPE(int16_t, signed);
+extern GU_DECLARE_TYPE(int32_t, signed);
+extern GU_DECLARE_TYPE(int64_t, signed);
+extern GU_DECLARE_TYPE(intptr_t, signed);
+extern GU_DECLARE_TYPE(intmax_t, signed);
+
+#define GU_TYPE_INIT_unsigned GU_TYPE_INIT_integer
+typedef GuType_integer GuType_unsigned;
+extern GU_DECLARE_KIND(unsigned);
+extern GU_DECLARE_TYPE(uint8_t, unsigned);
+extern GU_DECLARE_TYPE(uint16_t, unsigned);
+extern GU_DECLARE_TYPE(uint32_t, unsigned);
+extern GU_DECLARE_TYPE(uint64_t, unsigned);
+extern GU_DECLARE_TYPE(uintmax_t, unsigned);
+extern GU_DECLARE_TYPE(size_t, unsigned);
+
+typedef size_t GuLength;
+extern GU_DECLARE_TYPE(GuLength, unsigned); // TODO: get rid
+
+
+#define GU_TYPE_INIT_GuFloating GU_TYPE_INIT_primitive
+typedef GuType_primitive GuType_GuFloating;
+extern GU_DECLARE_KIND(GuFloating);
+extern GU_DECLARE_TYPE(float, GuFloating);
+extern GU_DECLARE_TYPE(double, GuFloating);
+typedef long double GuLongDouble;
+extern GU_DECLARE_TYPE(GuLongDouble, GuFloating);
+
+
+
+//
+// enum
+//
+
+extern GU_DECLARE_KIND(enum);
+
+typedef const struct GuEnumConstant GuEnumConstant;
+
+struct GuEnumConstant {
+ const char* name;
+ int64_t value;
+ const void* enum_value;
+};
+
+typedef const struct GuEnumType GuEnumType, GuType_enum;
+
+struct GuEnumType {
+ GuType_repr repr_base;
+ GuSList(GuEnumConstant) constants;
+};
+
+#define GU_ENUM_C(t_, x) { \
+ .name = #x, \
+ .value = x, \
+ .enum_value = (const t_[1]){ x } \
+ }
+
+#define GU_TYPE_INIT_enum(k_, t_, ...) { \
+ .repr_base = GU_TYPE_INIT_repr(k_, t_, _), \
+ .constants = GU_SLIST(GuEnumConstant, __VA_ARGS__) \
+}
+
+GuEnumConstant*
+gu_enum_value(GuEnumType* etype, const void* enump);
+
+
+
+
+bool gu_type_has_kind(const GuType* type, const GuKind* kind);
+
+
+
+
+typedef const struct GuTypeTableEntry GuTypeTableEntry;
+
+struct GuTypeTableEntry {
+ GuKind* kind;
+ void* val;
+};
+
+typedef const struct GuTypeTable GuTypeTable;
+
+struct GuTypeTable {
+ GuSList(const GuTypeTable*) parents;
+ GuSList(GuTypeTableEntry) entries;
+};
+
+#define GU_TYPETABLE(parents_, ...) { \
+ .parents = parents_, \
+ .entries = GU_SLIST(GuTypeTableEntry, \
+ __VA_ARGS__) \
+ }
+
+typedef struct GuTypeMap GuTypeMap;
+
+GuTypeMap*
+gu_new_type_map(GuTypeTable* table, GuPool* pool);
+
+void*
+gu_type_map_get(GuTypeMap* tmap, GuType* type);
+
+size_t
+gu_type_size(GuType* type);
+
+GuTypeRepr*
+gu_type_repr(GuType* type);
+
+const void*
+gu_type_check_cast(GuType* t, GuKind* k);
+
+const void*
+gu_type_dyn_cast(GuType* t, GuKind* k);
+
+#define gu_type_try_cast(type_, k_) \
+ ((GU_KIND_TYPE(k_)*)gu_type_dyn_cast(type_, gu_kind(k_)))
+
+#ifndef NDEBUG
+#define gu_type_cast(type_, k_) \
+ ((GU_KIND_TYPE(k_)*)gu_type_check_cast(type_, gu_kind(k_)))
+#else
+#define gu_type_cast(type_, k_) \
+ ((GU_KIND_TYPE(k_)*)(type_))
+#endif
+
+void* gu_type_malloc(GuType* type, GuPool* pool);
+
+#if 0
+void* gu_type_ptr_get(GuType* type, const void* pp);
+void gu_type_ptr_set(GuType* type, void* pp, void* p);
+#endif
+
+
+#endif // GU_TYPE_H_
diff --git a/src/runtime/c/gu/ucs.c b/src/runtime/c/gu/ucs.c
new file mode 100644
index 000000000..34649f36a
--- /dev/null
+++ b/src/runtime/c/gu/ucs.c
@@ -0,0 +1,135 @@
+#include <gu/ucs.h>
+#include <gu/assert.h>
+#include <guconfig.h>
+
+GU_DEFINE_TYPE(GuUCSExn, abstract, _);
+
+
+#ifdef GU_CHAR_ASCII
+
+bool
+gu_char_is_valid(char c)
+{
+ if (c < 0) {
+ return false;
+ } else if (c < 64) {
+ return UINT64_C(0xffffffef00003f81) & (UINT64_C(1) << c);
+ }
+#if CHAR_MAX > 127 // Let's avoid spurious warnings
+ else if (c > 127) {
+ return false;
+ }
+#endif
+ return UINT64_C(0x7ffffffefffffffe) & (UINT64_C(1) << (c - 64));
+}
+
+char
+gu_ucs_char(GuUCS uc, GuExn* err)
+{
+ if (0 <= uc && uc <= 127) {
+ char c = (char) uc;
+ if (gu_char_is_valid(c)) {
+ return c;
+ }
+ }
+ gu_raise(err, GuUCSExn);
+ return 0;
+}
+
+#else // defined(GU_CHAR_ASCII)
+
+static const char gu_ucs_ascii[128] =
+ "\0\0\0\0\0\0\0\a\b\t\n\v\f\r\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ " !\"#\0%&'()*+,-./0123456789:;<=>?"
+ "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ "\0abcdefghijklmnopqrstuvwxyz{|}~\0";
+
+const uint8_t gu_ucs_ascii_reverse_[CHAR_MAX] = {
+ ['\0'] = 0x00, ['\a'] = 0x07, ['\b'] = 0x08, ['\t'] = 0x09,
+ ['\n'] = 0x0a, ['\v'] = 0x0b, ['\f'] = 0x0c, ['\r'] = 0x0d,
+ [' '] = 0x20, ['!'] = 0x21, ['"'] = 0x22, ['#'] = 0x23, ['%'] = 0x25,
+ ['&'] = 0x26, ['\''] = 0x27, ['('] = 0x28, [')'] = 0x29, ['*'] = 0x2a,
+ ['+'] = 0x2b, [','] = 0x2c, ['-'] = 0x2d, ['.'] = 0x2e, ['/'] = 0x2f,
+ ['0'] = 0x30, ['1'] = 0x31, ['2'] = 0x32, ['3'] = 0x33, ['4'] = 0x34,
+ ['5'] = 0x35, ['6'] = 0x36, ['7'] = 0x37, ['8'] = 0x38, ['9'] = 0x39,
+ [':'] = 0x3a, [';'] = 0x3b, ['<'] = 0x3c, ['='] = 0x3d, ['>'] = 0x3e,
+ ['?'] = 0x3f, ['A'] = 0x41, ['B'] = 0x42, ['C'] = 0x43, ['D'] = 0x44,
+ ['E'] = 0x45, ['F'] = 0x46, ['G'] = 0x47, ['H'] = 0x48, ['I'] = 0x49,
+ ['J'] = 0x4a, ['K'] = 0x4b, ['L'] = 0x4c, ['M'] = 0x4d, ['N'] = 0x4e,
+ ['O'] = 0x4f, ['P'] = 0x50, ['Q'] = 0x51, ['R'] = 0x52, ['S'] = 0x53,
+ ['T'] = 0x54, ['U'] = 0x55, ['V'] = 0x56, ['W'] = 0x57, ['X'] = 0x58,
+ ['Y'] = 0x59, ['Z'] = 0x5a, ['['] = 0x5b, ['\\'] = 0x5c, [']'] = 0x5d,
+ ['^'] = 0x5e, ['_'] = 0x5f, ['a'] = 0x61, ['b'] = 0x62, ['c'] = 0x63,
+ ['d'] = 0x64, ['e'] = 0x65, ['f'] = 0x66, ['g'] = 0x67, ['h'] = 0x68,
+ ['i'] = 0x69, ['j'] = 0x6a, ['k'] = 0x6b, ['l'] = 0x6c, ['m'] = 0x6d,
+ ['n'] = 0x6e, ['o'] = 0x6f, ['p'] = 0x70, ['q'] = 0x71, ['r'] = 0x72,
+ ['s'] = 0x73, ['t'] = 0x74, ['u'] = 0x75, ['v'] = 0x76, ['w'] = 0x77,
+ ['x'] = 0x78, ['y'] = 0x79, ['z'] = 0x7a, ['{'] = 0x7b, ['|'] = 0x7c,
+ ['}'] = 0x7d, ['~'] = 0x7e
+};
+
+
+bool
+gu_char_is_valid(char c)
+{
+ if (c > 0) {
+ return (gu_ucs_ascii_reverse_[(int) c] > 0);
+ }
+ return (c == '\0');
+}
+
+char
+gu_ucs_char(GuUCS uc, GuExn* err)
+{
+ if (uc == 0) {
+ return '\0';
+ } else if (0 < uc && uc <= 127) {
+ char c = gu_ucs_ascii[uc];
+ if (c != '\0') {
+ return (unsigned char) c;
+ }
+ }
+ gu_raise(err, GuUCSExn);
+ return 0;
+}
+
+#endif
+
+size_t
+gu_str_to_ucs(const char* cbuf, size_t len, GuUCS* ubuf, GuExn* err)
+{
+ size_t n = 0;
+ while (n < len) {
+ char c = cbuf[n];
+ if (!gu_char_is_valid(c)) {
+ gu_raise(err, GuUCSExn);
+ return n;
+ }
+ ubuf[n] = gu_char_ucs(c);
+ n++;
+ }
+ return n;
+}
+
+size_t
+gu_ucs_to_str(const GuUCS* ubuf, size_t len, char* cbuf, GuExn* err)
+{
+ size_t n = 0;
+ while (n < len) {
+ char c = gu_ucs_char(ubuf[n], err);
+ if (!gu_ok(err)) {
+ break;
+ }
+ cbuf[n] = c;
+ n++;
+ }
+ return n;
+}
+
+
+extern inline bool
+gu_ucs_valid(GuUCS ucs);
+
+extern inline GuUCS
+gu_char_ucs(char c);
diff --git a/src/runtime/c/gu/ucs.h b/src/runtime/c/gu/ucs.h
new file mode 100644
index 000000000..f1662a602
--- /dev/null
+++ b/src/runtime/c/gu/ucs.h
@@ -0,0 +1,53 @@
+#ifndef GU_UCS_H_
+#define GU_UCS_H_
+
+#include <gu/defs.h>
+#include <gu/exn.h>
+#include <gu/assert.h>
+
+
+#if defined(__STDC_ISO_10646__) && WCHAR_MAX >= 0x10FFFF
+#include <wchar.h>
+#define GU_UCS_WCHAR
+typedef wchar_t GuUCS;
+#else
+typedef int32_t GuUCS;
+#endif
+
+#define GU_UCS_MAX ((GuUCS)(0x10FFFF))
+
+bool
+gu_char_is_valid(char c);
+
+inline bool
+gu_ucs_valid(GuUCS ucs)
+{
+ return ucs >= 0 && ucs <= GU_UCS_MAX;
+}
+
+inline GuUCS
+gu_char_ucs(char c)
+{
+ gu_require(gu_char_is_valid(c));
+#ifdef GU_CHAR_ASCII
+ GuUCS u = (GuUCS) c;
+#else
+ extern const uint8_t gu_ucs_ascii_reverse_[CHAR_MAX];
+ GuUCS u = gu_ucs_ascii_reverse_[(unsigned char) c];
+#endif
+ gu_ensure(u < 0x80);
+ return u;
+}
+
+char
+gu_ucs_char(GuUCS uc, GuExn* err);
+
+size_t
+gu_str_to_ucs(const char* cbuf, size_t len, GuUCS* ubuf, GuExn* err);
+
+size_t
+gu_ucs_to_str(const GuUCS* ubuf, size_t len, char* cbuf, GuExn* err);
+
+extern GU_DECLARE_TYPE(GuUCSExn, abstract);
+
+#endif // GU_ISO10646_H_
diff --git a/src/runtime/c/gu/utf8.c b/src/runtime/c/gu/utf8.c
new file mode 100644
index 000000000..a416c2dac
--- /dev/null
+++ b/src/runtime/c/gu/utf8.c
@@ -0,0 +1,220 @@
+#include <gu/assert.h>
+#include <gu/utf8.h>
+#include <guconfig.h>
+
+GuUCS
+gu_utf8_decode(const uint8_t** src_inout)
+{
+ const uint8_t* src = *src_inout;
+ uint8_t c = src[0];
+ if (c < 0x80) {
+ *src_inout = src + 1;
+ return (GuUCS) c;
+ }
+ size_t len = (c < 0xe0 ? 1 :
+ c < 0xf0 ? 2 :
+ 3);
+ uint32_t mask = 0x07071f7f;
+ uint32_t u = c & (mask >> (len * 8));
+ for (size_t i = 1; i <= len; i++) {
+ c = src[i];
+ u = u << 6 | (c & 0x3f);
+ }
+ *src_inout = &src[len + 1];
+ return (GuUCS) u;
+}
+
+GuUCS
+gu_in_utf8_(GuIn* in, GuExn* err)
+{
+ uint8_t c = gu_in_u8(in, err);
+ if (!gu_ok(err)) {
+ return 0;
+ }
+ int len = (c < 0x80 ? 0 :
+ c < 0xc2 ? -1 :
+ c < 0xe0 ? 1 :
+ c < 0xf0 ? 2 :
+ c < 0xf5 ? 3 :
+ -1);
+ if (len < 0) {
+ goto fail;
+ } else if (len == 0) {
+ return c;
+ }
+ static const uint8_t mask[4] = { 0x7f, 0x1f, 0x0f, 0x07 };
+ uint32_t u = c & mask[len];
+ uint8_t buf[3];
+ // If reading the extra bytes causes EOF, it is an encoding
+ // error, not a legitimate end of character stream.
+ GuExn* tmp_err = gu_exn(err, GuEOF, NULL);
+ gu_in_bytes(in, buf, len, tmp_err);
+ if (tmp_err->caught) {
+ goto fail;
+ }
+ if (!gu_ok(err)) {
+ return 0;
+ }
+ for (int i = 0; i < len; i++) {
+ c = buf[i];
+ if ((c & 0xc0) != 0x80) {
+ goto fail;
+ }
+ u = u << 6 | (c & 0x3f);
+ }
+ GuUCS ucs = (GuUCS) u;
+ if (!gu_ucs_valid(ucs)) {
+ goto fail;
+ }
+ return ucs;
+
+fail:
+ gu_raise(err, GuUCSExn);
+ return 0;
+}
+
+
+size_t
+gu_advance_utf8(GuUCS ucs, uint8_t* buf)
+{
+ gu_require(gu_ucs_valid(ucs));
+ if (ucs < 0x80) {
+ buf[0] = (uint8_t) ucs;
+ return 1;
+ } else if (ucs < 0x800) {
+ buf[0] = 0xc0 | (ucs >> 6);
+ buf[1] = 0x80 | (ucs & 0x3f);
+ return 2;
+ } else if (ucs < 0x10000) {
+ buf[0] = 0xe0 | (ucs >> 12);
+ buf[1] = 0x80 | ((ucs >> 6) & 0x3f);
+ buf[2] = 0x80 | (ucs & 0x3f);
+ return 3;
+ } else {
+ buf[0] = 0xf0 | (ucs >> 18);
+ buf[1] = 0x80 | ((ucs >> 12) & 0x3f);
+ buf[2] = 0x80 | ((ucs >> 6) & 0x3f);
+ buf[3] = 0x80 | (ucs & 0x3f);
+ return 4;
+ }
+}
+
+char
+gu_in_utf8_char_(GuIn* in, GuExn* err)
+{
+ return gu_ucs_char(gu_in_utf8(in, err), err);
+}
+
+void
+gu_out_utf8_long_(GuUCS ucs, GuOut* out, GuExn* err)
+{
+ uint8_t buf[4];
+ size_t sz = gu_advance_utf8(ucs, buf);
+ switch (sz) {
+ case 2:
+ gu_out_bytes(out, buf, 2, err);
+ break;
+ case 3:
+ gu_out_bytes(out, buf, 3, err);
+ break;
+ case 4:
+ gu_out_bytes(out, buf, 4, err);
+ break;
+ default:
+ gu_impossible();
+ }
+}
+
+extern inline void
+gu_out_utf8(GuUCS ucs, GuOut* out, GuExn* err);
+
+static size_t
+gu_utf32_out_utf8_buffered_(const GuUCS* src, size_t len, GuOut* out,
+ GuExn* err)
+{
+ size_t src_i = 0;
+ while (src_i < len) {
+ size_t dst_sz;
+ uint8_t* dst = gu_out_begin_span(out, len - src_i, &dst_sz, err);
+ if (!gu_ok(err)) {
+ return src_i;
+ }
+ if (!dst) {
+ gu_out_utf8(src[src_i], out, err);
+ if (!gu_ok(err)) {
+ return src_i;
+ }
+ src_i++;
+ break;
+ }
+ size_t dst_i = 0;
+ while (true) {
+ size_t safe = (dst_sz - dst_i) / 4;
+ size_t end = GU_MIN(len, src_i + safe);
+ if (end == src_i) {
+ break;
+ }
+ do {
+ GuUCS ucs = src[src_i++];
+ dst_i += gu_advance_utf8(ucs, &dst[dst_i]);
+ } while (src_i < end);
+ }
+ gu_out_end_span(out, dst_i);
+ }
+ return src_i;
+}
+
+size_t
+gu_utf32_out_utf8(const GuUCS* src, size_t len, GuOut* out, GuExn* err)
+{
+ if (gu_out_is_buffered(out)) {
+ return gu_utf32_out_utf8_buffered_(src, len, out, err);
+ }
+ for (size_t i = 0; i < len; i++) {
+ gu_out_utf8(src[i], out, err);
+ if (!gu_ok(err)) {
+ return i;
+ }
+ }
+ return len;
+
+}
+
+#ifndef GU_CHAR_ASCII
+
+void gu_str_out_utf8_(const char* str, GuOut* out, GuExn* err)
+{
+ size_t len = strlen(str);
+ size_t sz = 0;
+ uint8_t* buf = gu_out_begin_span(out, len, &sz, err);
+ if (!gu_ok(err)) {
+ return;
+ }
+ if (buf != NULL && sz < len) {
+ gu_out_end_span(out, 0);
+ buf = NULL;
+ }
+ GuPool* tmp_pool = buf ? NULL : gu_local_pool();
+ buf = buf ? buf : gu_new_n(uint8_t, len, tmp_pool);
+ for (size_t i = 0; i < len; i++) {
+ GuUCS ucs = gu_char_ucs(str[i]);
+ buf[i] = (uint8_t) ucs;
+ }
+ if (tmp_pool) {
+ gu_out_bytes(out, buf, len, err);
+ gu_pool_free(tmp_pool);
+ } else {
+ gu_out_end_span(out, len);
+ }
+}
+
+#endif
+
+extern inline void
+gu_str_out_utf8(const char* str, GuOut* out, GuExn* err);
+
+extern inline GuUCS
+gu_in_utf8(GuIn* in, GuExn* err);
+
+extern inline char
+gu_in_utf8_char(GuIn* in, GuExn* err);
diff --git a/src/runtime/c/gu/utf8.h b/src/runtime/c/gu/utf8.h
new file mode 100644
index 000000000..053d8664e
--- /dev/null
+++ b/src/runtime/c/gu/utf8.h
@@ -0,0 +1,67 @@
+#ifndef GU_UTF8_H_
+#define GU_UTF8_H_
+
+#include <gu/in.h>
+#include <gu/out.h>
+#include <gu/ucs.h>
+
+inline GuUCS
+gu_in_utf8(GuIn* in, GuExn* err)
+{
+ int i = gu_in_peek_u8(in);
+ if (i >= 0 && i < 0x80) {
+ gu_in_consume(in, 1);
+ return (GuUCS) i;
+ }
+ extern GuUCS gu_in_utf8_(GuIn* in, GuExn* err);
+ return gu_in_utf8_(in, err);
+}
+
+
+inline char
+gu_in_utf8_char(GuIn* in, GuExn* err)
+{
+#ifdef GU_CHAR_ASCII
+ int i = gu_in_peek_u8(in);
+ if (i >= 0 && i < 0x80) {
+ gu_in_consume(in, 1);
+ return (char) i;
+ }
+#endif
+ extern char gu_in_utf8_char_(GuIn* in, GuExn* err);
+ return gu_in_utf8_char_(in, err);
+}
+
+void
+gu_out_utf8_long_(GuUCS ucs, GuOut* out, GuExn* err);
+
+inline void
+gu_out_utf8(GuUCS ucs, GuOut* out, GuExn* err)
+{
+ gu_require(gu_ucs_valid(ucs));
+ if (GU_LIKELY(ucs < 0x80)) {
+ gu_out_u8(out, ucs, err);
+ } else {
+ gu_out_utf8_long_(ucs, out, err);
+ }
+}
+
+size_t
+gu_utf32_out_utf8(const GuUCS* src, size_t len, GuOut* out, GuExn* err);
+
+GuUCS
+gu_utf8_decode(const uint8_t** utf8);
+
+inline void
+gu_str_out_utf8(const char* str, GuOut* out, GuExn* err)
+{
+#ifdef GU_CHAR_ASCII
+ gu_out_bytes(out, (const uint8_t*) str, strlen(str), err);
+#else
+ extern void
+ gu_str_out_utf8_(const char* str, GuOut* out, GuExn* err);
+ gu_str_out_utf8_(str, out, err);
+#endif
+}
+
+#endif // GU_UTF8_H_
diff --git a/src/runtime/c/gu/variant.c b/src/runtime/c/gu/variant.c
new file mode 100644
index 000000000..62fdb92c1
--- /dev/null
+++ b/src/runtime/c/gu/variant.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "variant.h"
+#include "bits.h"
+
+enum {
+ GU_VARIANT_ALIGNMENT = sizeof(uintptr_t)
+};
+
+void*
+gu_alloc_variant(uint8_t tag, size_t size,
+ size_t align, GuVariant* variant_out, GuPool* pool)
+{
+ align = gu_max(align, GU_VARIANT_ALIGNMENT);
+ if (((size_t)tag) > GU_VARIANT_ALIGNMENT - 2) {
+ uint8_t* alloc = gu_malloc_aligned(pool, align + size, align);
+ alloc[align - 1] = tag;
+ void* p = &alloc[align];
+ variant_out->p = (uintptr_t)p;
+ return p;
+ }
+ void* p = gu_malloc_aligned(pool, size, align);
+ variant_out->p = ((uintptr_t)p) | (tag + 1);
+ return p;
+}
+
+GuVariant
+gu_make_variant(uint8_t tag, size_t size, size_t align, const void* init,
+ GuPool* pool)
+{
+ GuVariant v;
+ void* data = gu_alloc_variant(tag, size, align, &v, pool);
+ memcpy(data, init, size);
+ return v;
+}
+
+int
+gu_variant_tag(GuVariant variant)
+{
+ if (gu_variant_is_null(variant)) {
+ return GU_VARIANT_NULL;
+ }
+ int u = variant.p % GU_VARIANT_ALIGNMENT;
+ if (u == 0) {
+ uint8_t* mem = (uint8_t*)variant.p;
+ return mem[-1];
+ }
+ return u - 1;
+}
+
+void*
+gu_variant_data(GuVariant variant)
+{
+ if (gu_variant_is_null(variant)) {
+ return NULL;
+ }
+ return (void*)gu_align_backward(variant.p, GU_VARIANT_ALIGNMENT);
+}
+
+GuVariantInfo gu_variant_open(GuVariant variant)
+{
+ GuVariantInfo info = {
+ .tag = gu_variant_tag(variant),
+ .data = gu_variant_data(variant)
+ };
+ return info;
+}
+
+int
+gu_variant_intval(GuVariant variant)
+{
+ int u = variant.p % GU_VARIANT_ALIGNMENT;
+ if (u == 0) {
+ int* mem = (int*)variant.p;
+ return *mem;
+ }
+ return (variant.p / GU_VARIANT_ALIGNMENT);
+}
+
+const GuVariant gu_null_variant = { (GuWord) NULL };
+
+GU_DEFINE_KIND(GuVariant, repr);
+GU_DEFINE_KIND(GuVariantAsPtr, repr);
diff --git a/src/runtime/c/gu/variant.h b/src/runtime/c/gu/variant.h
new file mode 100644
index 000000000..a9bb10a4a
--- /dev/null
+++ b/src/runtime/c/gu/variant.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2010 University of Helsinki.
+ *
+ * This file is part of libgu.
+ *
+ * Libgu is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Libgu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with libgu. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file
+ *
+ * Lightweight tagged data.
+ */
+
+#ifndef GU_VARIANT_H_
+#define GU_VARIANT_H_
+
+#include <gu/defs.h>
+#include <gu/mem.h>
+#include <gu/type.h>
+
+/** @name Variants
+ * @{
+ */
+
+typedef struct GuVariant GuVariant;
+
+
+void* gu_alloc_variant(uint8_t tag,
+ size_t size, size_t align,
+ GuVariant* variant_out, GuPool* pool);
+
+GuVariant gu_make_variant(uint8_t tag,
+ size_t size, size_t align,
+ const void* init, GuPool* pool);
+
+#define gu_new_variant(tag, type, variant_out, pool) \
+ ((type*)gu_alloc_variant(tag, sizeof(type), \
+ gu_alignof(type), variant_out, pool))
+
+/**<
+ * @hideinitializer */
+
+#define gu_new_variant_i(POOL, TAG, T, ...) \
+ gu_make_variant(TAG, sizeof(T), gu_alignof(T), \
+ &(T){ __VA_ARGS__ }, POOL)
+
+
+
+#define gu_new_flex_variant(tag, type, flex_mem, n_elems, variant_out, pool) \
+ ((type*)gu_alloc_variant(tag, \
+ GU_FLEX_SIZE(type, flex_mem, n_elems), \
+ gu_flex_alignof(type), \
+ variant_out, pool))
+/**<
+ * @hideinitializer */
+
+enum {
+ GU_VARIANT_NULL = -1
+};
+
+int gu_variant_tag(GuVariant variant);
+
+void* gu_variant_data(GuVariant variant);
+
+
+typedef struct GuVariantInfo GuVariantInfo;
+
+struct GuVariantInfo {
+ int tag;
+ void* data;
+};
+
+GuVariantInfo gu_variant_open(GuVariant variant);
+
+/** @privatesection */
+struct GuVariant {
+ uintptr_t p;
+ /**< @private */
+};
+
+/** @} */
+
+static inline void*
+gu_variant_to_ptr(GuVariant variant)
+{
+ return (void*)variant.p;
+}
+
+static inline GuVariant
+gu_variant_from_ptr(const void* p)
+{
+ GuVariant v = { (uintptr_t)p };
+ return v;
+}
+
+extern const GuVariant gu_null_variant;
+
+static inline bool
+gu_variant_is_null(GuVariant v) {
+ return ((void*)v.p == NULL);
+}
+
+
+// variant
+
+typedef const struct GuConstructor GuConstructor;
+
+struct GuConstructor {
+ int c_tag;
+ const char* c_name;
+ const GuType* type;
+};
+
+#define GU_CONSTRUCTOR_V(ctag, c_type) { \
+ .c_tag = ctag, \
+ .c_name = #ctag, \
+ .type = c_type \
+}
+
+#define GU_CONSTRUCTOR(ctag, t_) \
+ GU_CONSTRUCTOR_V(ctag, gu_type(t_))
+
+#define GU_CONSTRUCTOR_P(ctag, t_) \
+ GU_CONSTRUCTOR_V(ctag, gu_ptr_type(t_))
+
+#define GU_CONSTRUCTOR_S(ctag, t_, ...) \
+ GU_CONSTRUCTOR_V(ctag, GU_TYPE_LIT(struct, t_, __VA_ARGS__))
+
+#define GU_CONSTRUCTOR_S1(ctag, t_, mem1_, type1_) \
+ GU_CONSTRUCTOR_S(ctag, t_, \
+ GU_MEMBER(t_, mem1_, type1_))
+
+#define GU_CONSTRUCTOR_S2(ctag, t_, mem1_, type1_, mem2_, type2_) \
+ GU_CONSTRUCTOR_S(ctag, t_, \
+ GU_MEMBER(t_, mem1_, type1_), \
+ GU_MEMBER(t_, mem2_, type2_))
+
+
+
+typedef GuSList(GuConstructor) GuConstructors;
+
+typedef const struct GuVariantType GuVariantType, GuType_GuVariant;
+
+struct GuVariantType {
+ GuType_repr repr_base;
+ GuConstructors ctors;
+};
+
+#define GU_TYPE_INIT_GuVariant(k_, t_, ...) { \
+ .repr_base = GU_TYPE_INIT_repr(k_, GuVariant, _), \
+ .ctors = GU_SLIST(GuConstructor, __VA_ARGS__) \
+}
+
+extern GU_DECLARE_KIND(GuVariant);
+
+#endif // GU_VARIANT_H_
diff --git a/src/runtime/c/gu/write.c b/src/runtime/c/gu/write.c
new file mode 100644
index 000000000..69573fb0d
--- /dev/null
+++ b/src/runtime/c/gu/write.c
@@ -0,0 +1,174 @@
+#include <gu/write.h>
+
+
+size_t
+gu_utf32_write(const GuUCS* src, size_t len, GuWriter* wtr, GuExn* err)
+{
+ return gu_utf32_out_utf8(src, len, &wtr->out_, err);
+}
+
+
+void
+gu_vprintf(const char* fmt, va_list args, GuWriter* wtr, GuExn* err)
+{
+ GuPool* tmp_pool = gu_local_pool();
+ char* str = gu_vasprintf(fmt, args, tmp_pool);
+ gu_puts(str, wtr, err);
+ gu_pool_free(tmp_pool);
+}
+
+void
+gu_printf(GuWriter* wtr, GuExn* err, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gu_vprintf(fmt, args, wtr, err);
+ va_end(args);
+}
+
+
+GuWriter*
+gu_new_utf8_writer(GuOut* utf8_out, GuPool* pool)
+{
+ GuOutStream* stream = gu_out_proxy_stream(utf8_out, pool);
+ GuWriter* wtr = gu_new(GuWriter, pool);
+ wtr->out_ = gu_init_out(stream);
+ return wtr;
+}
+
+
+#if 0
+#ifdef GU_UCS_WCHAR
+#include <stdlib.h>
+#include <wchar.h>
+static const mbstate_t gu_init_mbstate; // implicitly initialized to zero
+#endif
+
+typedef struct GuLocaleWriter GuLocaleWriter;
+
+struct GuLocaleWriter {
+ GuOutWriter owtr;
+#ifdef GU_UCS_WCHAR
+ mbstate_t ps;
+ size_t mb_cur_max;
+#endif
+};
+
+size_t
+gu_locale_writer_write(GuWriter* wtr, const uint8_t* utf8_src, size_t sz,
+ GuExn* err)
+{
+ GuLocaleWriter* lwtr = (GuLocaleWriter*) wtr;
+ size_t done = 0;
+ static const size_t bufsize = 256;
+#ifdef GU_UCS_WCHAR
+ size_t margin = lwtr->mb_cur_max;
+#else
+ size_t margin = 1;
+#endif
+ GuOut* out = lwtr->owtr.out;
+ if (gu_out_is_buffered(out)) {
+ while (done < sz) {
+ size_t dst_sz;
+ uint8_t* dst = gu_out_begin_span(out, &dst_sz);
+ if (!dst) {
+ break;
+ }
+ if (dst_sz <= margin) {
+ gu_out_end_span(out, 0);
+ break;
+ }
+ size_t end = dst_sz - margin;
+ const uint8_t*
+ size_t n = done;
+ while (n < sz && dst_i <= end) {
+#ifdef GU_UCS_WCHAR
+ GuUCS ucs = gu_
+ wchar_t wc = src[n];
+ size_t nb = wcrtomb((char*) p, wc, &lwtr->ps);
+#else
+ *p = (uint8_t) gu_ucs_char(buf[n], err);
+ size_t nb = 1;
+ if (!gu_ok(err)) {
+ gu_exn_clear(err);
+ nb = (size_t) -1;
+ }
+#endif
+ if (nb == (size_t) -1) {
+ *p++ = (uint8_t) '?';
+ } else {
+ p += nb;
+ }
+
+ }
+ for (
+
+ }
+
+
+
+ }
+
+ uint8_t cbuf[256];
+ while (done < size && gu_ok(err)) {
+ uint8_t* p = cbuf;
+ uint8_t* edge = &cbuf[bufsize - margin];
+ size_t n;
+ for (n = done; p <= edge && n < size; n++) {
+#ifdef GU_UCS_WCHAR
+ wchar_t wc = buf[n];
+ size_t nb = wcrtomb((char*) p, wc, &lwtr->ps);
+#else
+ *p = (uint8_t) gu_ucs_char(buf[n], err);
+ size_t nb = 1;
+ if (!gu_ok(err)) {
+ gu_exn_clear(err);
+ nb = (size_t) -1;
+ }
+#endif
+ if (nb == (size_t) -1) {
+ *p++ = (uint8_t) '?';
+ } else {
+ p += nb;
+ }
+ }
+ gu_out_bytes(lwtr->owtr.out, cbuf, p - cbuf, err);
+ if (gu_ok(err)) {
+ done = n;
+ }
+ }
+ return done;
+}
+
+GuWriter*
+gu_locale_writer(GuOut* out, GuPool* pool)
+{
+ GuLocaleWriter* lwtr = gu_new_s(
+ pool, GuLocaleWriter,
+ .wtr.out.output = gu_locale_writer_output,
+ .wtr.out.flush = gu_locale_writer_flush,
+ .out = out);
+#ifdef GU_UCS_WCHAR
+ lwtr->ps = gu_init_mbstate;
+ lwtr->mb_cur_max = MB_CUR_MAX;
+#endif
+ return (GuWriter*) lwtr;
+}
+
+#endif
+
+extern inline void
+gu_ucs_write(GuUCS ucs, GuWriter* wtr, GuExn* err);
+
+extern inline void
+gu_writer_flush(GuWriter* wtr, GuExn* err);
+
+extern inline void
+gu_putc(char c, GuWriter* wtr, GuExn* err);
+
+extern inline void
+gu_puts(const char* str, GuWriter* wtr, GuExn* err);
+
+extern inline size_t
+gu_utf8_write(const uint8_t* src, size_t sz, GuWriter* wtr, GuExn* err);
+
diff --git a/src/runtime/c/gu/write.h b/src/runtime/c/gu/write.h
new file mode 100644
index 000000000..414a7bc2c
--- /dev/null
+++ b/src/runtime/c/gu/write.h
@@ -0,0 +1,64 @@
+#ifndef GU_WRITE_H_
+#define GU_WRITE_H_
+
+#include <gu/exn.h>
+#include <gu/ucs.h>
+#include <gu/out.h>
+#include <gu/utf8.h>
+
+typedef struct GuWriter GuWriter;
+
+struct GuWriter {
+ GuOut out_;
+};
+
+size_t
+gu_utf32_write(const GuUCS* buf, size_t size, GuWriter* wtr, GuExn* err);
+
+inline void
+gu_writer_flush(GuWriter* wtr, GuExn* err)
+{
+ gu_out_flush(&wtr->out_, err);
+}
+
+inline void
+gu_ucs_write(GuUCS ucs, GuWriter* wtr, GuExn* err)
+{
+ gu_out_utf8(ucs, &wtr->out_, err);
+}
+
+inline void
+gu_putc(char c, GuWriter* wtr, GuExn* err)
+{
+ GuUCS ucs = gu_char_ucs(c);
+ gu_out_u8(&wtr->out_, (uint8_t) ucs, err);
+}
+
+inline void
+gu_puts(const char* str, GuWriter* wtr, GuExn* err)
+{
+ gu_str_out_utf8(str, &wtr->out_, err);
+}
+
+inline size_t
+gu_utf8_write(const uint8_t* src, size_t sz, GuWriter* wtr, GuExn* err)
+{
+ return gu_out_bytes(&wtr->out_, src, sz, err);
+}
+
+void
+gu_vprintf(const char* fmt, va_list args, GuWriter* wtr, GuExn* err);
+
+void
+gu_printf(GuWriter* wtr, GuExn* err, const char* fmt, ...);
+
+//GuWriter
+//gu_init_utf8_writer(GuOut* utf8_out);
+
+GuWriter*
+gu_new_utf8_writer(GuOut* utf8_out, GuPool* pool);
+
+GuWriter*
+gu_make_locale_writer(GuOut* locale_out, GuPool* pool);
+
+#endif // GU_WRITE_H_
diff --git a/src/runtime/c/gu/yaml.c b/src/runtime/c/gu/yaml.c
new file mode 100644
index 000000000..4d769a4aa
--- /dev/null
+++ b/src/runtime/c/gu/yaml.c
@@ -0,0 +1,339 @@
+#include <gu/yaml.h>
+#include <gu/seq.h>
+#include <gu/assert.h>
+#include <gu/read.h>
+#include <gu/ucs.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+
+const GuYamlAnchor gu_yaml_null_anchor = 0;
+
+typedef const struct GuYamlState GuYamlState;
+
+struct GuYamlState {
+ const char* prefix;
+ const char* suffix;
+ GuYamlState* next;
+};
+
+static const struct {
+ GuYamlState document, first_key, key, value, first_elem, elem;
+} gu_yaml_states = {
+ .document = {
+ .prefix = "---\n",
+ .suffix = "\n...\n",
+ .next = &gu_yaml_states.document,
+ },
+ .key = {
+ .prefix = "? ",
+ .next = &gu_yaml_states.value,
+ },
+ .value = {
+ .prefix = ": ",
+ .suffix = ",",
+ .next = &gu_yaml_states.key,
+ },
+ .elem = {
+ .suffix = ",",
+ .next = &gu_yaml_states.elem,
+ },
+};
+
+typedef const struct GuYamlFrameClass GuYamlFrameClass;
+
+struct GuYamlFrameClass {
+ const char* open;
+ GuYamlState* first;
+ const char* close;
+};
+
+static const struct {
+ GuYamlFrameClass document, mapping, sequence;
+} gu_yaml_frame_classes = {
+ .mapping = {
+ .open = "{",
+ .first = &gu_yaml_states.key,
+ .close = "}",
+ },
+ .sequence = {
+ .open = "[",
+ .first = &gu_yaml_states.elem,
+ .close = "]",
+ },
+};
+
+typedef struct GuYamlFrame GuYamlFrame;
+
+struct GuYamlFrame {
+ GuYamlFrameClass* klass;
+ GuYamlState* next;
+};
+
+typedef GuBuf GuYamlStack;
+
+struct GuYaml {
+ GuWriter* wtr;
+ GuExn* err;
+ GuPool* pool;
+ GuYamlState* state;
+ bool in_node;
+ bool have_anchor;
+ bool have_tag;
+ int next_anchor;
+ bool indent;
+ int indent_level;
+ bool indented;
+ GuYamlStack* stack;
+};
+
+
+GuYaml*
+gu_new_yaml(GuWriter* wtr, GuExn* err, GuPool* pool)
+{
+ GuYaml* yaml = gu_new(GuYaml, pool);
+ yaml->wtr = wtr;
+ yaml->pool = pool;
+ yaml->err = err;
+ yaml->state = &gu_yaml_states.document;
+ yaml->in_node = false;
+ yaml->have_anchor = false;
+ yaml->have_tag = false;
+ yaml->next_anchor = 1;
+ yaml->stack = gu_new_buf(GuYamlFrame, pool);
+ yaml->indent = true;
+ yaml->indent_level = 0;
+ yaml->indented = false;
+ return yaml;
+}
+
+static void
+gu_yaml_printf(GuYaml* yaml, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gu_vprintf(fmt, args, yaml->wtr, yaml->err);
+ va_end(args);
+}
+
+static void
+gu_yaml_putc(GuYaml* yaml, char c)
+{
+ gu_putc(c, yaml->wtr, yaml->err);
+}
+
+static void
+gu_yaml_puts(GuYaml* yaml, const char* str)
+{
+ gu_puts(str, yaml->wtr, yaml->err);
+}
+
+static void
+gu_yaml_begin_line(GuYaml* yaml)
+{
+ if (yaml->indent && !yaml->indented) {
+ for (int i = 0; i < yaml->indent_level; i++) {
+ gu_yaml_putc(yaml, ' ');
+ }
+ yaml->indented = true;
+ }
+}
+
+static void
+gu_yaml_end_line(GuYaml* yaml)
+{
+ if (yaml->indent) {
+ gu_yaml_putc(yaml, '\n');
+ }
+ yaml->indented = false;
+}
+
+
+static void
+gu_yaml_begin_node(GuYaml* yaml)
+{
+ gu_yaml_begin_line(yaml);
+ if (!yaml->in_node) {
+ if (yaml->state->prefix != NULL) {
+ gu_yaml_puts(yaml, yaml->state->prefix);
+ }
+ yaml->in_node = true;
+ }
+}
+
+static void
+gu_yaml_end_node(GuYaml* yaml)
+{
+ gu_assert(yaml->in_node);
+ if (yaml->state->suffix != NULL) {
+ gu_yaml_puts(yaml, yaml->state->suffix);
+ }
+ gu_yaml_end_line(yaml);
+ yaml->in_node = false;
+ yaml->have_anchor = false;
+ yaml->have_tag = false;
+ if (yaml->state != NULL) {
+ yaml->state = yaml->state->next;
+ }
+}
+
+static void
+gu_yaml_begin(GuYaml* yaml, GuYamlFrameClass* klass)
+{
+ gu_yaml_begin_node(yaml);
+ gu_yaml_puts(yaml, klass->open);
+ gu_buf_push(yaml->stack, GuYamlFrame,
+ ((GuYamlFrame) { .klass = klass, .next = yaml->state}));
+ yaml->state = klass->first;
+ yaml->in_node = yaml->have_anchor = yaml->have_tag = false;
+ gu_yaml_end_line(yaml);
+ yaml->indent_level++;
+}
+
+void
+gu_yaml_begin_mapping(GuYaml* yaml)
+{
+ gu_yaml_begin(yaml, &gu_yaml_frame_classes.mapping);
+}
+
+void
+gu_yaml_begin_sequence(GuYaml* yaml)
+{
+ gu_yaml_begin(yaml, &gu_yaml_frame_classes.sequence);
+}
+
+void
+gu_yaml_end(GuYaml* yaml)
+{
+ gu_assert(!yaml->in_node);
+ yaml->indent_level--;
+ gu_yaml_begin_line(yaml);
+ GuYamlFrame f = gu_buf_pop(yaml->stack, GuYamlFrame);
+ gu_yaml_puts(yaml, f.klass->close);
+ yaml->state = f.next;
+ yaml->in_node = true;
+ gu_yaml_end_node(yaml);
+}
+
+
+void
+gu_yaml_scalar(GuYaml* yaml, GuString s)
+{
+ gu_yaml_begin_node(yaml);
+ gu_yaml_putc(yaml, '"');
+ GuPool* tmp_pool = gu_local_pool();
+ GuReader* rdr = gu_string_reader(s, tmp_pool);
+ GuExn* err = gu_exn(yaml->err, GuEOF, NULL);
+
+ static const char esc[0x20] = {
+ [0x00] = '0',
+ [0x07] = 'a', 'b', 't', 'n', 'v', 'f', 'r',
+ [0x1b] = 'e'
+ };
+
+ while (true) {
+ GuUCS u = gu_read_ucs(rdr, err);
+ if (!gu_ok(err)) {
+ break;
+ }
+ if (GU_LIKELY(u >= 0x20 && u < 0x7f)) {
+ if (GU_UNLIKELY(u == 0x22 || u == 0x5c)) {
+ gu_yaml_putc(yaml, '\\');
+ }
+ gu_ucs_write(u, yaml->wtr, yaml->err);
+ } else if (GU_UNLIKELY(u < 0x20 && esc[u])) {
+ gu_yaml_printf(yaml, "\\%c", esc[u]);
+ } else if (GU_UNLIKELY(u <= 0x9f)) {
+ gu_yaml_printf(yaml, "\\x%02x", (unsigned) u);
+ } else if (GU_UNLIKELY((u >= 0xd800 && u <= 0xdfff) ||
+ (u >= 0xfffe && u <= 0xffff))) {
+ gu_yaml_printf(yaml, "\\u%04x", (unsigned) u);
+ } else {
+ gu_ucs_write(u, yaml->wtr, yaml->err);
+ }
+ }
+ gu_pool_free(tmp_pool);
+ gu_yaml_putc(yaml, '"');
+ gu_yaml_end_node(yaml);
+}
+
+static void
+gu_yaml_tag(GuYaml* yaml, const char* format, ...)
+{
+ gu_yaml_begin_node(yaml);
+ gu_assert(!yaml->have_tag);
+ gu_yaml_putc(yaml, '!');
+ va_list args;
+ va_start(args, format);
+ gu_vprintf(format, args, yaml->wtr, yaml->err);
+ va_end(args);
+ gu_yaml_putc(yaml, ' ');
+ yaml->have_tag = true;
+}
+
+void
+gu_yaml_tag_primary(GuYaml* yaml, const char* tag)
+{
+ // TODO: check tag validity
+ gu_yaml_tag(yaml, "%s", tag);
+}
+
+void
+gu_yaml_tag_secondary(GuYaml* yaml, const char* tag)
+{
+ // TODO: check tag validity
+ gu_yaml_tag(yaml, "!%s", tag);
+}
+
+void
+gu_yaml_tag_named(GuYaml* yaml, const char* handle, const char* tag)
+{
+ // TODO: check tag validity
+ gu_yaml_tag(yaml, "%s!%s", handle, tag);
+}
+
+void
+gu_yaml_tag_verbatim(GuYaml* yaml, const char* uri)
+{
+ // XXX: uri escaping?
+ gu_yaml_tag(yaml, "<%s>", uri);
+}
+
+void
+gu_yaml_tag_non_specific(GuYaml* yaml)
+{
+ gu_yaml_tag(yaml, "");
+}
+
+GuYamlAnchor
+gu_yaml_anchor(GuYaml* yaml)
+{
+ gu_yaml_begin_node(yaml);
+ gu_assert(!yaml->have_anchor);
+ yaml->have_anchor = true;
+ int anchor = yaml->next_anchor++;
+ gu_yaml_printf(yaml, "&%d ", anchor);
+ return anchor;
+}
+
+void
+gu_yaml_alias(GuYaml* yaml, GuYamlAnchor anchor)
+{
+ gu_yaml_begin_node(yaml);
+ gu_assert(!yaml->have_anchor && !yaml->have_tag);
+ gu_yaml_printf(yaml, "*%d ", anchor);
+ gu_yaml_end_node(yaml);
+ return;
+}
+
+void gu_yaml_comment(GuYaml* yaml, GuString s)
+{
+ gu_yaml_begin_line(yaml);
+ gu_yaml_puts(yaml, "# ");
+ // TODO: verify no newlines in comment
+ gu_string_write(s, yaml->wtr, yaml->err);
+ gu_yaml_puts(yaml, "\n");
+ yaml->indented = false;
+}
+
diff --git a/src/runtime/c/gu/yaml.h b/src/runtime/c/gu/yaml.h
new file mode 100644
index 000000000..5a61d0786
--- /dev/null
+++ b/src/runtime/c/gu/yaml.h
@@ -0,0 +1,38 @@
+#ifndef GU_YAML_H_
+#define GU_YAML_H_
+
+#include <gu/mem.h>
+#include <gu/write.h>
+#include <gu/string.h>
+
+typedef struct GuYaml GuYaml;
+
+typedef int GuYamlAnchor;
+
+extern const GuYamlAnchor gu_yaml_null_anchor;
+
+GuYaml* gu_new_yaml(GuWriter* wtr, GuExn* err, GuPool* pool);
+
+GuYamlAnchor gu_yaml_anchor(GuYaml* yaml);
+
+void gu_yaml_tag_primary(GuYaml* yaml, const char* tag);
+void gu_yaml_tag_secondary(GuYaml* yaml, const char* tag);
+void gu_yaml_tag_named(GuYaml* yaml, const char* handle, const char* tag);
+void gu_yaml_tag_verbatim(GuYaml* yaml, const char* uri);
+void gu_yaml_tag_non_specific(GuYaml* yaml);
+void gu_yaml_comment(GuYaml* yaml, GuString comment);
+
+
+void gu_yaml_scalar(GuYaml* yaml, GuString scalar);
+
+void gu_yaml_alias(GuYaml* yaml, GuYamlAnchor anchor);
+
+void gu_yaml_begin_document(GuYaml* yaml);
+
+void gu_yaml_begin_sequence(GuYaml* yaml);
+
+void gu_yaml_begin_mapping(GuYaml* yaml);
+
+void gu_yaml_end(GuYaml* yaml);
+
+#endif // GU_YAML_H_