diff --git a/src/Makefile b/src/Makefile
index 870aad5c37e037c39d8d829336ff5b16d5d23eb3..13cbf84b6180880ff467cd9077ba11a706bf0cd1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -41,6 +41,7 @@ HAMMER_PARTS := \
 	system_allocator.o \
 	benchmark.o \
 	cfgrammar.o \
+	actions.o \
 	$(PARSERS:%=parsers/%.o) \
 	$(BACKENDS:%=backends/%.o)
 
diff --git a/src/cfgrammar.c b/src/cfgrammar.c
index 67361c836b62f4f1c4be0cd82a892e6f221fe41c..d3ee9c64417bb15b31f7ad0f81c3a42224ce05b3 100644
--- a/src/cfgrammar.c
+++ b/src/cfgrammar.c
@@ -37,16 +37,6 @@ static void collect_nts(HCFGrammar *grammar, HCFChoice *symbol);
 static void collect_geneps(HCFGrammar *grammar);
 
 
-// XXX to be consolidated with glue.c when merged upstream
-const HParsedToken *h_act_first(const HParseResult *p)
-{
-  assert(p->ast);
-  assert(p->ast->token_type == TT_SEQUENCE);
-  assert(p->ast->seq->used > 0);
-
-  return p->ast->seq->elements[0];
-}
-
 /* Convert 'parser' into CFG representation by desugaring and compiling the set
  * of nonterminals.
  * A NULL return means we are unable to represent the parser as a CFG.
diff --git a/src/hammer.h b/src/hammer.h
index 45f0dda96884e4e81f976c758c51bcdfb77544d3..b872ee6a75379334b0f72c4f1212389a9b3edd85 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -596,6 +596,14 @@ const uint8_t* h_bit_writer_get_buffer(HBitWriter* w, size_t *len);
  */
 void h_bit_writer_free(HBitWriter* w);
 
+// General-purpose actions for use with h_action
+// XXX to be consolidated with glue.h when merged upstream
+const HParsedToken *h_act_first(const HParseResult *p);
+const HParsedToken *h_act_second(const HParseResult *p);
+const HParsedToken *h_act_last(const HParseResult *p);
+const HParsedToken *h_act_flatten(const HParseResult *p);
+const HParsedToken *h_act_ignore(const HParseResult *p);
+
 // {{{ Benchmark functions
 HAMMER_FN_DECL(HBenchmarkResults *, h_benchmark, HParser* parser, HParserTestcase* testcases);
 void h_benchmark_report(FILE* stream, HBenchmarkResults* results);
diff --git a/src/parsers/ignoreseq.c b/src/parsers/ignoreseq.c
index e4bf43238e981a398d4e8cb0e2fddb3517870d07..6a61f3ead5436fb675412df5f459c2e1406f351f 100644
--- a/src/parsers/ignoreseq.c
+++ b/src/parsers/ignoreseq.c
@@ -31,18 +31,6 @@ static HParseResult* parse_ignoreseq(void* env, HParseState *state) {
   return res;
 }
 
-extern const HParsedToken *h_act_first(const HParseResult *p);
-extern const HParsedToken *h_act_last(const HParseResult *p);
-
-// XXX to be consolidated with glue.c when merged upstream
-const HParsedToken *h_act_second(const HParseResult *p) {
-  assert(p->ast);
-  assert(p->ast->token_type == TT_SEQUENCE);
-  assert(p->ast->seq->used > 0);
-
-  return p->ast->seq->elements[1];
-}
-
 static HCFChoice* desugar_ignoreseq(HAllocator *mm__, void *env) {
   HIgnoreSeq *seq = (HIgnoreSeq*)env;
   HCFSequence *hseq = h_new(HCFSequence, 1);
diff --git a/src/parsers/many.c b/src/parsers/many.c
index d26eec933fe9d34a20cea470b0b8abd8cc563e46..6f6e8591a2903abdb1a6c2d59783add9aa0823a1 100644
--- a/src/parsers/many.c
+++ b/src/parsers/many.c
@@ -56,35 +56,6 @@ static bool many_isValidCF(void *env) {
 	  repeat->sep->vtable->isValidCF(repeat->sep->env));
 }
 
-static void act_flatten_(HCountedArray *seq, const HParsedToken *tok) {
-  if(tok == NULL) {
-    return;
-  } else if(tok->token_type == TT_SEQUENCE) {
-    size_t i;
-    for(i=0; i<tok->seq->used; i++)
-      act_flatten_(seq, tok->seq->elements[i]);
-  } else {
-    h_carray_append(seq, (HParsedToken *)tok);
-  }
-}
-
-const HParsedToken *h_act_flatten(const HParseResult *p) {
-  HCountedArray *seq = h_carray_new(p->arena);
-
-  act_flatten_(seq, p->ast);
-
-  HParsedToken *res = a_new_(p->arena, HParsedToken, 1);
-  res->token_type = TT_SEQUENCE;
-  res->seq = seq;
-  res->index = p->ast->index;
-  res->bit_offset = p->ast->bit_offset;
-  return res;
-}
-
-const HParsedToken *h_act_ignore(const HParseResult *p) {
-  return NULL;
-}
-
 static HCFChoice* desugar_many(HAllocator *mm__, void *env) {
   HRepeat *repeat = (HRepeat*)env;
   if(repeat->count > 1) {
diff --git a/src/parsers/whitespace.c b/src/parsers/whitespace.c
index f9fa59f6fb15ec923f956042e3abbab591d9129a..73c558ef7d0a010953420bb72628008b324cd4d6 100644
--- a/src/parsers/whitespace.c
+++ b/src/parsers/whitespace.c
@@ -17,15 +17,6 @@ static HParseResult* parse_whitespace(void* env, HParseState *state) {
 
 static const char SPACE_CHRS[6] = {' ', '\f', '\n', '\r', '\t', '\v'};
 
-// XXX to be consolidated with glue.c when merged upstream
-const HParsedToken *h_act_last(const HParseResult *p) {
-  assert(p->ast);
-  assert(p->ast->token_type == TT_SEQUENCE);
-  assert(p->ast->seq->used > 0);
-
-  return p->ast->seq->elements[p->ast->seq->used-1];
-}
-
 static HCFChoice* desugar_whitespace(HAllocator *mm__, void *env) {
   HCFChoice *ws = h_new(HCFChoice, 1);
   ws->type = HCF_CHOICE;