diff --git a/src/Makefile b/src/Makefile
index 7fac881ae0882fb0d436005d65dd63125914af4c..1a2bff3530ed897f50814fdfae949ff7d8ef635b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -28,6 +28,7 @@ PARSERS := \
 BACKENDS := \
 	packrat \
 	llk \
+	lalr \
 	regex
 
 HAMMER_PARTS := \
diff --git a/src/allocator.c b/src/allocator.c
index e345c875317716da362e5b8ca9ed35298dc32e59..80fa92172eb9f0c785fdc4f94960d34cf6d0ff75 100644
--- a/src/allocator.c
+++ b/src/allocator.c
@@ -65,10 +65,10 @@ void* h_arena_malloc(HArena *arena, size_t size) {
   if (size <= arena->head->free) {
     // fast path..
     void* ret = arena->head->rest + arena->head->used;
-    arena->used += size + 1;
+    arena->used += size;
     arena->wasted -= size;
-    arena->head->used += size + 1;
-    arena->head->free -= size + 1;
+    arena->head->used += size;
+    arena->head->free -= size;
     return ret;
   } else if (size > arena->block_size) {
     // We need a new, dedicated block for it, because it won't fit in a standard sized one.
diff --git a/src/backends/lalr.c b/src/backends/lalr.c
new file mode 100644
index 0000000000000000000000000000000000000000..7a1c04ac2b7334683d0fb9a2453eb6f4554407c4
--- /dev/null
+++ b/src/backends/lalr.c
@@ -0,0 +1,1053 @@
+#include <assert.h>
+#include "../internal.h"
+#include "../cfgrammar.h"
+#include "../parsers/parser_internal.h"
+#include "contextfree.h"
+
+
+
+/* Data structures */
+
+typedef HHashSet HLRState;  // states are sets of LRItems
+
+typedef struct HLRDFA_ {
+  size_t nstates;
+  const HLRState **states;  // array of size nstates
+  HSlist *transitions;
+} HLRDFA;
+
+typedef struct HLRTransition_ {
+  size_t from;              // index into 'states' array
+  const HCFChoice *symbol;
+  size_t to;                // index into 'states' array
+} HLRTransition;
+
+typedef struct HLRItem_ {
+  HCFChoice *lhs;
+  HCFChoice **rhs;          // NULL-terminated
+  size_t len;               // number of elements in rhs
+  size_t mark;
+} HLRItem;
+
+typedef struct HLRAction_ {
+  enum {HLR_SHIFT, HLR_REDUCE} type;
+  union {
+    size_t nextstate;   // used with SHIFT
+    struct {
+      HCFChoice *lhs;   // symbol carrying semantic actions etc.
+      size_t length;    // # of symbols in rhs
+#ifndef NDEBUG
+      HCFChoice **rhs;  // NB: the rhs symbols are not needed for the parse
+#endif
+    } production;       // used with REDUCE
+  };
+} HLRAction;
+
+typedef struct HLRTable_ {
+  size_t     nrows;
+  HHashTable **rows;    // map symbols to HLRActions
+  HLRAction  **forall;  // shortcut to set an action for an entire row
+  HCFChoice  *start;    // start symbol
+  HSlist     *inadeq;   // indices of any inadequate states
+  HArena     *arena;
+  HAllocator *mm__;
+} HLRTable;
+
+typedef struct HLREnhGrammar_ {
+  HCFGrammar *grammar;  // enhanced grammar
+  HHashTable *tmap;     // maps transitions to enhanced-grammar symbols
+  HHashTable *smap;     // maps enhanced-grammar symbols to transitions
+  HHashTable *corr;     // maps symbols to sets of corresponding e. symbols
+  HArena *arena;
+} HLREnhGrammar;
+
+
+// XXX move to internal.h or something
+// XXX replace other hashtable iterations with this
+#define H_FOREACH_(HT) {                                                    \
+    const HHashTable *ht__ = HT;                                            \
+    for(size_t i__=0; i__ < ht__->capacity; i__++) {                        \
+      for(HHashTableEntry *hte__ = &ht__->contents[i__];                    \
+          hte__;                                                            \
+          hte__ = hte__->next) {                                            \
+        if(hte__->key == NULL) continue;
+
+#define H_FOREACH_KEY(HT, KEYVAR) H_FOREACH_(HT)                            \
+        const KEYVAR = hte__->key;
+
+#define H_FOREACH(HT, KEYVAR, VALVAR) H_FOREACH_KEY(HT, KEYVAR)             \
+        VALVAR = hte__->value;
+
+#define H_END_FOREACH                                                       \
+      }                                                                     \
+    }                                                                       \
+  }
+
+// compare symbols - terminals by value, others by pointer
+static bool eq_symbol(const void *p, const void *q)
+{
+  const HCFChoice *x=p, *y=q;
+  return (x==y
+          || (x->type==HCF_END && y->type==HCF_END)
+          || (x->type==HCF_CHAR && y->type==HCF_CHAR && x->chr==y->chr));
+}
+
+// hash symbols - terminals by value, others by pointer
+static HHashValue hash_symbol(const void *p)
+{
+  const HCFChoice *x=p;
+  if(x->type == HCF_END)
+    return 0;
+  else if(x->type == HCF_CHAR)
+    return x->chr * 33;
+  else
+    return h_hash_ptr(p);
+}
+
+// compare LALR items by value
+static bool eq_lalr_item(const void *p, const void *q)
+{
+  const HLRItem *a=p, *b=q;
+
+  if(!eq_symbol(a->lhs, b->lhs)) return false;
+  if(a->mark != b->mark) return false;
+  if(a->len != b->len) return false;
+
+  for(size_t i=0; i<a->len; i++)
+    if(!eq_symbol(a->rhs[i], b->rhs[i])) return false;
+
+  return true;
+}
+
+// compare LALR item sets (DFA states)
+static inline bool eq_lalr_itemset(const void *p, const void *q)
+{
+  return h_hashset_equal(p, q);
+}
+
+// hash LALR items
+static inline HHashValue hash_lalr_item(const void *p)
+{
+  const HLRItem *x = p;
+  HHashValue hash = 0;
+
+  hash += hash_symbol(x->lhs);
+  for(HCFChoice **p=x->rhs; *p; p++)
+    hash += hash_symbol(*p);
+  hash += x->mark;
+
+  return hash;
+}
+
+// hash LALR item sets (DFA states) - hash the elements and sum
+static HHashValue hash_lalr_itemset(const void *p)
+{
+  HHashValue hash = 0;
+
+  H_FOREACH_KEY((const HHashSet *)p, HLRItem *item)
+    hash += hash_lalr_item(item);
+  H_END_FOREACH
+
+  return hash;
+}
+
+HLRItem *h_lritem_new(HArena *a, HCFChoice *lhs, HCFChoice **rhs, size_t mark)
+{
+  HLRItem *ret = h_arena_malloc(a, sizeof(HLRItem));
+
+  size_t len = 0;
+  for(HCFChoice **p=rhs; *p; p++) len++;
+  assert(mark <= len);
+
+  ret->lhs = lhs;
+  ret->rhs = rhs;
+  ret->len = len;
+  ret->mark = mark;
+
+  return ret;
+}
+
+static inline HLRState *h_lrstate_new(HArena *arena)
+{
+  return h_hashset_new(arena, eq_lalr_item, hash_lalr_item);
+}
+
+HLRTable *h_lrtable_new(HAllocator *mm__, size_t nrows)
+{
+  HArena *arena = h_new_arena(mm__, 0);    // default blocksize
+  assert(arena != NULL);
+
+  HLRTable *ret = h_new(HLRTable, 1);
+  ret->nrows = nrows;
+  ret->rows = h_arena_malloc(arena, nrows * sizeof(HHashTable *));
+  ret->forall = h_arena_malloc(arena, nrows * sizeof(HLRAction *));
+  ret->inadeq = h_slist_new(arena);
+  ret->arena = arena;
+  ret->mm__ = mm__;
+
+  for(size_t i=0; i<nrows; i++) {
+    ret->rows[i] = h_hashtable_new(arena, eq_symbol, hash_symbol);
+    ret->forall[i] = NULL;
+  }
+
+  return ret;
+}
+
+void h_lrtable_free(HLRTable *table)
+{
+  HAllocator *mm__ = table->mm__;
+  h_delete_arena(table->arena);
+  h_free(table);
+}
+
+
+
+/* Constructing the characteristic automaton (handle recognizer) */
+
+static HLRItem *advance_mark(HArena *arena, const HLRItem *item)
+{
+  assert(item->rhs[item->mark] != NULL);
+  HLRItem *ret = h_arena_malloc(arena, sizeof(HLRItem));
+  *ret = *item;
+  ret->mark++;
+  return ret;
+}
+
+static void expand_to_closure(HCFGrammar *g, HHashSet *items)
+{
+  HAllocator *mm__ = g->mm__;
+  HArena *arena = g->arena;
+  HSlist *work = h_slist_new(arena);
+
+  // initialize work list with items
+  H_FOREACH_KEY(items, HLRItem *item)
+    h_slist_push(work, (void *)item);
+  H_END_FOREACH
+
+  while(!h_slist_empty(work)) {
+    const HLRItem *item = h_slist_pop(work);
+    HCFChoice *sym = item->rhs[item->mark]; // symbol after mark
+
+    // if there is a non-terminal after the mark, follow it
+    // NB: unlike LLk, we do consider HCF_CHARSET a non-terminal here
+    if(sym != NULL && (sym->type==HCF_CHOICE || sym->type==HCF_CHARSET)) {
+      // add items corresponding to the productions of sym
+      if(sym->type == HCF_CHOICE) {
+        for(HCFSequence **p=sym->seq; *p; p++) {
+          HLRItem *it = h_lritem_new(arena, sym, (*p)->items, 0);
+          if(!h_hashset_present(items, it)) {
+            h_hashset_put(items, it);
+            h_slist_push(work, it);
+          }
+        }
+      } else {  // HCF_CHARSET
+        for(unsigned int i=0; i<256; i++) {
+          if(charset_isset(sym->charset, i)) {
+            // XXX allocate these single-character symbols statically somewhere
+            HCFChoice **rhs = h_new(HCFChoice *, 2);
+            rhs[0] = h_new(HCFChoice, 1);
+            rhs[0]->type = HCF_CHAR;
+            rhs[0]->chr = i;
+            rhs[1] = NULL;
+            HLRItem *it = h_lritem_new(arena, sym, rhs, 0);
+            h_hashset_put(items, it);
+            // single-character item needs no further work
+          }
+        }
+        // if sym is a non-terminal, we need a reshape on it
+        // this seems as good a place as any to set it
+        sym->reshape = h_act_first;
+      }
+    }
+  }
+}
+
+HLRDFA *h_lr0_dfa(HCFGrammar *g)
+{
+  HArena *arena = g->arena;
+
+  HHashSet *states = h_hashset_new(arena, eq_lalr_itemset, hash_lalr_itemset);
+      // maps itemsets to assigned array indices
+  HSlist *transitions = h_slist_new(arena);
+
+  // list of states that need to be processed
+  // to save lookups, we push two elements per state, the itemset and its
+  // assigned index.
+  HSlist *work = h_slist_new(arena);
+
+  // make initial state (kernel)
+  HLRState *start = h_lrstate_new(arena);
+  assert(g->start->type == HCF_CHOICE);
+  for(HCFSequence **p=g->start->seq; *p; p++)
+    h_hashset_put(start, h_lritem_new(arena, g->start, (*p)->items, 0));
+  expand_to_closure(g, start);
+  h_hashtable_put(states, start, 0);
+  h_slist_push(work, start);
+  h_slist_push(work, 0);
+  
+  // while work to do (on some state)
+  //   determine edge symbols
+  //   for each edge symbol:
+  //     advance respective items -> destination state (kernel)
+  //     compute closure
+  //     if destination is a new state:
+  //       add it to state set
+  //       add transition to it
+  //       add it to the work list
+
+  while(!h_slist_empty(work)) {
+    size_t state_idx = (uintptr_t)h_slist_pop(work);
+    HLRState *state = h_slist_pop(work);
+
+    // maps edge symbols to neighbor states (item sets) of s
+    HHashTable *neighbors = h_hashtable_new(arena, eq_symbol, hash_symbol);
+
+    // iterate over state (closure) and generate neighboring sets
+    H_FOREACH_KEY(state, HLRItem *item)
+      HCFChoice *sym = item->rhs[item->mark]; // symbol after mark
+
+      if(sym != NULL) { // mark was not at the end
+        // find or create prospective neighbor set
+        HLRState *neighbor = h_hashtable_get(neighbors, sym);
+        if(neighbor == NULL) {
+          neighbor = h_lrstate_new(arena);
+          h_hashtable_put(neighbors, sym, neighbor);
+        }
+
+        // ...and add the advanced item to it
+        h_hashset_put(neighbor, advance_mark(arena, item));
+      }
+    H_END_FOREACH
+
+    // merge expanded neighbor sets into the set of existing states
+    H_FOREACH(neighbors, HCFChoice *symbol, HLRState *neighbor)
+      expand_to_closure(g, neighbor);
+
+      // look up existing state, allocate new if not found
+      size_t neighbor_idx;
+      if(!h_hashset_present(states, neighbor)) {
+        neighbor_idx = states->used;
+        h_hashtable_put(states, neighbor, (void *)(uintptr_t)neighbor_idx);
+        h_slist_push(work, neighbor);
+        h_slist_push(work, (void *)(uintptr_t)neighbor_idx);
+      } else {
+        neighbor_idx = (uintptr_t)h_hashtable_get(states, neighbor);
+      }
+
+      // add transition "state --symbol--> neighbor"
+      HLRTransition *t = h_arena_malloc(arena, sizeof(HLRTransition));
+      t->from = state_idx;
+      t->to = neighbor_idx;
+      t->symbol = symbol;
+      h_slist_push(transitions, t);
+    H_END_FOREACH
+  } // end while(work)
+
+  // fill DFA struct
+  HLRDFA *dfa = h_arena_malloc(arena, sizeof(HLRDFA));
+  dfa->nstates = states->used;
+  dfa->states = h_arena_malloc(arena, dfa->nstates*sizeof(HLRState *));
+  H_FOREACH(states, HLRState *state, void *v)
+    size_t idx = (uintptr_t)v;
+    dfa->states[idx] = state;
+  H_END_FOREACH
+  dfa->transitions = transitions;
+
+  return dfa;
+}
+
+
+
+/* LR(0) table generation */
+
+static HLRAction *shift_action(HArena *arena, size_t nextstate)
+{
+  HLRAction *action = h_arena_malloc(arena, sizeof(HLRAction));
+  action->type = HLR_SHIFT;
+  action->nextstate = nextstate;
+  return action;
+}
+
+static HLRAction *reduce_action(HArena *arena, const HLRItem *item)
+{
+  HLRAction *action = h_arena_malloc(arena, sizeof(HLRAction));
+  action->type = HLR_REDUCE;
+  action->production.lhs = item->lhs;
+  action->production.length = item->len;
+#ifndef NDEBUG
+  action->production.rhs = item->rhs;
+#endif
+  return action;
+}
+
+HLRTable *h_lr0_table(HCFGrammar *g, const HLRDFA *dfa)
+{
+  HAllocator *mm__ = g->mm__;
+
+  HLRTable *table = h_lrtable_new(mm__, dfa->nstates);
+  HArena *arena = table->arena;
+
+  // remember start symbol
+  table->start = g->start;
+
+  // add shift entries
+  for(HSlistNode *x = dfa->transitions->head; x; x = x->next) {
+    // for each transition x-A->y, add "shift, goto y" to table entry (x,A)
+    HLRTransition *t = x->elem;
+
+    HLRAction *action = shift_action(arena, t->to);
+    h_hashtable_put(table->rows[t->from], t->symbol, action);
+  }
+
+  // add reduce entries, record inadequate states
+  for(size_t i=0; i<dfa->nstates; i++) {
+    // find reducible items in state
+    H_FOREACH_KEY(dfa->states[i], HLRItem *item)
+      if(item->mark == item->len) { // mark at the end
+        // check for conflicts
+        // XXX store more informative stuff in the inadeq records?
+        if(table->forall[i]) {
+          // reduce/reduce conflict with a previous item
+          h_slist_push(table->inadeq, (void *)(uintptr_t)i);
+        } else if(!h_hashtable_empty(table->rows[i])) {
+          // shift/reduce conflict with one of the row's entries
+          h_slist_push(table->inadeq, (void *)(uintptr_t)i);
+        }
+
+        // set reduce action for the entire row
+        table->forall[i] = reduce_action(arena, item);
+      }
+    H_END_FOREACH
+  }
+
+  return table;
+}
+
+
+
+/* LALR-via-SLR grammar transformation */
+
+static inline size_t seqsize(void *p_)
+{
+  size_t n=0;
+  for(void **p=p_; *p; p++) n++;
+  return n+1;
+}
+
+static size_t follow_transition(const HLRTable *table, size_t x, HCFChoice *A)
+{
+  HLRAction *action = h_hashtable_get(table->rows[x], A);
+  assert(action != NULL);
+  assert(action->type == HLR_SHIFT);
+  return action->nextstate;
+}
+
+static inline HLRTransition *transition(HArena *arena,
+                                        size_t x, const HCFChoice *A, size_t y)
+{
+  HLRTransition *t = h_arena_malloc(arena, sizeof(HLRTransition));
+  t->from = x;
+  t->symbol = A;
+  t->to = y;
+  return t;
+}
+
+// no-op on terminal symbols
+static void transform_productions(const HLRTable *table, HLREnhGrammar *eg,
+                                  size_t x, HCFChoice *xAy)
+{
+  if(xAy->type != HCF_CHOICE)
+    return;
+  // XXX CHARSET?
+
+  HArena *arena = eg->arena;
+
+  HCFSequence **seq = h_arena_malloc(arena, seqsize(xAy->seq)
+                                            * sizeof(HCFSequence *));
+  HCFSequence **p, **q;
+  for(p=xAy->seq, q=seq; *p; p++, q++) {
+    // trace rhs starting in state x and following the transitions
+    // xAy -> ... iBj ...
+
+    size_t i = x;
+    HCFChoice **B = (*p)->items;
+    HCFChoice **items = h_arena_malloc(arena, seqsize(B) * sizeof(HCFChoice *));
+    HCFChoice **iBj = items;
+    for(; *B; B++, iBj++) {
+      size_t j = follow_transition(table, i, *B);
+      HLRTransition *i_B_j = transition(arena, i, *B, j);
+      *iBj = h_hashtable_get(eg->tmap, i_B_j);
+      assert(*iBj != NULL);
+      i = j;
+    }
+    *iBj = NULL;
+
+    *q = h_arena_malloc(arena, sizeof(HCFSequence));
+    (*q)->items = items;
+  }
+  *q = NULL;
+  xAy->seq = seq;
+}
+
+static bool eq_transition(const void *p, const void *q)
+{
+  const HLRTransition *a=p, *b=q;
+  return (a->from == b->from && a->to == b->to && eq_symbol(a->symbol, b->symbol));
+}
+
+static HHashValue hash_transition(const void *p)
+{
+  const HLRTransition *t = p;
+  return (hash_symbol(t->symbol) + t->from + t->to); // XXX ?
+}
+
+HCFChoice *new_enhanced_symbol(HLREnhGrammar *eg, const HCFChoice *sym)
+{
+  HArena *arena = eg->arena;
+  HCFChoice *esym = h_arena_malloc(arena, sizeof(HCFChoice));
+  *esym = *sym;
+
+  HHashSet *cs = h_hashtable_get(eg->corr, sym);
+  if(!cs) {
+    cs = h_hashset_new(arena, eq_symbol, hash_symbol);
+    h_hashtable_put(eg->corr, sym, cs);
+  }
+  h_hashset_put(cs, esym);
+
+  return esym;
+}
+static HLREnhGrammar *enhance_grammar(const HCFGrammar *g, const HLRDFA *dfa,
+                                      const HLRTable *table)
+{
+  HAllocator *mm__ = g->mm__;
+  HArena *arena = g->arena;
+
+  HLREnhGrammar *eg = h_arena_malloc(arena, sizeof(HLREnhGrammar));
+  eg->tmap = h_hashtable_new(arena, eq_transition, hash_transition);
+  eg->smap = h_hashtable_new(arena, h_eq_ptr, h_hash_ptr);
+  eg->corr = h_hashtable_new(arena, eq_symbol, hash_symbol);
+  // XXX must use h_eq/hash_ptr for symbols! so enhanced CHARs are different
+  eg->arena = arena;
+
+  // establish mapping between transitions and symbols
+  for(HSlistNode *x=dfa->transitions->head; x; x=x->next) {
+    HLRTransition *t = x->elem;
+
+    assert(!h_hashtable_present(eg->tmap, t));
+
+    HCFChoice *sym = new_enhanced_symbol(eg, t->symbol);
+    h_hashtable_put(eg->tmap, t, sym);
+    h_hashtable_put(eg->smap, sym, t);
+  }
+
+  // transform the productions
+  H_FOREACH(eg->tmap, HLRTransition *t, HCFChoice *sym)
+    transform_productions(table, eg, t->from, sym);
+  H_END_FOREACH
+
+  // add the start symbol
+  HCFChoice *start = new_enhanced_symbol(eg, g->start);
+  transform_productions(table, eg, 0, start);
+
+  eg->grammar = h_cfgrammar_(mm__, start);
+  return eg;
+}
+
+
+
+/* LALR table generation */
+
+static inline bool has_conflicts(HLRTable *table)
+{
+  return !h_slist_empty(table->inadeq);
+}
+
+// place a new entry in tbl; records conflicts in tbl->inadeq
+// returns 0 on success, -1 on conflict
+// ignores forall entries
+int h_lrtable_put(HLRTable *tbl, size_t state, HCFChoice *x, HLRAction *action)
+{
+  HLRAction *prev = h_hashtable_get(tbl->rows[state], x);
+  if(prev && prev != action) {
+    // conflict
+    h_slist_push(tbl->inadeq, (void *)(uintptr_t)state);
+    return -1;
+  } else {
+    h_hashtable_put(tbl->rows[state], x, action);
+    return 0;
+  }
+}
+
+// check whether a sequence of enhanced-grammar symbols (p) matches the given
+// (original-grammar) production rhs and terminates in the given end state.
+bool match_production(HLREnhGrammar *eg, HCFChoice **p,
+                      HCFChoice **rhs, size_t endstate)
+{
+  size_t state = endstate;  // initialized to end in case of empty rhs
+  for(; *p && *rhs; p++, rhs++) {
+    HLRTransition *t = h_hashtable_get(eg->smap, *p);
+    assert(t != NULL);
+    if(!eq_symbol(t->symbol, *rhs))
+      return false;
+    state = t->to;
+  }
+  return (*p == *rhs    // both NULL
+          && state == endstate);
+}
+
+// desugar parser with a fresh start symbol
+// this guarantees that the start symbol will not occur in any productions
+static HCFChoice *augment(HAllocator *mm__, HParser *parser)
+{
+  HCFChoice *augmented = h_new(HCFChoice, 1);
+
+  HCFStack *stk__ = h_cfstack_new(mm__);
+  stk__->prealloc = augmented;
+  HCFS_BEGIN_CHOICE() {
+    HCFS_BEGIN_SEQ() {
+      HCFS_DESUGAR(parser);
+    } HCFS_END_SEQ();
+    HCFS_THIS_CHOICE->reshape = h_act_first;
+  } HCFS_END_CHOICE();
+  h_cfstack_free(mm__, stk__);
+
+  return augmented;
+}
+
+int h_lalr_compile(HAllocator* mm__, HParser* parser, const void* params)
+{
+  // generate (augmented) CFG from parser
+  // construct LR(0) DFA
+  // build LR(0) table
+  // if necessary, resolve conflicts "by conversion to SLR"
+
+  HCFGrammar *g = h_cfgrammar_(mm__, augment(mm__, parser));
+  if(g == NULL)     // backend not suitable (language not context-free)
+    return -1;
+
+  HLRDFA *dfa = h_lr0_dfa(g);
+  if(dfa == NULL) {     // this should normally not happen
+    h_cfgrammar_free(g);
+    return -1;
+  }
+
+  HLRTable *table = h_lr0_table(g, dfa);
+  if(table == NULL) {   // this should normally not happen
+    h_cfgrammar_free(g);
+    return -1;
+  }
+
+  if(has_conflicts(table)) {
+    HArena *arena = table->arena;
+
+    HLREnhGrammar *eg = enhance_grammar(g, dfa, table);
+    if(eg == NULL) {    // this should normally not happen
+      h_cfgrammar_free(g);
+      h_lrtable_free(table);
+      return -1;
+    }
+
+    // go through the inadequate states; replace inadeq with a new list
+    HSlist *inadeq = table->inadeq;
+    table->inadeq = h_slist_new(arena);
+    
+    for(HSlistNode *x=inadeq->head; x; x=x->next) {
+      size_t state = (uintptr_t)x->elem;
+      
+      // clear old forall entry, it's being replaced by more fine-grained ones
+      table->forall[state] = NULL;
+
+      // go through each reducible item of state
+      H_FOREACH_KEY(dfa->states[state], HLRItem *item)
+        if(item->mark < item->len)
+          continue;
+
+        // action to place in the table cells indicated by lookahead
+        HLRAction *action = reduce_action(arena, item);
+
+        // find all LR(0)-enhanced productions matching item
+        HHashSet *lhss = h_hashtable_get(eg->corr, item->lhs);
+        assert(lhss != NULL);
+        H_FOREACH_KEY(lhss, HCFChoice *lhs)
+          assert(lhs->type == HCF_CHOICE);  // XXX could be CHARSET?
+
+          for(HCFSequence **p=lhs->seq; *p; p++) {
+            HCFChoice **rhs = (*p)->items;
+            if(!match_production(eg, rhs, item->rhs, state))
+              continue;
+
+            // the left-hand symbol's follow set is this production's
+            // contribution to the lookahead
+            const HStringMap *fs = h_follow(1, eg->grammar, lhs);
+            assert(fs != NULL);
+            assert(fs->epsilon_branch == NULL);
+            assert(!h_stringmap_empty(fs));
+
+            // for each lookahead symbol, put action into table cell
+            if(fs->end_branch) {
+              HCFChoice *terminal = h_arena_malloc(arena, sizeof(HCFChoice));
+              terminal->type = HCF_END;
+              h_lrtable_put(table, state, terminal, action);
+            }
+            H_FOREACH(fs->char_branches, void *key, HStringMap *m)
+              if(!m->epsilon_branch)
+                continue;
+
+              HCFChoice *terminal = h_arena_malloc(arena, sizeof(HCFChoice));
+              terminal->type = HCF_CHAR; 
+              terminal->chr = key_char((HCharKey)key);
+
+              h_lrtable_put(table, state, terminal, action);
+            H_END_FOREACH  // lookahead character
+        } H_END_FOREACH // enhanced production
+      H_END_FOREACH  // reducible item
+    }
+  }
+
+  h_cfgrammar_free(g);
+  parser->backend_data = table;
+  return has_conflicts(table)? -1 : 0;
+}
+
+void h_lalr_free(HParser *parser)
+{
+  HLRTable *table = parser->backend_data;
+  h_lrtable_free(table);
+  parser->backend_data = NULL;
+  parser->backend = PB_PACKRAT;
+}
+
+
+
+/* LR driver */
+
+const HLRAction *
+h_lr_lookup(const HLRTable *table, size_t state, const HCFChoice *symbol)
+{
+  assert(state < table->nrows);
+  if(table->forall[state]) {
+    assert(h_hashtable_empty(table->rows[state]));  // that would be a conflict
+    return table->forall[state];
+  } else {
+    return h_hashtable_get(table->rows[state], symbol);
+  }
+}
+
+HParseResult *h_lr_parse(HAllocator* mm__, const HParser* parser, HInputStream* stream)
+{
+  HLRTable *table = parser->backend_data;
+  if(!table)
+    return NULL;
+
+  HArena *arena  = h_new_arena(mm__, 0);    // will hold the results
+  HArena *tarena = h_new_arena(mm__, 0);    // tmp, deleted after parse
+  HSlist *left = h_slist_new(tarena);   // left stack; reductions happen here
+  HSlist *right = h_slist_new(tarena);  // right stack; input appears here
+
+  // stack layout:
+  // on the left stack, we put pairs:  (saved state, semantic value)
+  // on the right stack, we put pairs: (symbol, semantic value)
+
+  // run while the recognizer finds handles in the input
+  size_t state = 0;
+  while(1) {
+    // make sure there is input on the right stack
+    if(h_slist_empty(right)) {
+      // XXX use statically-allocated terminal symbols
+      HCFChoice *x = h_arena_malloc(tarena, sizeof(HCFChoice));
+      HParsedToken *v;
+
+      uint8_t c = h_read_bits(stream, 8, false);
+
+      if(stream->overrun) {     // end of input
+        x->type = HCF_END;
+        v = NULL;
+      } else {
+        x->type = HCF_CHAR;
+        x->chr = c;
+        v = h_arena_malloc(arena, sizeof(HParsedToken));
+        v->token_type = TT_UINT;
+        v->uint = c;
+      }
+
+      h_slist_push(right, v);
+      h_slist_push(right, x);
+    }
+
+    // peek at input symbol on the right side
+    HCFChoice *symbol = right->head->elem;
+
+    // table lookup
+    const HLRAction *action = h_lr_lookup(table, state, symbol);
+    if(action == NULL)
+      break;    // no handle recognizable in input, terminate parsing
+
+    if(action->type == HLR_SHIFT) {
+      h_slist_push(left, (void *)(uintptr_t)state);
+      h_slist_pop(right);                       // symbol (discard)
+      h_slist_push(left, h_slist_pop(right));   // semantic value
+      state = action->nextstate;
+    } else {
+      assert(action->type == HLR_REDUCE);
+      size_t len = action->production.length;
+      HCFChoice *symbol = action->production.lhs;
+
+      // semantic value of the reduction result
+      HParsedToken *value = h_arena_malloc(arena, sizeof(HParsedToken));
+      value->token_type = TT_SEQUENCE;
+      value->seq = h_carray_new_sized(arena, len);
+      
+      // pull values off the left stack, rewinding state accordingly
+      HParsedToken *v = NULL;
+      for(size_t i=0; i<len; i++) {
+        v = h_slist_pop(left);
+        state = (uintptr_t)h_slist_pop(left);
+
+        // collect values in result sequence
+        value->seq->elements[len-1-i] = v;
+        value->seq->used++;
+      }
+      if(v) {
+        // result position equals position of left-most symbol
+        value->index = v->index;
+        value->bit_offset = v->bit_offset;
+      } else {
+        // XXX how to get the position in this case?
+      }
+
+      // perform token reshape if indicated
+      if(symbol->reshape)
+        value = (HParsedToken *)symbol->reshape(make_result(arena, value));
+
+      // call validation and semantic action, if present
+      if(symbol->pred && !symbol->pred(make_result(tarena, value)))
+        break;  // validation failed -> no parse
+      if(symbol->action)
+        value = (HParsedToken *)symbol->action(make_result(arena, value));
+
+      // push result (value, symbol) onto the right stack
+      h_slist_push(right, value);
+      h_slist_push(right, symbol);
+    }
+  }
+
+
+
+  // parsing was successful iff the start symbol is on top of the right stack
+  HParseResult *result = NULL;
+  if(h_slist_pop(right) == table->start) {
+    // next on the right stack is the start symbol's semantic value
+    assert(!h_slist_empty(right));
+    HParsedToken *tok = h_slist_pop(right);
+    result = make_result(arena, tok);
+  } else {
+    h_delete_arena(arena);
+    result = NULL;
+  }
+
+  h_delete_arena(tarena);
+  return result;
+}
+
+
+
+/* Pretty-printers */
+
+void h_pprint_lritem(FILE *f, const HCFGrammar *g, const HLRItem *item)
+{
+  h_pprint_symbol(f, g, item->lhs);
+  fputs(" ->", f);
+
+  HCFChoice **x = item->rhs;
+  HCFChoice **mark = item->rhs + item->mark;
+  if(*x == NULL) {
+    fputc('.', f);
+  } else {
+    while(*x) {
+      if(x == mark)
+        fputc('.', f);
+      else
+        fputc(' ', f);
+
+      if((*x)->type == HCF_CHAR) {
+        // condense character strings
+        fputc('"', f);
+        h_pprint_char(f, (*x)->chr);
+        for(x++; *x; x++) {
+          if(x == mark)
+            break;
+          if((*x)->type != HCF_CHAR)
+            break;
+          h_pprint_char(f, (*x)->chr);
+        }
+        fputc('"', f);
+      } else {
+        h_pprint_symbol(f, g, *x);
+        x++;
+      }
+    }
+    if(x == mark)
+      fputs(".", f);
+  }
+}
+
+void h_pprint_lrstate(FILE *f, const HCFGrammar *g,
+                      const HLRState *state, unsigned int indent)
+{
+  bool first = true;
+  H_FOREACH_KEY(state, HLRItem *item)
+    if(!first)
+      for(unsigned int i=0; i<indent; i++) fputc(' ', f);
+    first = false;
+    h_pprint_lritem(f, g, item);
+    fputc('\n', f);
+  H_END_FOREACH
+}
+
+static void pprint_transition(FILE *f, const HCFGrammar *g, const HLRTransition *t)
+{
+  fputs("-", f);
+  h_pprint_symbol(f, g, t->symbol);
+  fprintf(f, "->%lu", t->to);
+}
+
+void h_pprint_lrdfa(FILE *f, const HCFGrammar *g,
+                    const HLRDFA *dfa, unsigned int indent)
+{
+  for(size_t i=0; i<dfa->nstates; i++) {
+    unsigned int indent2 = indent + fprintf(f, "%4lu: ", i);
+    h_pprint_lrstate(f, g, dfa->states[i], indent2);
+    for(HSlistNode *x = dfa->transitions->head; x; x = x->next) {
+      const HLRTransition *t = x->elem;
+      if(t->from == i) {
+        for(unsigned int i=0; i<indent2-2; i++) fputc(' ', f);
+        pprint_transition(f, g, t);
+        fputc('\n', f);
+      }
+    }
+  }
+}
+
+void pprint_lraction(FILE *f, const HCFGrammar *g, const HLRAction *action)
+{
+  if(action->type == HLR_SHIFT) {
+    fprintf(f, "s%lu", action->nextstate);
+  } else {
+    fputs("r(", f);
+    h_pprint_symbol(f, g, action->production.lhs);
+    fputs(" -> ", f);
+#ifdef NDEBUG
+    // if we can't print the production, at least print its length
+    fprintf(f, "[%lu]", action->production.length);
+#else
+    HCFSequence seq = {action->production.rhs};
+    h_pprint_sequence(f, g, &seq);
+#endif
+    fputc(')', f);
+  }
+}
+
+void h_pprint_lrtable(FILE *f, const HCFGrammar *g, const HLRTable *table,
+                      unsigned int indent)
+{
+  for(size_t i=0; i<table->nrows; i++) {
+    for(unsigned int j=0; j<indent; j++) fputc(' ', f);
+    fprintf(f, "%4lu:", i);
+    if(table->forall[i]) {
+      fputs(" - ", f);
+      pprint_lraction(f, g, table->forall[i]);
+      fputs(" -", f);
+      if(!h_hashtable_empty(table->rows[i]))
+        fputs(" !!", f);
+    }
+    H_FOREACH(table->rows[i], HCFChoice *symbol, HLRAction *action)
+      fputc(' ', f);    // separator
+      h_pprint_symbol(f, g, symbol);
+      fputc(':', f);
+      if(table->forall[i]) {
+        fputc(action->type == HLR_SHIFT? 's' : 'r', f);
+        fputc('/', f);
+        fputc(table->forall[i]->type == HLR_SHIFT? 's' : 'r', f);
+      } else {
+        pprint_lraction(f, g, action);
+      }
+    H_END_FOREACH
+    fputc('\n', f);
+  }
+
+#if 0
+  fputs("inadeq=", f);
+  for(HSlistNode *x=table->inadeq->head; x; x=x->next) {
+    fprintf(f, "%lu ", (uintptr_t)x->elem);
+  }
+  fputc('\n', f);
+#endif
+}
+
+
+
+HParserBackendVTable h__lalr_backend_vtable = {
+  .compile = h_lalr_compile,
+  .parse = h_lr_parse,
+  .free = h_lalr_free
+};
+
+
+
+
+// dummy!
+int test_lalr(void)
+{
+  /* 
+     E -> E '-' T
+        | T
+     T -> '(' E ')'
+        | 'n'               -- also try [0-9] for the charset paths
+  */
+
+  HParser *n = h_ch('n');
+  HParser *E = h_indirect();
+  HParser *T = h_choice(h_sequence(h_ch('('), E, h_ch(')'), NULL), n, NULL);
+  HParser *E_ = h_choice(h_sequence(E, h_ch('-'), T, NULL), T, NULL);
+  h_bind_indirect(E, E_);
+  HParser *p = E;
+
+  printf("\n==== G R A M M A R ====\n");
+  HCFGrammar *g = h_cfgrammar(&system_allocator, p);
+  if(g == NULL) {
+    fprintf(stderr, "h_cfgrammar failed\n");
+    return 1;
+  }
+  h_pprint_grammar(stdout, g, 0);
+
+  printf("\n==== D F A ====\n");
+  HLRDFA *dfa = h_lr0_dfa(g);
+  if(dfa)
+    h_pprint_lrdfa(stdout, g, dfa, 0);
+  else
+    fprintf(stderr, "h_lalr_dfa failed\n");
+
+  printf("\n==== L R ( 0 )  T A B L E ====\n");
+  HLRTable *table0 = h_lr0_table(g, dfa);
+  if(table0)
+    h_pprint_lrtable(stdout, g, table0, 0);
+  else
+    fprintf(stderr, "h_lr0_table failed\n");
+  h_lrtable_free(table0);
+
+  printf("\n==== L A L R  T A B L E ====\n");
+  if(h_compile(p, PB_LALR, NULL)) {
+    fprintf(stderr, "does not compile\n");
+    return 2;
+  }
+  h_pprint_lrtable(stdout, g, (HLRTable *)p->backend_data, 0);
+
+  printf("\n==== P A R S E  R E S U L T ====\n");
+  HParseResult *res = h_parse(p, (uint8_t *)"n-(n-((n)))-n", 13);
+  if(res)
+    h_pprint(stdout, res->ast, 0, 2);
+  else
+    printf("no parse\n");
+
+  return 0;
+}
diff --git a/src/backends/llk.c b/src/backends/llk.c
index aeafd6a0cd5ba7a44734c69d5bf87a09cb17d617..4f73c469829f6cb7a86d0f3edc1a07ea25753943 100644
--- a/src/backends/llk.c
+++ b/src/backends/llk.c
@@ -8,7 +8,7 @@ static const size_t DEFAULT_KMAX = 1;
 
 /* Generating the LL(k) parse table */
 
-/* Maps each nonterminal (HCFChoice) of the grammar to a HCFStringMap that
+/* Maps each nonterminal (HCFChoice) of the grammar to a HStringMap that
  * maps lookahead strings to productions (HCFSequence).
  */
 typedef struct HLLkTable_ {
@@ -23,13 +23,13 @@ typedef struct HLLkTable_ {
 const HCFSequence *h_llk_lookup(const HLLkTable *table, const HCFChoice *x,
                                 HInputStream lookahead)
 {
-  const HCFStringMap *row = h_hashtable_get(table->rows, x);
+  const HStringMap *row = h_hashtable_get(table->rows, x);
   assert(row != NULL);  // the table should have one row for each nonterminal
 
   assert(!row->epsilon_branch); // would match without looking at the input
                                 // XXX cases where this could be useful?
 
-  const HCFStringMap *m = row;
+  const HStringMap *m = row;
   while(m) {
     if(m->epsilon_branch) {     // input matched
       // assert: another lookahead would not bring a more specific match.
@@ -103,7 +103,7 @@ static void *combine_entries(HHashSet *workset, void *dst, const void *src)
 // add the mappings of src to dst, marking conflicts and adding the conflicting
 // values to workset.
 // note: reuses parts of src to build dst!
-static void stringmap_merge(HHashSet *workset, HCFStringMap *dst, HCFStringMap *src)
+static void stringmap_merge(HHashSet *workset, HStringMap *dst, HStringMap *src)
 {
   if(src->epsilon_branch) {
     if(dst->epsilon_branch)
@@ -135,10 +135,10 @@ static void stringmap_merge(HHashSet *workset, HCFStringMap *dst, HCFStringMap *
         continue;
 
       HCharKey c = (HCharKey)hte->key;
-      HCFStringMap *src_ = hte->value;
+      HStringMap *src_ = hte->value;
 
       if(src_) {
-        HCFStringMap *dst_ = h_hashtable_get(dst->char_branches, (void *)c);
+        HStringMap *dst_ = h_hashtable_get(dst->char_branches, (void *)c);
         if(dst_)
           stringmap_merge(workset, dst_, src_);
         else
@@ -149,7 +149,7 @@ static void stringmap_merge(HHashSet *workset, HCFStringMap *dst, HCFStringMap *
 }
 
 /* Generate entries for the productions of A in the given table row. */
-static int fill_table_row(size_t kmax, HCFGrammar *g, HCFStringMap *row,
+static int fill_table_row(size_t kmax, HCFGrammar *g, HStringMap *row,
                           const HCFChoice *A)
 {
   HHashSet *workset;
@@ -177,7 +177,7 @@ static int fill_table_row(size_t kmax, HCFGrammar *g, HCFStringMap *row,
         assert(rhs != CONFLICT);  // just to be sure there's no mixup
 
         // calculate predict set; let values map to rhs
-        HCFStringMap *pred = h_predict(k, g, A, rhs);
+        HStringMap *pred = h_predict(k, g, A, rhs);
         h_stringmap_replace(pred, NULL, rhs);
 
         // merge predict set into the row
@@ -220,7 +220,7 @@ static int fill_table(size_t kmax, HCFGrammar *g, HLLkTable *table)
       assert(a->type == HCF_CHOICE);
 
       // create table row for this nonterminal
-      HCFStringMap *row = h_stringmap_new(table->arena);
+      HStringMap *row = h_stringmap_new(table->arena);
       h_hashtable_put(table->rows, a, row);
 
       if(fill_table_row(kmax, g, row, a) < 0) {
@@ -339,10 +339,12 @@ HParseResult *h_llk_parse(HAllocator* mm__, const HParser* parser, HInputStream*
 
     // the top of stack is such that there will be a result...
     HParsedToken *tok;  // will hold result token
+    tok = h_arena_malloc(arena, sizeof(HParsedToken));
+    tok->index = stream->index;
+    tok->bit_offset = stream->bit_offset;
     if(x == mark) {
       // hit stack frame boundary...
       // wrap the accumulated parse result, this sequence is finished
-      tok = h_arena_malloc(arena, sizeof(HParsedToken));
       tok->token_type = TT_SEQUENCE;
       tok->seq = seq;
 
@@ -361,13 +363,13 @@ HParseResult *h_llk_parse(HAllocator* mm__, const HParser* parser, HInputStream*
       case HCF_END:
         if(!stream->overrun)
           goto no_parse;
+        h_arena_free(arena, tok);
         tok = NULL;
         break;
 
       case HCF_CHAR:
         if(input != x->chr)
           goto no_parse;
-        tok = h_arena_malloc(arena, sizeof(HParsedToken));
         tok->token_type = TT_UINT;
         tok->uint = x->chr;
         break;
@@ -377,7 +379,6 @@ HParseResult *h_llk_parse(HAllocator* mm__, const HParser* parser, HInputStream*
           goto no_parse;
         if(!charset_isset(x->charset, input))
           goto no_parse;
-        tok = h_arena_malloc(arena, sizeof(HParsedToken));
         tok->token_type = TT_UINT;
         tok->uint = input;
         break;
@@ -390,8 +391,6 @@ HParseResult *h_llk_parse(HAllocator* mm__, const HParser* parser, HInputStream*
 
     // 'tok' has been parsed; process it
 
-    // XXX set tok->index and tok->bit_offset (don't take directly from stream, cuz peek!)
-
     // perform token reshape if indicated
     if(x->reshape)
       tok = (HParsedToken *)x->reshape(make_result(arena, tok));
diff --git a/src/backends/packrat.c b/src/backends/packrat.c
index c5c9565f272caab47aeab2f59592bf93dd40d524..8aa1f8ed670502f4b59e9be6498d22eaa74723ad 100644
--- a/src/backends/packrat.c
+++ b/src/backends/packrat.c
@@ -3,14 +3,6 @@
 #include "../internal.h"
 #include "../parsers/parser_internal.h"
 
-static uint32_t djbhash(const uint8_t *buf, size_t len) {
-  uint32_t hash = 5381;
-  while (len--) {
-    hash = hash * 33 + *buf++;
-  }
-  return hash;
-}
-
 // short-hand for constructing HCachedResult's
 static HCachedResult *cached_result(const HParseState *state, HParseResult *result) {
   HCachedResult *ret = a_new(HCachedResult, 1);
@@ -214,7 +206,7 @@ void h_packrat_free(HParser *parser) {
 }
 
 static uint32_t cache_key_hash(const void* key) {
-  return djbhash(key, sizeof(HParserCacheKey));
+  return h_djbhash(key, sizeof(HParserCacheKey));
 }
 static bool cache_key_equal(const void* key1, const void* key2) {
   return memcmp(key1, key2, sizeof(HParserCacheKey)) == 0;
diff --git a/src/cfgrammar.c b/src/cfgrammar.c
index a69123073cb7f1cfed12355aada8ee7ad8c00d1b..199ef5f1295b96774d1cbd6b3d1499a8f739b9b1 100644
--- a/src/cfgrammar.c
+++ b/src/cfgrammar.c
@@ -18,12 +18,13 @@ HCFGrammar *h_cfgrammar_new(HAllocator *mm__)
   g->mm__   = mm__;
   g->arena  = h_new_arena(mm__, 0);     // default blocksize
   g->nts    = h_hashset_new(g->arena, h_eq_ptr, h_hash_ptr);
+  g->start  = NULL;
   g->geneps = NULL;
   g->first  = NULL;
   g->follow = NULL;
   g->kmax   = 0;    // will be increased as needed by ensure_k
 
-  HCFStringMap *eps = h_stringmap_new(g->arena);
+  HStringMap *eps = h_stringmap_new(g->arena);
   h_stringmap_put_epsilon(eps, INSET);
   g->singleton_epsilon = eps;
 
@@ -50,6 +51,11 @@ HCFGrammar *h_cfgrammar(HAllocator* mm__, const HParser *parser)
   if(desugared == NULL)
     return NULL;  // -> backend not suitable for this parser
 
+  return h_cfgrammar_(mm__, desugared);
+}
+
+HCFGrammar *h_cfgrammar_(HAllocator* mm__, HCFChoice *desugared)
+{
   HCFGrammar *g = h_cfgrammar_new(mm__);
 
   // recursively traverse the desugared form and collect all HCFChoices that
@@ -219,32 +225,34 @@ static void collect_geneps(HCFGrammar *g)
 }
 
 
-HCFStringMap *h_stringmap_new(HArena *a)
+HStringMap *h_stringmap_new(HArena *a)
 {
-  HCFStringMap *m = h_arena_malloc(a, sizeof(HCFStringMap));
+  HStringMap *m = h_arena_malloc(a, sizeof(HStringMap));
+  m->epsilon_branch = NULL;
+  m->end_branch = NULL;
   m->char_branches = h_hashtable_new(a, h_eq_ptr, h_hash_ptr);
   m->arena = a;
   return m;
 }
 
-void h_stringmap_put_end(HCFStringMap *m, void *v)
+void h_stringmap_put_end(HStringMap *m, void *v)
 {
   m->end_branch = v;
 }
 
-void h_stringmap_put_epsilon(HCFStringMap *m, void *v)
+void h_stringmap_put_epsilon(HStringMap *m, void *v)
 {
   m->epsilon_branch = v;
 }
 
-void h_stringmap_put_after(HCFStringMap *m, uint8_t c, HCFStringMap *ends)
+void h_stringmap_put_after(HStringMap *m, uint8_t c, HStringMap *ends)
 {
   h_hashtable_put(m->char_branches, (void *)char_key(c), ends);
 }
 
-void h_stringmap_put_char(HCFStringMap *m, uint8_t c, void *v)
+void h_stringmap_put_char(HStringMap *m, uint8_t c, void *v)
 {
-  HCFStringMap *node = h_stringmap_new(m->arena);
+  HStringMap *node = h_stringmap_new(m->arena);
   h_stringmap_put_epsilon(node, v);
   h_stringmap_put_after(m, c, node);
 }
@@ -252,8 +260,8 @@ void h_stringmap_put_char(HCFStringMap *m, uint8_t c, void *v)
 // helper for h_stringmap_update
 static void *combine_stringmap(void *v1, const void *v2)
 {
-  HCFStringMap *m1 = v1;
-  const HCFStringMap *m2 = v2;
+  HStringMap *m1 = v1;
+  const HStringMap *m2 = v2;
   if(!m1)
     m1 = h_stringmap_new(m2->arena);
   h_stringmap_update(m1, m2);
@@ -262,7 +270,7 @@ static void *combine_stringmap(void *v1, const void *v2)
 }
 
 /* Note: Does *not* reuse submaps from n in building m. */
-void h_stringmap_update(HCFStringMap *m, const HCFStringMap *n)
+void h_stringmap_update(HStringMap *m, const HStringMap *n)
 {
   if(n->epsilon_branch)
     m->epsilon_branch = n->epsilon_branch;
@@ -277,7 +285,7 @@ void h_stringmap_update(HCFStringMap *m, const HCFStringMap *n)
  * If old is NULL, replace all values in m with new.
  * If new is NULL, remove the respective values.
  */
-void h_stringmap_replace(HCFStringMap *m, void *old, void *new)
+void h_stringmap_replace(HStringMap *m, void *old, void *new)
 {
   if(!old) {
     if(m->epsilon_branch) m->epsilon_branch = new;
@@ -294,14 +302,14 @@ void h_stringmap_replace(HCFStringMap *m, void *old, void *new)
       if(hte->key == NULL)
         continue;
 
-      HCFStringMap *m_ = hte->value;
+      HStringMap *m_ = hte->value;
       if(m_)
         h_stringmap_replace(m_, old, new);
     }
   }
 }
 
-void *h_stringmap_get(const HCFStringMap *m, const uint8_t *str, size_t n, bool end)
+void *h_stringmap_get(const HStringMap *m, const uint8_t *str, size_t n, bool end)
 {
   for(size_t i=0; i<n; i++) {
     if(i==n-1 && end && m->end_branch)
@@ -313,20 +321,26 @@ void *h_stringmap_get(const HCFStringMap *m, const uint8_t *str, size_t n, bool
   return m->epsilon_branch;
 }
 
-bool h_stringmap_present(const HCFStringMap *m, const uint8_t *str, size_t n, bool end)
+bool h_stringmap_present(const HStringMap *m, const uint8_t *str, size_t n, bool end)
 {
   return (h_stringmap_get(m, str, n, end) != NULL);
 }
 
-bool h_stringmap_present_epsilon(const HCFStringMap *m)
+bool h_stringmap_present_epsilon(const HStringMap *m)
 {
   return (m->epsilon_branch != NULL);
 }
 
+bool h_stringmap_empty(const HStringMap *m)
+{
+  return (m->epsilon_branch == NULL
+          && m->end_branch == NULL
+          && h_hashtable_empty(m->char_branches));
+}
 
-const HCFStringMap *h_first(size_t k, HCFGrammar *g, const HCFChoice *x)
+const HStringMap *h_first(size_t k, HCFGrammar *g, const HCFChoice *x)
 {
-  HCFStringMap *ret;
+  HStringMap *ret;
   HCFSequence **p;
   uint8_t c;
 
@@ -372,18 +386,18 @@ const HCFStringMap *h_first(size_t k, HCFGrammar *g, const HCFChoice *x)
 }
 
 // helpers for h_first_seq, definitions below
-static bool is_singleton_epsilon(const HCFStringMap *m);
-static bool any_string_shorter(size_t k, const HCFStringMap *m);
+static bool is_singleton_epsilon(const HStringMap *m);
+static bool any_string_shorter(size_t k, const HStringMap *m);
 
 // pointer to functions like h_first_seq
-typedef const HCFStringMap *(*StringSetFun)(size_t, HCFGrammar *, HCFChoice **);
+typedef const HStringMap *(*StringSetFun)(size_t, HCFGrammar *, HCFChoice **);
 
 // helper for h_first_seq and h_follow
-static void stringset_extend(HCFGrammar *g, HCFStringMap *ret,
-                             size_t k, const HCFStringMap *as,
+static void stringset_extend(HCFGrammar *g, HStringMap *ret,
+                             size_t k, const HStringMap *as,
                              StringSetFun f, HCFChoice **tail);
 
-const HCFStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s)
+const HStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s)
 {
   // shortcut: the first set of the empty sequence, for any k, is {""}
   if(*s == NULL)
@@ -394,7 +408,7 @@ const HCFStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s)
   HCFChoice *x = s[0];
   HCFChoice **tail = s+1;
 
-  const HCFStringMap *first_x = h_first(k, g, x);
+  const HStringMap *first_x = h_first(k, g, x);
 
   // shortcut: if first_k(X) = {""}, just return first_k(tail)
   if(is_singleton_epsilon(first_x))
@@ -405,7 +419,7 @@ const HCFStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s)
     return first_x;
 
   // create a new result set and build up the set described above
-  HCFStringMap *ret = h_stringmap_new(g->arena);
+  HStringMap *ret = h_stringmap_new(g->arena);
 
   // extend the elements of first_k(X) up to length k from tail
   stringset_extend(g, ret, k, first_x, h_first_seq, tail);
@@ -413,14 +427,14 @@ const HCFStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s)
   return ret;
 }
 
-static bool is_singleton_epsilon(const HCFStringMap *m)
+static bool is_singleton_epsilon(const HStringMap *m)
 {
   return ( m->epsilon_branch
            && !m->end_branch
            && h_hashtable_empty(m->char_branches) );
 }
 
-static bool any_string_shorter(size_t k, const HCFStringMap *m)
+static bool any_string_shorter(size_t k, const HStringMap *m)
 {
   if(k==0)
     return false;
@@ -434,7 +448,7 @@ static bool any_string_shorter(size_t k, const HCFStringMap *m)
     for(HHashTableEntry *hte = &ht->contents[i]; hte; hte = hte->next) {
       if(hte->key == NULL)
         continue;
-      HCFStringMap *m_ = hte->value;
+      HStringMap *m_ = hte->value;
 
       // check subtree for strings shorter than k-1
       if(any_string_shorter(k-1, m_))
@@ -446,7 +460,7 @@ static bool any_string_shorter(size_t k, const HCFStringMap *m)
 }
 
 // helper for h_predict
-static void remove_all_shorter(size_t k, HCFStringMap *m)
+static void remove_all_shorter(size_t k, HStringMap *m)
 {
   if(k==0) return;
   m->epsilon_branch = NULL;
@@ -465,12 +479,12 @@ static void remove_all_shorter(size_t k, HCFStringMap *m)
 
 // h_follow adapted to the signature of StringSetFun
 static inline
-const HCFStringMap *h_follow_(size_t k, HCFGrammar *g, HCFChoice **s)
+const HStringMap *h_follow_(size_t k, HCFGrammar *g, HCFChoice **s)
 {
   return h_follow(k, g, *s);
 }
 
-const HCFStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x)
+const HStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x)
 {
   // consider all occurances of X in g
   // the follow set of X is the union of:
@@ -481,7 +495,7 @@ const HCFStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x)
   // first_k(tail follow_k(A)) =
   //   { a b | a <- first_k(tail), b <- follow_l(A), l=k-|a| }
 
-  HCFStringMap *ret;
+  HStringMap *ret;
 
   // shortcut: follow_0(X) is always {""}
   if(k==0)
@@ -519,7 +533,7 @@ const HCFStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x)
           if(*s == x) { // occurance found
             HCFChoice **tail = s+1;
 
-            const HCFStringMap *first_tail = h_first_seq(k, g, tail);
+            const HStringMap *first_tail = h_first_seq(k, g, tail);
 
             // extend the elems of first_k(tail) up to length k from follow(A)
             stringset_extend(g, ret, k, first_tail, h_follow_, &a);
@@ -532,15 +546,15 @@ const HCFStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x)
   return ret;
 }
 
-HCFStringMap *h_predict(size_t k, HCFGrammar *g,
+HStringMap *h_predict(size_t k, HCFGrammar *g,
                         const HCFChoice *A, const HCFSequence *rhs)
 {
-  HCFStringMap *ret = h_stringmap_new(g->arena);
+  HStringMap *ret = h_stringmap_new(g->arena);
 
   // predict_k(A -> rhs) =
   //   { ab | a <- first_k(rhs), b <- follow_k(A), |ab|=k }
   
-  const HCFStringMap *first_rhs = h_first_seq(k, g, rhs->items);
+  const HStringMap *first_rhs = h_first_seq(k, g, rhs->items);
 
   // casting the const off of A below. note: stringset_extend does
   // not touch this argument, only passes it through to h_follow
@@ -554,8 +568,8 @@ HCFStringMap *h_predict(size_t k, HCFGrammar *g,
 }
 
 // add the set { a b | a <- as, b <- f_l(S), l=k-|a| } to ret
-static void stringset_extend(HCFGrammar *g, HCFStringMap *ret,
-                             size_t k, const HCFStringMap *as,
+static void stringset_extend(HCFGrammar *g, HStringMap *ret,
+                             size_t k, const HStringMap *as,
                              StringSetFun f, HCFChoice **tail)
 {
   if(as->epsilon_branch) {
@@ -578,12 +592,12 @@ static void stringset_extend(HCFGrammar *g, HCFStringMap *ret,
       uint8_t c = key_char((HCharKey)hte->key);
       
       // follow the branch to find the set { a' | t a' <- as }
-      HCFStringMap *as_ = (HCFStringMap *)hte->value;
+      HStringMap *as_ = (HStringMap *)hte->value;
 
       // now the elements of ret that begin with t are given by
       // t { a b | a <- as_, b <- f_l(tail), l=k-|a|-1 }
       // so we can use recursion over k
-      HCFStringMap *ret_ = h_stringmap_new(g->arena);
+      HStringMap *ret_ = h_stringmap_new(g->arena);
       h_stringmap_put_after(ret, c, ret_);
 
       stringset_extend(g, ret_, k-1, as_, f, tail);
@@ -592,7 +606,7 @@ static void stringset_extend(HCFGrammar *g, HCFStringMap *ret,
 }
 
 
-static void pprint_char(FILE *f, char c)
+void h_pprint_char(FILE *f, char c)
 {
   switch(c) {
   case '"': fputs("\\\"", f); break;
@@ -616,7 +630,7 @@ static void pprint_charset_char(FILE *f, char c)
   case '"': fputc(c, f); break;
   case '-': fputs("\\-", f); break;
   case ']': fputs("\\-", f); break;
-  default:  pprint_char(f, c);
+  default:  h_pprint_char(f, c);
   }
 }
 
@@ -664,7 +678,7 @@ static HCFChoice **pprint_string(FILE *f, HCFChoice **x)
   for(; *x; x++) {
     if((*x)->type != HCF_CHAR)
       break;
-    pprint_char(f, (*x)->chr);
+    h_pprint_char(f, (*x)->chr);
   }
   fputc('"', f);
   return x;
@@ -675,7 +689,7 @@ void h_pprint_symbol(FILE *f, const HCFGrammar *g, const HCFChoice *x)
   switch(x->type) {
   case HCF_CHAR:
     fputc('"', f);
-    pprint_char(f, x->chr);
+    h_pprint_char(f, x->chr);
     fputc('"', f);
     break;
   case HCF_END:
@@ -800,7 +814,7 @@ void h_pprint_symbolset(FILE *file, const HCFGrammar *g, const HHashSet *set, in
 
 static bool
 pprint_stringset_elems(FILE *file, bool first, char *prefix, size_t n,
-                       const HCFStringMap *set)
+                       const HStringMap *set)
 {
   assert(n < BUFSIZE-4);
 
@@ -827,7 +841,7 @@ pprint_stringset_elems(FILE *file, bool first, char *prefix, size_t n,
       if(hte->key == NULL)
         continue;
       uint8_t c = key_char((HCharKey)hte->key);
-      HCFStringMap *ends = hte->value;
+      HStringMap *ends = hte->value;
 
       size_t n_ = n;
       switch(c) {
@@ -852,7 +866,7 @@ pprint_stringset_elems(FILE *file, bool first, char *prefix, size_t n,
   return first;
 }
 
-void h_pprint_stringset(FILE *file, const HCFStringMap *set, int indent)
+void h_pprint_stringset(FILE *file, const HStringMap *set, int indent)
 {
   int j;
   for(j=0; j<indent; j++) fputc(' ', file);
diff --git a/src/cfgrammar.h b/src/cfgrammar.h
index d2270ff08dbe296cf9d5d6d0152ccd307e77a019..57f6f68bf55ed574edbc6596f4c1321b24f39b58 100644
--- a/src/cfgrammar.h
+++ b/src/cfgrammar.h
@@ -16,7 +16,7 @@ typedef struct HCFGrammar_ {
 
   // constant set containing only the empty string.
   // this is only a member of HCFGrammar because it needs a pointer to arena.
-  const struct HCFStringMap_ *singleton_epsilon;
+  const struct HStringMap_ *singleton_epsilon;
 } HCFGrammar;
 
 
@@ -32,25 +32,26 @@ static inline uint8_t key_char(HCharKey k) { return (0xFF & k); }
  * input tokens.
  * Each path through the tree represents the string along its branches.
  */
-typedef struct HCFStringMap_ {
+typedef struct HStringMap_ {
   void *epsilon_branch;         // points to leaf value
   void *end_branch;             // points to leaf value
-  HHashTable *char_branches;    // maps to inner nodes (HCFStringMaps)
+  HHashTable *char_branches;    // maps to inner nodes (HStringMaps)
   HArena *arena;
-} HCFStringMap;
-
-HCFStringMap *h_stringmap_new(HArena *a);
-void h_stringmap_put_end(HCFStringMap *m, void *v);
-void h_stringmap_put_epsilon(HCFStringMap *m, void *v);
-void h_stringmap_put_after(HCFStringMap *m, uint8_t c, HCFStringMap *ends);
-void h_stringmap_put_char(HCFStringMap *m, uint8_t c, void *v);
-void h_stringmap_update(HCFStringMap *m, const HCFStringMap *n);
-void h_stringmap_replace(HCFStringMap *m, void *old, void *new);
-void *h_stringmap_get(const HCFStringMap *m, const uint8_t *str, size_t n, bool end);
-bool h_stringmap_present(const HCFStringMap *m, const uint8_t *str, size_t n, bool end);
-bool h_stringmap_present_epsilon(const HCFStringMap *m);
-
-static inline HCFStringMap *h_stringmap_get_char(const HCFStringMap *m, const uint8_t c)
+} HStringMap;
+
+HStringMap *h_stringmap_new(HArena *a);
+void h_stringmap_put_end(HStringMap *m, void *v);
+void h_stringmap_put_epsilon(HStringMap *m, void *v);
+void h_stringmap_put_after(HStringMap *m, uint8_t c, HStringMap *ends);
+void h_stringmap_put_char(HStringMap *m, uint8_t c, void *v);
+void h_stringmap_update(HStringMap *m, const HStringMap *n);
+void h_stringmap_replace(HStringMap *m, void *old, void *new);
+void *h_stringmap_get(const HStringMap *m, const uint8_t *str, size_t n, bool end);
+bool h_stringmap_present(const HStringMap *m, const uint8_t *str, size_t n, bool end);
+bool h_stringmap_present_epsilon(const HStringMap *m);
+bool h_stringmap_empty(const HStringMap *m);
+
+static inline HStringMap *h_stringmap_get_char(const HStringMap *m, const uint8_t c)
  { return h_hashtable_get(m->char_branches, (void *)char_key(c)); }
 
 
@@ -59,6 +60,9 @@ static inline HCFStringMap *h_stringmap_get_char(const HCFStringMap *m, const ui
  * A NULL return means we are unable to represent the parser as a CFG.
  */
 HCFGrammar *h_cfgrammar(HAllocator* mm__, const HParser *parser);
+HCFGrammar *h_cfgrammar_(HAllocator* mm__, HCFChoice *start);
+
+HCFGrammar *h_cfgrammar_new(HAllocator *mm__);
 
 /* Frees the given grammar and associated data.
  * Does *not* free parsers' CFG forms as created by h_desugar.
@@ -72,18 +76,18 @@ bool h_derives_epsilon(HCFGrammar *g, const HCFChoice *symbol);
 bool h_derives_epsilon_seq(HCFGrammar *g, HCFChoice **s);
 
 /* Compute first_k set of symbol x. Memoized. */
-const HCFStringMap *h_first(size_t k, HCFGrammar *g, const HCFChoice *x);
+const HStringMap *h_first(size_t k, HCFGrammar *g, const HCFChoice *x);
 
 /* Compute first_k set of sentential form s. s NULL-terminated. */
-const HCFStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s);
+const HStringMap *h_first_seq(size_t k, HCFGrammar *g, HCFChoice **s);
 
 /* Compute follow_k set of symbol x. Memoized. */
-const HCFStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x);
+const HStringMap *h_follow(size_t k, HCFGrammar *g, const HCFChoice *x);
 
 /* Compute the predict_k set of production "A -> rhs".
- * Always returns a newly-allocated HCFStringMap.
+ * Always returns a newly-allocated HStringMap.
  */
-HCFStringMap *h_predict(size_t k, HCFGrammar *g,
+HStringMap *h_predict(size_t k, HCFGrammar *g,
                         const HCFChoice *A, const HCFSequence *rhs);
 
 
@@ -92,4 +96,5 @@ void h_pprint_grammar(FILE *file, const HCFGrammar *g, int indent);
 void h_pprint_sequence(FILE *f, const HCFGrammar *g, const HCFSequence *seq);
 void h_pprint_symbol(FILE *f, const HCFGrammar *g, const HCFChoice *x);
 void h_pprint_symbolset(FILE *file, const HCFGrammar *g, const HHashSet *set, int indent);
-void h_pprint_stringset(FILE *file, const HCFStringMap *set, int indent);
+void h_pprint_stringset(FILE *file, const HStringMap *set, int indent);
+void h_pprint_char(FILE *file, char c);
diff --git a/src/datastructures.c b/src/datastructures.c
index a12707ef9758db93ad79dc052533b40cdd4edcbb..075b966bc7c67d3fc266b2bce2938b64f78862b4 100644
--- a/src/datastructures.c
+++ b/src/datastructures.c
@@ -147,6 +147,8 @@ void* h_hashtable_get(const HHashTable* ht, const void* key) {
   for (hte = &ht->contents[hashval & (ht->capacity - 1)];
        hte != NULL;
        hte = hte->next) {
+    if (hte->key == NULL)
+      continue;
     if (hte->hashval != hashval)
       continue;
     if (ht->equalFunc(key, hte->key))
@@ -232,6 +234,7 @@ int   h_hashtable_present(const HHashTable* ht, const void* key) {
   }
   return false;
 }
+
 void  h_hashtable_del(HHashTable* ht, const void* key) {
   HHashValue hashval = ht->hashFunc(key);
 #ifdef CONSISTENCY_CHECK
@@ -257,6 +260,7 @@ void  h_hashtable_del(HHashTable* ht, const void* key) {
     }
   }
 }
+
 void  h_hashtable_free(HHashTable* ht) {
   for (size_t i = 0; i < ht->capacity; i++) {
     HHashTableEntry *hten, *hte = &ht->contents[i];
@@ -272,11 +276,72 @@ void  h_hashtable_free(HHashTable* ht) {
   h_arena_free(ht->arena, ht->contents);
 }
 
+// helper for hte_equal
+static bool hte_same_length(HHashTableEntry *xs, HHashTableEntry *ys) {
+  while(xs && ys) {
+    xs=xs->next;
+    ys=ys->next;
+    // skip NULL keys (= element not present)
+    while(xs && xs->key == NULL) xs=xs->next;
+    while(ys && ys->key == NULL) ys=ys->next;
+  }
+  return (xs == ys);    // both NULL
+}
+
+// helper for hte_equal: are all elements of xs present in ys?
+static bool hte_subset(HEqualFunc eq, HHashTableEntry *xs, HHashTableEntry *ys)
+{
+  for(; xs; xs=xs->next) {
+    if(xs->key == NULL) continue;   // element not present
+
+    HHashTableEntry *hte;
+    for(hte=ys; hte; hte=hte->next) {
+      if(hte->key == xs->key) break; // assume an element is equal to itself
+      if(hte->hashval != xs->hashval) continue; // shortcut
+      if(eq(hte->key, xs->key)) break;
+    }
+    if(hte == NULL) return false;   // element not found
+  }
+  return true;                      // all found
+}
+
+// compare two lists of HHashTableEntries
+static inline bool hte_equal(HEqualFunc eq, HHashTableEntry *xs, HHashTableEntry *ys) {
+  return (hte_same_length(xs, ys) && hte_subset(eq, xs, ys));
+}
+
+/* Set equality of HHashSets.
+ * Obviously, 'a' and 'b' must use the same equality function.
+ * Not strictly necessary, but we also assume the same hash function.
+ */
+bool h_hashset_equal(const HHashSet *a, const HHashSet *b) {
+  if(a->capacity == b->capacity) {
+    // iterate over the buckets in parallel
+    for(size_t i=0; i < a->capacity; i++) {
+      if(!hte_equal(a->equalFunc, &a->contents[i], &b->contents[i]))
+        return false;
+    }
+  } else {
+    assert_message(0, "h_hashset_equal called on sets of different capacity");
+    // TODO implement general case
+  }
+  return true;
+}
+
 bool h_eq_ptr(const void *p, const void *q) {
   return (p==q);
 }
 
 HHashValue h_hash_ptr(const void *p) {
-  // XXX just djbhash it
+  // XXX just djbhash it? it does make the benchmark ~7% slower.
+  //return h_djbhash((const uint8_t *)&p, sizeof(void *));
   return (uintptr_t)p >> 4;
 }
+
+uint32_t h_djbhash(const uint8_t *buf, size_t len) {
+  uint32_t hash = 5381;
+  while (len--) {
+    hash = hash * 33 + *buf++;
+  }
+  return hash;
+}
diff --git a/src/hammer.c b/src/hammer.c
index 5f94142908f48f86a0dde79ccd376c2625063635..7d5b4e90b2edd224f6f28a57d0b65bacf2de94b7 100644
--- a/src/hammer.c
+++ b/src/hammer.c
@@ -30,6 +30,7 @@ static HParserBackendVTable *backends[PB_MAX + 1] = {
   &h__packrat_backend_vtable,
   &h__regex_backend_vtable,
   &h__llk_backend_vtable,
+  &h__lalr_backend_vtable,
 };
 
 
diff --git a/src/hammer.h b/src/hammer.h
index 455684cc92edbfbf9b9352625e373ca408f61261..a5ebcfff640bd33ba4459b1b59ea106033cb5eb2 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -34,11 +34,11 @@ typedef struct HParseState_ HParseState;
 typedef enum HParserBackend_ {
   PB_MIN = 0,
   PB_PACKRAT = PB_MIN, // PB_MIN is always the default.
-  PB_REGULAR,	// 
-  PB_LLk,	//
-  PB_LALR,	// Not Implemented
+  PB_REGULAR,
+  PB_LLk,
+  PB_LALR,
   PB_GLR,	// Not Implemented
-  PB_MAX = PB_LLk
+  PB_MAX = PB_LALR
 } HParserBackend;
 
 typedef enum HTokenType_ {
diff --git a/src/internal.h b/src/internal.h
index 926bf02a6e54da52cf193443a12d0e3c7547ef35..2f3018df2aebe3e61b2af74c3b4b5a70997211f2 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -219,6 +219,7 @@ struct HBitWriter_ {
 // Backends {{{
 extern HParserBackendVTable h__packrat_backend_vtable;
 extern HParserBackendVTable h__llk_backend_vtable;
+extern HParserBackendVTable h__lalr_backend_vtable;
 // }}}
 
 // TODO(thequux): Set symbol visibility for these functions so that they aren't exported.
@@ -271,9 +272,11 @@ typedef HHashTable HHashSet;
 #define h_hashset_empty(ht)      h_hashtable_empty(ht)
 #define h_hashset_del(ht,el)     h_hashtable_del(ht,el)
 #define h_hashset_free(ht)       h_hashtable_free(ht)
+bool h_hashset_equal(const HHashSet *a, const HHashSet *b);
 
 bool h_eq_ptr(const void *p, const void *q);
 HHashValue h_hash_ptr(const void *p);
+uint32_t h_djbhash(const uint8_t *buf, size_t len);
 
 typedef struct HCFSequence_ HCFSequence;
 
diff --git a/src/t_parser.c b/src/t_parser.c
index fa191510cdf7513d160e3c2e76f1c676401ca0db..a0e4040b2d5378c423b07d0699001b6a5018594e 100644
--- a/src/t_parser.c
+++ b/src/t_parser.c
@@ -405,7 +405,7 @@ static void test_not(gconstpointer backend) {
   g_check_parse_ok(not_2, (HParserBackend)GPOINTER_TO_INT(backend), "a+b", 3, "(u0x61 (u0x2b) u0x62)");
   g_check_parse_ok(not_2, (HParserBackend)GPOINTER_TO_INT(backend), "a++b", 4, "(u0x61 <2b.2b> u0x62)");
 }
-/*
+
 static void test_leftrec(gconstpointer backend) {
   HParser *a_ = h_ch('a');
 
@@ -416,7 +416,7 @@ static void test_leftrec(gconstpointer backend) {
   g_check_parse_ok(lr_, (HParserBackend)GPOINTER_TO_INT(backend), "aa", 2, "(u0x61 u0x61)");
   g_check_parse_ok(lr_, (HParserBackend)GPOINTER_TO_INT(backend), "aaa", 3, "((u0x61 u0x61) u0x61)");
 }
-*/
+
 static void test_rightrec(gconstpointer backend) {
   HParser *a_ = h_ch('a');
 
@@ -547,4 +547,42 @@ void register_parser_tests(void) {
   g_test_add_data_func("/core/parser/regex/epsilon_p", GINT_TO_POINTER(PB_REGULAR), test_epsilon_p);
   g_test_add_data_func("/core/parser/regex/attr_bool", GINT_TO_POINTER(PB_REGULAR), test_attr_bool);
   g_test_add_data_func("/core/parser/regex/ignore", GINT_TO_POINTER(PB_REGULAR), test_ignore);
+
+  g_test_add_data_func("/core/parser/lalr/token", GINT_TO_POINTER(PB_LALR), test_token);
+  g_test_add_data_func("/core/parser/lalr/ch", GINT_TO_POINTER(PB_LALR), test_ch);
+  g_test_add_data_func("/core/parser/lalr/ch_range", GINT_TO_POINTER(PB_LALR), test_ch_range);
+  g_test_add_data_func("/core/parser/lalr/int64", GINT_TO_POINTER(PB_LALR), test_int64);
+  g_test_add_data_func("/core/parser/lalr/int32", GINT_TO_POINTER(PB_LALR), test_int32);
+  g_test_add_data_func("/core/parser/lalr/int16", GINT_TO_POINTER(PB_LALR), test_int16);
+  g_test_add_data_func("/core/parser/lalr/int8", GINT_TO_POINTER(PB_LALR), test_int8);
+  g_test_add_data_func("/core/parser/lalr/uint64", GINT_TO_POINTER(PB_LALR), test_uint64);
+  g_test_add_data_func("/core/parser/lalr/uint32", GINT_TO_POINTER(PB_LALR), test_uint32);
+  g_test_add_data_func("/core/parser/lalr/uint16", GINT_TO_POINTER(PB_LALR), test_uint16);
+  g_test_add_data_func("/core/parser/lalr/uint8", GINT_TO_POINTER(PB_LALR), test_uint8);
+  g_test_add_data_func("/core/parser/lalr/int_range", GINT_TO_POINTER(PB_LALR), test_int_range);
+#if 0
+  g_test_add_data_func("/core/parser/lalr/float64", GINT_TO_POINTER(PB_LALR), test_float64);
+  g_test_add_data_func("/core/parser/lalr/float32", GINT_TO_POINTER(PB_LALR), test_float32);
+#endif
+  g_test_add_data_func("/core/parser/lalr/whitespace", GINT_TO_POINTER(PB_LALR), test_whitespace);
+  g_test_add_data_func("/core/parser/lalr/left", GINT_TO_POINTER(PB_LALR), test_left);
+  g_test_add_data_func("/core/parser/lalr/right", GINT_TO_POINTER(PB_LALR), test_right);
+  g_test_add_data_func("/core/parser/lalr/middle", GINT_TO_POINTER(PB_LALR), test_middle);
+  g_test_add_data_func("/core/parser/lalr/action", GINT_TO_POINTER(PB_LALR), test_action);
+  g_test_add_data_func("/core/parser/lalr/in", GINT_TO_POINTER(PB_LALR), test_in);
+  g_test_add_data_func("/core/parser/lalr/not_in", GINT_TO_POINTER(PB_LALR), test_not_in);
+  g_test_add_data_func("/core/parser/lalr/end_p", GINT_TO_POINTER(PB_LALR), test_end_p);
+  g_test_add_data_func("/core/parser/lalr/nothing_p", GINT_TO_POINTER(PB_LALR), test_nothing_p);
+  g_test_add_data_func("/core/parser/lalr/sequence", GINT_TO_POINTER(PB_LALR), test_sequence);
+  g_test_add_data_func("/core/parser/lalr/choice", GINT_TO_POINTER(PB_LALR), test_choice);
+  g_test_add_data_func("/core/parser/lalr/many", GINT_TO_POINTER(PB_LALR), test_many);
+  g_test_add_data_func("/core/parser/lalr/many1", GINT_TO_POINTER(PB_LALR), test_many1);
+  g_test_add_data_func("/core/parser/lalr/optional", GINT_TO_POINTER(PB_LALR), test_optional);
+  g_test_add_data_func("/core/parser/lalr/sepBy", GINT_TO_POINTER(PB_LALR), test_sepBy);
+  g_test_add_data_func("/core/parser/lalr/sepBy1", GINT_TO_POINTER(PB_LALR), test_sepBy1);
+  g_test_add_data_func("/core/parser/lalr/epsilon_p", GINT_TO_POINTER(PB_LALR), test_epsilon_p);
+  g_test_add_data_func("/core/parser/lalr/attr_bool", GINT_TO_POINTER(PB_LALR), test_attr_bool);
+  g_test_add_data_func("/core/parser/lalr/ignore", GINT_TO_POINTER(PB_LALR), test_ignore);
+  g_test_add_data_func("/core/parser/lalr/leftrec", GINT_TO_POINTER(PB_LALR), test_leftrec);
+  g_test_add_data_func("/core/parser/lalr/rightrec", GINT_TO_POINTER(PB_LALR), test_rightrec);
 }
diff --git a/src/test_suite.h b/src/test_suite.h
index 168ab641ba7968730deea69ad8aa0df09b47650c..fc008e7fb96b6524b6298f6d27e7b45e4c7c5b3a 100644
--- a/src/test_suite.h
+++ b/src/test_suite.h
@@ -153,7 +153,7 @@
   } while(0)
 
 #define g_check_stringmap_absent(table, key) do {			\
-    bool end = (key[strlen(key)-2] == '$');				\
+    bool end = (key[strlen(key)-1] == '$');				\
     if(h_stringmap_present(table, (uint8_t *)key, strlen(key), end)) {	\
       g_test_message("Check failed: \"%s\" shouldn't have been in map, but was", key); \
       g_test_fail();							\