diff --git a/src/backends/packrat.c b/src/backends/packrat.c
index e6f86f2957e5988dd94557534ab66b6050fda915..b77197378783f6a7468fd27c041608cb358960e7 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);
@@ -186,18 +186,23 @@ HParseResult* h_do_parse(const HParser* parser, HParseState *state) {
   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));
diff --git a/src/internal.h b/src/internal.h
index b35b7d5a3a22cb19bcfbcb341078070bda2bc93b..776f636811183a4b3e19de9de8c8ce5b2b66f27d 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -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 f024a82fe9952efa82fed5dcdb4bf28b1d9e8545..7fa821dc7d9837ef717114e2245d7c31fa80cacb 100644
--- a/src/parsers/bind.c
+++ b/src/parsers/bind.c
@@ -60,6 +60,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)
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 e562136fdf94eb28cf3f0796463d72f22f42932a..07bdc65c16b7eb1fb6b868999482fdb23dffa700 100644
--- a/src/parsers/ignoreseq.c
+++ b/src/parsers/ignoreseq.c
@@ -103,6 +103,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..6496bbe61860a9a20c83b2eff6ab007c630a3e77 100644
--- a/src/parsers/many.c
+++ b/src/parsers/many.c
@@ -199,6 +199,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..30de34a4885d7c56afdf6a0f00e0d34167a08dc2 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, ...) {
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) {