diff --git a/src/backends/lr.c b/src/backends/lr.c
index c3062d5c652a0f76a0fdf18195af7583ae7856f6..0ad45693f1646e237b02a9b88b6ba18e90d30c28 100644
--- a/src/backends/lr.c
+++ b/src/backends/lr.c
@@ -190,18 +190,12 @@ HLREngine *h_lrengine_new(HArena *arena, HArena *tarena, const HLRTable *table)
   return engine;
 }
 
-void h_lrengine_step(HLREngine *engine, HInputStream *stream)
+const HLRAction *h_lrengine_action(HLREngine *engine, HInputStream *stream)
 {
-  // short-hand names
-  HSlist *left = engine->left;
   HSlist *right = engine->right;
   HArena *arena = engine->arena;
   HArena *tarena = engine->tarena;
 
-  // stack layout:
-  // on the left stack, we put pairs:  (saved state, semantic value)
-  // on the right stack, we put pairs: (symbol, semantic value)
-
   // make sure there is input on the right stack
   if(h_slist_empty(right)) {
     // XXX use statically-allocated terminal symbols
@@ -230,6 +224,18 @@ void h_lrengine_step(HLREngine *engine, HInputStream *stream)
 
   // table lookup
   const HLRAction *action = h_lr_lookup(engine->table, engine->state, symbol);
+
+  return action;
+}
+
+void h_lrengine_step(HLREngine *engine, const HLRAction *action)
+{
+  // short-hand names
+  HSlist *left = engine->left;
+  HSlist *right = engine->right;
+  HArena *arena = engine->arena;
+  HArena *tarena = engine->tarena;
+
   if(action == NULL) {
     // no handle recognizable in input, terminate
     engine->running = false;
@@ -313,7 +319,7 @@ HParseResult *h_lr_parse(HAllocator* mm__, const HParser* parser, HInputStream*
 
   // run while the recognizer finds handles in the input
   while(engine->running)
-    h_lrengine_step(engine, stream);
+    h_lrengine_step(engine, h_lrengine_action(engine, stream));
 
   HParseResult *result = h_lrengine_result(engine);
   if(!result)
diff --git a/src/backends/lr.h b/src/backends/lr.h
index afd4042636f7fc47e677a0c32dd8b35dd8673159..9312237345ef3966ec147c5f4060bf25968c6587 100644
--- a/src/backends/lr.h
+++ b/src/backends/lr.h
@@ -61,8 +61,13 @@ typedef struct HLREnhGrammar_ {
 
 typedef struct HLREngine_ {
   const HLRTable *table;
+
+  // stack layout:
+  // on the left stack, we put pairs:  (saved state, semantic value)
+  // on the right stack, we put pairs: (symbol, semantic value)
   HSlist *left;     // left stack; reductions happen here
   HSlist *right;    // right stack; input appears here
+
   size_t state;
   bool running;
   HArena *arena;    // will hold the results
@@ -116,7 +121,8 @@ int h_lalr_compile(HAllocator* mm__, HParser* parser, const void* params);
 void h_lalr_free(HParser *parser);
 
 const HLRAction *h_lr_lookup(const HLRTable *table, size_t state, const HCFChoice *symbol);
-void h_lrengine_step(HLREngine *engine, HInputStream *stream);
+const HLRAction *h_lrengine_action(HLREngine *engine, HInputStream *stream);
+void h_lrengine_step(HLREngine *engine, const HLRAction *action);
 HParseResult *h_lrengine_result(HLREngine *engine);
 HParseResult *h_lr_parse(HAllocator* mm__, const HParser* parser, HInputStream* stream);