diff --git a/.gitignore b/.gitignore
index 721dcf9377a334d6d4f75617ea31df6f7c2ee639..570fbf81029442ab30ef3f4c6e691fa04146efd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ Session.vim
 *.gcov
 cscope.out
 build/
+libhammer.pc
 .sconsign.dblite
 *.os
 *.pyc
diff --git a/.travis.yml b/.travis.yml
index 8973c57ee8376d774f79bd15d9ee4cf71ce485f7..b533da3191e75912e6d34f8c0bdd4a1212e684f9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -92,6 +92,8 @@ matrix:
       env: BINDINGS=cpp CC=clang
 before_install:
   - sudo apt-get update -qq
+  - sudo apt-get install lcov
+  - gem install coveralls-lcov
   - if [ "$BINDINGS" != "none" ]; then sudo apt-get install -qq swig; fi
   - if [ "$BINDINGS" == "perl" ]; then sudo add-apt-repository ppa:dns/irc -y; sudo apt-get update -qq; sudo apt-get install -qq swig=2.0.8-1irc1~12.04; fi
   - if [ "$BINDINGS" == "python" ]; then sudo apt-get install -qq python-dev; fi
@@ -99,12 +101,14 @@ before_install:
 install: true
 before_script:
   - if [ "$BINDINGS" == "php" ]; then phpenv config-add src/bindings/php/hammer.ini; fi
-script: 
-  - scons bindings=$BINDINGS test
+script:
+  - if [ "$BINDINGS" == "none" ]; then scons test --variant=debug --coverage; else scons bindings=$BINDINGS test; fi
+after_success:
+  - if [ "$BINDINGS" == "none" ]; then if [ "$CC" == "clang" ]; then llvm-cov gcov -o coverage.info build/debug/src/test_suite.gcda; else lcov --capture --directory build/debug/src --output-file coverage.info; fi; fi
+  - coveralls-lcov coverage.info
 notifications:
   irc: 
     channels: 
       - "irc.upstandinghackers.com#hammer"
     use_notice: true
     skip_join: true
-        
diff --git a/SConstruct b/SConstruct
index a8f7ce8b9d39964458dea9fd1ee1fbe3d0a4b474..bb2bb858e51ecf84db7588ba1c212bdf164e2e5d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -90,15 +90,18 @@ if GetOption("variant") == 'debug':
 else:
     env = opt
 
-if GetOption("coverage"):
-    env.Append(CFLAGS=["-fprofile-arcs", "-ftest-coverage"],
-               CXXFLAGS=["-fprofile-arcs", "-ftest-coverage"],
-               LDFLAGS=["-fprofile-arcs", "-ftest-coverage"],
-               LIBS=['gcov'])
-
 env["CC"] = os.getenv("CC") or env["CC"]
 env["CXX"] = os.getenv("CXX") or env["CXX"]
 
+if GetOption("coverage"):
+    env.Append(CFLAGS=["--coverage"],
+               CXXFLAGS=["--coverage"],
+               LDFLAGS=["--coverage"])
+    if env["CC"] == "gcc":
+        env.Append(LIBS=['gcov'])
+    else:
+        env.ParseConfig('llvm-config --ldflags')
+
 if os.getenv("CC") == "clang" or env['PLATFORM'] == 'darwin':
     env.Replace(CC="clang",
                 CXX="clang++")
diff --git a/src/SConscript b/src/SConscript
index 05ffa983674488db41cc0c6f32f642c2f43e30f8..7a1b9d495b1e7d3fe018ad4342da18eae4a4f9e6 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -7,7 +7,8 @@ dist_headers = [
     "allocator.h",
     "compiler_specifics.h",
     "glue.h",
-    "internal.h"
+    "internal.h",
+    "platform.h"
 ]
 
 parsers_headers = [
@@ -87,8 +88,9 @@ env.Install("$pkgconfigpath", "../../../libhammer.pc")
 
 testenv = env.Clone()
 testenv.ParseConfig('pkg-config --cflags --libs glib-2.0')
-testenv.Append(LIBS=['hammer'], LIBPATH=['.'])
-ctestexec = testenv.Program('test_suite', ctests + ['test_suite.c'])
+testenv.Append(LIBS=['hammer'])
+testenv.Prepend(LIBPATH=['.'])
+ctestexec = testenv.Program('test_suite', ctests + ['test_suite.c'], LINKFLAGS="--coverage" if testenv.GetOption("coverage") else None)
 ctest = Alias('testc', [ctestexec], "".join(["env LD_LIBRARY_PATH=", os.path.dirname(ctestexec[0].path), " ", ctestexec[0].path]))
 AlwaysBuild(ctest)
 testruns.append(ctest)
diff --git a/src/allocator.c b/src/allocator.c
index 258edfa5643a6123a4ca3ce640b06e89bd39a5a2..cc259e605c56573b506f39194793e804ab4bf8b6 100644
--- a/src/allocator.c
+++ b/src/allocator.c
@@ -18,6 +18,7 @@
 #include <string.h>
 #include <stdint.h>
 #include <sys/types.h>
+#include <setjmp.h>
 
 #include "hammer.h"
 #include "internal.h"
@@ -42,17 +43,24 @@ struct HArena_ {
   size_t block_size;
   size_t used;
   size_t wasted;
+
+  jmp_buf *except;
 };
 
+void* h_alloc(HAllocator* mm__, size_t size) {
+  void *p = mm__->alloc(mm__, size);
+  if(!p)
+    h_platform_errx(1, "memory allocation failed (%uB requested)\n", (unsigned int)size);
+  return p;
+}
+
 HArena *h_new_arena(HAllocator* mm__, size_t block_size) {
   if (block_size == 0)
     block_size = 4096;
   struct HArena_ *ret = h_new(struct HArena_, 1);
-  struct arena_link *link = (struct arena_link*)mm__->alloc(mm__, sizeof(struct arena_link) + block_size);
-  if (!link) {
-    // TODO: error-reporting -- let user know that arena link couldn't be allocated
-    return NULL;
-  }
+  struct arena_link *link = (struct arena_link*)h_alloc(mm__, sizeof(struct arena_link) + block_size);
+  assert(ret != NULL);
+  assert(link != NULL);
   memset(link, 0, sizeof(struct arena_link) + block_size);
   link->free = block_size;
   link->used = 0;
@@ -62,9 +70,26 @@ HArena *h_new_arena(HAllocator* mm__, size_t block_size) {
   ret->used = 0;
   ret->mm__ = mm__;
   ret->wasted = sizeof(struct arena_link) + sizeof(struct HArena_) + block_size;
+  ret->except = NULL;
   return ret;
 }
 
+void h_arena_set_except(HArena *arena, jmp_buf *except)
+{
+  arena->except = except;
+}
+
+static void *alloc_block(HArena *arena, size_t size)
+{
+  void *block = arena->mm__->alloc(arena->mm__, size);
+  if (!block) {
+    if (arena->except)
+      longjmp(*arena->except, 1);
+    h_platform_errx(1, "memory allocation failed (%uB requested)\n", (unsigned int)size);
+  }
+  return block;
+}
+
 void* h_arena_malloc(HArena *arena, size_t size) {
   if (size <= arena->head->free) {
     // fast path..
@@ -79,22 +104,16 @@ void* h_arena_malloc(HArena *arena, size_t size) {
     // This involves some annoying casting...
     arena->used += size;
     arena->wasted += sizeof(struct arena_link*);
-    void* link = arena->mm__->alloc(arena->mm__, size + sizeof(struct arena_link*));
-    if (!link) {
-      // TODO: error-reporting -- let user know that arena link couldn't be allocated
-      return NULL;
-    }
+    void* link = alloc_block(arena, size + sizeof(struct arena_link*));
+    assert(link != NULL);
     memset(link, 0, size + sizeof(struct arena_link*));
     *(struct arena_link**)link = arena->head->next;
     arena->head->next = (struct arena_link*)link;
     return (void*)(((uint8_t*)link) + sizeof(struct arena_link*));
   } else {
     // we just need to allocate an ordinary new block.
-    struct arena_link *link = (struct arena_link*)arena->mm__->alloc(arena->mm__, sizeof(struct arena_link) + arena->block_size);
-    if (!link) {
-      // TODO: error-reporting -- let user know that arena link couldn't be allocated
-      return NULL;
-    }
+    struct arena_link *link = alloc_block(arena, sizeof(struct arena_link) + arena->block_size);
+    assert(link != NULL);
     memset(link, 0, sizeof(struct arena_link) + arena->block_size);
     link->free = arena->block_size - size;
     link->used = size;
diff --git a/src/allocator.h b/src/allocator.h
index 4a486936a058c0a619a83e7afdf0c5dfffc50d48..dc88af68f22895f584065a491463b3f8576c09e9 100644
--- a/src/allocator.h
+++ b/src/allocator.h
@@ -18,22 +18,12 @@
 #ifndef HAMMER_ALLOCATOR__H__
 #define HAMMER_ALLOCATOR__H__
 #include <sys/types.h>
+#include <setjmp.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-// TODO(thequux): Turn this into an "HAllocatorVtable", and add a wrapper that also takes an environment pointer.
-typedef struct HAllocator_ {
-  void* (*alloc)(struct HAllocator_* allocator, size_t size);
-  void* (*realloc)(struct HAllocator_* allocator, void* ptr, size_t size);
-  void (*free)(struct HAllocator_* allocator, void* ptr);
-} HAllocator;
-
-typedef struct HArena_ HArena ; // hidden implementation
-
-HArena *h_new_arena(HAllocator* allocator, size_t block_size); // pass 0 for default...
-
 #if defined __llvm__
 # if __has_attribute(malloc)
 #   define ATTR_MALLOC(n) __attribute__((malloc))
@@ -48,9 +38,23 @@ HArena *h_new_arena(HAllocator* allocator, size_t block_size); // pass 0 for def
 # define ATTR_MALLOC(n)
 #endif
 
+// TODO(thequux): Turn this into an "HAllocatorVtable", and add a wrapper that also takes an environment pointer.
+typedef struct HAllocator_ {
+  void* (*alloc)(struct HAllocator_* allocator, size_t size);
+  void* (*realloc)(struct HAllocator_* allocator, void* ptr, size_t size);
+  void (*free)(struct HAllocator_* allocator, void* ptr);
+} HAllocator;
+
+void* h_alloc(HAllocator* allocator, size_t size) ATTR_MALLOC(2);
+
+typedef struct HArena_ HArena ; // hidden implementation
+
+HArena *h_new_arena(HAllocator* allocator, size_t block_size); // pass 0 for default...
+
 void* h_arena_malloc(HArena *arena, size_t count) ATTR_MALLOC(2);
 void h_arena_free(HArena *arena, void* ptr); // For future expansion, with alternate memory managers.
 void h_delete_arena(HArena *arena);
+void h_arena_set_except(HArena *arena, jmp_buf *except);
 
 typedef struct {
   size_t used;
diff --git a/src/backends/glr.c b/src/backends/glr.c
index e753ea55d938cc07582ceed83db92354cbacf68f..535dc2860c59018324893da1450cfc4ff4fadf8b 100644
--- a/src/backends/glr.c
+++ b/src/backends/glr.c
@@ -198,6 +198,16 @@ HParseResult *h_glr_parse(HAllocator* mm__, const HParser* parser, HInputStream*
   HArena *arena  = h_new_arena(mm__, 0);    // will hold the results
   HArena *tarena = h_new_arena(mm__, 0);    // tmp, deleted after parse
 
+  // out-of-memory handling
+  jmp_buf except;
+  h_arena_set_except(arena, &except);
+  h_arena_set_except(tarena, &except);
+  if(setjmp(except)) {
+    h_delete_arena(arena);
+    h_delete_arena(tarena);
+    return NULL;
+  }
+
   // allocate engine lists (will hold one engine per state)
   // these are swapped each iteration
   HSlist *engines = h_slist_new(tarena);
diff --git a/src/backends/llk.c b/src/backends/llk.c
index 0ab4610a29a1fcdefd1ca163ea2be8785b3ed0e6..4e8209b30f4aa7bd97f5df1c49202643d4efedd4 100644
--- a/src/backends/llk.c
+++ b/src/backends/llk.c
@@ -383,12 +383,20 @@ static HCountedArray *llk_parse_chunk_(HLLkState *s, const HParser* parser,
   HArena *arena = s->arena;
   HArena *tarena = s->tarena;
   HSlist *stack = s->stack;
-  HCountedArray *seq = s->seq;
   size_t kmax = table->kmax;
 
-  if(!seq)
+  if(!s->seq)
     return NULL;  // parse already failed
 
+  // out-of-memory handling
+  jmp_buf except;
+  h_arena_set_except(arena, &except);
+  h_arena_set_except(tarena, &except);
+  if(setjmp(except))
+    goto no_parse;
+
+  HCountedArray *seq = s->seq;
+
   if(s->win.length > 0) {
     append_win(kmax, s, chunk);
     stream = &s->win;
@@ -527,12 +535,15 @@ static HCountedArray *llk_parse_chunk_(HLLkState *s, const HParser* parser,
   // since we started with a single nonterminal on the stack, seq should
   // contain exactly the parse result.
   assert(seq->used == 1);
+
+ end:
+  h_arena_set_except(arena, NULL);
+  h_arena_set_except(tarena, NULL);
   return seq;
 
  no_parse:
-  h_delete_arena(arena);
-  s->arena = NULL;
-  return NULL;
+  seq = NULL;
+  goto end;
 
  need_input:
   if(stream->last_chunk)
@@ -540,7 +551,7 @@ static HCountedArray *llk_parse_chunk_(HLLkState *s, const HParser* parser,
   if(tok)
     h_arena_free(arena, tok);   // no result, yet
   h_slist_push(stack, x);       // try this symbol again next time
-  return seq;
+  goto end;
 }
 
 static HParseResult *llk_parse_finish_(HAllocator *mm__, HLLkState *s)
@@ -550,6 +561,8 @@ static HParseResult *llk_parse_finish_(HAllocator *mm__, HLLkState *s)
   if(s->seq) {
     assert(s->seq->used == 1);
     res = make_result(s->arena, s->seq->elements[0]);
+  } else {
+    h_delete_arena(s->arena);
   }
 
   h_delete_arena(s->tarena);
@@ -582,6 +595,9 @@ bool h_llk_parse_chunk(HSuspendedParser *s, HInputStream *input)
 
   state->seq = llk_parse_chunk_(state, s->parser, input);
 
+  h_arena_set_except(state->arena, NULL);
+  h_arena_set_except(state->tarena, NULL);
+
   return (state->seq == NULL || h_slist_empty(state->stack));
 }
 
diff --git a/src/backends/lr.c b/src/backends/lr.c
index fb256c0bfafa0b6c53b32307bea64f61d4885919..f2ac4956d80358e51d35c0e70484013bbfde212a 100644
--- a/src/backends/lr.c
+++ b/src/backends/lr.c
@@ -388,6 +388,16 @@ HParseResult *h_lr_parse(HAllocator* mm__, const HParser* parser, HInputStream*
   HArena *tarena = h_new_arena(mm__, 0);    // tmp, deleted after parse
   HLREngine *engine = h_lrengine_new(arena, tarena, table, stream);
 
+  // out-of-memory handling
+  jmp_buf except;
+  h_arena_set_except(arena, &except);
+  h_arena_set_except(tarena, &except);
+  if(setjmp(except)) {
+    h_delete_arena(arena);
+    h_delete_arena(tarena);
+    return NULL;
+  }
+
   // iterate engine to completion
   while(h_lrengine_step(engine, h_lrengine_action(engine)));
 
@@ -416,6 +426,16 @@ bool h_lr_parse_chunk(HSuspendedParser* s, HInputStream *stream)
   engine->input = *stream;
 
   bool run = true;
+
+  // out-of-memory handling
+  jmp_buf except;
+  h_arena_set_except(engine->arena, &except);
+  h_arena_set_except(engine->tarena, &except);
+  if(setjmp(except)) {
+    run = false;                            // done immediately
+    assert(engine->state != HLR_SUCCESS);   // h_parse_finish will return NULL
+  }
+
   while(run) {
     // check input against table to determine which action to take
     const HLRAction *action = h_lrengine_action(engine);
@@ -431,6 +451,9 @@ bool h_lr_parse_chunk(HSuspendedParser* s, HInputStream *stream)
       break;
   }
 
+  h_arena_set_except(engine->arena, NULL);
+  h_arena_set_except(engine->tarena, NULL);
+
   *stream = engine->input;
   return !run;  // done if engine no longer running
 }
diff --git a/src/backends/packrat.c b/src/backends/packrat.c
index e6f86f2957e5988dd94557534ab66b6050fda915..b7e47aef07422a1520849d94e5420b56e6112d79 100644
--- a/src/backends/packrat.c
+++ b/src/backends/packrat.c
@@ -3,7 +3,7 @@
 #include "../internal.h"
 #include "../parsers/parser_internal.h"
 
-// short-hand for creating cache values (regular case)
+// short-hand for creating lowlevel parse cache values (parse result case)
 static
 HParserCacheValue * cached_result(HParseState *state, HParseResult *result) {
   HParserCacheValue *ret = a_new(HParserCacheValue, 1);
@@ -13,7 +13,7 @@ HParserCacheValue * cached_result(HParseState *state, HParseResult *result) {
   return ret;
 }
 
-// short-hand for caching parse results (left recursion case)
+// short-hand for creating lowlevel parse cache values (left recursion case)
 static
 HParserCacheValue *cached_lr(HParseState *state, HLeftRec *lr) {
   HParserCacheValue *ret = a_new(HParserCacheValue, 1);
@@ -181,23 +181,31 @@ HParseResult* lr_answer(HParserCacheKey *k, HParseState *state, HLeftRec *growab
 HParseResult* h_do_parse(const HParser* parser, HParseState *state) {
   HParserCacheKey *key = a_new(HParserCacheKey, 1);
   key->input_pos = state->input_stream; key->parser = parser;
-  HParserCacheValue *m = recall(key, state);
+  HParserCacheValue *m = NULL;
+  if (parser->vtable->higher) {
+    m = recall(key, state);
+  }
   // check to see if there is already a result for this object...
   if (!m) {
     // It doesn't exist, so create a dummy result to cache
     HLeftRec *base = a_new(HLeftRec, 1);
-    base->seed = NULL; base->rule = parser; base->head = NULL;
-    h_slist_push(state->lr_stack, base);
-    // cache it
-    h_hashtable_put(state->cache, key, cached_lr(state, base));
-    // parse the input
+    // But only cache it now if there's some chance it could grow; primitive parsers can't
+    if (parser->vtable->higher) {
+      base->seed = NULL; base->rule = parser; base->head = NULL;
+      h_slist_push(state->lr_stack, base);
+      // cache it
+      h_hashtable_put(state->cache, key, cached_lr(state, base));
+      // parse the input
+    }
     HParseResult *tmp_res = perform_lowlevel_parse(state, parser);
-    // the base variable has passed equality tests with the cache
-    h_slist_pop(state->lr_stack);
-    // update the cached value to our new position
-    HParserCacheValue *cached = h_hashtable_get(state->cache, key);
-    assert(cached != NULL);
-    cached->input_stream = state->input_stream;
+    if (parser->vtable->higher) {
+      // the base variable has passed equality tests with the cache
+      h_slist_pop(state->lr_stack);
+      // update the cached value to our new position
+      HParserCacheValue *cached = h_hashtable_get(state->cache, key);
+      assert(cached != NULL);
+      cached->input_stream = state->input_stream;
+    }
     // setupLR, used below, mutates the LR to have a head if appropriate, so we check to see if we have one
     if (NULL == base->head) {
       h_hashtable_put(state->cache, key, cached_result(state, tmp_res));
@@ -246,6 +254,15 @@ static bool pos_equal(const void* key1, const void* key2) {
 
 HParseResult *h_packrat_parse(HAllocator* mm__, const HParser* parser, HInputStream *input_stream) {
   HArena * arena = h_new_arena(mm__, 0);
+
+  // out-of-memory handling
+  jmp_buf except;
+  h_arena_set_except(arena, &except);
+  if(setjmp(except)) {
+    h_delete_arena(arena);
+    return NULL;
+  }
+
   HParseState *parse_state = a_new_(arena, HParseState, 1);
   parse_state->cache = h_hashtable_new(arena, cache_key_equal, // key_equal_func
 				       cache_key_hash); // hash_func
diff --git a/src/backends/regex.c b/src/backends/regex.c
index f6494fa98afea084ab347511ec0f25dc0e11379c..f7dd98add85844366cca53aa96be31c596b4221c 100644
--- a/src/backends/regex.c
+++ b/src/backends/regex.c
@@ -52,11 +52,21 @@ HRVMTrace *invert_trace(HRVMTrace *trace) {
 
 void* h_rvm_run__m(HAllocator *mm__, HRVMProg *prog, const uint8_t* input, size_t len) {
   HArena *arena = h_new_arena(mm__, 0);
-  HSArray *heads_n = h_sarray_new(mm__, prog->length), // Both of these contain HRVMTrace*'s
-    *heads_p = h_sarray_new(mm__, prog->length);
+  HSArray *heads_a = h_sarray_new(mm__, prog->length), // Both of these contain HRVMTrace*'s
+          *heads_b = h_sarray_new(mm__, prog->length);
 
   HRVMTrace *ret_trace = NULL;
+  HParseResult *ret = NULL;
   
+  // out of memory handling
+  if(!arena || !heads_a || !heads_b)
+    goto end;
+  jmp_buf except;
+  h_arena_set_except(arena, &except);
+  if(setjmp(except))
+    goto end;
+
+  HSArray *heads_n = heads_a, *heads_p = heads_b;
   uint8_t *insn_seen = a_new(uint8_t, prog->length); // 0 -> not seen, 1->processed, 2->queued
   HRVMThread *ip_queue = a_new(HRVMThread, prog->length);
   size_t ipq_top;
@@ -164,18 +174,19 @@ void* h_rvm_run__m(HAllocator *mm__, HRVMProg *prog, const uint8_t* input, size_
   }
   // No accept was reached.
  match_fail:
-  if (ret_trace == NULL) {
-    // No match found; definite failure.
-    h_delete_arena(arena);
-    return NULL;
+
+  h_arena_set_except(arena, NULL);  // there should be no more allocs from this
+  if (ret_trace) {
+    // Invert the direction of the trace linked list.
+    ret_trace = invert_trace(ret_trace);
+    ret = run_trace(mm__, prog, ret_trace, input, len);
+    // NB: ret is in its own arena
   }
   
-  // Invert the direction of the trace linked list.
-
-  ret_trace = invert_trace(ret_trace);
-  HParseResult *ret = run_trace(mm__, prog, ret_trace, input, len);
-  // ret is in its own arena
-  h_delete_arena(arena);
+ end:
+  if (arena)   h_delete_arena(arena);
+  if (heads_a) h_sarray_free(heads_a);
+  if (heads_b) h_sarray_free(heads_b);
   return ret;
 }
 #undef PUSH_SVM
@@ -203,6 +214,14 @@ HParseResult *run_trace(HAllocator *mm__, HRVMProg *orig_prog, HRVMTrace *trace,
   ctx.stack_capacity = 16;
   ctx.stack = h_new(HParsedToken*, ctx.stack_capacity);
 
+  // out of memory handling
+  if(!arena || !ctx.stack)
+    goto fail;
+  jmp_buf except;
+  h_arena_set_except(arena, &except);
+  if(setjmp(except))
+    goto fail;
+
   HParsedToken *tmp_res;
   HRVMTrace *cur;
   for (cur = trace; cur; cur = cur->next) {
@@ -242,7 +261,7 @@ HParseResult *run_trace(HAllocator *mm__, HRVMProg *orig_prog, HRVMTrace *trace,
       break;
     case SVM_ACCEPT:
       assert(ctx.stack_count <= 1);
-	HParseResult *res = a_new(HParseResult, 1);
+      HParseResult *res = a_new(HParseResult, 1);
       if (ctx.stack_count == 1) {
 	res->ast = ctx.stack[0];
       } else {
@@ -250,11 +269,14 @@ HParseResult *run_trace(HAllocator *mm__, HRVMProg *orig_prog, HRVMTrace *trace,
       }
       res->bit_length = cur->input_pos * 8;
       res->arena = arena;
+      h_arena_set_except(arena, NULL);
+      h_free(ctx.stack);
       return res;
     }
   }
  fail:
-  h_delete_arena(arena);
+  if (arena) h_delete_arena(arena);
+  if (ctx.stack) h_free(ctx.stack);
   return NULL;
 }
 
diff --git a/src/bitwriter.c b/src/bitwriter.c
index 74e273448aded2ae40ec99666f230447054c0dd0..bcc21dd308a743a7dd98a6bc52364f75f2d75328 100644
--- a/src/bitwriter.c
+++ b/src/bitwriter.c
@@ -12,10 +12,8 @@
 HBitWriter *h_bit_writer_new(HAllocator* mm__) {
   HBitWriter *writer = h_new(HBitWriter, 1);
   memset(writer, 0, sizeof(*writer));
-  writer->buf = mm__->alloc(mm__, writer->capacity = 8);
-  if (!writer) {
-    return NULL;
-  }
+  writer->buf = h_alloc(mm__, writer->capacity = 8);
+  assert(writer != NULL);
   memset(writer->buf, 0, writer->capacity);
   writer->mm__ = mm__;
   writer->flags = BYTE_BIG_ENDIAN | BIT_BIG_ENDIAN;
diff --git a/src/datastructures.c b/src/datastructures.c
index 0feeb2176b0422471f65a053a7ef9a716af1821c..af8477be36123a53506871827d4dae13d8c9002b 100644
--- a/src/datastructures.c
+++ b/src/datastructures.c
@@ -25,11 +25,12 @@ HCountedArray *h_carray_new(HArena * arena) {
 
 void h_carray_append(HCountedArray *array, void* item) {
   if (array->used >= array->capacity) {
-    HParsedToken **elements = h_arena_malloc(array->arena, (array->capacity *= 2) * sizeof(HCountedArray*));
+    HParsedToken **elements = h_arena_malloc(array->arena, (array->capacity *= 2) * sizeof(void*));
     for (size_t i = 0; i < array->used; i++)
       elements[i] = array->elements[i];
     for (size_t i = array->used; i < array->capacity; i++)
       elements[i] = 0;
+    h_arena_free(array->arena, array->elements);
     array->elements = elements;
   }
   array->elements[array->used++] = item;
diff --git a/src/internal.h b/src/internal.h
index b35b7d5a3a22cb19bcfbcb341078070bda2bc93b..10db4b20f429283bbf0aa99928487b2754379d91 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -49,7 +49,7 @@
   rtype_t name##__m(HAllocator* mm__)
 // Functions with arguments are difficult to forward cleanly. Alas, we will need to forward them manually.
 
-#define h_new(type, count) ((type*)(mm__->alloc(mm__, sizeof(type)*(count))))
+#define h_new(type, count) ((type*)(h_alloc(mm__, sizeof(type)*(count))))
 #define h_free(addr) (mm__->free(mm__, (addr)))
 
 #ifndef __cplusplus
@@ -278,9 +278,9 @@ typedef struct HRecursionHead_ {
 /* A left recursion.
  *
  * Members:
- *   seed -
- *   rule -
- *   head -
+ *   seed - the HResult yielded by rule
+ *   rule - the HParser that produces seed
+ *   head - the 
  */
 typedef struct HLeftRec_ {
   HParseResult *seed;
@@ -419,6 +419,7 @@ struct HParserVtable_ {
   bool (*isValidCF)(void *env);
   bool (*compile_to_rvm)(HRVMProg *prog, void* env); // FIXME: forgot what the bool return value was supposed to mean.
   void (*desugar)(HAllocator *mm__, HCFStack *stk__, void *env);
+  bool higher; // false if primitive
 };
 
 bool h_false(void*);
diff --git a/src/parsers/action.c b/src/parsers/action.c
index 04eb7a4c85c71f8ea3bc60b3371f052cc43d7603..a32433348c14c4d88b304e0b298e35f06f2dbd2e 100644
--- a/src/parsers/action.c
+++ b/src/parsers/action.c
@@ -81,6 +81,7 @@ static const HParserVtable action_vt = {
   .isValidCF = action_isValidCF,
   .desugar = desugar_action,
   .compile_to_rvm = action_ctrvm,
+  .higher = true,
 };
 
 HParser* h_action(const HParser* p, const HAction a, void* user_data) {
diff --git a/src/parsers/and.c b/src/parsers/and.c
index c5c9836db57cc8864f785870a613e2ceb406b28c..e07bc9fcd36c91005d01eeef8d554a8202dce9ae 100644
--- a/src/parsers/and.c
+++ b/src/parsers/and.c
@@ -17,6 +17,7 @@ static const HParserVtable and_vt = {
 				revision. --mlp, 18/12/12 */
   .isValidCF = h_false,      /* despite TODO above, this remains false. */
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 
diff --git a/src/parsers/attr_bool.c b/src/parsers/attr_bool.c
index e8359ab03e06e681061dd75788d6ca6bb6e9b89b..f766774026074d49c9609891638eb33575211918 100644
--- a/src/parsers/attr_bool.c
+++ b/src/parsers/attr_bool.c
@@ -79,6 +79,7 @@ static const HParserVtable attr_bool_vt = {
   .isValidCF = ab_isValidCF,
   .desugar = desugar_ab,
   .compile_to_rvm = ab_ctrvm,
+  .higher = true,
 };
 
 
diff --git a/src/parsers/bind.c b/src/parsers/bind.c
index e301e746ddbfcc1fa4b14e6873def81191207a6b..87f42330ee8a5a35ef5a026f1060e5937bdc2d16 100644
--- a/src/parsers/bind.c
+++ b/src/parsers/bind.c
@@ -4,7 +4,6 @@ typedef struct {
     const HParser *p;
     HContinuation k;
     void *env;
-    HAllocator *mm__;
 } BindEnv;
 
 // an HAllocator backed by an HArena
@@ -39,13 +38,11 @@ static HParseResult *parse_bind(void *be_, HParseState *state) {
     if(!res)
         return NULL;
 
-    // create a temporary arena allocator for the continuation
-    HArena *arena = h_new_arena(be->mm__, 0);
-    ArenaAllocator aa = {{aa_alloc, aa_realloc, aa_free}, arena};
+    // create a wrapper arena allocator for the continuation
+    ArenaAllocator aa = {{aa_alloc, aa_realloc, aa_free}, state->arena};
 
     HParser *kx = be->k((HAllocator *)&aa, res->ast, be->env);
     if(!kx) {
-        h_delete_arena(arena);
         return NULL;
     }
 
@@ -53,7 +50,6 @@ static HParseResult *parse_bind(void *be_, HParseState *state) {
     if(res2)
         res2->bit_length = 0;   // recalculate
 
-    h_delete_arena(arena);
     return res2;
 }
 
@@ -62,6 +58,7 @@ static const HParserVtable bind_vt = {
     .isValidRegular = h_false,
     .isValidCF = h_false,
     .compile_to_rvm = h_not_regular,
+    .higher = true,
 };
 
 HParser *h_bind(const HParser *p, HContinuation k, void *env)
@@ -77,7 +74,6 @@ HParser *h_bind__m(HAllocator *mm__,
     be->p = p;
     be->k = k;
     be->env = env;
-    be->mm__ = mm__;
 
     return h_new_parser(mm__, &bind_vt, be);
 }
diff --git a/src/parsers/bits.c b/src/parsers/bits.c
index 716524ce61a0a35cb0f9581646f72a0efa491c7f..be8f13f10a65f67e50d134c5f3557a1a7a209d62 100644
--- a/src/parsers/bits.c
+++ b/src/parsers/bits.c
@@ -102,6 +102,7 @@ static const HParserVtable bits_vt = {
   .isValidCF = h_true,
   .desugar = desugar_bits,
   .compile_to_rvm = bits_ctrvm,
+  .higher = false,
 };
 
 HParser* h_bits(size_t len, bool sign) {
diff --git a/src/parsers/butnot.c b/src/parsers/butnot.c
index f114a1fa5dbff8cdbee6bdf22670c271c2044e2e..24ece4bec6f7f80b0905401be6e72b10f73769f8 100644
--- a/src/parsers/butnot.c
+++ b/src/parsers/butnot.c
@@ -40,6 +40,7 @@ static const HParserVtable butnot_vt = {
   .isValidRegular = h_false,
   .isValidCF = h_false, // XXX should this be true if both p1 and p2 are CF?
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 HParser* h_butnot(const HParser* p1, const HParser* p2) {
diff --git a/src/parsers/ch.c b/src/parsers/ch.c
index b4386cff2be1e95158776323d50ff76b00f2afd5..3da1091a4b71505aebdc6ed5b396084d12b1fde4 100644
--- a/src/parsers/ch.c
+++ b/src/parsers/ch.c
@@ -47,6 +47,7 @@ static const HParserVtable ch_vt = {
   .isValidCF = h_true,
   .desugar = desugar_ch,
   .compile_to_rvm = ch_ctrvm,
+  .higher = false,
 };
 
 HParser* h_ch(const uint8_t c) {
diff --git a/src/parsers/charset.c b/src/parsers/charset.c
index e1a910f8df149a16cb74fd7c661c5490e6d80198..a4b8c89c7daca326cf77ee9bf5c8ae4660884c56 100644
--- a/src/parsers/charset.c
+++ b/src/parsers/charset.c
@@ -76,6 +76,7 @@ static const HParserVtable charset_vt = {
   .isValidCF = h_true,
   .desugar = desugar_charset,
   .compile_to_rvm = cs_ctrvm,
+  .higher = false,
 };
 
 HParser* h_ch_range(const uint8_t lower, const uint8_t upper) {
diff --git a/src/parsers/choice.c b/src/parsers/choice.c
index bfc3f904f19a6d88ddc0bd77702b2c45f89c2b0f..dd3908ce93168f468ba6e1ff531a59476e404411 100644
--- a/src/parsers/choice.c
+++ b/src/parsers/choice.c
@@ -75,6 +75,7 @@ static const HParserVtable choice_vt = {
   .isValidCF = choice_isValidCF,
   .desugar = desugar_choice,
   .compile_to_rvm = choice_ctrvm,
+  .higher = true,
 };
 
 HParser* h_choice(HParser* p, ...) {
diff --git a/src/parsers/difference.c b/src/parsers/difference.c
index 76a2cc447002da5a0e04119c016f7bf83fec443e..a24f5acf378c6b801677364f7a5902ae49ec60f1 100644
--- a/src/parsers/difference.c
+++ b/src/parsers/difference.c
@@ -39,6 +39,7 @@ static HParserVtable difference_vt = {
   .isValidRegular = h_false,
   .isValidCF = h_false, // XXX should this be true if both p1 and p2 are CF?
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 HParser* h_difference(const HParser* p1, const HParser* p2) {
diff --git a/src/parsers/end.c b/src/parsers/end.c
index 30b3ba121a859b87399a59dc04dc86f3a6104a88..85499d9348cd1df6503428a55d7a2ab878d1ef63 100644
--- a/src/parsers/end.c
+++ b/src/parsers/end.c
@@ -25,6 +25,7 @@ static const HParserVtable end_vt = {
   .isValidCF = h_true,
   .desugar = desugar_end,
   .compile_to_rvm = end_ctrvm,
+  .higher = false,
 };
 
 HParser* h_end_p() {
diff --git a/src/parsers/endianness.c b/src/parsers/endianness.c
index e3f53ab8225a75bde08ff7e3dd456822e1234b86..cb3abc3d3d2bf84dfe465aaec7833badbec2b5f6 100644
--- a/src/parsers/endianness.c
+++ b/src/parsers/endianness.c
@@ -46,6 +46,7 @@ static const HParserVtable endianness_vt = {
     .isValidCF = h_false,
     .desugar = NULL,
     .compile_to_rvm = h_not_regular,
+    .higher = true,
 };
 
 HParser* h_with_endianness(char endianness, const HParser *p)
diff --git a/src/parsers/epsilon.c b/src/parsers/epsilon.c
index e8ef525ff79d523ab45c6357cfb852a6c3b4dd96..bb6e8beb31cca3ff09a565171b4e554e07f2ffad 100644
--- a/src/parsers/epsilon.c
+++ b/src/parsers/epsilon.c
@@ -18,6 +18,7 @@ static const HParserVtable epsilon_vt = {
   .isValidCF = h_true,
   .desugar = desugar_epsilon,
   .compile_to_rvm = epsilon_ctrvm,
+  .higher = false,
 };
 
 HParser* h_epsilon_p() {
diff --git a/src/parsers/ignore.c b/src/parsers/ignore.c
index af606b0ea7567cf4e5068260386d907f10e0c8a7..c56802ac0885fc11429925f353a516d622b88a9d 100644
--- a/src/parsers/ignore.c
+++ b/src/parsers/ignore.c
@@ -49,6 +49,7 @@ static const HParserVtable ignore_vt = {
   .isValidCF = ignore_isValidCF,
   .desugar = desugar_ignore,
   .compile_to_rvm = ignore_ctrvm,
+  .higher = true,
 };
 
 HParser* h_ignore(const HParser* p) {
diff --git a/src/parsers/ignoreseq.c b/src/parsers/ignoreseq.c
index 1e078d84850278582dbdb0280135b9dd6bdffdcd..af74bccd08d901cc5862b105e7251d491e64f2a0 100644
--- a/src/parsers/ignoreseq.c
+++ b/src/parsers/ignoreseq.c
@@ -105,6 +105,7 @@ static const HParserVtable ignoreseq_vt = {
   .isValidCF = is_isValidCF,
   .desugar = desugar_ignoreseq,
   .compile_to_rvm = is_ctrvm,
+  .higher = true,
 };
 
 
diff --git a/src/parsers/indirect.c b/src/parsers/indirect.c
index 026286d3eb3d56be961050fc1467ccae1fdc8516..b36cb947921497f89cd35687f9259052e23a343e 100644
--- a/src/parsers/indirect.c
+++ b/src/parsers/indirect.c
@@ -31,6 +31,7 @@ static const HParserVtable indirect_vt = {
   .isValidCF = indirect_isValidCF,
   .desugar = desugar_indirect,
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 void h_bind_indirect__m(HAllocator *mm__, HParser* indirect, const HParser* inner) {
diff --git a/src/parsers/int_range.c b/src/parsers/int_range.c
index 2937993034c9b18a98f8aeeda7a8eaa6014bdd99..49b064218b507ca13d90dcc2ec654c8ae3a9ee19 100644
--- a/src/parsers/int_range.c
+++ b/src/parsers/int_range.c
@@ -117,6 +117,7 @@ static const HParserVtable int_range_vt = {
   .isValidCF = h_true,
   .desugar = desugar_int_range,
   .compile_to_rvm = ir_ctrvm,
+  .higher = false,
 };
 
 HParser* h_int_range(const HParser *p, const int64_t lower, const int64_t upper) {
diff --git a/src/parsers/many.c b/src/parsers/many.c
index cae2b0eade03450cae13f48e8f53c37db4237721..071e3fcd2d30ed35f4622962751ebc63bea3d37c 100644
--- a/src/parsers/many.c
+++ b/src/parsers/many.c
@@ -10,7 +10,10 @@ typedef struct {
 
 static HParseResult *parse_many(void* env, HParseState *state) {
   HRepeat *env_ = (HRepeat*) env;
-  HCountedArray *seq = h_carray_new_sized(state->arena, (env_->count > 0 ? env_->count : 4));
+  size_t size = env_->count;
+  if(size <= 0) size = 4;
+  if(size > 1024) size = 1024;  // let's try parsing some elements first...
+  HCountedArray *seq = h_carray_new_sized(state->arena, size);
   size_t count = 0;
   HInputStream bak;
   while (env_->min_p || env_->count > count) {
@@ -199,6 +202,7 @@ static const HParserVtable many_vt = {
   .isValidCF = many_isValidCF,
   .desugar = desugar_many,
   .compile_to_rvm = many_ctrvm,
+  .higher = true,
 };
 
 HParser* h_many(const HParser* p) {
diff --git a/src/parsers/not.c b/src/parsers/not.c
index 6c34bad48dc09ca2a290ce351b89e921422da265..8c2003dec77b946c50db3d0f62b7117a8ff12f69 100644
--- a/src/parsers/not.c
+++ b/src/parsers/not.c
@@ -15,6 +15,7 @@ static const HParserVtable not_vt = {
   .isValidRegular = h_false,  /* see and.c for why */
   .isValidCF = h_false,
   .compile_to_rvm = h_not_regular, // Is actually regular, but the generation step is currently unable to handle it. TODO: fix this.
+  .higher = true,
 };
 
 HParser* h_not(const HParser* p) {
diff --git a/src/parsers/nothing.c b/src/parsers/nothing.c
index 120c1e01d0824ab5a70e39f96c2c19657ea0bf18..0a60108bcc2c0fe69a656fb1cfb4f067ff290922 100644
--- a/src/parsers/nothing.c
+++ b/src/parsers/nothing.c
@@ -22,6 +22,7 @@ static const HParserVtable nothing_vt = {
   .isValidCF = h_true,
   .desugar = desugar_nothing,
   .compile_to_rvm = nothing_ctrvm,
+  .higher = false,
 };
 
 HParser* h_nothing_p() {
diff --git a/src/parsers/optional.c b/src/parsers/optional.c
index ccee53fa864469600db64bb76562ce469559d09e..726606643056b103f9481cb882dadc19417dd607 100644
--- a/src/parsers/optional.c
+++ b/src/parsers/optional.c
@@ -84,6 +84,7 @@ static const HParserVtable optional_vt = {
   .isValidCF = opt_isValidCF,
   .desugar = desugar_optional,
   .compile_to_rvm = opt_ctrvm,
+  .higher = true,
 };
 
 HParser* h_optional(const HParser* p) {
diff --git a/src/parsers/permutation.c b/src/parsers/permutation.c
index 564565af555a0059a8a85773a86f2ae9a320df0f..b16758413eeafe2ce2ae91db2ebbe7593681d3cd 100644
--- a/src/parsers/permutation.c
+++ b/src/parsers/permutation.c
@@ -104,6 +104,7 @@ static const HParserVtable permutation_vt = {
   .isValidCF = h_false,
   .desugar = NULL,
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 HParser* h_permutation(HParser* p, ...) {
diff --git a/src/parsers/sequence.c b/src/parsers/sequence.c
index 93c0cfb983200a33b7909fd1b2c73114711beac5..55c0c8885573ef7779714efd49eaf64cc59ac878 100644
--- a/src/parsers/sequence.c
+++ b/src/parsers/sequence.c
@@ -93,6 +93,7 @@ static const HParserVtable sequence_vt = {
   .isValidCF = sequence_isValidCF,
   .desugar = desugar_sequence,
   .compile_to_rvm = sequence_ctrvm,
+  .higher = true,
 };
 
 HParser* h_sequence(HParser* p, ...) {
@@ -116,26 +117,33 @@ HParser* h_sequence__v(HParser* p, va_list ap) {
 }
 
 HParser* h_sequence__mv(HAllocator* mm__, HParser *p, va_list ap_) {
-  va_list ap;
-  size_t len = 0;
-  const HParser *arg;
-  va_copy(ap, ap_);
-  do {
-    len++;
-    arg = va_arg(ap, HParser *);
-  } while (arg);
-  va_end(ap);
   HSequence *s = h_new(HSequence, 1);
-  s->p_array = h_new(HParser *, len);
-
-  va_copy(ap, ap_);
-  s->p_array[0] = p;
-  for (size_t i = 1; i < len; i++) {
-    s->p_array[i] = va_arg(ap, HParser *);
-  } while (arg);
-  va_end(ap);
+  s->len = 0;
+
+  if(p) {
+    // non-empty sequence
+    const HParser *arg;
+    size_t len = 0;
+    va_list ap;
+
+    va_copy(ap, ap_);
+    do {
+      len++;
+      arg = va_arg(ap, HParser *);
+    } while (arg);
+    va_end(ap);
+    s->p_array = h_new(HParser *, len);
+
+    va_copy(ap, ap_);
+    s->p_array[0] = p;
+    for (size_t i = 1; i < len; i++) {
+      s->p_array[i] = va_arg(ap, HParser *);
+    } while (arg);
+    va_end(ap);
+
+    s->len = len;
+  }
 
-  s->len = len;
   return h_new_parser(mm__, &sequence_vt, s);
 }
 
diff --git a/src/parsers/token.c b/src/parsers/token.c
index d36ec54be4c07a35b729da71455c5bc3b3555cbc..19029726ad11a52fa0eadf62b67a7b15cd2e4744 100644
--- a/src/parsers/token.c
+++ b/src/parsers/token.c
@@ -73,6 +73,7 @@ const HParserVtable token_vt = {
   .isValidCF = h_true,
   .desugar = desugar_token,
   .compile_to_rvm = token_ctrvm,
+  .higher = false,
 };
 
 HParser* h_token(const uint8_t *str, const size_t len) {
diff --git a/src/parsers/unimplemented.c b/src/parsers/unimplemented.c
index e3f3039407eacaa1d24689767a4a1038fce66a93..e085858bcf45f4219f111be3ba1328868a4aad4d 100644
--- a/src/parsers/unimplemented.c
+++ b/src/parsers/unimplemented.c
@@ -18,6 +18,7 @@ static const HParserVtable unimplemented_vt = {
   .isValidCF = h_false,
   .desugar = NULL,
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 static HParser unimplemented = {
diff --git a/src/parsers/value.c b/src/parsers/value.c
index 531db7cb5274c30d3d482ee5bc84add58c1e9af7..7fa863a15b4abacdec1f5463f72c335121584c72 100644
--- a/src/parsers/value.c
+++ b/src/parsers/value.c
@@ -26,6 +26,7 @@ static const HParserVtable put_vt = {
   .isValidRegular = h_false,
   .isValidCF = h_false,
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 HParser* h_put_value(const HParser* p, const char* name) {
@@ -55,6 +56,7 @@ static const HParserVtable get_vt = {
   .isValidRegular = h_false,
   .isValidCF = h_false,
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 HParser* h_get_value(const char* name) {
diff --git a/src/parsers/whitespace.c b/src/parsers/whitespace.c
index 04284e86e61d242c58a1c42689607ecfd3794dfe..970a32c8b57209a66f3588bddb4ea30de9f87454 100644
--- a/src/parsers/whitespace.c
+++ b/src/parsers/whitespace.c
@@ -75,6 +75,7 @@ static const HParserVtable whitespace_vt = {
   .isValidCF = ws_isValidCF,
   .desugar = desugar_whitespace,
   .compile_to_rvm = ws_ctrvm,
+  .higher = false,
 };
 
 HParser* h_whitespace(const HParser* p) {
diff --git a/src/parsers/xor.c b/src/parsers/xor.c
index e031d5d542f80d345324c746e63d255e3b308655..3a3f21d27a928bf6d2d180eeb39763c918275fd0 100644
--- a/src/parsers/xor.c
+++ b/src/parsers/xor.c
@@ -36,6 +36,7 @@ static const HParserVtable xor_vt = {
   .isValidRegular = h_false,
   .isValidCF = h_false, // XXX should this be true if both p1 and p2 are CF?
   .compile_to_rvm = h_not_regular,
+  .higher = true,
 };
 
 HParser* h_xor(const HParser* p1, const HParser* p2) {
diff --git a/src/pprint.c b/src/pprint.c
index 11ec3d67411df66043ddd7880edeb22cb5d7db51..9c7c6522c0a201cf544ffaea3227510fec4bf827 100644
--- a/src/pprint.c
+++ b/src/pprint.c
@@ -186,13 +186,11 @@ static void unamb_sub(const HParsedToken* tok, struct result_buf *buf) {
 
 char* h_write_result_unamb(const HParsedToken* tok) {
   struct result_buf buf = {
-    .output = (&system_allocator)->alloc(&system_allocator, 16),
+    .output = h_alloc(&system_allocator, 16),
     .len = 0,
     .capacity = 16
   };
-  if (!buf.output) {
-    return NULL;
-  }
+  assert(buf.output != NULL);
   unamb_sub(tok, &buf);
   append_buf_c(&buf, 0);
   return buf.output;
diff --git a/src/registry.c b/src/registry.c
index 2ebac1a94ad37a501c7c10a50c3eb6a45fc74dd0..d905320bbdf6131e5ab449c525086e4b1205a28b 100644
--- a/src/registry.c
+++ b/src/registry.c
@@ -46,10 +46,8 @@ static int compare_entries(const void* v1, const void* v2) {
 }
 
 HTokenType h_allocate_token_type(const char* name) {
-  Entry* new_entry = (&system_allocator)->alloc(&system_allocator, sizeof(*new_entry));
-  if (!new_entry) {
-    return TT_INVALID;
-  }
+  Entry* new_entry = h_alloc(&system_allocator, sizeof(*new_entry));
+  assert(new_entry != NULL);
   new_entry->name = name;
   new_entry->value = 0;
   Entry* probe = *(Entry**)tsearch(new_entry, &tt_registry, compare_entries);
diff --git a/src/t_misc.c b/src/t_misc.c
index 92c2b326471d48a82fcae8f110d1febad58c6fe3..04ce96c1f6153de2b196be2dbacda55f8e6d9e23 100644
--- a/src/t_misc.c
+++ b/src/t_misc.c
@@ -1,5 +1,6 @@
 #include <glib.h>
 #include <string.h>
+#include <sys/resource.h>
 #include "test_suite.h"
 #include "hammer.h"
 
@@ -29,7 +30,54 @@ static void test_tt_registry(void) {
   g_check_cmp_int32(h_get_token_type_number("com.upstandinghackers.test.unkown_token_type"), ==, 0);
 }
 
+// test out-of-memory handling with a selectively failing allocator
+static void *fail_alloc(HAllocator *mm__, size_t size) {
+  if(size - 0xdead <= 0x30) // allow for overhead of arena link structure
+    return NULL;
+  return system_allocator.alloc(&system_allocator, size);
+}
+static void *fail_realloc(HAllocator *mm__, void *ptr, size_t size) {
+  return system_allocator.realloc(&system_allocator, ptr, size);
+}
+static void fail_free(HAllocator *mm__, void *ptr) {
+  return system_allocator.free(&system_allocator, ptr);
+}
+static HAllocator fail_allocator = {fail_alloc, fail_realloc, fail_free};
+static HParsedToken *act_oom(const HParseResult *r, void *user) {
+  void *buf = h_arena_malloc(r->arena, 0xdead);
+  assert(buf != NULL);
+  return NULL;  // succeed with null token
+}
+static void test_oom(void) {
+  HParser *p = h_action(h_ch('x'), act_oom, NULL);
+    // this should always fail, but never crash
+
+  // sanity-check: parses should succeed with the normal allocator...
+  g_check_parse_ok(p, PB_PACKRAT, "x",1);
+  g_check_parse_ok(p, PB_REGULAR, "x",1);
+  g_check_parse_ok(p, PB_LLk, "x",1);
+  g_check_parse_ok(p, PB_LALR, "x",1);
+  g_check_parse_ok(p, PB_GLR, "x",1);
+  //XXX g_check_parse_chunks_ok(p, PB_REGULAR, "",0, "x",1);
+  g_check_parse_chunks_ok(p, PB_LLk, "",0, "x",1);
+  g_check_parse_chunks_ok(p, PB_LALR, "",0, "x",1);
+  //XXX g_check_parse_chunks_ok(p, PB_GLR, "",0, "x",1);
+
+  // ...and fail gracefully with the broken one
+  HAllocator *mm__ = &fail_allocator;
+  g_check_parse_failed__m(mm__, p, PB_PACKRAT, "x",1);
+  g_check_parse_failed__m(mm__, p, PB_REGULAR, "x",1);
+  g_check_parse_failed__m(mm__, p, PB_LLk, "x",1);
+  g_check_parse_failed__m(mm__, p, PB_LALR, "x",1);
+  g_check_parse_failed__m(mm__, p, PB_GLR, "x",1);
+  //XXX g_check_parse_chunks_failed__m(mm__, p, PB_REGULAR, "",0, "x",1);
+  g_check_parse_chunks_failed__m(mm__, p, PB_LLk, "",0, "x",1);
+  g_check_parse_chunks_failed__m(mm__, p, PB_LALR, "",0, "x",1);
+  //XXX g_check_parse_chunks_failed__m(mm__, p, PB_GLR, "",0, "x",1);
+}
+
 void register_misc_tests(void) {
   g_test_add_func("/core/misc/tt_user", test_tt_user);
   g_test_add_func("/core/misc/tt_registry", test_tt_registry);
+  g_test_add_func("/core/misc/oom", test_oom);
 }
diff --git a/src/t_parser.c b/src/t_parser.c
index c42eca91321c241a1987b99116c8c90deefbdf64..331d2629018b40717bf49309ba0b561ce7a618a3 100644
--- a/src/t_parser.c
+++ b/src/t_parser.c
@@ -241,6 +241,7 @@ static void test_nothing_p(gconstpointer backend) {
 static void test_sequence(gconstpointer backend) {
   const HParser *sequence_1 = h_sequence(h_ch('a'), h_ch('b'), NULL);
   const HParser *sequence_2 = h_sequence(h_ch('a'), h_whitespace(h_ch('b')), NULL);
+  const HParser *sequence_3 = h_sequence(NULL, NULL);   // second NULL is to silence GCC
 
   g_check_parse_match(sequence_1, (HParserBackend)GPOINTER_TO_INT(backend), "ab", 2, "(u0x61 u0x62)");
   g_check_parse_failed(sequence_1, (HParserBackend)GPOINTER_TO_INT(backend), "a", 1);
@@ -248,6 +249,7 @@ static void test_sequence(gconstpointer backend) {
   g_check_parse_match(sequence_2, (HParserBackend)GPOINTER_TO_INT(backend), "ab", 2, "(u0x61 u0x62)");
   g_check_parse_match(sequence_2, (HParserBackend)GPOINTER_TO_INT(backend), "a b", 3, "(u0x61 u0x62)");
   g_check_parse_match(sequence_2, (HParserBackend)GPOINTER_TO_INT(backend), "a  b", 4, "(u0x61 u0x62)");  
+  g_check_parse_match(sequence_3, (HParserBackend)GPOINTER_TO_INT(backend), "", 0, "()");
 }
 
 static void test_choice(gconstpointer backend) {
diff --git a/src/test_suite.h b/src/test_suite.h
index 49f13cf81c50864eb8ae03ed705f582a7dd1ca0f..83359f9ec0a623f0c9f7ae5fa69175b94d6a98a8 100644
--- a/src/test_suite.h
+++ b/src/test_suite.h
@@ -78,29 +78,24 @@
   } while(0)
 
   
-// TODO: replace uses of this with g_check_parse_failed
-#define g_check_failed(res) do {					\
-    const HParseResult *result = (res);					\
-    if (NULL != result) {						\
-      g_test_message("Check failed: shouldn't have succeeded, but did"); \
-      g_test_fail();							\
-    }									\
-  } while(0)
-
-#define g_check_parse_failed(parser, backend, input, inp_len) do {	\
-    int skip = h_compile((HParser *)(parser), (HParserBackend)backend, NULL); \
+#define g_check_parse_failed__m(mm__, parser, backend, input, inp_len) do { \
+    int skip = h_compile__m(mm__, (HParser *)(parser), (HParserBackend)backend, NULL); \
     if(skip != 0) {	\
       g_test_message("Compile failed");					\
       g_test_fail();							\
       break;	\
     }	\
-    const HParseResult *result = h_parse(parser, (const uint8_t*)input, inp_len); \
+    HParseResult *result = h_parse__m(mm__, parser, (const uint8_t*)input, inp_len); \
     if (NULL != result) {						\
+      h_parse_result_free(result);					\
       g_test_message("Check failed: shouldn't have succeeded, but did"); \
       g_test_fail();							\
     }									\
   } while(0)
 
+#define g_check_parse_failed(p, be, input, len)				\
+    g_check_parse_failed__m(&system_allocator, p, be, input, len)
+
 #define g_check_parse_ok(parser, backend, input, inp_len) do {		\
     int skip = h_compile((HParser *)(parser), (HParserBackend) backend, NULL); \
     if(skip) {								\
@@ -119,7 +114,7 @@
                      "Inefficiency: %5f%%",				\
 		     stats.used, stats.wasted,				\
 		     stats.wasted * 100. / (stats.used+stats.wasted));	\
-      h_delete_arena(res->arena);					\
+      h_parse_result_free(res);						\
     }									\
   } while(0)
 
@@ -144,22 +139,22 @@
                      "Inefficiency: %5f%%",				\
 		     stats.used, stats.wasted,				\
 		     stats.wasted * 100. / (stats.used+stats.wasted));	\
-      h_delete_arena(res->arena);					\
+      h_parse_result_free(res);						\
     }									\
   } while(0)
 
-#define g_check_parse_chunks_failed(parser, backend, chunk1, c1_len, chunk2, c2_len) do {	\
-    int skip = h_compile((HParser *)(parser), (HParserBackend)backend, NULL); \
+#define g_check_parse_chunks_failed__m(mm__, parser, backend, chunk1, c1_len, chunk2, c2_len) do { \
+    int skip = h_compile__m(mm__, (HParser *)(parser), (HParserBackend)backend, NULL); \
     if(skip) {								\
       g_test_message("Compile failed");					\
       g_test_fail();							\
       break;								\
     }									\
-    g_check_parse_chunks_failed_(parser, chunk1, c1_len, chunk2, c2_len); \
+    g_check_parse_chunks_failed___m(mm__, parser, chunk1, c1_len, chunk2, c2_len); \
   } while(0)
 
-#define g_check_parse_chunks_failed_(parser, chunk1, c1_len, chunk2, c2_len) do {	\
-    HSuspendedParser *s = h_parse_start(parser);			\
+#define g_check_parse_chunks_failed___m(mm__, parser, chunk1, c1_len, chunk2, c2_len) do { \
+    HSuspendedParser *s = h_parse_start__m(mm__, parser);		\
     if(!s) {								\
       g_test_message("Chunk-wise parsing not available");		\
       g_test_fail();							\
@@ -167,13 +162,54 @@
     }									\
     h_parse_chunk(s, (const uint8_t*)chunk1, c1_len);			\
     h_parse_chunk(s, (const uint8_t*)chunk2, c2_len);			\
-    const HParseResult *res = h_parse_finish(s);			\
+    HParseResult *res = h_parse_finish(s);				\
     if (NULL != res) {							\
+      h_parse_result_free(res);						\
       g_test_message("Check failed: shouldn't have succeeded, but did"); \
       g_test_fail();							\
     }									\
   } while(0)
 
+#define g_check_parse_chunks_failed(p, be, c1, c1_len, c2, c2_len) \
+  g_check_parse_chunks_failed__m(&system_allocator, p, be, c1, c1_len, c2, c2_len)
+
+#define g_check_parse_chunks_failed_(p, c1, c1_len, c2, c2_len) \
+  g_check_parse_chunks_failed___m(&system_allocator, p, c1, c1_len, c2, c2_len)
+
+#define g_check_parse_chunks_ok(parser, backend, chunk1, c1_len, chunk2, c2_len) do {	\
+    int skip = h_compile((HParser *)(parser), (HParserBackend)backend, NULL); \
+    if(skip) {								\
+      g_test_message("Compile failed");					\
+      g_test_fail();							\
+      break;								\
+    }									\
+    g_check_parse_chunks_ok_(parser, chunk1, c1_len, chunk2, c2_len); \
+  } while(0)
+
+#define g_check_parse_chunks_ok_(parser, chunk1, c1_len, chunk2, c2_len) do {	\
+    HSuspendedParser *s = h_parse_start(parser);			\
+    if(!s) {								\
+      g_test_message("Chunk-wise parsing not available");		\
+      g_test_fail();							\
+      break;								\
+    }									\
+    h_parse_chunk(s, (const uint8_t*)chunk1, c1_len);			\
+    h_parse_chunk(s, (const uint8_t*)chunk2, c2_len);			\
+    HParseResult *res = h_parse_finish(s);				\
+    if (!res) {								\
+      g_test_message("Parse failed on line %d", __LINE__);		\
+      g_test_fail();							\
+    } else {								\
+      HArenaStats stats;						\
+      h_allocator_stats(res->arena, &stats);				\
+      g_test_message("Parse used %zd bytes, wasted %zd bytes. "		\
+                     "Inefficiency: %5f%%",				\
+		     stats.used, stats.wasted,				\
+		     stats.wasted * 100. / (stats.used+stats.wasted));	\
+      h_parse_result_free(res);						\
+    }									\
+  } while(0)
+
 #define g_check_parse_chunks_match(parser, backend, chunk1, c1_len, chunk2, c2_len, result) do { \
     int skip = h_compile((HParser *)(parser), (HParserBackend) backend, NULL); \
     if(skip) {								\
@@ -207,7 +243,7 @@
                      "Inefficiency: %5f%%",				\
 		     stats.used, stats.wasted,				\
 		     stats.wasted * 100. / (stats.used+stats.wasted));	\
-      h_delete_arena(res->arena);					\
+      h_parse_result_free(res);						\
     }									\
   } while(0)