diff --git a/src/bindings/python/hammer.py b/src/bindings/python/hammer.py
index e7e082298e7f99ecd8de4c8b07c756bea4d7a70e..a25dd933f46775f4c4537b31ec556aa8970b3f6a 100644
--- a/src/bindings/python/hammer.py
+++ b/src/bindings/python/hammer.py
@@ -4,7 +4,8 @@ import sys
 
 _ffi = FFI()
 
-## Types
+# {{{ Types
+
 _ffi.cdef("typedef struct HAllocator_ HAllocator;")
 _ffi.cdef("typedef struct HArena_ HArena;")
 _ffi.cdef("typedef int bool;")
@@ -110,10 +111,12 @@ typedef struct HBenchmarkResults_ {
 } HBenchmarkResults;
 """)
 
-## Arena functions
+# }}}
+# {{{ 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);")
@@ -216,7 +219,7 @@ _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 """
@@ -263,8 +266,8 @@ def _toHParsedToken(arena, pyobj):
     hpt = _ffi.cast("HParsedToken*", _lib.h_arena_malloc(_ffi.sizeof(parseResult.arena, "HParsedToken")))
     hpt.token_type = _lib.TT_PYTHON
     hpt.user = cobj
-    hpt.bit_offset = 127;
-    hpt.index = 0;
+    hpt.bit_offset = chr(127)
+    hpt.index = 0
     return hpt
 
 def _fromParseResult(cobj):
@@ -311,6 +314,11 @@ class Parser(object):
             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)
@@ -326,12 +334,20 @@ def token(token):
     return Parser(_lib.h_token(token, len(token)), ())
 
 def ch(char):
-    return token(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")
-    return Parser(_lib.h_ch_range(chr1, chr2), ())
+    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:
@@ -436,3 +452,36 @@ def run_test():
                            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)
+
+# }}}