diff --git a/SConstruct b/SConstruct
index 8590d4bb4c0d718b075197bea5ed0372322612f2..872a9747f54086ceb5c4def9b92378facae16f38 100644
--- a/SConstruct
+++ b/SConstruct
@@ -7,7 +7,7 @@ import sys
 vars = Variables(None, ARGUMENTS)
 vars.Add(PathVariable('DESTDIR', "Root directory to install in (useful for packaging scripts)", None, PathVariable.PathIsDirCreate))
 vars.Add(PathVariable('prefix', "Where to install in the FHS", "/usr/local", PathVariable.PathAccept))
-vars.Add(ListVariable('bindings', 'Language bindings to build', 'none', ['python']))
+vars.Add(ListVariable('bindings', 'Language bindings to build', 'none', ['python', 'perl']))
 
 env = Environment(ENV = {'PATH' : os.environ['PATH']}, variables = vars, tools=['default', 'scanreplace'], toolpath=['tools'])
 
@@ -114,6 +114,7 @@ else:
     lib = env.SConscript(["src/SConscript"])
     env.Alias(env.SConscript(["examples/SConscript"]))
 
-env.Alias("test", testruns)
+for testrun in testruns:
+    env.Alias("test", testrun)
 
 env.Alias("install", targets)
diff --git a/src/bindings/.gitignore b/src/bindings/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8f0fde88f32a382af022bbeed57721e876ea4628
--- /dev/null
+++ b/src/bindings/.gitignore
@@ -0,0 +1 @@
+hammer_wrap.c
diff --git a/src/bindings/perl/.gitignore b/src/bindings/perl/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0f12fd2795151775c92ab47a2c10ac232591c8d0
--- /dev/null
+++ b/src/bindings/perl/.gitignore
@@ -0,0 +1 @@
+hammer.pm
diff --git a/src/bindings/perl/Makefile.PL b/src/bindings/perl/Makefile.PL
new file mode 100644
index 0000000000000000000000000000000000000000..c9bf9db947a5cd9637de62b816574b7fb0e77009
--- /dev/null
+++ b/src/bindings/perl/Makefile.PL
@@ -0,0 +1,15 @@
+use ExtUtils::MakeMaker;
+use File::Basename;
+use Config;
+
+# Scons hack...
+chdir(dirname($0));
+
+WriteMakefile(
+    NAME => "hammer",
+    LIBS => ["-lhammer"],
+    OBJECT => 'hammer_wrap.o',
+    INC => '-I../..',
+    CCFLAGS => "$Config{ccflags} -DSWIG -DHAMMER_INTERNAL__NO_STDARG_H -std=gnu99",
+    );
+
diff --git a/src/bindings/perl/README.md b/src/bindings/perl/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b2f7442a99905862da3cc1776c0cdc691e2b2a17
--- /dev/null
+++ b/src/bindings/perl/README.md
@@ -0,0 +1,9 @@
+Perl Hammer bindings
+====================
+
+To build and run these bindings, you will need to have
+ExtUtils::MakeMaker and make installed. On a Debian system, this just
+means that you need perl installed. On a lesser Linux distribution,
+this may be all you need, but you're on your own. On Windows or
+another UNIX, you're *really* on your own (until we get PRs with
+better instructions).
\ No newline at end of file
diff --git a/src/bindings/perl/SConscript b/src/bindings/perl/SConscript
new file mode 100644
index 0000000000000000000000000000000000000000..c9a20fd250809c050e74322184840671a8163d99
--- /dev/null
+++ b/src/bindings/perl/SConscript
@@ -0,0 +1,42 @@
+# -*- python -*-
+import os.path
+Import("env libhammer_shared testruns targets")
+
+perlenv = env.Clone()
+
+perlenv.Append(CCFLAGS=["-fpic", '-DSWIG', '-Wno-all',
+                        '-Wno-extra', '-Wno-error',
+                        '-DHAMMER_INTERNAL__NO_STDARG_H'],
+               CPPPATH=["../.."],
+               LIBS=['hammer'],
+               LIBPATH=["../.."],
+               SWIGFLAGS=["-DHAMMER_INTERNAL__NO_STDARG_H",
+                          "-Isrc/", "-perl"])
+import os
+if 'PERL_MM_OPT' in os.environ:
+    perlenv['ENV']['PERL_MM_OPT'] = os.environ['PERL_MM_OPT']
+if 'PERL5LIB' in os.environ:
+    perlenv['ENV']['PERL5LIB'] = os.environ['PERL5LIB']
+
+swig = ['hammer.i']
+
+hammer_wrap = perlenv.Command(['hammer_wrap.c', 'hammer.pm'], swig, "swig $SWIGFLAGS $SOURCE")
+makefile = perlenv.Command(['Makefile'], ['Makefile.PL'], "perl $SOURCE")
+
+targetdir = os.path.dirname(str(hammer_wrap[0].path))
+
+libhammer_perl = perlenv.Command(['hammer.so'], makefile + hammer_wrap, "make -C " + targetdir)
+
+Default(libhammer_perl)
+
+perltestenv = perlenv.Clone()
+perltestenv['ENV']['LD_LIBRARY_PATH'] = os.path.dirname(str(libhammer_shared[0]))
+perltests = ['t/hammer.t']
+perltestexec = perltestenv.Command(None, perltests + libhammer_perl + libhammer_shared, "make test -C " + targetdir)
+perltest = Alias("testperl", [perltestexec], perltestexec)
+AlwaysBuild(perltestexec)
+testruns.append(perltest)
+
+perlinstallexec = perlenv.Command(None, libhammer_perl, "make install -C " + targetdir)
+perlinstall = Alias("installperl", [perlinstallexec], perlinstallexec)
+targets.append(perlinstall)
diff --git a/src/bindings/perl/hammer.i b/src/bindings/perl/hammer.i
new file mode 100644
index 0000000000000000000000000000000000000000..ff9d7f4ebc7cb71c08f0d17872c42159bd41d628
--- /dev/null
+++ b/src/bindings/perl/hammer.i
@@ -0,0 +1,287 @@
+%module hammer;
+%begin %{
+#include <unistd.h>
+#include <stdint.h>
+%}
+
+%inline %{
+  static int h_tt_perl;
+  %}
+%init %{
+  h_tt_perl = h_allocate_token_type("com.upstandinghackers.hammer.perl");
+  %}
+
+
+%apply (char *STRING, size_t LENGTH) {(uint8_t* str, size_t len)}
+%apply (uint8_t* str, size_t len) {(const uint8_t* input, size_t length)}
+%apply (uint8_t* str, size_t len) {(const uint8_t* str, const size_t len)}
+%apply (uint8_t* str, size_t len) {(const uint8_t* charset, size_t length)}
+
+%typemap(out) struct HParseResult_* {
+  SV* hpt_to_perl(const struct HParsedToken_ *token);
+  if ($1 == NULL) {
+    // TODO: raise parse failure
+    $result = newSV(0);
+  } else {
+    $result = hpt_to_perl($1->ast);
+    //hpt_to_perl($1->ast);
+  }
+ }
+
+%typemap(in) void*[] {
+  if (!SvROK($input))
+    SWIG_exception_fail(SWIG_TypeError, "Expected array ref");
+
+  if (SvTYPE(SvRV($input)) != SVt_PVAV)
+    SWIG_exception_fail(SWIG_TypeError, "Expected array ref");
+
+  AV* av = (AV*) SvRV($input);
+  size_t amax = av_top_index(av) + 1; // I want the length, not the top index...
+  // TODO: is this array copied?
+  $1 = malloc((amax+1) * sizeof(*$1));
+  $1[amax] = NULL;
+  for (int i = 0; i < amax; i++) {
+    int res = SWIG_ConvertPtr(*av_fetch(av, i, 0), &($1[i]), SWIGTYPE_p_HParser_, 0|0);
+    if (!SWIG_IsOK(res)) {
+      SWIG_exception_fail(SWIG_ArgError(res), "Expected a list of parsers and only parsers");
+    }
+  }
+ }
+
+%typemap(in) uint8_t {
+  if (SvIOKp($input)) {
+    $1 = SvIV($input);
+  } else if (SvPOKp($input)) {
+    IV len;
+    uint8_t* ival = SvPV($input, len);
+    if (len < 1) {
+      %type_error("Expected string with at least one character");
+      SWIG_fail;
+    }
+    $1 = ival[0];
+  } else {
+    %type_error("Expected int or string");
+    SWIG_fail;
+  }
+ }
+
+
+%typemap(newfree) struct HParseResult_* {
+  h_parse_result_free($input);
+ }
+
+%rename("token") h_token;
+%rename("%(regex:/^h_(.*)/\\1/)s", regextarget=1) "^h_u?int(64|32|16|8)";
+
+%define %combinator %rename("%(regex:/^h_(.*)$/\\1/)s") %enddef
+  
+%combinator h_end_p;
+%combinator h_left;
+%combinator h_middle;
+%combinator h_right;
+%combinator h_int_range;
+%combinator h_whitespace;
+%combinator h_nothing_p;
+
+%combinator h_butnot;
+%combinator h_difference;
+%combinator h_xor;
+%combinator h_many;
+%combinator h_many1;
+%combinator h_sepBy;
+%combinator h_sepBy1;
+%combinator h_repeat_n;
+%combinator h_ignore;
+%combinator h_optional;
+%combinator h_epsilon_p;
+%combinator h_and;
+%combinator h_not;
+%combinator h_indirect;
+%combinator h_bind_indirect;
+
+%include "../swig/hammer.i";
+  
+
+%{
+  SV* hpt_to_perl(const HParsedToken *token) {
+    // All values that this function returns have a refcount of exactly 1.
+    SV *ret;
+    if (token == NULL) {
+      return newSV(0); // Same as TT_NONE
+    }
+    switch (token->token_type) {
+    case TT_NONE:
+      return newSV(0);
+      break;
+    case TT_BYTES:
+      return newSVpvn((char*)token->token_data.bytes.token, token->token_data.bytes.len);
+    case TT_SINT:
+      // TODO: return PyINT if appropriate
+      return newSViv(token->token_data.sint);
+    case TT_UINT:
+      // TODO: return PyINT if appropriate
+      return newSVuv(token->token_data.uint);
+    case TT_SEQUENCE: {
+      AV* aret = newAV();
+      av_extend(aret, token->token_data.seq->used);
+      for (int i = 0; i < token->token_data.seq->used; i++) {
+	av_store(aret, i, hpt_to_perl(token->token_data.seq->elements[i]));
+      }
+      return newRV_noinc((SV*)aret);
+    }
+    default:
+      if (token->token_type == h_tt_perl) {
+	return  SvREFCNT_inc((SV*)token->token_data.user);
+      } else {
+	return SWIG_NewPointerObj((void*)token, SWIGTYPE_p_HParsedToken_, 0 | 0);
+	// TODO: support registry
+      }
+      
+    }
+  
+  }
+  /*
+  HParser* ch(uint8_t chr) {
+    return h_action(h_ch(chr), h__to_dual_char, NULL);
+  }
+  HParser* in(const uint8_t *charset, size_t length) {
+    return h_action(h_in(charset, length), h__to_dual_char, NULL);
+  }
+ HParser* not_in(const uint8_t *charset, size_t length) {
+   return h_action(h_not_in(charset, length), h__to_dual_char, NULL);
+ }
+  */
+ HParsedToken* h__to_char(const HParseResult* result, void* user_data) {
+    assert(result != NULL);
+    assert(result->ast != NULL);
+    assert(result->ast->token_type == TT_UINT);
+    
+    uint8_t buf = result->ast->token_data.uint;
+    SV *sv = newSVpvn(&buf, 1);
+    // This was a failed experiment; for now, you'll have to use ord yourself.
+    //sv_setuv(sv, buf);
+    //SvPOK_on(sv);
+      
+    HParsedToken *res = h_arena_malloc(result->arena, sizeof(HParsedToken));
+    res->token_type = h_tt_perl;
+    res->token_data.user = sv;
+    return res;
+  }
+
+ static HParsedToken* call_action(const HParseResult *p, void* user_data  ) {
+   SV *func = (SV*)user_data;
+
+   dSP;
+   ENTER;
+   SAVETMPS;
+   PUSHMARK(SP);
+   if (p->ast != NULL) {
+     mXPUSHs(hpt_to_perl(p->ast));
+   } else {
+     mXPUSHs(newSV(0));
+   }
+   PUTBACK;
+   
+   int nret = call_sv(func, G_SCALAR);
+
+   SPAGAIN;
+   if (nret != 1)
+     croak("Expected 1 return value, got %d", nret);
+   
+   HParsedToken *ret = h_arena_malloc(p->arena, sizeof(*ret));
+   memset(ret, 0, sizeof(*ret));
+   ret->token_type = h_tt_perl;
+   ret->token_data.user = SvREFCNT_inc(POPs);
+   if (p->ast != NULL) {
+     ret->index = p->ast->index;
+     ret->bit_offset = p->ast->bit_offset;
+   }
+   PUTBACK;
+   FREETMPS;
+   LEAVE;
+
+   return ret;
+ }
+
+ static int call_predicate(HParseResult *p, void* user_data) {
+   SV *func = (SV*)user_data;
+
+   dSP;
+   ENTER;
+   SAVETMPS;
+   PUSHMARK(SP);
+   if (p->ast != NULL) {
+     mXPUSHs(hpt_to_perl(p->ast));
+   } else {
+     mXPUSHs(newSV(0));
+   }
+   PUTBACK;
+   
+   int nret = call_sv(func, G_SCALAR);
+
+   SPAGAIN;
+   if (nret != 1)
+     croak("Expected 1 return value, got %d", nret);
+
+   SV* svret = POPs;
+   int ret = SvTRUE(svret);
+   PUTBACK;
+   FREETMPS;
+   LEAVE;
+
+   return ret;
+ }
+
+%}
+%inline {
+  HParser* ch(uint8_t chr) {
+    return h_action(h_ch(chr), h__to_char, NULL);
+  }
+  HParser* ch_range(uint8_t c0, uint8_t c1) {
+    return h_action(h_ch_range(c0,c1), h__to_char, NULL);
+  }
+  HParser* h__in(const uint8_t *charset, size_t length) {
+    return h_action(h_in(charset, length), h__to_char, NULL);
+  }
+  HParser* h__not_in(const uint8_t *charset, size_t length) {
+    return h_action(h_not_in(charset, length), h__to_char, NULL);
+  }
+  HParser* action(HParser *parser, SV* sub) {
+    return h_action(parser, call_action, SvREFCNT_inc(sub));
+  }
+  HParser* attr_bool(HParser *parser, SV* sub) {
+    return h_attr_bool(parser, call_predicate, SvREFCNT_inc(sub));
+  }
+ }
+
+%extend HParser_ {
+    SV* parse(const uint8_t* input, size_t length) {
+      SV* hpt_to_perl(const struct HParsedToken_ *token);
+      HParseResult *res = h_parse($self, input, length);
+      if (res) {
+	return hpt_to_perl(res->ast);
+      } else {
+	croak("Parse failure");
+      }
+    }
+    bool compile(HParserBackend backend) {
+        return h_compile($self, backend, NULL) == 0;
+    }
+}
+
+%perlcode %{
+  sub sequence {
+    return hammerc::h_sequence__a([@_]);
+  }
+  sub choice {
+    return hammerc::h_choice__a([@_]);
+  }
+  sub in {
+    return h__in(join('',@_));
+  }
+  sub not_in {
+    return h__not_in(join('',@_));
+  }
+
+
+  %}
diff --git a/src/bindings/perl/t/hammer.t b/src/bindings/perl/t/hammer.t
new file mode 100644
index 0000000000000000000000000000000000000000..0d402b05f12fc0bc9b3cabbbcd23027572cbb9c6
--- /dev/null
+++ b/src/bindings/perl/t/hammer.t
@@ -0,0 +1,390 @@
+# -*- cperl -*-
+use warnings;
+use strict;
+use Data::Dumper;
+use Test::More tests => 41;
+use hammer;
+
+# differences from C version:
+
+# - in takes any number of arguments, which are concatenated. This
+#   makes ch_range irrelevant.
+#
+# - foo
+
+
+sub check_parse_eq {
+  my ($parser, $input, $expected) = @_;
+  my $actual;
+  eval {
+    $actual = $parser->parse($input);
+  };
+  if ($@) {
+    diag($@);
+    ok($@ eq "");
+  } else {
+    #diag(Dumper($actual));
+    is_deeply($actual, $expected);
+  }
+}
+
+sub check_parse_failed {
+  my ($parser, $input) = @_;
+  eval {
+    my $actual = $parser->parse($input);
+  };
+  ok($@ ne "");
+}
+
+subtest "token" => sub {
+  my $parser = hammer::token("95\xa2");
+
+  check_parse_eq($parser, "95\xa2", "95\xa2");
+  check_parse_failed($parser, "95");
+};
+
+subtest "ch" => sub {
+  my $parser = hammer::ch("\xa2");
+  #check_parse_eq($parser, "\xa2", 0xa2);
+  check_parse_eq($parser, "\xa2", "\xa2");
+  check_parse_failed($parser, "\xa3");
+};
+
+subtest "ch_range" => sub {
+  # ch_range doesn't need to be part of hammer-perl; the equivalent
+  # effect can be achieved with hammer::in('a'..'z')
+  #
+  # However, the function is provided just in case.
+  my $parser = hammer::ch_range('a','c');
+  check_parse_eq($parser, 'b', 'b');
+  #check_parse_eq($parser, 'b', 0x62);
+  check_parse_failed($parser, 'd');
+};
+
+SKIP: {
+  use integer;
+  no warnings 'portable'; # I know the hex constants are not portable. that's why this test is skipped on <64 bit systems.
+  skip "Needs 64-bit support", 2 if 0x4000000 * 2 eq -1; # TODO: Not sure if this works; may need $Config{ivsize} >= 8
+  subtest "int64" => sub {
+    my $parser = hammer::int64();
+    check_parse_eq($parser, "\xff\xff\xff\xfe\x00\x00\x00\x00", -0x200000000);
+    check_parse_failed($parser, "\xff\xff\xff\xfe\x00\x00\x00");
+  };
+  subtest "uint64" => sub {
+    my $parser = hammer::uint64();
+    check_parse_eq($parser, "\x00\x00\x00\x02\x00\x00\x00\x00", 0x200000000);
+    check_parse_failed($parser, "\x00\x00\x00\x02\x00\x00\x00");
+  };
+}
+
+subtest "int32" => sub {
+  my $parser = hammer::int32();
+  check_parse_eq($parser, "\xff\xfe\x00\x00", -0x20000);
+  check_parse_eq($parser, "\x00\x02\x00\x00",  0x20000);
+  check_parse_failed($parser, "\xff\xfe\x00");
+  check_parse_failed($parser, "\x00\x02\x00");
+};
+
+subtest "uint32" => sub {
+  my $parser = hammer::uint32();
+  check_parse_eq($parser, "\x00\x02\x00\x00", 0x20000);
+  check_parse_failed($parser, "\x00\x02\x00")
+};
+
+subtest "int16" => sub {
+  my $parser = hammer::int16();
+  check_parse_eq($parser, "\xfe\x00", -0x200);
+  check_parse_eq($parser, "\x02\x00", 0x200);
+  check_parse_failed($parser, "\xfe");
+  check_parse_failed($parser, "\x02");
+};
+
+subtest "uint16" => sub {
+  my $parser = hammer::uint16();
+  check_parse_eq($parser, "\x02\x00", 0x200);
+  check_parse_failed($parser, "\x02");
+};
+
+subtest "int8" => sub {
+  my $parser = hammer::int8();
+  check_parse_eq($parser, "\x88", -0x78);
+  check_parse_failed($parser, "");
+};
+
+subtest "uint8" => sub {
+  my $parser = hammer::uint8();
+  check_parse_eq($parser, "\x78", 0x78);
+  check_parse_failed($parser, "");
+};
+
+subtest "int_range" => sub { # test 12
+  my $parser = hammer::int_range(hammer::uint8(), 3, 10);
+  check_parse_eq($parser, "\x05", 5);
+  check_parse_failed($parser, "\x0b");
+};
+
+subtest "whitespace" => sub {
+  my $parser = hammer::whitespace(hammer::ch('a'));
+  check_parse_eq($parser, "a", "a");
+  check_parse_eq($parser, " a", "a");
+  check_parse_eq($parser, "  a", "a");
+  check_parse_eq($parser, "\t\n\ra", "a");
+};
+
+subtest "whitespace-end" => sub {
+  my $parser = hammer::whitespace(hammer::end_p());
+  check_parse_eq($parser, "", undef);
+  check_parse_eq($parser, "  ", undef);
+  check_parse_failed($parser, "  x", undef)
+};
+
+subtest "left" => sub { # test 15
+  my $parser = hammer::left(hammer::ch('a'),
+			    hammer::ch(' '));
+  check_parse_eq($parser, "a ", "a");
+  check_parse_failed($parser, "a");
+  check_parse_failed($parser, " ");
+};
+
+subtest "right" => sub {
+  my $parser = hammer::right(hammer::ch(' '),
+			     hammer::ch('a'));
+  check_parse_eq($parser, " a", "a");
+  check_parse_failed($parser, "a");
+  check_parse_failed($parser, " ");
+};
+
+subtest "middle" => sub {
+  my $parser = hammer::middle(hammer::ch(' '),
+			      hammer::ch('a'),
+			      hammer::ch(' '));
+  check_parse_eq($parser, " a ", "a");
+  for my $test_string (split('/', "a/ / a/a / b /ba / ab")) {
+    check_parse_failed($parser, $test_string);
+  }
+};
+
+subtest "action" => sub {
+  my $parser = hammer::action(hammer::sequence(hammer::choice(hammer::ch('a'),
+							      hammer::ch('A')),
+					       hammer::choice(hammer::ch('b'),
+							      hammer::ch('B'))),
+			      sub { [map(uc, @{+shift})]; });
+  check_parse_eq($parser, "ab", ['A', 'B']);
+  check_parse_eq($parser, "AB", ['A', 'B']);
+  check_parse_eq($parser, 'Ab', ['A', 'B']);
+  check_parse_failed($parser, "XX");
+};
+
+
+subtest "in" => sub {
+  my $parser = hammer::in('a'..'c');
+  check_parse_eq($parser, 'a', 'a');
+  check_parse_eq($parser, 'b', 'b');
+  check_parse_eq($parser, 'c', 'c');
+  check_parse_failed($parser, 'd');
+};
+
+subtest "not_in" => sub { # test 20
+  my $parser = hammer::not_in('a'..'c');
+  check_parse_failed($parser, 'a');
+  check_parse_failed($parser, 'b');
+  check_parse_failed($parser, 'c');
+  check_parse_eq($parser, 'd', 'd');
+};
+
+subtest "end_p" => sub {
+  my $parser = hammer::sequence(hammer::ch('a'), hammer::end_p());
+  check_parse_eq($parser, 'a', ['a']);
+  check_parse_failed($parser, 'aa');
+};
+
+subtest "nothing_p" => sub {
+  my $parser = hammer::nothing_p();
+  check_parse_failed($parser, "");
+  check_parse_failed($parser, "foo");
+};
+
+subtest "sequence" => sub {
+  my $parser = hammer::sequence(hammer::ch('a'), hammer::ch('b'));
+  check_parse_eq($parser, "ab", ['a','b']);
+  check_parse_failed($parser, 'a');
+  check_parse_failed($parser, 'b');
+};
+
+subtest "sequence-whitespace" => sub {
+  my $parser = hammer::sequence(hammer::ch('a'),
+				hammer::whitespace(hammer::ch('b')));
+  check_parse_eq($parser, "ab", ['a', 'b']);
+  check_parse_eq($parser, "a b", ['a', 'b']);
+  check_parse_eq($parser, "a  b", ['a', 'b']);
+  check_parse_failed($parser, "a  c");
+};
+
+subtest "choice" => sub { # test 25
+  my $parser = hammer::choice(hammer::ch('a'),
+			      hammer::ch('b'));
+  check_parse_eq($parser, 'a', 'a');
+  check_parse_eq($parser, 'b', 'b');
+  check_parse_failed($parser, 'c');
+};
+
+subtest "butnot" => sub {
+  my $parser = hammer::butnot(hammer::ch('a'), hammer::token('ab'));
+  check_parse_eq($parser, 'a', 'a');
+  check_parse_eq($parser, 'aa', 'a');
+  check_parse_failed($parser, 'ab');
+};
+
+subtest "butnot-range" => sub {
+  my $parser = hammer::butnot(hammer::ch_range('0', '9'), hammer::ch('6'));
+  check_parse_eq($parser, '4', '4');
+  check_parse_failed($parser, '6');
+};
+
+subtest "difference" => sub {
+  my $parser = hammer::difference(hammer::token('ab'),
+				  hammer::ch('a'));
+  check_parse_eq($parser, 'ab', 'ab');
+  check_parse_failed($parser, 'a');
+};
+
+subtest "xor" => sub {
+  my $parser = hammer::xor(hammer::in('0'..'6'),
+			   hammer::in('5'..'9'));
+  check_parse_eq($parser, '0', '0');
+  check_parse_eq($parser, '9', '9');
+  check_parse_failed($parser, '5');
+  check_parse_failed($parser, 'a');
+};
+
+subtest "many" => sub { # test 30
+  my $parser = hammer::many(hammer::in('ab'));
+  check_parse_eq($parser, '', []);
+  check_parse_eq($parser, 'a', ['a']);
+  check_parse_eq($parser, 'b', ['b']);
+  check_parse_eq($parser, 'aabbaba', [qw/a a b b a b a/]);
+};
+
+subtest "many1" => sub {
+  my $parser = hammer::many1(hammer::in('ab'));
+  check_parse_eq($parser, 'a', ['a']);
+  check_parse_eq($parser, 'b', ['b']);
+  check_parse_eq($parser, 'aabbaba', [qw/a a b b a b a/]);
+  check_parse_failed($parser, '');
+  check_parse_failed($parser, 'daabbabadef');
+};
+subtest "repeat_n" => sub {
+  my $parser = hammer::repeat_n(hammer::in('ab'), 2);
+  check_parse_eq($parser, 'abdef', ['a','b']);
+  check_parse_failed($parser, 'adef');
+};
+
+subtest "optional" => sub {
+  my $parser = hammer::sequence(hammer::ch('a'),
+				hammer::optional(hammer::in('bc')),
+				hammer::ch('d'));
+  check_parse_eq($parser, 'abd', [qw/a b d/]);
+  check_parse_eq($parser, 'abd', [qw/a b d/]);
+  check_parse_eq($parser, 'ad', ['a',undef,'d']);
+  check_parse_failed($parser, 'aed');
+  check_parse_failed($parser, 'ab');
+  check_parse_failed($parser, 'ac');
+};
+
+subtest "ignore" => sub {
+  my $parser = hammer::sequence(hammer::ch('a'),
+				hammer::ignore(hammer::ch('b')),
+				hammer::ch('c'));
+  check_parse_eq($parser, "abc", ['a','c']);
+  check_parse_failed($parser, 'ac');
+};
+
+subtest "sepBy" => sub { # Test 35
+  my $parser = hammer::sepBy(hammer::in('1'..'3'),
+			     hammer::ch(','));
+  check_parse_eq($parser, '1,2,3', ['1','2','3']);
+  check_parse_eq($parser, '1,3,2', ['1','3','2']);
+  check_parse_eq($parser, '1,3', ['1','3']);
+  check_parse_eq($parser, '3', ['3']);
+  check_parse_eq($parser, '', []);
+};
+
+subtest "sepBy1" => sub {
+  my $parser = hammer::sepBy1(hammer::in("123"),
+			      hammer::ch(','));
+  check_parse_eq($parser, '1,2,3', ['1','2','3']);
+  check_parse_eq($parser, '1,3,2', ['1','3','2']);
+  check_parse_eq($parser, '1,3', ['1','3']);
+  check_parse_eq($parser, '3', ['3']);
+  check_parse_failed($parser, '');
+};
+
+subtest "epsilon" => sub {
+  check_parse_eq(hammer::sequence(hammer::ch('a'),
+				  hammer::epsilon_p(),
+				  hammer::ch('b')),
+		 'ab', ['a','b']);
+  check_parse_eq(hammer::sequence(hammer::epsilon_p(),
+				  hammer::ch('a')),
+		 'a', ['a']);
+  check_parse_eq(hammer::sequence(hammer::ch('a'),
+				  hammer::epsilon_p()),
+		 'a', ['a']);
+};
+
+
+subtest "attr_bool" => sub {
+  my $parser = hammer::attr_bool(hammer::many1(hammer::in('ab')),
+				 sub { my ($a, $b) = @{+shift}; $a eq $b });
+  check_parse_eq($parser, "aa", ['a','a']);
+  check_parse_eq($parser, "bb", ['b','b']);
+  check_parse_failed($parser, "ab");
+};
+
+subtest "and" => sub {
+  check_parse_eq(hammer::sequence(hammer::and(hammer::ch('0')),
+				  hammer::ch('0')),
+		 '0', ['0']);
+  check_parse_failed(hammer::sequence(hammer::and(hammer::ch('0')),
+				      hammer::ch('1')),
+		     '0');
+  my $parser = hammer::sequence(hammer::ch('1'),
+				hammer::and(hammer::ch('2')));
+  check_parse_eq($parser, '12', ['1']);
+  check_parse_failed($parser, '1');
+  check_parse_failed($parser, '13');
+};
+
+subtest "not" => sub { # test 40
+  # This is not how you'd *actually* write the parser for this
+  # language; in case of Packrat, it's better to swap the order of the
+  # arguments, and for other backends, the problem doesn't appear at
+  # all.
+  my $parser = hammer::sequence(hammer::ch('a'),
+				hammer::choice(hammer::ch('+'),
+					       hammer::token('++')),
+				hammer::ch('b'));
+  check_parse_eq($parser, 'a+b', ['a','+','b']);
+  check_parse_failed($parser, 'a++b'); # ordered choice
+
+  $parser = hammer::sequence(hammer::ch('a'),
+			     hammer::choice(hammer::sequence(hammer::ch('+'),
+							     hammer::not(hammer::ch('+'))),
+					    hammer::token('++')),
+			     hammer::ch('b'));
+  check_parse_eq($parser, 'a+b', ['a',['+'],'b']);
+  check_parse_eq($parser, 'a++b', ['a', '++', 'b']);
+};
+
+subtest "rightrec" => sub {
+  my $parser = hammer::indirect();
+  hammer::bind_indirect($parser,
+			hammer::choice(hammer::sequence(hammer::ch('a'),
+							$parser),
+				       hammer::epsilon_p));
+  check_parse_eq($parser, 'a', ['a']);
+  check_parse_eq($parser, 'aa', ['a', ['a']]);
+  check_parse_eq($parser, 'aaa', ['a', ['a', ['a']]]);
+};
+  
diff --git a/src/bindings/python/SConscript b/src/bindings/python/SConscript
index d91a942300a17def47bf3188c3fe34f82ccdf8d2..e7b956fac0b8f7ead58797d456834f35f8914f5c 100644
--- a/src/bindings/python/SConscript
+++ b/src/bindings/python/SConscript
@@ -15,8 +15,8 @@ pytestenv['ENV']['LD_LIBRARY_PATH'] = os.path.dirname(str(libhammer_shared[0]))
 pytests = ['hammer_tests.py']
 pytestexec = pytestenv.Command(['hammer.pyc', 'hammer_tests.pyc'], pytests + libhammer_python, "LD_LIBRARY_PATH=" + os.path.dirname(str(libhammer_shared[0])) + " nosetests -vv $SOURCE")
 pytest = Alias("testpython", [pytestexec], pytestexec)
-AlwaysBuild(pytest)
-testruns.append(pytest)
+AlwaysBuild(pytestexec)
+testruns.extend(pytest)
 
 pyinstallexec = pythonenv.Command(None, libhammer_python, 'python ' + os.path.join(pydir, 'setup.py ') + ' install')
 pyinstall = Alias("installpython", [pyinstallexec], pyinstallexec)
diff --git a/src/bindings/python/hammer.py b/src/bindings/python/hammer.py
deleted file mode 100644
index 36b78c8c8d3408b68e7df1e92a70be53e159f877..0000000000000000000000000000000000000000
--- a/src/bindings/python/hammer.py
+++ /dev/null
@@ -1,488 +0,0 @@
-from cffi import FFI
-import threading
-import sys
-
-_ffi = FFI()
-
-# {{{ Types
-
-_ffi.cdef("typedef struct HAllocator_ HAllocator;")
-_ffi.cdef("typedef struct HArena_ HArena;")
-_ffi.cdef("typedef int bool;")
-_ffi.cdef("typedef struct HParseState_ HParseState;")
-_ffi.cdef("""
-typedef enum HParserBackend_ {
-  PB_MIN = 0,
-  PB_PACKRAT = 0, // PB_MIN is always the default.
-  PB_REGULAR,
-  PB_LLk,
-  PB_LALR,
-  PB_GLR
-// TODO: support PB_MAX
-} HParserBackend;
-""")
-_ffi.cdef("""
-typedef enum HTokenType_ {
-  // Before you change the explicit values of these, think of the poor bindings ;_;
-  TT_NONE = 1,
-  TT_BYTES = 2,
-  TT_SINT = 4,
-  TT_UINT = 8,
-  TT_SEQUENCE = 16,
-  TT_RESERVED_1, // reserved for backend-specific internal use
-  TT_ERR = 32,
-  TT_USER = 64,
-  TT_MAX
-} HTokenType;
-""")
-_ffi.cdef("""
-typedef struct HCountedArray_ {
-  size_t capacity;
-  size_t used;
-  HArena * arena;
-  struct HParsedToken_ **elements;
-} HCountedArray;
-""")
-_ffi.cdef("""
-typedef struct HBytes_ {
-  const uint8_t *token;
-  size_t len;
-} HBytes;
-""")
-_ffi.cdef("""
-typedef struct HParsedToken_ {
-  HTokenType token_type;
-  union {
-    HBytes bytes;
-    int64_t sint;
-    uint64_t uint;
-    double dbl;
-    float flt;
-    HCountedArray *seq; // a sequence of HParsedToken's
-    void *user;
-  };
-  size_t index;
-  char bit_offset;
-} HParsedToken;
-""")
-_ffi.cdef("""
-typedef struct HParseResult_ {
-  const HParsedToken *ast;
-  long long bit_length;
-  HArena * arena;
-} HParseResult;
-""")
-
-_ffi.cdef("""typedef HParsedToken* (*HAction)(const HParseResult *p);""")
-_ffi.cdef("""typedef bool (*HPredicate)(HParseResult *p);""")
-_ffi.cdef("""
-typedef struct HCFChoice_ HCFChoice;
-typedef struct HRVMProg_ HRVMProg;
-typedef struct HParserVtable_ HParserVtable;
-""")
-
-_ffi.cdef("typedef struct HParser_ HParser;")
-_ffi.cdef("""
-typedef struct HParserTestcase_ {
-  unsigned char* input;
-  size_t length;
-  char* output_unambiguous;
-} HParserTestcase;
-
-typedef struct HCaseResult_ {
-  bool success;
-  union {
-    const char* actual_results; // on failure, filled in with the results of h_write_result_unamb
-    size_t parse_time; // on success, filled in with time for a single parse, in nsec
-  };
-} HCaseResult;
-
-typedef struct HBackendResults_ {
-  HParserBackend backend;
-  bool compile_success;
-  size_t n_testcases;
-  size_t failed_testcases; // actually a count...
-  HCaseResult *cases;
-} HBackendResults;
-
-typedef struct HBenchmarkResults_ {
-  size_t len;
-  HBackendResults *results;
-} HBenchmarkResults;
-""")
-
-# }}}
-# {{{ Arena functions
-_ffi.cdef("void* h_arena_malloc(HArena *arena, size_t count);")
-_ffi.cdef("void h_arena_free(HArena *arena, void* ptr);")
-# }}}
-# {{{ cdefs
-## The following section was generated by
-## $ perl ../desugar-header.pl <../../hammer.h |sed -e 's/.*/_ffi.cdef("&")/'
-_ffi.cdef("HParseResult* h_parse(const HParser* parser, const uint8_t* input, size_t length);")
-_ffi.cdef("HParseResult* h_parse__m(HAllocator* mm__, const HParser* parser, const uint8_t* input, size_t length);")
-_ffi.cdef("HParser* h_token(const uint8_t *str, const size_t len);")
-_ffi.cdef("HParser* h_token__m(HAllocator* mm__, const uint8_t *str, const size_t len);")
-_ffi.cdef("HParser* h_ch(const uint8_t c);")
-_ffi.cdef("HParser* h_ch__m(HAllocator* mm__, const uint8_t c);")
-_ffi.cdef("HParser* h_ch_range(const uint8_t lower, const uint8_t upper);")
-_ffi.cdef("HParser* h_ch_range__m(HAllocator* mm__, const uint8_t lower, const uint8_t upper);")
-_ffi.cdef("HParser* h_int_range(const HParser *p, const int64_t lower, const int64_t upper);")
-_ffi.cdef("HParser* h_int_range__m(HAllocator* mm__, const HParser *p, const int64_t lower, const int64_t upper);")
-_ffi.cdef("HParser* h_bits(size_t len, bool sign);")
-_ffi.cdef("HParser* h_bits__m(HAllocator* mm__, size_t len, bool sign);")
-_ffi.cdef("HParser* h_int64(void);")
-_ffi.cdef("HParser* h_int64__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_int32(void);")
-_ffi.cdef("HParser* h_int32__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_int16(void);")
-_ffi.cdef("HParser* h_int16__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_int8(void);")
-_ffi.cdef("HParser* h_int8__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_uint64(void);")
-_ffi.cdef("HParser* h_uint64__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_uint32(void);")
-_ffi.cdef("HParser* h_uint32__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_uint16(void);")
-_ffi.cdef("HParser* h_uint16__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_uint8(void);")
-_ffi.cdef("HParser* h_uint8__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_whitespace(const HParser* p);")
-_ffi.cdef("HParser* h_whitespace__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_left(const HParser* p, const HParser* q);")
-_ffi.cdef("HParser* h_left__m(HAllocator* mm__, const HParser* p, const HParser* q);")
-_ffi.cdef("HParser* h_right(const HParser* p, const HParser* q);")
-_ffi.cdef("HParser* h_right__m(HAllocator* mm__, const HParser* p, const HParser* q);")
-_ffi.cdef("HParser* h_middle(const HParser* p, const HParser* x, const HParser* q);")
-_ffi.cdef("HParser* h_middle__m(HAllocator* mm__, const HParser* p, const HParser* x, const HParser* q);")
-_ffi.cdef("HParser* h_action(const HParser* p, const HAction a);")
-_ffi.cdef("HParser* h_action__m(HAllocator* mm__, const HParser* p, const HAction a);")
-_ffi.cdef("HParser* h_in(const uint8_t *charset, size_t length);")
-_ffi.cdef("HParser* h_in__m(HAllocator* mm__, const uint8_t *charset, size_t length);")
-_ffi.cdef("HParser* h_not_in(const uint8_t *charset, size_t length);")
-_ffi.cdef("HParser* h_not_in__m(HAllocator* mm__, const uint8_t *charset, size_t length);")
-_ffi.cdef("HParser* h_end_p(void);")
-_ffi.cdef("HParser* h_end_p__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_nothing_p(void);")
-_ffi.cdef("HParser* h_nothing_p__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_sequence(HParser* p, ...);")
-_ffi.cdef("HParser* h_sequence__m(HAllocator *mm__, HParser* p, ...);")
-_ffi.cdef("HParser* h_sequence__a(void* args);")
-_ffi.cdef("HParser* h_sequence__ma(HAllocator* mm__, void* args);")
-_ffi.cdef("HParser* h_choice(HParser* p, ...);")
-_ffi.cdef("HParser* h_choice__m(HAllocator *mm__, HParser* p, ...);")
-_ffi.cdef("HParser* h_choice__a(void* args);")
-_ffi.cdef("HParser* h_choice__ma(HAllocator* mm__, void* args);")
-_ffi.cdef("HParser* h_butnot(const HParser* p1, const HParser* p2);")
-_ffi.cdef("HParser* h_butnot__m(HAllocator* mm__, const HParser* p1, const HParser* p2);")
-_ffi.cdef("HParser* h_difference(const HParser* p1, const HParser* p2);")
-_ffi.cdef("HParser* h_difference__m(HAllocator* mm__, const HParser* p1, const HParser* p2);")
-_ffi.cdef("HParser* h_xor(const HParser* p1, const HParser* p2);")
-_ffi.cdef("HParser* h_xor__m(HAllocator* mm__, const HParser* p1, const HParser* p2);")
-_ffi.cdef("HParser* h_many(const HParser* p);")
-_ffi.cdef("HParser* h_many__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_many1(const HParser* p);")
-_ffi.cdef("HParser* h_many1__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_repeat_n(const HParser* p, const size_t n);")
-_ffi.cdef("HParser* h_repeat_n__m(HAllocator* mm__, const HParser* p, const size_t n);")
-_ffi.cdef("HParser* h_optional(const HParser* p);")
-_ffi.cdef("HParser* h_optional__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_ignore(const HParser* p);")
-_ffi.cdef("HParser* h_ignore__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_sepBy(const HParser* p, const HParser* sep);")
-_ffi.cdef("HParser* h_sepBy__m(HAllocator* mm__, const HParser* p, const HParser* sep);")
-_ffi.cdef("HParser* h_sepBy1(const HParser* p, const HParser* sep);")
-_ffi.cdef("HParser* h_sepBy1__m(HAllocator* mm__, const HParser* p, const HParser* sep);")
-_ffi.cdef("HParser* h_epsilon_p(void);")
-_ffi.cdef("HParser* h_epsilon_p__m(HAllocator* mm__);")
-_ffi.cdef("HParser* h_length_value(const HParser* length, const HParser* value);")
-_ffi.cdef("HParser* h_length_value__m(HAllocator* mm__, const HParser* length, const HParser* value);")
-_ffi.cdef("HParser* h_attr_bool(const HParser* p, HPredicate pred);")
-_ffi.cdef("HParser* h_attr_bool__m(HAllocator* mm__, const HParser* p, HPredicate pred);")
-_ffi.cdef("HParser* h_and(const HParser* p);")
-_ffi.cdef("HParser* h_and__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_not(const HParser* p);")
-_ffi.cdef("HParser* h_not__m(HAllocator* mm__, const HParser* p);")
-_ffi.cdef("HParser* h_indirect(void);")
-_ffi.cdef("HParser* h_indirect__m(HAllocator* mm__);")
-_ffi.cdef("void h_bind_indirect(HParser* indirect, const HParser* inner);")
-_ffi.cdef("void h_bind_indirect__m(HAllocator* mm__, HParser* indirect, const HParser* inner);")
-_ffi.cdef("void h_parse_result_free(HParseResult *result);")
-_ffi.cdef("void h_parse_result_free__m(HAllocator* mm__, HParseResult *result);")
-_ffi.cdef("void h_pprint(FILE* stream, const HParsedToken* tok, int indent, int delta);")
-_ffi.cdef("int h_compile(HParser* parser, HParserBackend backend, const void* params);")
-_ffi.cdef("int h_compile__m(HAllocator* mm__, HParser* parser, HParserBackend backend, const void* params);")
-_ffi.cdef("HBenchmarkResults * h_benchmark(HParser* parser, HParserTestcase* testcases);")
-_ffi.cdef("HBenchmarkResults * h_benchmark__m(HAllocator* mm__, HParser* parser, HParserTestcase* testcases);")
-
-_lib = _ffi.verify("#include <hammer/hammer.h>",
-                 libraries=['hammer'])
-
-_lib.TT_PYTHON = _lib.TT_USER # TODO: Use the token type allocator from #45
-# }}}
-class _DynamicScopeHolder(threading.local):
-    """A dynamically-scoped holder of python objects, which may or may not
-    otherwise appear in the object graph. Intended for use with CFFI """
-    def __init__(self):
-        self._ctxstack = []
-    def __enter__(self):
-        self._ctxstack.append([])
-    def __exit__(self, exc_type, exc_value, traceback):
-        self._ctxstack.pop()
-        return False
-    def stash(self, *objs):
-        if len(self._ctxstack) < 1:
-            raise Exception("Not in any dynamic scope")
-        for obj in objs:
-            self._ctxstack[-1].append(obj)
-def _fromHParsedToken(cobj):
-    # TODO: Free the toplevel parser
-    tt = cobj.token_type
-
-    if cobj.token_type == _lib.TT_BYTES:
-        return _ffi.buffer(cobj.bytes.token, cobj.bytes.len)[:]
-    elif cobj.token_type == _lib.TT_ERR:
-        # I have no idea what this is for
-        pass
-    elif cobj.token_type == _lib.TT_NONE:
-        return None
-    elif cobj.token_type == _lib.TT_SEQUENCE:
-        return [_fromHParsedToken(cobj.seq.elements[i])
-                for i in range(cobj.seq.used)]
-    elif cobj.token_type == _lib.TT_SINT:
-        return cobj.sint
-    elif cobj.token_type == _lib.TT_UINT:
-        return cobj.uint
-    elif cobj.token_type == _lib.TT_PYTHON:
-        return _ffi.from_handle(cobj.user)
-
-_parser_result_holder = _DynamicScopeHolder()
-def _toHParsedToken(arena, pyobj):
-    if pyobj is None:
-        return _ffi.NULL
-    cobj = _ffi.new_handle(pyobj)
-    _parser_result_holder.stash(cobj)
-
-    hpt = _ffi.cast("HParsedToken*", _lib.h_arena_malloc(arena, _ffi.sizeof("HParsedToken")))
-    hpt.token_type = _lib.TT_PYTHON
-    hpt.user = cobj
-    hpt.bit_offset = chr(127)
-    hpt.index = 0
-    return hpt
-
-def _fromParseResult(cobj):
-    ret = _fromHParsedToken(cobj.ast)
-    _lib.h_parse_result_free(cobj)
-    return ret
-
-def _to_haction(fn):
-    """Turn a function that transforms a parsed value into an HAction"""
-    def action(parse_result):
-        res = _toHParsedToken(parse_result.arena,  fn(_fromParseResult(parse_result)))
-        if res != _ffi.NULL and parse_result.ast != _ffi.NULL:
-            res.index = parse_result.ast.index
-            res.bit_offset = parse_result.ast.bit_offset
-        return res
-    return _ffi.callback("HParsedToken*(HParseResult*)", action)
-
-def _to_hpredicate(fn):
-    """Turn a function that transforms a parsed value into an HAction"""
-    def predicate(parse_result):
-        res = fn(_fromParseResult(parse_result))
-        # TODO: Handle exceptions; parse should fail.
-        if type(res) != bool:
-            raise TypeError("Predicates should return a bool")
-        return res
-    return _ffi.callback("bool(HParseResult*)", predicate)
-
-class Parser(object):
-    # TODO: Map these to individually garbage-collected blocks of
-    # memory. Perhaps with an arena allocator with block size of 1?
-    # There has to be something more efficient than that, though.
-
-    # TODO: How do we handle encodings? By default, we're using UTF-8
-    def __init__(self, internal, deps):
-        """Create a new parser from an FFI object. Not for user code"""
-        self._parser = internal
-        self._deps = deps
-
-    def parse(self, string):
-        with _parser_result_holder:
-            pres = _lib.h_parse(self._parser, string, len(string))
-            if pres:
-                return _fromParseResult(pres)
-            else:
-                return None
-
-    def __mul__(self, count):
-        return repeat_n(self, count)
-
-
-                
-class IndirectParser(Parser):
-    def bind(self, inner):
-        _lib.h_bind_indirect(self._parser, inner._parser)
-        self._deps = (inner,)
-
-class BitsParser(Parser):
-    pass
-
-def token(token):
-    # TODO: Does not clone argument.
-    if isinstance(token, unicode):
-        token = token.encode("utf-8")
-    return Parser(_lib.h_token(token, len(token)), ())
-
-def ch(char):
-    """Returns either a token or an int, depending on the type of the
-    argument"""
-    if isinstance(char, int):
-        return Parser(_lib.h_ch(char), ())
-    else:
-        return token(char)
-
-def ch_range(chr1, chr2):
-    if not isinstance(chr1, str) or not isinstance(chr2, str):
-        raise TypeError("ch_range can't handle unicode")
-    def my_action(pr):
-        # print "In action: ", pr
-        return pr
-    return action(Parser(_lib.h_ch_range(ord(chr1), ord(chr2)), ()), my_action)
-
-def int_range(parser, i1, i2):
-    if type(parser) != BitsParser:
-        raise TypeError("int_range is only valid when used with a bits parser")
-    return Parser(_lib.h_int_range(parser._parser, i1, i2), (parser,))
-
-def bits(length, signedp):
-    return BitsParser(_lib.h_bits(length, signedp), ())
-
-def int64(): return bits(64, True)
-def int32(): return bits(32, True)
-def int16(): return bits(16, True)
-def int8 (): return bits(8,  True)
-def uint64(): return bits(64, False)
-def uint32(): return bits(32, False)
-def uint16(): return bits(16, False)
-def uint8 (): return bits(8,  False)
-
-def whitespace(p):
-    return Parser(_lib.h_whitespace(p._parser), (p,))
-def left(p1, p2):
-    return Parser(_lib.h_left(p1._parser, p2._parser), (p1, p2))
-def right(p1, p2):
-    return Parser(_lib.h_right(p1._parser, p2._parser), (p1, p2))
-def middle(p1, p2, p3):
-    return Parser(_lib.h_middle(p1._parser, p2._parser, p3._parser), (p1, p2, p3))
-def action(parser, action):
-    caction = _to_haction(action)
-    return Parser(_lib.h_action(parser._parser, caction), (parser, caction))
-
-def in_(charset):
-    if not isinstance(charset, str):
-        # TODO/Python3: change str to bytes
-        raise TypeError("in_ can't deal with unicode")
-    return Parser(_lib.h_in(charset, len(charset)), ())
-def not_in(charset):
-    if not isinstance(charset, str):
-        # TODO/Python3: change str to bytes
-        raise TypeError("in_ can't deal with unicode")
-    return Parser(_lib.h_not_in(charset, len(charset)), ())
-def end_p():
-    return Parser(_lib.h_end_p(), ())
-def nothing_p():
-    return Parser(_lib.h_nothing_p(), ())
-def sequence(*parsers):
-    plist = [p._parser for p in parsers]
-    plist.append(_ffi.NULL)
-    return Parser(_lib.h_sequence(*plist), (plist,))
-def choice(*parsers):
-    plist = [p._parser for p in parsers]
-    plist.append(_ffi.NULL)
-    return Parser(_lib.h_choice(*plist), (plist,))
-def butnot(p1, p2):
-    return Parser(_lib.h_butnot(p1._parser, p2._parser), (p1, p2))
-def difference(p1, p2):
-    return Parser(_lib.h_difference(p1._parser, p2._parser), (p1, p2))
-def xor(p1, p2):
-    return Parser(_lib.h_xor(p1._parser, p2._parser), (p1, p2))
-def many(p1):
-    return Parser(_lib.h_many(p1._parser), (p1,))
-def many1(p1):
-    return Parser(_lib.h_many1(p1._parser), (p1,))
-def repeat_n(p1, n):
-    return Parser(_lib.h_repeat_n(p1._parser, n), (p1,))
-def optional(p1):
-    return Parser(_lib.h_optional(p1._parser), (p1,))
-def ignore(p1):
-    return Parser(_lib.h_ignore(p1._parser), (p1,))
-def sepBy(p, sep):
-    return Parser(_lib.h_sepBy(p._parser, sep._parser), (p, sep))
-def sepBy1(p, sep):
-    return Parser(_lib.h_sepBy1(p._parser, sep._parser), (p, sep))
-def epsilon_p():
-    return Parser(_lib.h_epsilon_p(), ())
-def length_value(p_len, p_value):
-    return Parser(_lib.h_length_value(p_len._parser, p_value._parser), (p_len, p_value))
-def attr_bool(parser, predicate):
-    cpredicate = _to_hpredicate(predicate)
-    return Parser(_lib.h_attr_bool(parser._parser, cpredicate), (parser, cpredicate))
-def and_(parser):
-    return Parser(_lib.h_and(parser._parser), (parser,))
-def not_(parser):
-    return Parser(_lib.h_not(parser._parser), (parser,))
-def indirect():
-    return IndirectParser(_lib.h_indirect(), ())
-def bind_indirect(indirect, inner):
-    indirect.bind(inner)
-
-def parse(parser):
-    return parser.parse()
-
-# Unfortunately, "in", "and", and "not" are keywords. This makes them
-# show up in the module namespace for the use of automated tools. Do
-# not attempt to use them by hand; only use the mangled forms (with
-# the '_')
-sys.modules[__name__].__dict__["in"] = in_
-sys.modules[__name__].__dict__["and"] = and_
-sys.modules[__name__].__dict__["not"] = not_
-
-def run_test():
-    p_test = sepBy1(choice(ch('1'),
-                           ch('2'),
-                           ch('3')),
-                    ch(','))
-    return p_test.parse("1,2,3")
-
-# {{{ Automatic parser construction... python specific
-
-# TODO: Implement Parsable metaclass, which requires the existence of
-# a "parse" method.
-
-# This is expected to be extended by user code. As a general rule,
-# only provide auto-parsers for your own types.
-AUTO_PARSERS = {
-    str: token,
-    unicode: token,
-}
-
-def _auto_seq(lst):
-    return sequence(*(auto_1(p, default_method=_auto_choice)
-                      for p in lst))
-        
-def _auto_choice(lst):
-    return choice(*(auto_1(p, default_method=_auto_seq)
-                    for p in lst))
-
-def auto_1(arg, default_method=_auto_choice):
-    if isinstance(arg, Parser):
-        return arg
-    elif type(arg) in AUTO_PARSERS:
-        return AUTO_PARSERS[type(arg)](arg)
-    else:
-        return default_method(arg)
-
-def auto(*args):
-    return auto_1(args, default_method=_auto_choice)
-
-# }}}
diff --git a/src/bindings/swig/hammer.i b/src/bindings/swig/hammer.i
index 5ac8c3767b2c639e2a5444de532ca1e59c8a450a..13c30a4dee457d6c3fdd2112bf92fb652d7c99d0 100644
--- a/src/bindings/swig/hammer.i
+++ b/src/bindings/swig/hammer.i
@@ -135,7 +135,10 @@
 %{
 #include "allocator.h"
 #include "hammer.h"
+#ifndef SWIGPERL
+// Perl's embed.h conflicts with err.h, which internal.h includes. Ugh.
 #include "internal.h"
+#endif
 #include "glue.h"
 %}
 %include "allocator.h"
diff --git a/src/test_suite.c b/src/test_suite.c
index e065f138ece71d5b09c4aa0e1060a2e944f1d283..81f86b2c5007f11375995ad50751dfcb4618b7f5 100644
--- a/src/test_suite.c
+++ b/src/test_suite.c
@@ -35,7 +35,8 @@ int main(int argc, char** argv) {
   register_parser_tests();
   register_grammar_tests();
   register_misc_tests();
-  register_benchmark_tests();
+  if (g_test_slow() || g_test_perf())
+    register_benchmark_tests();
 
   g_test_run();
 }