diff --git a/src/bindings/ruby/hammer-parser.gemspec b/src/bindings/ruby/hammer-parser.gemspec
index 80b7529065fa99fc4b8e192c055c05d29662583c..18b4db738ad9325526dbdca381d0acf93f112d3c 100644
--- a/src/bindings/ruby/hammer-parser.gemspec
+++ b/src/bindings/ruby/hammer-parser.gemspec
@@ -11,7 +11,15 @@ Gem::Specification.new do |s|
 
   files = []
   files << 'README.md'
-  files << Dir['{lib,test}/**/*.rb']
+  files << [
+    "lib/hammer/internal.rb",
+    "lib/hammer/parser.rb",
+    "lib/hammer/parser_builder.rb",
+    "lib/hammer.rb",
+    "lib/minitest/hamer-parser_plugin.rb",
+    "test/autogen_test.rb",
+    "test/parser_test.rb"
+  ]
   s.files = files
   s.test_files = s.files.select { |path| path =~ /^test\/.*_test.rb/ }
 
diff --git a/src/bindings/ruby/lib/hammer/internal.rb b/src/bindings/ruby/lib/hammer/internal.rb
index 469dd730132c6972c3f12ba2e3e1c0495c57cacc..62a4bc6df31445dfe335cee1fa79d006a96927ba 100644
--- a/src/bindings/ruby/lib/hammer/internal.rb
+++ b/src/bindings/ruby/lib/hammer/internal.rb
@@ -6,22 +6,123 @@ module Hammer
 
     ffi_lib 'hammer'
 
+    class DynamicVariable
+      SYMBOL_PREFIX = "Hammer::Internal::DynamicVariable gensym "
+      @@current_symbol = 0
+
+      def initialize(default=nil, name=nil, &block)
+        # This can take either a default value or a block.  If a
+        # default value is given, all threads' dynvars are initialized
+        # to that object.  If a block is given, the block is lazilly
+        # called on each thread to generate the initial value.  If
+        # both a block and a default value are passed, the block is
+        # called with the literal value.
+        @default = default
+        @block = block || Proc.new{|x| x}
+        @@current_symbol += 1
+        @sym = (SYMBOL_PREFIX + @@current_symbol.to_s).to_sym
+      end
+
+      def value
+        if Thread.current.key? @sym
+          return Thread.current[@sym]
+        else
+          return Thread.current[@sym] = @block.call(@default)
+        end
+      end
+
+      def value=(new_value)
+        Thread.current[@sym] = new_value
+      end
+
+      def with(new_value, &block)
+        old_value = value
+        begin
+          self.value = new_value
+          return block.call
+        ensure
+          self.value = old_value
+        end
+      end
+    end
+
     # Maybe we can implement Hammer::Parser with FFI::DataConverter.
     # That way, most hammer functions won't need to be wrapped.
     # (Probably need to wrap token, sequence and choice only).
     # See http://www.elabs.se/blog/61-advanced-topics-in-ruby-ffi
     typedef :pointer, :h_parser
 
-    HTokenType = enum(:none, 1,
-                      :bytes, 2,
-                      :sint, 4,
-                      :uint, 8,
-                      :sequence, 16,
-                      :reserved_1,
-                      :err, 32,
-                      :user, 64,
-                      :max)
+    class HTokenType
+      extend FFI::DataConverter
+
+      @@known_type_map = {
+        :none => 1,
+        :bytes => 2,
+        :sint => 4,
+        :uint => 8,
+        :sequence => 16,
+      }
+
+      @@inverse_type_map = @@known_type_map.invert
+
+      def self.new(name)
+        if name.is_a?(Symbol)
+          name_sym = name
+          name_str = name.to_s
+        else
+          name_str = name.to_s
+          name_sym = name.to_sym
+        end
+        num = h_allocate_token_type(name_str)
+        @@known_type_map[name_sym] = num
+        @@inverse_type_map[num] = name
+      end
 
+      def self.from_name(name)
+        unless @@known_type_map.key? name
+          num =  h_get_token_type_number(name.to_s)
+          if num <= 0
+            raise ArgumentError, "Unknown token type #{name}"
+          end
+          @@known_type_map[name] = num
+          @@inverse_type_map[num] = name
+        end
+        return @@known_type_map[name]
+      end
+
+      def self.from_num(num)
+        unless @@inverse_type_map.key? num
+          name = h_get_token_type_name(num)
+          if name.nil?
+            return nil
+          end
+          name = name.to_sym
+          @@known_type_map[name] = num
+          @@inverse_type_map_type_map[num] = name
+        end
+        return @@inverse_type_map[num]
+      end
+
+      def self.native_type
+        FFI::Type::INT
+      end
+
+      def self.to_native(val, ctx)
+        return val if val.is_a?(Integer)
+        return from_name(val)
+      end
+
+      def self.from_native(val, ctx)
+        return from_num(val) || val
+      end
+    end
+
+    # Define these as soon as possible, so that they can be used
+    # without fear elsewhere
+    attach_function :h_allocate_token_type, [:string], HTokenType
+    attach_function :h_get_token_type_number, [:string], HTokenType
+    attach_function :h_get_token_type_name, [HTokenType], :string
+    
     class HCountedArray < FFI::Struct
       layout  :capacity, :size_t,
               :used, :size_t,
@@ -213,5 +314,7 @@ module Hammer
     attach_function :h_parse_result_free, [HParseResult.by_ref], :void
 
     # TODO: Does the HParser* need to be freed?
+
+    # Token type registry
   end
 end
diff --git a/src/bindings/ruby/lib/hammer/parser.rb b/src/bindings/ruby/lib/hammer/parser.rb
index 4d9f43259c0b396bc92a6633741ba9a8fcff30fc..09f2ff4157127549b987236a49fbf8934d0c3784 100644
--- a/src/bindings/ruby/lib/hammer/parser.rb
+++ b/src/bindings/ruby/lib/hammer/parser.rb
@@ -1,6 +1,10 @@
+require 'hammer/internal'
+
 module Hammer
   class Parser
 
+    @@saved_objects = Hammer::Internal::DynamicVariable.new nil, "Hammer parse-time pins"
+
     # Don't create new instances with Hammer::Parser.new,
     # use the constructor methods instead (i.e. Hammer::Parser.int64 etc.)
     #
@@ -26,17 +30,20 @@ module Hammer
       raise ArgumentError, 'expecting a String' unless data.is_a? String # TODO: Not needed, FFI checks that.
 
       ibuf = FFI::MemoryPointer.from_string(data)
-      result = Hammer::Internal.h_parse(@h_parser, ibuf, data.bytesize) # Don't include the trailing null
-      if result.null?
-        return nil
-      else
-        # NOTE:
-        # The parse result *must* hold a reference to the parser that created it!
-        # Otherwise, the parser might get garbage-collected while the result is still valid.
-        # Any pointers to token strings will then be invalid.
-        result.instance_variable_set :@parser, self
-        return result
-      end
+      @@saved_objects.with([]) do
+          result = Hammer::Internal.h_parse(@h_parser, ibuf, data.bytesize) # Don't include the trailing null
+          if result.null?
+            return nil
+          else
+            # NOTE:
+            # The parse result *must* hold a reference to the parser that created it!
+            # Otherwise, the parser might get garbage-collected while the result is still valid.
+            # Any pointers to token strings will then be invalid.
+            result.instance_variable_set :@parser, self
+            result.instance_variable_set :@pins, @@saved_objects.value
+            return result
+          end
+        end
     end
 
     # Binds an indirect parser.
@@ -71,7 +78,7 @@ module Hammer
       buffer = FFI::MemoryPointer.from_string(string)
       h_parser = Hammer::Internal.h_token(buffer, buffer.size-1) # buffer.size includes the null byte at the end
 
-      return Hammer::Parser.new(:token, h_parser, buffer)
+      return Hammer::Parser.new(:token, h_parser, [buffer, string])
     end
 
     def self.marshal_ch_arg(num)
@@ -100,7 +107,7 @@ module Hammer
 
     def self.int_range(parser, i1, i2)
       h_parser = Hammer::Internal.h_int_range(parser.h_parser, i1, i2)
-      return Hammer::Parser.new(:int_range, h_parser, nil)
+      return Hammer::Parser.new(:int_range, h_parser, [parser])
     end
 
     def self.in(charset)