diff --git a/examples/dns.c b/examples/dns.c
index 5effb8951a60c9c14b1e3d9b8008d88eabf5bcf5..63df3a443377dfd93717f091d107ffbccb41ac05 100644
--- a/examples/dns.c
+++ b/examples/dns.c
@@ -15,7 +15,7 @@
 // Validations
 ///
 
-bool validate_hdzero(HParseResult *p) {
+bool validate_hdzero(HParseResult *p, void* user_data) {
   if (TT_UINT != p->ast->token_type)
     return false;
   return (0 == p->ast->uint);
@@ -25,7 +25,7 @@ bool validate_hdzero(HParseResult *p) {
  * Every DNS message should have QDCOUNT entries in the question
  * section, and ANCOUNT+NSCOUNT+ARCOUNT resource records.
  */
-bool validate_message(HParseResult *p) {
+bool validate_message(HParseResult *p, void* user_data) {
   if (TT_SEQUENCE != p->ast->token_type)
     return false;
 
diff --git a/examples/dns_common.c b/examples/dns_common.c
index 3cdd04e0be2b8e1fce4e0a02b008c63f2c26d059..bf934d6184190d419495aa6011118d5a67e4f7fa 100644
--- a/examples/dns_common.c
+++ b/examples/dns_common.c
@@ -10,7 +10,7 @@ H_ACT_APPLY(act_index0, h_act_index, 0)
 /**
  * A label can't be more than 63 characters.
  */
-bool validate_label(HParseResult *p) {
+bool validate_label(HParseResult *p, void* user_data) {
   if (TT_SEQUENCE != p->ast->token_type)
     return false;
   return (64 > p->ast->seq->used);
diff --git a/examples/rr.c b/examples/rr.c
index 4a7c4be687afd66b22b5c2a6bf76ea02c8bc444d..c179922d664986be6277748c55e9050d1bfcb687 100644
--- a/examples/rr.c
+++ b/examples/rr.c
@@ -11,7 +11,7 @@
 // Validations and Semantic Actions
 ///
 
-bool validate_null(HParseResult *p) {
+bool validate_null(HParseResult *p, void* user_data) {
   if (TT_SEQUENCE != p->ast->token_type)
     return false;
   return (65536 > p->ast->seq->used);
diff --git a/src/backends/llk.c b/src/backends/llk.c
index 39ac07a6740f0e1d4febabea6736fab6f168a9a9..2bc39daf92b371b3b22b783623442eee36053bc0 100644
--- a/src/backends/llk.c
+++ b/src/backends/llk.c
@@ -375,7 +375,7 @@ HParseResult *h_llk_parse(HAllocator* mm__, const HParser* parser, HInputStream*
       tok = (HParsedToken *)x->reshape(make_result(arena, tok), x->user_data);
 
     // call validation and semantic action, if present
-    if(x->pred && !x->pred(make_result(tarena, tok)))
+    if(x->pred && !x->pred(make_result(tarena, tok), x->user_data))
       goto no_parse;    // validation failed -> no parse
     if(x->action)
       tok = (HParsedToken *)x->action(make_result(arena, tok), x->user_data);
diff --git a/src/backends/lr.c b/src/backends/lr.c
index 3739ec9bad306bb67a747a866a9ad3df2d526791..e7f237756361303102440700af4ceb5fcfb5abdf 100644
--- a/src/backends/lr.c
+++ b/src/backends/lr.c
@@ -310,7 +310,7 @@ bool h_lrengine_step(HLREngine *engine, const HLRAction *action)
       value = (HParsedToken *)symbol->reshape(make_result(arena, value), symbol->user_data);
 
     // call validation and semantic action, if present
-    if(symbol->pred && !symbol->pred(make_result(tarena, value)))
+    if(symbol->pred && !symbol->pred(make_result(tarena, value), symbol->user_data))
       return false;     // validation failed -> no parse; terminate
     if(symbol->action)
       value = (HParsedToken *)symbol->action(make_result(arena, value), symbol->user_data);
diff --git a/src/glue.h b/src/glue.h
index 7486e46c57c2a4a832159e171395ea93bc9c3dc1..74963b066b964125cec09603ed35542df1130b55 100644
--- a/src/glue.h
+++ b/src/glue.h
@@ -58,17 +58,19 @@
 #define H_RULE(rule, def)  HParser *rule = def
 #define H_ARULE(rule, def) HParser *rule = h_action(def, act_ ## rule, NULL)
 #define H_VRULE(rule, def) HParser *rule = \
-    h_attr_bool(def, validate_ ## rule)
+    h_attr_bool(def, validate_ ## rule, NULL)
 #define H_VARULE(rule, def) HParser *rule = \
-    h_attr_bool(h_action(def, act_ ## rule, NULL), validate_ ## rule)
+    h_attr_bool(h_action(def, act_ ## rule, NULL), validate_ ## rule, NULL)
 #define H_AVRULE(rule, def) HParser *rule = \
-    h_action(h_attr_bool(def, validate_ ## rule), act_ ## rule, NULL)
+    h_action(h_attr_bool(def, validate_ ## rule, NULL), act_ ## rule, NULL)
 #define H_ADRULE(rule, def, data) HParser *rule =	\
     h_action(def, act_ ## rule, data)
+#define H_VDRULE(rule, def, data) HParser *rule =	\
+    h_attr_bool(def, validate_ ## rule, data)
 #define H_VADRULE(rule, def, data) HParser *rule =		\
-    h_attr_bool(h_action(def, act_ ## rule, data), validate_ ## rule)
+    h_attr_bool(h_action(def, act_ ## rule, data), validate_ ## rule, data)
 #define H_AVDRULE(rule, def, data) HParser *rule =		\
-    h_action(h_attr_bool(def, validate_ ## rule), act_ ## rule, data)
+    h_action(h_attr_bool(def, validate_ ## rule, data), act_ ## rule, data)
 
 
 //
diff --git a/src/hammer.h b/src/hammer.h
index bd3ae02290855006d80b8005ebac9dc01af4b902..541e38df13f4c0f044faebd058de6c70590d9a61 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -120,7 +120,7 @@ typedef HParsedToken* (*HAction)(const HParseResult *p, void* user_data);
  * attr_bool() parser. It can be any (user-defined) function that takes
  * a HParseResult* and returns true or false. 
  */
-typedef bool (*HPredicate)(HParseResult *p);
+typedef bool (*HPredicate)(HParseResult *p, void* user_data);
 
 typedef struct HCFChoice_ HCFChoice;
 typedef struct HRVMProg_ HRVMProg;
@@ -515,7 +515,7 @@ HAMMER_FN_DECL(HParser*, h_length_value, const HParser* length, const HParser* v
  * 
  * Result token type: p's result type if pred succeeded, NULL otherwise.
  */
-HAMMER_FN_DECL(HParser*, h_attr_bool, const HParser* p, HPredicate pred);
+HAMMER_FN_DECL(HParser*, h_attr_bool, const HParser* p, HPredicate pred, void* user_data);
 
 /**
  * The 'and' parser asserts that a conditional syntax is satisfied, 
diff --git a/src/parsers/attr_bool.c b/src/parsers/attr_bool.c
index fc980b24f8eaff2fbf29da79cc630ac0a6cdd988..e8359ab03e06e681061dd75788d6ca6bb6e9b89b 100644
--- a/src/parsers/attr_bool.c
+++ b/src/parsers/attr_bool.c
@@ -4,13 +4,14 @@
 typedef struct {
   const HParser *p;
   HPredicate pred;
+  void* user_data;
 } HAttrBool;
 
 static HParseResult* parse_attr_bool(void *env, HParseState *state) {
   HAttrBool *a = (HAttrBool*)env;
   HParseResult *res = h_do_parse(a->p, state);
   if (res && res->ast) {
-    if (a->pred(res))
+    if (a->pred(res, a->user_data))
       return res;
     else
       return NULL;
@@ -42,12 +43,13 @@ static void desugar_ab(HAllocator *mm__, HCFStack *stk__, void *env) {
     } HCFS_END_SEQ();
     HCFS_THIS_CHOICE->pred = a->pred;
     HCFS_THIS_CHOICE->reshape = h_act_first;
+    HCFS_THIS_CHOICE->user_data = a->user_data;
   } HCFS_END_CHOICE();
 }
 
 static bool h_svm_action_attr_bool(HArena *arena, HSVMContext *ctx, void* arg) {
   HParseResult res;
-  HPredicate pred = arg;
+  HAttrBool *ab = arg;
   assert(ctx->stack_count >= 1);
   if (ctx->stack[ctx->stack_count-1]->token_type != TT_MARK) {
     assert(ctx->stack_count >= 2 && ctx->stack[ctx->stack_count-2]->token_type == TT_MARK);
@@ -59,7 +61,7 @@ static bool h_svm_action_attr_bool(HArena *arena, HSVMContext *ctx, void* arg) {
     res.ast = NULL;
   }
   res.arena = arena;
-  return pred(&res);
+  return ab->pred(&res, ab->user_data);
 }
 
 static bool ab_ctrvm(HRVMProg *prog, void *env) {
@@ -67,7 +69,7 @@ static bool ab_ctrvm(HRVMProg *prog, void *env) {
   h_rvm_insert_insn(prog, RVM_PUSH, 0);
   if (!h_compile_regex(prog, ab->p))
     return false;
-  h_rvm_insert_insn(prog, RVM_ACTION, h_rvm_create_action(prog, h_svm_action_attr_bool, ab->pred));
+  h_rvm_insert_insn(prog, RVM_ACTION, h_rvm_create_action(prog, h_svm_action_attr_bool, ab));
   return true;
 }
 
@@ -80,12 +82,13 @@ static const HParserVtable attr_bool_vt = {
 };
 
 
-HParser* h_attr_bool(const HParser* p, HPredicate pred) {
-  return h_attr_bool__m(&system_allocator, p, pred);
+HParser* h_attr_bool(const HParser* p, HPredicate pred, void* user_data) {
+  return h_attr_bool__m(&system_allocator, p, pred, user_data);
 }
-HParser* h_attr_bool__m(HAllocator* mm__, const HParser* p, HPredicate pred) { 
+HParser* h_attr_bool__m(HAllocator* mm__, const HParser* p, HPredicate pred, void* user_data) { 
   HAttrBool *env = h_new(HAttrBool, 1);
   env->p = p;
   env->pred = pred;
+  env->user_data = user_data;
   return h_new_parser(mm__, &attr_bool_vt, env);
 }
diff --git a/src/t_parser.c b/src/t_parser.c
index 292d1c456fa932265ddc834de0132bbad349f439..12edba93b6b65d94dd2d11bf8a9c1d854de28376 100644
--- a/src/t_parser.c
+++ b/src/t_parser.c
@@ -365,7 +365,7 @@ static void test_epsilon_p(gconstpointer backend) {
   g_check_parse_match(epsilon_p_3, (HParserBackend)GPOINTER_TO_INT(backend), "a", 1, "(u0x61)");
 }
 
-bool validate_test_ab(HParseResult *p) {
+bool validate_test_ab(HParseResult *p, void* user_data) {
   if (TT_SEQUENCE != p->ast->token_type) 
     return false;
   if (TT_UINT != p->ast->seq->elements[0]->token_type)
@@ -377,7 +377,8 @@ bool validate_test_ab(HParseResult *p) {
 
 static void test_attr_bool(gconstpointer backend) {
   const HParser *ab_ = h_attr_bool(h_many1(h_choice(h_ch('a'), h_ch('b'), NULL)),
-				   validate_test_ab);
+				   validate_test_ab,
+				   NULL);
 
   g_check_parse_match(ab_, (HParserBackend)GPOINTER_TO_INT(backend), "aa", 2, "(u0x61 u0x61)");
   g_check_parse_match(ab_, (HParserBackend)GPOINTER_TO_INT(backend), "bb", 2, "(u0x62 u0x62)");