diff --git a/src/bindings/php/Tests/ActionTest.php b/src/bindings/php/Tests/ActionTest.php
index 0b7540d0e0ce367cf532e67c7bcb890817fa2982..619ea4a2b0daa4114f2187ddccc6fde47f0380c7 100644
--- a/src/bindings/php/Tests/ActionTest.php
+++ b/src/bindings/php/Tests/ActionTest.php
@@ -11,21 +11,21 @@ class ActionTest extends PHPUnit_Framework_TestCase
     }
     protected function setUp()
     {
-        //$this->parser = h_action(sequence(choice(ch("a"), ch("A")), choice(ch("b"), ch("B"))), "actTest");
+        $this->parser = h_action(sequence(choice(ch("a"), ch("A")), choice(ch("b"), ch("B"))), "actTest");
     }
     public function testSuccess()
     {
-        /* $result1 = h_parse($this->parser, "ab"); */
-        /* $result2 = h_parse($this->parser, "AB"); */
-        /* $result3 = h_parse($this->parser, "aB"); */
-        /* $this->assertEquals("AB", $result1); */
-        /* $this->assertEquals("AB", $result2); */
-        /* $this->assertEquals("AB", $result3); */
+        $result1 = h_parse($this->parser, "ab");
+        $result2 = h_parse($this->parser, "AB");
+        $result3 = h_parse($this->parser, "aB");
+        $this->assertEquals("AB", $result1);
+        $this->assertEquals("AB", $result2);
+        $this->assertEquals("AB", $result3);
     }
     public function testFailure()
     {
-        //$result = h_parse($this->parser, "XX");
-        //$this->assertEquals(NULL, $result);
+        $result = h_parse($this->parser, "XX");
+        $this->assertEquals(NULL, $result);
     }
 }
 
diff --git a/src/bindings/php/hammer.i b/src/bindings/php/hammer.i
index 97d7e648e459e7ca101944258731d81d2ad97adf..67f6dd52a5e01294542d96588b3dc03fab09e6af 100644
--- a/src/bindings/php/hammer.i
+++ b/src/bindings/php/hammer.i
@@ -1,4 +1,5 @@
 %module hammer;
+%include "exception.i";
 
 %ignore HCountedArray_;
 
@@ -13,7 +14,7 @@
 %inline {
   struct HParsedToken_;
   struct HParseResult_;
-  static zval* hpt_to_php(const struct HParsedToken_ *token);
+  void hpt_to_php(zval *return_value, const struct HParsedToken_ *token);
   
   static struct HParsedToken_* call_action(const struct HParseResult_ *p, void* user_data);
  }
@@ -37,18 +38,17 @@
     for (i=0; i<size; i++) {
       zval **data;
       if (zend_hash_index_find(arr, i, (void**)&data) == FAILURE) {
-	// FIXME raise some error
-	arg1 = NULL;
+	SWIG_exception(SWIG_IndexError, "index in parser array out of bounds");
+	$1 = NULL;
       } else {
 	res = SWIG_ConvertPtr(*data, &($1[i]), SWIGTYPE_p_HParser_, 0 | 0);
 	if (!SWIG_IsOK(res)) {
-	  // TODO do we not *have* SWIG_TypeError?
-	  SWIG_exception_fail(res, "that wasn't an HParser");
+	  SWIG_exception(SWIG_TypeError, "that wasn't an HParser");
 	}
       }
     } 
   } else {
-    // FIXME raise some error
+    SWIG_exception(SWIG_TypeError, "that wasn't an array of HParsers");
     $1 = NULL;
   }
  }
@@ -74,43 +74,11 @@
 */
 %typemap(out) struct HParseResult_* {
   if ($1 == NULL) {
-    // TODO: raise parse failure
+    /* If we want parse errors to be exceptions, this is the place to do it */
+    //SWIG_exception(SWIG_TypeError, "typemap: should have been an HParseResult*, was NULL");
     RETVAL_NULL();
   } else {
-    if ($1->ast == NULL) {
-      RETVAL_NULL();
-    } else {
-      switch($1->ast->token_type) {
-      case TT_NONE:
-	RETVAL_NULL();
-	break;
-      case TT_BYTES:
-	RETVAL_STRINGL((char*)$1->ast->token_data.bytes.token, $1->ast->token_data.bytes.len, 1);
-	break;
-      case TT_SINT:
-	RETVAL_LONG($1->ast->token_data.sint);
-	break;
-      case TT_UINT:
-	RETVAL_LONG($1->ast->token_data.uint);
-	break;
-      case TT_SEQUENCE:
-	array_init($result);
-	for (int i=0; i < $1->ast->token_data.seq->used; i++) {
-	  add_next_index_zval($result, hpt_to_php($1->ast->token_data.seq->elements[i]));
-	}
-	break;
-      default:
-	/* if (token->token_type == h_tt_php) { */
-	/* 	ZEND_REGISTER_RESOURCE(return_value, token->token_data.user, le_swig__p_void); // it's a void*, what else could I do with it? */
-	/* 	return return_value; */
-	/* } else { */
-	/* 	// I guess that's a python thing */
-	/* 	//return (zval*)SWIG_NewPointerObj((void*)token, SWIGTYPE_p_HParsedToken_, 0 | 0); */
-	/* 	// TODO: support registry */
-	/* } */
-	break;
-      }
-    }
+    hpt_to_php($result, $1->ast);
   }
  }
 /*
@@ -126,41 +94,44 @@
 %include "../swig/hammer.i";
 
 %inline {
-  static zval* hpt_to_php(const HParsedToken *token) {
-    zval *ret;
-    ALLOC_INIT_ZVAL(ret);
-    if (token == NULL) {
-      ZVAL_NULL(ret);
-      return ret;
+  void hpt_to_php(zval *return_value, const HParsedToken *token) {
+    if (!token) {
+      RETVAL_NULL();
+      return;
     }
     switch (token->token_type) {
     case TT_NONE:
-      ZVAL_NULL(ret);
-      return ret;
+      RETVAL_NULL();
+      break;
     case TT_BYTES:
-      ZVAL_STRINGL(ret, (char*)token->token_data.bytes.token, token->token_data.bytes.len, 1);
-      return ret;
+      RETVAL_STRINGL((char*)token->token_data.bytes.token, token->token_data.bytes.len, 1);
+      break;
     case TT_SINT:
-      ZVAL_LONG(ret, token->token_data.sint);
-      return ret;
+      RETVAL_LONG(token->token_data.sint);
+      break;
     case TT_UINT:
-      ZVAL_LONG(ret, token->token_data.uint);
-      return ret;
+      RETVAL_LONG(token->token_data.uint);
+      break;
     case TT_SEQUENCE:
-      array_init(ret);
+      array_init(return_value);
       for (int i=0; i < token->token_data.seq->used; i++) {
-       add_next_index_zval(ret, hpt_to_php(token->token_data.seq->elements[i]));
+	zval *tmp;
+	ALLOC_INIT_ZVAL(tmp);
+	hpt_to_php(tmp, token->token_data.seq->elements[i]);
+	add_next_index_zval(return_value, tmp);
       }
-      return ret;
+      break;
     default:
-      /* if (token->token_type == h_tt_php) { */
-      /*       ZEND_REGISTER_RESOURCE(return_value, token->token_data.user, le_swig__p_void); // it's a void*, wh
-      /*       return return_value; */
-      /* } else { */
-      /*       // I guess that's a python thing */
-      /*       //return (zval*)SWIG_NewPointerObj((void*)token, SWIGTYPE_p_HParsedToken_, 0 | 0); */
-      /*       // TODO: support registry */
-      /* } */
+      if (token->token_type == h_tt_php) { 
+	RETVAL_RESOURCE(token->token_data.user);
+      } else {
+	int res = 0;
+	res = SWIG_ConvertPtr(return_value, (void*)token, SWIGTYPE_p_HParsedToken_, 0 | 0);
+	if (!SWIG_IsOK(res)) {
+	  SWIG_exception(SWIG_TypeError, "hpt_to_php: that wasn't an HParsedToken");
+	}
+	// TODO: support registry
+      }
       break;
     }
   }
@@ -170,13 +141,13 @@
     zval ret;
     // in PHP land, the HAction is passed by its name as a string
     if (IS_STRING != Z_TYPE_P((zval*)user_data)) {
-      printf("user_data wasn't a string");
+      printf("user_data wasn't a string\n");
       // FIXME throw some error
       return NULL;
     }
     zval *callable;
     callable = user_data;
-    args[0] = hpt_to_php(p->ast);
+    hpt_to_php(args[0], p->ast);
     int ok = call_user_function(EG(function_table), NULL, callable, &ret, 1, args TSRMLS_CC);
     if (ok != SUCCESS) {
       printf("call_user_function failed");