[DRE-commits] [ruby-parslet] 01/04: Imported Upstream 1.7.0

Jonas Genannt genannt at moszumanska.debian.org
Wed Aug 19 13:40:58 UTC 2015


This is an automated email from the git hooks/post-receive script.

genannt pushed a commit to branch master
in repository ruby-parslet.

commit 0c9f3eba0ca3ccd5aa54ac5c4260ac7ae6907e4e
Author: Jonas Genannt <jonas at brachium-system.net>
Date:   Wed Aug 19 15:31:54 2015 +0200

    Imported Upstream 1.7.0
---
 HISTORY.txt                                    |   9 +-
 README                                         |   2 +-
 checksums.yaml.gz                              | Bin 269 -> 0 bytes
 lib/parslet.rb                                 |   4 +-
 lib/parslet/atoms/base.rb                      |  12 +-
 lib/parslet/atoms/context.rb                   |  10 +-
 lib/parslet/atoms/entity.rb                    |  13 +-
 lib/parslet/atoms/sequence.rb                  |   2 +-
 lib/parslet/cause.rb                           |  19 +-
 lib/parslet/error_reporter.rb                  |   3 +-
 lib/parslet/error_reporter/contextual.rb       | 120 +++++++
 lib/parslet/error_reporter/deepest.rb          |   5 +
 lib/parslet/error_reporter/tree.rb             |   8 +-
 lib/parslet/rig/rspec.rb                       |   2 +-
 lib/parslet/slice.rb                           |   4 +-
 lib/parslet/transform.rb                       |  31 +-
 metadata.yml                                   |  61 +++-
 parslet.gemspec                                |  18 ++
 spec/acceptance/examples_spec.rb               |  37 +++
 spec/acceptance/infix_parser_spec.rb           | 112 +++++++
 spec/acceptance/regression_spec.rb             | 314 ++++++++++++++++++
 spec/acceptance/repetition_and_maybe_spec.rb   |  42 +++
 spec/acceptance/unconsumed_input_spec.rb       |  21 ++
 spec/parslet/atom_results_spec.rb              |  39 +++
 spec/parslet/atoms/alternative_spec.rb         |  26 ++
 spec/parslet/atoms/base_spec.rb                | 126 ++++++++
 spec/parslet/atoms/capture_spec.rb             |  21 ++
 spec/parslet/atoms/combinations_spec.rb        |   5 +
 spec/parslet/atoms/dsl_spec.rb                 |  25 ++
 spec/parslet/atoms/entity_spec.rb              |  77 +++++
 spec/parslet/atoms/infix_spec.rb               |   5 +
 spec/parslet/atoms/lookahead_spec.rb           |  22 ++
 spec/parslet/atoms/named_spec.rb               |   4 +
 spec/parslet/atoms/re_spec.rb                  |  14 +
 spec/parslet/atoms/repetition_spec.rb          |  24 ++
 spec/parslet/atoms/scope_spec.rb               |  26 ++
 spec/parslet/atoms/sequence_spec.rb            |  28 ++
 spec/parslet/atoms/str_spec.rb                 |  15 +
 spec/parslet/atoms/visitor_spec.rb             |  80 +++++
 spec/parslet/atoms_spec.rb                     | 429 +++++++++++++++++++++++++
 spec/parslet/convenience_spec.rb               |  48 +++
 spec/parslet/error_reporter/contextual_spec.rb | 115 +++++++
 spec/parslet/error_reporter/deepest_spec.rb    |  73 +++++
 spec/parslet/error_reporter/tree_spec.rb       |   7 +
 spec/parslet/export_spec.rb                    |  67 ++++
 spec/parslet/expression/treetop_spec.rb        |  74 +++++
 spec/parslet/minilisp.citrus                   |  29 ++
 spec/parslet/minilisp.tt                       |  29 ++
 spec/parslet/parser_spec.rb                    |  31 ++
 spec/parslet/parslet_spec.rb                   |  38 +++
 spec/parslet/pattern_spec.rb                   | 272 ++++++++++++++++
 spec/parslet/position_spec.rb                  |  14 +
 spec/parslet/rig/rspec_spec.rb                 |  54 ++++
 spec/parslet/scope_spec.rb                     |  45 +++
 spec/parslet/slice_spec.rb                     | 144 +++++++++
 spec/parslet/source/line_cache_spec.rb         |  74 +++++
 spec/parslet/source_spec.rb                    | 168 ++++++++++
 spec/parslet/transform/context_spec.rb         |  35 ++
 spec/parslet/transform_spec.rb                 | 165 ++++++++++
 spec/spec_helper.rb                            |  38 +++
 60 files changed, 3299 insertions(+), 36 deletions(-)

diff --git a/HISTORY.txt b/HISTORY.txt
index ad81ce2..8f04f9f 100644
--- a/HISTORY.txt
+++ b/HISTORY.txt
@@ -3,11 +3,13 @@
   - prsnt? and absnt? are now finally banned into oblivion. Wasting vocals for
     the win. 
 
-= 1.7 / ???
+= 1.7 / 12Mar2015
 
   ! Small speed gains from improvements on the hot spots. 
+  + Contextual error reporter, a flavor of error reporting that emphasizes 
+    context. 
 
-= 1.6 / 1May2014
+= 1.6 / 1May2014, 13Okt14
 
   + EXPERIMENTAL: Parslet accelerators permit replacing parts of your parser 
     with optimized atoms using pattern matching. Look at 
@@ -27,6 +29,9 @@
 
   ! A few small bug fixes and optimisations have been introduced. API should 
     remain unchanged. 
+
+  + More lenient on the blankslate version.
+  + Modernizes the test suite to run with rspec again. (!)
     
 = 1.5 / 27Dec2012
     
diff --git a/README b/README
index 90444b1..9d19787 100644
--- a/README
+++ b/README
@@ -11,7 +11,7 @@ the atoms of your language first; _parslet_ takes pride in making this
 possible.
 
 Eager to try this out? Please see the associated web site:
-http://kschiess.github.com/parslet
+http://kschiess.github.io/parslet
 
 SYNOPSIS
 
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
deleted file mode 100644
index 7fea2e9..0000000
Binary files a/checksums.yaml.gz and /dev/null differ
diff --git a/lib/parslet.rb b/lib/parslet.rb
index 2fef5ce..fb332bd 100644
--- a/lib/parslet.rb
+++ b/lib/parslet.rb
@@ -99,7 +99,7 @@ module Parslet
     #     root :twobar
     #   end
     #
-    def rule(name, &definition)
+    def rule(name, opts={}, &definition)
       define_method(name) do
         @rules ||= {}     # <name, rule> memoization
         return @rules[name] if @rules.has_key?(name)
@@ -109,7 +109,7 @@ module Parslet
           self.instance_eval(&definition)
         }
         
-        @rules[name] = Atoms::Entity.new(name, &definition_closure)
+        @rules[name] = Atoms::Entity.new(name, opts[:label], &definition_closure)
       end
     end
   end
diff --git a/lib/parslet/atoms/base.rb b/lib/parslet/atoms/base.rb
index e145ecf..ea11cb7 100644
--- a/lib/parslet/atoms/base.rb
+++ b/lib/parslet/atoms/base.rb
@@ -7,7 +7,10 @@ class Parslet::Atoms::Base
   include Parslet::Atoms::Precedence
   include Parslet::Atoms::DSL
   include Parslet::Atoms::CanFlatten
-  
+
+  # Parslet label as provided in grammar
+  attr_accessor :label
+
   # Given a string or an IO object, this will attempt a parse of its contents
   # and return a result. If the parse fails, a Parslet::ParseFailed exception
   # will be thrown. 
@@ -83,6 +86,8 @@ class Parslet::Atoms::Base
     success, value = result = context.try_with_cache(self, source, consume_all)
 
     if success
+      # Notify context
+      context.succ(source)
       # If a consume_all parse was made and doesn't result in the consumption
       # of all the input, that is considered an error. 
       if consume_all && source.chars_left>0
@@ -132,10 +137,11 @@ class Parslet::Atoms::Base
   end
   precedence BASE
   def to_s(outer_prec=OUTER)
+    str = @label || to_s_inner(precedence)
     if outer_prec < precedence
-      "("+to_s_inner(precedence)+")"
+      "(#{str})"
     else
-      to_s_inner(precedence)
+      str
     end
   end
   def inspect
diff --git a/lib/parslet/atoms/context.rb b/lib/parslet/atoms/context.rb
index 7167a74..fb71146 100644
--- a/lib/parslet/atoms/context.rb
+++ b/lib/parslet/atoms/context.rb
@@ -62,7 +62,15 @@ module Parslet::Atoms
       return [false, @reporter.err(*args)] if @reporter
       return [false, nil]
     end
-  
+
+    # Report a successful parse.
+    # @see ErrorReporter::Contextual
+    #
+    def succ(*args)
+      return [true, @reporter.succ(*args)] if @reporter
+      return [true, nil]
+    end
+
     # Returns the current captures made on the input (see
     # Parslet::Atoms::Base#capture). Use as follows: 
     # 
diff --git a/lib/parslet/atoms/entity.rb b/lib/parslet/atoms/entity.rb
index 4df6050..00215f4 100644
--- a/lib/parslet/atoms/entity.rb
+++ b/lib/parslet/atoms/entity.rb
@@ -5,15 +5,16 @@
 # * Be able to print things by their name, not by their sometimes
 #   complicated content.
 #
-# You don't normally use this directly, instead you should generated it by
+# You don't normally use this directly, instead you should generate it by
 # using the structuring method Parslet.rule.
 #
 class Parslet::Atoms::Entity < Parslet::Atoms::Base
   attr_reader :name, :block
-  def initialize(name, &block)
+  def initialize(name, label=nil, &block)
     super()
     
     @name = name
+    @label = label
     @block = block
   end
 
@@ -22,9 +23,11 @@ class Parslet::Atoms::Entity < Parslet::Atoms::Base
   end
   
   def parslet
-    @parslet ||= @block.call.tap { |p| 
-      raise_not_implemented unless p
-    }
+    return @parslet unless @parslet.nil?
+    @parslet = @block.call
+    raise_not_implemented if @parslet.nil?
+    @parslet.label = @label
+    @parslet
   end
 
   def to_s_inner(prec)
diff --git a/lib/parslet/atoms/sequence.rb b/lib/parslet/atoms/sequence.rb
index b556cc9..2d54b28 100644
--- a/lib/parslet/atoms/sequence.rb
+++ b/lib/parslet/atoms/sequence.rb
@@ -31,7 +31,7 @@ class Parslet::Atoms::Sequence < Parslet::Atoms::Base
       unless success
         return context.err(self, source, @error_msgs[:failed], [value]) 
       end
-      
+
       result[idx+1] = value
     end
     
diff --git a/lib/parslet/cause.rb b/lib/parslet/cause.rb
index ce5672c..8d2e48b 100644
--- a/lib/parslet/cause.rb
+++ b/lib/parslet/cause.rb
@@ -44,15 +44,22 @@ module Parslet
     def self.format(source, pos, str, children=[])
       self.new(str, source, pos, children)
     end
-    
+
+    # Update error message to include context provided by label
+    # Update all child causes too (the same context applies to all causes)
+    def set_label(l)
+      @context = " when parsing #{l}"
+      children.each { |c| c.set_label(l) }
+    end
+
     def to_s
       line, column = source.line_and_column(pos)
       # Allow message to be a list of objects. Join them here, since we now
-      # really need it. 
-      Array(message).map { |o| 
-        o.respond_to?(:to_slice) ? 
-          o.str.inspect : 
-          o.to_s }.join + " at line #{line} char #{column}."
+      # really need it.
+      Array(message).map { |o|
+        o.respond_to?(:to_slice) ?
+          o.str.inspect :
+          o.to_s }.join + " at line #{line} char #{column}#{@context}."
     end
     
     # Signals to the outside that the parse has failed. Use this in
diff --git a/lib/parslet/error_reporter.rb b/lib/parslet/error_reporter.rb
index 567c63a..3998e4e 100644
--- a/lib/parslet/error_reporter.rb
+++ b/lib/parslet/error_reporter.rb
@@ -4,4 +4,5 @@ module Parslet::ErrorReporter
 end
 
 require 'parslet/error_reporter/tree'
-require 'parslet/error_reporter/deepest'
\ No newline at end of file
+require 'parslet/error_reporter/deepest'
+require 'parslet/error_reporter/contextual'
diff --git a/lib/parslet/error_reporter/contextual.rb b/lib/parslet/error_reporter/contextual.rb
new file mode 100644
index 0000000..0fe1237
--- /dev/null
+++ b/lib/parslet/error_reporter/contextual.rb
@@ -0,0 +1,120 @@
+module Parslet
+  module ErrorReporter
+
+    # A reporter that tries to improve on the deepest error reporter by
+    # using heuristics to find the most relevant error and provide more
+    # context.
+    # The heuristic chooses the deepest error when parsing a sequence for which
+    # no alternative parsed successfully.
+    #
+    # Given the following parser:
+    #
+    # root(:call)
+    #
+    # rule(:call, label: 'call') {
+    #   identifier >> str('.') >> method
+    # }
+    #
+    # rule(:method, label: 'method call') {
+    #   identifier >> str('(') >> arguments.maybe >> str(')')
+    # }
+    #
+    # rule(:identifier, label: 'identifier') {
+    #   match['[:alnum:]'].repeat(1)
+    # }
+    #
+    # rule(:arguments, label: 'method call arguments') {
+    #   argument >> str(',') >> arguments | argument
+    # }
+    #
+    # rule(:argument) {
+    #    call | identifier
+    # }
+    #
+    # and the following source:
+    #
+    #   foo.bar(a,goo.baz(),c,)
+    #
+    # The contextual reporter returns the following causes:
+    #
+    # 0: Failed to match sequence (identifier '.' method call) at line 1 char 5
+    #    when parsing method call arguments.
+    # 1: Failed to match sequence (identifier '(' method call arguments? ')') at
+    #    line 1 char 22 when parsing method call arguments.
+    # 2: Failed to match [[:alnum:]] at line 1 char 23 when parsing method call
+    #    arguments.
+    #
+    # (where 2 is a child cause of 1 and 1 a child cause of 0)
+    #
+    # The last piece used by the reporter is the (newly introduced) ability
+    # to attach a label to rules that describe a sequence in the grammar. The
+    # labels are used in two places:
+    #   - In the "to_s" of Atom::Base so that any error message uses labels to
+    #     refer to atoms
+    #   - In the cause error messages to give information about which expression
+    #     failed to parse
+    #
+    class Contextual < Deepest
+
+      def initialize
+        @last_reset_pos = 0
+        reset
+      end
+
+      # A sequence expression successfully parsed, reset all errors reported
+      # for previous expressions in the sequence (an alternative matched)
+      # Only reset errors if the position of the source that matched is higher
+      # than the position of the source that was last successful (so we keep
+      # errors that are the "deepest" but for which no alternative succeeded)
+      #
+      def succ(source)
+        source_pos = source.pos.bytepos
+        return if source_pos < @last_reset_pos
+        @last_reset_pos = source_pos
+        reset
+      end
+
+      # Reset deepest error and its position and sequence index
+      #
+      def reset
+        @deepest_cause = nil
+        @label_pos = -1
+      end
+
+      # Produces an error cause that combines the message at the current level
+      # with the errors that happened at a level below (children).
+      # Compute and set label used by Cause to produce error message.
+      #
+      # @param atom [Parslet::Atoms::Base] parslet that failed
+      # @param source [Source] Source that we're using for this parse. (line
+      #   number information...)
+      # @param message [String, Array] Error message at this level.
+      # @param children [Array] A list of errors from a deeper level (or nil).
+      # @return [Cause] An error tree combining children with message.
+      #
+      def err(atom, source, message, children=nil)
+        cause = super(atom, source, message, children)
+        if (label = atom.respond_to?(:label) && atom.label)
+          update_label(label, source.pos.bytepos)
+          cause.set_label(@label)
+        end
+        cause
+      end
+
+      # Update error message label if given label is more relevant.
+      # A label is more relevant if the position of the matched source is
+      # bigger.
+      #
+      # @param label [String] label to apply if more relevant
+      # @param bytepos [Integer] position in source code of matched source
+      #
+      def update_label(label, bytepos)
+        if bytepos >= @label_pos
+          @label_pos = bytepos
+          @label = label
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/parslet/error_reporter/deepest.rb b/lib/parslet/error_reporter/deepest.rb
index 102b4f8..e6fa622 100644
--- a/lib/parslet/error_reporter/deepest.rb
+++ b/lib/parslet/error_reporter/deepest.rb
@@ -50,6 +50,11 @@ module Parslet
       end
       
       # Returns the cause that is currently deepest. Mainly for specs. 
+
+      # Notification that an expression successfully parsed
+      # not used, see ErrorReporter::Contextual
+      def succ(source)
+      end
       #
       attr_reader :deepest_cause
       
diff --git a/lib/parslet/error_reporter/tree.rb b/lib/parslet/error_reporter/tree.rb
index 2fb2750..055363f 100644
--- a/lib/parslet/error_reporter/tree.rb
+++ b/lib/parslet/error_reporter/tree.rb
@@ -52,6 +52,12 @@ module Parslet
         position = pos
         Cause.format(source, position, message, children)
       end
+
+      # Notification that an expression successfully parsed
+      # not used, see ErrorReporter::Contextual
+      def succ(source)
+      end
+
     end
   end
-end
\ No newline at end of file
+end
diff --git a/lib/parslet/rig/rspec.rb b/lib/parslet/rig/rspec.rb
index 71f1aa7..df40508 100644
--- a/lib/parslet/rig/rspec.rb
+++ b/lib/parslet/rig/rspec.rb
@@ -52,7 +52,7 @@ RSpec::Matchers.define(:parse) do |input, opts|
 
   # NOTE: This has a nodoc tag since the rdoc parser puts this into 
   # Object, a thing I would never allow. 
-  chain :as do |expected_output, &block|
+  chain :as do |expected_output=nil, &block|
     as = expected_output
     block = block
   end
diff --git a/lib/parslet/slice.rb b/lib/parslet/slice.rb
index f8ad518..b2b78a6 100644
--- a/lib/parslet/slice.rb
+++ b/lib/parslet/slice.rb
@@ -57,6 +57,8 @@ class Parslet::Slice
   def size
     str.size
   end
+
+  alias length size
   
   # Concatenate two slices; it is assumed that the second slice begins 
   # where the first one ends. The offset of the resulting slice is the same
@@ -105,4 +107,4 @@ class Parslet::Slice
 
     str.inspect << "@#{offset}"
   end
-end
\ No newline at end of file
+end
diff --git a/lib/parslet/transform.rb b/lib/parslet/transform.rb
index b04fa29..6e6f1b2 100644
--- a/lib/parslet/transform.rb
+++ b/lib/parslet/transform.rb
@@ -124,7 +124,8 @@ class Parslet::Transform
     #
     def rule(expression, &block)
       @__transform_rules ||= []
-      @__transform_rules << [Parslet::Pattern.new(expression), block]
+      # Prepend new rules so they have higher precedence than older rules
+      @__transform_rules.unshift([Parslet::Pattern.new(expression), block])
     end
     
     # Allows accessing the class' rules
@@ -132,6 +133,11 @@ class Parslet::Transform
     def rules 
       @__transform_rules || []
     end
+
+    def inherited(subclass)
+      super
+      subclass.instance_variable_set(:@__transform_rules, rules.dup)
+    end
   end
   
   def initialize(&block) 
@@ -149,16 +155,31 @@ class Parslet::Transform
   # * a *transformation block*
   #
   def rule(expression, &block)
-    @rules << [
-      Parslet::Pattern.new(expression), 
-      block
-    ]
+    # Prepend new rules so they have higher precedence than older rules
+    @rules.unshift([Parslet::Pattern.new(expression), block])
   end
   
   # Applies the transformation to a tree that is generated by Parslet::Parser
   # or a simple parslet. Transformation will proceed down the tree, replacing
   # parts/all of it with new objects. The resulting object will be returned. 
   #
+  # Using the context parameter, you can inject bindings for the transformation.
+  # This can be used to allow access to the outside world from transform blocks,
+  # like so:
+  # 
+  #   document = # some class that you act on
+  #   transform.apply(tree, document: document)
+  #   
+  # The above will make document available to all your action blocks: 
+  #
+  #   # Variant A
+  #   rule(...) { document.foo(bar) }
+  #   # Variant B
+  #   rule(...) { |d| d[:document].foo(d[:bar]) }
+  #
+  # @param obj PORO ast to transform
+  # @param context start context to inject into the bindings. 
+  #
   def apply(obj, context=nil)
     transform_elt(
       case obj
diff --git a/metadata.yml b/metadata.yml
index c761d5a..7c0bcf4 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,29 +1,35 @@
 --- !ruby/object:Gem::Specification
 name: parslet
 version: !ruby/object:Gem::Version
-  version: 1.6.1
+  version: 1.7.0
 platform: ruby
 authors:
 - Kaspar Schiess
 autorequire: 
 bindir: bin
 cert_chain: []
-date: 2014-05-22 00:00:00.000000000 Z
+date: 2015-08-19 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: blankslate
   requirement: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - ">="
       - !ruby/object:Gem::Version
         version: '2.0'
+    - - "<="
+      - !ruby/object:Gem::Version
+        version: '4.0'
   type: :runtime
   prerelease: false
   version_requirements: !ruby/object:Gem::Requirement
     requirements:
-    - - "~>"
+    - - ">="
       - !ruby/object:Gem::Version
         version: '2.0'
+    - - "<="
+      - !ruby/object:Gem::Version
+        version: '4.0'
 description: 
 email: kaspar.schiess at absurd.li
 executables: []
@@ -118,6 +124,7 @@ files:
 - lib/parslet/context.rb
 - lib/parslet/convenience.rb
 - lib/parslet/error_reporter.rb
+- lib/parslet/error_reporter/contextual.rb
 - lib/parslet/error_reporter/deepest.rb
 - lib/parslet/error_reporter/tree.rb
 - lib/parslet/export.rb
@@ -134,7 +141,50 @@ files:
 - lib/parslet/source.rb
 - lib/parslet/source/line_cache.rb
 - lib/parslet/transform.rb
-homepage: http://kschiess.github.com/parslet
+- parslet.gemspec
+- spec/acceptance/examples_spec.rb
+- spec/acceptance/infix_parser_spec.rb
+- spec/acceptance/regression_spec.rb
+- spec/acceptance/repetition_and_maybe_spec.rb
+- spec/acceptance/unconsumed_input_spec.rb
+- spec/parslet/atom_results_spec.rb
+- spec/parslet/atoms/alternative_spec.rb
+- spec/parslet/atoms/base_spec.rb
+- spec/parslet/atoms/capture_spec.rb
+- spec/parslet/atoms/combinations_spec.rb
+- spec/parslet/atoms/dsl_spec.rb
+- spec/parslet/atoms/entity_spec.rb
+- spec/parslet/atoms/infix_spec.rb
+- spec/parslet/atoms/lookahead_spec.rb
+- spec/parslet/atoms/named_spec.rb
+- spec/parslet/atoms/re_spec.rb
+- spec/parslet/atoms/repetition_spec.rb
+- spec/parslet/atoms/scope_spec.rb
+- spec/parslet/atoms/sequence_spec.rb
+- spec/parslet/atoms/str_spec.rb
+- spec/parslet/atoms/visitor_spec.rb
+- spec/parslet/atoms_spec.rb
+- spec/parslet/convenience_spec.rb
+- spec/parslet/error_reporter/contextual_spec.rb
+- spec/parslet/error_reporter/deepest_spec.rb
+- spec/parslet/error_reporter/tree_spec.rb
+- spec/parslet/export_spec.rb
+- spec/parslet/expression/treetop_spec.rb
+- spec/parslet/minilisp.citrus
+- spec/parslet/minilisp.tt
+- spec/parslet/parser_spec.rb
+- spec/parslet/parslet_spec.rb
+- spec/parslet/pattern_spec.rb
+- spec/parslet/position_spec.rb
+- spec/parslet/rig/rspec_spec.rb
+- spec/parslet/scope_spec.rb
+- spec/parslet/slice_spec.rb
+- spec/parslet/source/line_cache_spec.rb
+- spec/parslet/source_spec.rb
+- spec/parslet/transform/context_spec.rb
+- spec/parslet/transform_spec.rb
+- spec/spec_helper.rb
+homepage: http://kschiess.github.io/parslet
 licenses:
 - MIT
 metadata: {}
@@ -161,4 +211,3 @@ signing_key:
 specification_version: 4
 summary: Parser construction library with great error reporting in Ruby.
 test_files: []
-has_rdoc: 
diff --git a/parslet.gemspec b/parslet.gemspec
new file mode 100644
index 0000000..572d37c
--- /dev/null
+++ b/parslet.gemspec
@@ -0,0 +1,18 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+  s.name = 'parslet'
+  s.version = '1.7.0'
+
+  s.authors = ['Kaspar Schiess']
+  s.email = 'kaspar.schiess at absurd.li'
+  s.extra_rdoc_files = ['README']
+  s.files = %w(HISTORY.txt LICENSE Rakefile README parslet.gemspec) + Dir.glob("{lib,spec,example}/**/*")
+  s.homepage = 'http://kschiess.github.io/parslet'
+  s.license = 'MIT'
+  s.rdoc_options = ['--main', 'README']
+  s.require_paths = ['lib']
+  s.summary = 'Parser construction library with great error reporting in Ruby.'  
+  
+  s.add_dependency 'blankslate', '>= 2.0', '<= 4.0'
+end
diff --git a/spec/acceptance/examples_spec.rb b/spec/acceptance/examples_spec.rb
new file mode 100644
index 0000000..7e0eae4
--- /dev/null
+++ b/spec/acceptance/examples_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'open3'
+
+describe "Regression on" do
+  Dir["example/*.rb"].each do |example|
+    context example do
+      # Generates a product path for a given example file. 
+      def product_path(str, ext)
+        str.
+          gsub('.rb', ".#{ext}").
+          gsub('example/','example/output/')
+      end
+      
+      it "runs successfully" do
+        stdin, stdout, stderr = Open3.popen3("ruby #{example}")
+        
+        handle_map = {
+          stdout => :out, 
+          stderr => :err
+        }
+        expectation_found = handle_map.any? do |io, ext|
+          name = product_path(example, ext)
+          
+          if File.exists?(name)
+            io.read.strip.should == File.read(name).strip
+            true
+          end
+        end
+        
+        unless expectation_found
+          fail "Example doesn't have either an .err or an .out file. "+
+            "Please create in examples/output!"
+        end
+      end
+    end
+  end
+end
diff --git a/spec/acceptance/infix_parser_spec.rb b/spec/acceptance/infix_parser_spec.rb
new file mode 100644
index 0000000..c676a29
--- /dev/null
+++ b/spec/acceptance/infix_parser_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+
+describe 'Infix expression parsing' do
+  class InfixExpressionParser < Parslet::Parser
+    rule(:space) { match['\s'] }
+
+    def cts atom
+      atom >> space.repeat
+    end
+    def infix *args
+      Infix.new(*args)
+    end
+
+    rule(:mul_op) { match['*/'] >> str(' ').maybe }
+    rule(:add_op) { match['+-'] >> str(' ').maybe }
+    rule(:digit) { match['0-9'] }
+    rule(:integer) { cts digit.repeat(1) }
+
+    rule(:expression) { infix_expression(integer, 
+      [mul_op, 2, :left], 
+      [add_op, 1, :right]) }
+  end
+
+  let(:p) { InfixExpressionParser.new }
+  describe '#integer' do
+    let(:i) { p.integer }
+    it "parses integers" do
+      i.should parse('1')
+      i.should parse('123')
+    end 
+    it "consumes trailing white space" do
+      i.should parse('1   ')
+      i.should parse('134   ')
+    end 
+    it "doesn't parse floats" do
+      i.should_not parse('1.3')
+    end 
+  end
+  describe '#multiplication' do
+    let(:m) { p.expression }
+    it "parses simple multiplication" do
+      m.should parse('1*2').as(l: '1', o: '*', r: '2')
+    end
+    it "parses simple multiplication with spaces" do
+      m.should parse('1 * 2').as(l: '1 ', o: '* ', r: '2')
+    end
+    it "parses division" do
+      m.should parse('1/2')
+    end 
+  end
+  describe '#addition' do
+    let(:a) { p.expression }
+    
+    it "parses simple addition" do
+      a.should parse('1+2')
+    end 
+    it "parses complex addition" do
+      a.should parse('1+2+3-4')
+    end
+    it "parses a single element" do
+      a.should parse('1')
+    end
+  end
+
+  describe 'mixed operations' do
+    let(:mo) { p.expression }
+
+    describe 'inspection' do
+      it 'produces useful expressions' do
+        p.expression.parslet.inspect.should == 
+          "infix_expression(INTEGER, [MUL_OP, ADD_OP])"
+      end
+    end
+    describe 'right associativity' do
+      it 'produces trees that lean right' do
+        mo.should parse('1+2+3').as(
+          l: '1', o: '+', r: {l: '2', o: '+', r: '3'})
+      end      
+    end
+    describe 'left associativity' do
+      it 'produces trees that lean left' do
+        mo.should parse('1*2*3').as(
+          l: {l:'1', o:'*', r:'2'}, o:'*', r:'3')
+      end      
+    end
+    describe 'error handling' do
+      describe 'incomplete expression' do
+        it 'produces the right error' do
+          cause = catch_failed_parse {
+            mo.parse('1+') }
+
+          cause.ascii_tree.to_s.should == <<-ERROR
+INTEGER was expected at line 1 char 3.
+`- Failed to match sequence (DIGIT{1, } SPACE{0, }) at line 1 char 3.
+   `- Expected at least 1 of DIGIT at line 1 char 3.
+      `- Premature end of input at line 1 char 3.
+          ERROR
+        end
+      end
+      describe 'invalid operator' do
+        it 'produces the right error' do
+          cause = catch_failed_parse {
+            mo.parse('1%') }
+
+          cause.ascii_tree.to_s.should == <<-ERROR
+Don't know what to do with "%" at line 1 char 2.
+          ERROR
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/acceptance/regression_spec.rb b/spec/acceptance/regression_spec.rb
new file mode 100644
index 0000000..8ef5947
--- /dev/null
+++ b/spec/acceptance/regression_spec.rb
@@ -0,0 +1,314 @@
+# Encoding: UTF-8
+
+require 'spec_helper'
+
+require 'parslet'
+
+describe "Regressions from real examples" do
+  # This parser piece produces on the left a subtree that is keyed (a hash)
+  # and on the right a subtree that is a repetition of such subtrees. I've
+  # for now decided that these would merge into the repetition such that the
+  # return value is an array. This avoids maybe loosing keys/values in a 
+  # hash merge. 
+  #
+  class ArgumentListParser
+    include Parslet
+
+    rule :argument_list do
+      expression.as(:argument) >> 
+        (comma >> expression.as(:argument)).repeat
+    end
+    rule :expression do
+      string
+    end
+    rule :string do
+      str('"') >> 
+      (
+        str('\\') >> any |
+        str('"').absent? >> any
+      ).repeat.as(:string) >>
+      str('"') >> space?
+    end
+    rule :comma do
+      str(',') >> space?
+    end
+    rule :space? do
+      space.maybe
+    end
+    rule :space do
+      match("[ \t]").repeat(1)
+    end
+    
+    def parse(str)
+      argument_list.parse(str)
+    end
+  end
+  describe ArgumentListParser do
+    let(:instance) { ArgumentListParser.new }
+    it "should have method expression" do
+      instance.should respond_to(:expression)
+    end 
+    it 'should parse "arg1", "arg2"' do
+      result = ArgumentListParser.new.parse('"arg1", "arg2"')
+      
+      result.size.should == 2
+      result.each do |r|
+        r[:argument]
+      end
+    end
+    it 'should parse "arg1", "arg2", "arg3"' do
+      result = ArgumentListParser.new.parse('"arg1", "arg2", "arg3"')
+      
+      result.size.should == 3
+      result.each do |r|
+        r[:argument]
+      end
+    end
+  end
+
+  class ParensParser < Parslet::Parser
+    rule(:balanced) {
+      str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
+    }
+  
+    root(:balanced)
+  end
+  describe ParensParser do
+    let(:instance) { ParensParser.new }
+    
+    context "statefulness: trying several expressions in sequence" do
+      it "should not be stateful" do
+        # NOTE: Since you've come here to read this, I'll explain why
+        # this is broken and not fixed: You're looking at the tuning branch, 
+        # which rewrites a bunch of stuff - so I have failing tests to 
+        # remind me of what is left to be done. And to remind you not to 
+        # trust this code. 
+        instance.parse('(())')
+        lambda {
+          instance.parse('((()))')
+          instance.parse('(((())))')
+        }.should_not raise_error
+      end 
+    end
+    context "expression '(())'" do
+      let(:result) { instance.parse('(())') }
+
+      it "should yield a doubly nested hash" do
+        result.should be_a(Hash)
+        result.should have_key(:m)
+        result[:m].should be_a(Hash)   # This was an array earlier
+      end 
+      context "inner hash" do
+        let(:inner) { result[:m] }
+        
+        it "should have nil as :m" do
+          inner[:m].should be_nil
+        end 
+      end
+    end
+  end
+
+  class ALanguage < Parslet::Parser
+    root(:expressions)
+
+    rule(:expressions) { (line >> eol).repeat(1) | line }
+    rule(:line) { space? >> an_expression.as(:exp).repeat }
+    rule(:an_expression) { str('a').as(:a) >> space? }
+
+    rule(:eol) { space? >> match["\n\r"].repeat(1) >> space? }
+
+    rule(:space?) { space.repeat }
+    rule(:space) { multiline_comment.as(:multi) | line_comment.as(:line) | str(' ') }
+
+    rule(:line_comment) { str('//') >> (match["\n\r"].absent? >> any).repeat }
+    rule(:multiline_comment) { str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') }
+  end
+  describe ALanguage do
+    def remove_indent(s)
+      s.to_s.lines.map { |l| l.chomp.strip }.join("\n")
+    end
+    
+    it "should count lines correctly" do
+      cause = catch_failed_parse {
+        subject.parse('a
+          a a a 
+          aaa // ff
+          /* 
+          a
+          */
+          b
+        ')
+      }
+
+      remove_indent(cause.ascii_tree).should == remove_indent(%q(
+      Expected one of [(LINE EOL){1, }, LINE] at line 1 char 1.
+      |- Extra input after last repetition at line 7 char 11.
+      |  `- Failed to match sequence (LINE EOL) at line 7 char 11.
+      |     `- Failed to match sequence (SPACE? [\n\r]{1, } SPACE?) at line 7 char 11.
+      |        `- Expected at least 1 of [\n\r] at line 7 char 11.
+      |           `- Failed to match [\n\r] at line 7 char 11.
+      `- Don't know what to do with "\n         " at line 1 char 2.).strip)
+    end 
+  end
+
+  class BLanguage < Parslet::Parser
+    root :expression
+    rule(:expression) { b.as(:one) >> b.as(:two) }
+    rule(:b) { str('b') }
+  end
+  describe BLanguage do
+    it "should parse 'bb'" do
+      subject.should parse('bb').as(:one => 'b', :two => 'b')
+    end 
+    it "should transform with binding constraint" do
+      transform = Parslet::Transform.new do |t|
+        t.rule(:one => simple(:b), :two => simple(:b)) { :ok }
+      end
+      transform.apply(subject.parse('bb')).should == :ok
+    end 
+  end
+
+  class UnicodeLanguage < Parslet::Parser
+    root :gobble
+    rule(:gobble) { any.repeat }
+  end
+  describe UnicodeLanguage do
+    it "should parse UTF-8 strings" do
+      subject.should parse('éèäöü').as('éèäöü')
+      subject.should parse('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の').as('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の')
+    end 
+  end
+  
+  class UnicodeSentenceLanguage < Parslet::Parser
+    rule(:sentence) { (match('[^。]').repeat(1) >> str("。")).as(:sentence) }
+    rule(:sentences) { sentence.repeat }
+    root(:sentences)
+  end
+  describe UnicodeSentenceLanguage do
+    let(:string) {
+      "RubyKaigi2009のテーマは、「変わる/変える」です。 前回の" +
+      "RubyKaigi2008のテーマであった「多様性」の言葉の通り、 " +
+      "2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 " +
+      "ますます多様化が進みつつあります。RubyKaigi2008は、そのような " +
+      "Rubyの生態系をあらためて認識する場となりました。 しかし、" +
+      "こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、" +
+      "その違いを認識したところであまり意味がありません。 異なる実装、" +
+      "異なる思想、異なる背景といった、様々な多様性を理解しつつ、 " +
+      "すり合わせるべきものをすり合わせ、変えていくべきところを " +
+      "変えていくことが、豊かな未来へとつながる道に違いありません。"
+    }
+    
+    it "should parse sentences" do
+      subject.should parse(string)
+    end 
+  end
+
+  class TwoCharLanguage < Parslet::Parser
+    root :twochar
+    rule(:twochar) { any >> str('2') }
+  end
+  describe TwoCharLanguage do
+    def di(s)
+      s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n")
+    end
+
+    it "should raise an error" do
+      error = catch_failed_parse {
+        subject.parse('123') }
+      di(error.ascii_tree).should == di(%q(
+        Failed to match sequence (. '2') at line 1 char 2.
+        `- Don't know what to do with "3" at line 1 char 3.
+      ))
+    end 
+  end
+
+  # Issue #68: Extra input reporting, written by jmettraux
+  class RepetitionParser < Parslet::Parser
+    rule(:nl)      { match('[\s]').repeat(1) }
+    rule(:nl?)     { nl.maybe }
+    rule(:sp)      { str(' ').repeat(1) }
+    rule(:sp?)     { str(' ').repeat(0) }
+    rule(:line)    { sp >> str('line') }
+    rule(:body)    { ((line | block) >> nl).repeat(0) }
+    rule(:block)   { sp? >> str('begin') >> sp >> match('[a-z]') >> nl >>
+                     body >> sp? >> str('end') }
+    rule(:blocks)  { nl? >> block >> (nl >> block).repeat(0) >> nl? }
+
+    root(:blocks)
+  end
+  describe RepetitionParser do
+    def di(s)
+      s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n")
+    end
+
+    it 'parses a block' do
+      subject.parse(%q{
+        begin a
+        end
+      })
+    end
+    it 'parses nested blocks' do
+      subject.parse(%q{
+        begin a
+          begin b
+          end
+        end
+      })
+    end
+    it 'parses successive blocks' do
+      subject.parse(%q{
+        begin a
+        end
+        begin b
+        end
+      })
+    end
+    it 'fails gracefully on a missing end' do
+      error = catch_failed_parse {
+        subject.parse(%q{
+          begin a
+            begin b
+          end
+        }) }
+      
+      di(error.ascii_tree).should == di(%q(
+        Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 2 char 11.
+        `- Failed to match sequence (SP? 'begin' SP [a-z] NL BODY SP? 'end') at line 5 char 9.
+           `- Premature end of input at line 5 char 9.
+        ))
+    end
+    it 'fails gracefully on a missing end (2)' do
+      error = catch_failed_parse {
+        subject.parse(%q{
+          begin a
+          end
+          begin b
+            begin c
+          end
+        }) }
+
+      di(error.ascii_tree).should == di(%q(
+        Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 14.
+        `- Don't know what to do with "begin b\n  " at line 4 char 11.
+        ))
+    end
+    it 'fails gracefully on a missing end (deepest reporter)' do
+      error = catch_failed_parse {
+        subject.parse(%q{
+            begin a
+            end
+            begin b
+              begin c
+                li
+              end
+            end
+          },
+          :reporter => Parslet::ErrorReporter::Deepest.new) }
+
+      di(error.ascii_tree).should == di(%q(
+        Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 16.
+        `- Expected "end", but got "li\n" at line 6 char 17.
+        ))
+    end
+  end
+end
diff --git a/spec/acceptance/repetition_and_maybe_spec.rb b/spec/acceptance/repetition_and_maybe_spec.rb
new file mode 100644
index 0000000..8bfcab5
--- /dev/null
+++ b/spec/acceptance/repetition_and_maybe_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe "Tree output" do
+  extend Parslet
+
+  def self.hash_examples(h)
+    h.each do |atom, expected|
+      it "should convert #{atom} to #{expected.inspect}" do
+        atom.parse(input).should == expected
+      end
+    end
+  end
+
+  context "when parsing the empty string" do
+    let(:input) { '' }
+    hash_examples( 
+      # No naming 
+      str('a').maybe              => '', 
+      str('a').repeat             => '',
+      
+      # Named contents: maybe yields nil
+      str('a').maybe.as(:f)       => {:f => nil},
+      str('a').repeat.as(:f)      => {:f => []}, 
+      
+      # Contents that aren't simple strings
+      (str('a') >> str('b')).maybe.as(:f)     => {:f => nil},
+      (str('a') >> str('b')).repeat.as(:f)    => {:f => []}, 
+      
+      # The other way around: Contents would be tagged, but nil result isn't
+      (str('a') >> str('b')).as(:f).maybe     => '',
+      (str('a') >> str('b')).as(:f).repeat    => ''
+    )
+  end
+  
+  context "when parsing 'aa'" do
+    let(:input) { 'aa' }
+    hash_examples(
+      # since they're not named, repetitions get merged together.
+      str('a').as(:a).repeat >> str('a').as(:a).repeat => [{:a=>'a'},{:a=>'a'}]
+    )
+  end
+end
\ No newline at end of file
diff --git a/spec/acceptance/unconsumed_input_spec.rb b/spec/acceptance/unconsumed_input_spec.rb
new file mode 100644
index 0000000..ffc48f8
--- /dev/null
+++ b/spec/acceptance/unconsumed_input_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe "Unconsumed input:" do
+  class RepeatingBlockParser < Parslet::Parser
+    root :expressions
+    rule(:expressions) { expression.repeat }
+    rule(:expression) { str('(') >> aab >> str(')') }
+    rule(:aab) { str('a').repeat(1) >> str('b') }
+  end
+  describe RepeatingBlockParser do
+    let(:parser) { described_class.new }
+    it "throws annotated error" do
+      error = catch_failed_parse { parser.parse('(aaac)') }
+    end
+    it "doesn't error out if prefix is true" do
+      expect {
+        parser.parse('(aaac)', :prefix => true)
+      }.not_to raise_error
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atom_results_spec.rb b/spec/parslet/atom_results_spec.rb
new file mode 100644
index 0000000..1dcfafa
--- /dev/null
+++ b/spec/parslet/atom_results_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'Result of a Parslet#parse' do
+  include Parslet; extend Parslet
+  
+  describe "regression" do
+    [
+      # Behaviour with maybe-nil
+      [str('foo').maybe >> str('bar'), "bar", "bar"],
+      [str('bar') >> str('foo').maybe, "bar", 'bar'], 
+      
+      # These might be hard to understand; look at the result of 
+      #   str.maybe >> str
+      # and 
+      #   str.maybe >> str first. 
+      [(str('f').maybe >> str('b')).repeat, "bb", "bb"],
+      [(str('b') >> str('f').maybe).repeat, "bb", 'bb'], 
+      
+      [str('a').as(:a) >> (str('b') >> str('c').as(:a)).repeat, 'abc', 
+        [{:a=>'a'}, {:a=>'c'}]], 
+      
+      [str('a').as(:a).repeat >> str('b').as(:b).repeat, 'ab', [{:a=>'a'}, {:b=>'b'}]], 
+      
+      # Repetition behaviour / named vs. unnamed
+      [str('f').repeat, '', ''], 
+      [str('f').repeat.as(:f), '', {:f => []}],
+
+      # Maybe behaviour / named vs. unnamed
+      [str('f').maybe, '', ''], 
+      [str('f').maybe.as(:f), '', {:f => nil}],
+    ].each do |parslet, input, result|
+      context "#{parslet.inspect}" do
+        it "should parse \"#{input}\" into \"#{result}\"" do
+          parslet.parse(input).should == result
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/alternative_spec.rb b/spec/parslet/atoms/alternative_spec.rb
new file mode 100644
index 0000000..79bd296
--- /dev/null
+++ b/spec/parslet/atoms/alternative_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Alternative do
+  include Parslet
+  
+  describe '| shortcut' do
+    let(:alternative) { str('a') | str('b') }
+    
+    context "when chained with different atoms" do
+      before(:each) { 
+        # Chain something else to the alternative parslet. If it modifies the
+        # parslet atom in place, we'll notice: 
+        
+        alternative | str('d')
+      }
+      let!(:chained) { alternative | str('c') }
+      
+      
+      it "is side-effect free" do
+        chained.should parse('c')
+        chained.should parse('a')
+        chained.should_not parse('d')
+      end 
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/base_spec.rb b/spec/parslet/atoms/base_spec.rb
new file mode 100644
index 0000000..9940ac0
--- /dev/null
+++ b/spec/parslet/atoms/base_spec.rb
@@ -0,0 +1,126 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Base do
+  let(:parslet) { Parslet::Atoms::Base.new }
+  let(:context) { Parslet::Atoms::Context.new }
+
+  describe "<- #try(io)" do
+    it "should raise NotImplementedError" do
+      lambda {
+        parslet.try(flexmock(:io), context, false)
+      }.should raise_error(NotImplementedError)
+    end 
+  end
+  describe "<- #flatten_sequence" do
+    [
+      # 9 possibilities for making a word of 2 letters from the alphabeth of
+      # A(rray), H(ash) and S(tring). Make sure that all results are valid.
+      #
+      ['a', 'b'], 'ab',                             # S S
+      [['a'], ['b']], ['a', 'b'],                   # A A
+      [{:a=>'a'}, {:b=>'b'}], {:a=>'a',:b=>'b'},    # H H
+      
+      [{:a=>'a'}, ['a']], [{:a=>'a'}, 'a'],         # H A
+      [{:a=>'a'}, 's'],   {:a=>'a'},                # H S
+
+      [['a'], {:a=>'a'}], ['a', {:a=>'a'}],         # A H (symmetric to H A)
+      [['a'], 'b'], ['a'],                          # A S 
+
+      ['a', {:b=>'b'}], {:b=>'b'},                  # S H (symmetric to H S)
+      ['a', ['b']], ['b'],                          # S A (symmetric to A S)
+      
+      [nil, ['a']], ['a'],                          # handling of lhs nil
+      [nil, {:a=>'a'}], {:a=>'a'},
+      [['a'], nil], ['a'],                          # handling of rhs nil
+      [{:a=>'a'}, nil], {:a=>'a'}
+    ].each_slice(2) do |sequence, result|
+      context "for " + sequence.inspect do
+        it "should equal #{result.inspect}" do
+          parslet.flatten_sequence(sequence).should == result
+        end
+      end
+    end
+  end
+  describe "<- #flatten_repetition" do
+    def unnamed(obj)
+      parslet.flatten_repetition(obj, false)
+    end
+    
+    it "should give subtrees precedence" do
+      unnamed([[{:a=>"a"}, {:m=>"m"}], {:a=>"a"}]).should == [{:a=>"a"}]
+    end 
+  end
+  describe '#parse(source)' do
+    context "when given something that looks like a source" do
+      let(:source) { flexmock("source lookalike", 
+        :line_and_column => [1,2], 
+        :bytepos => 1, 
+        :chars_left => 0) }
+      
+      it "should not rewrap in a source" do
+        flexmock(Parslet::Source).
+          should_receive(:new => :source_created).never
+        
+        begin
+          parslet.parse(source) 
+        rescue NotImplementedError
+        end
+      end 
+    end
+  end
+  
+  context "when the parse fails, the exception" do
+    it "should contain a string" do
+      begin
+        Parslet.str('foo').parse('bar')
+      rescue Parslet::ParseFailed => ex
+        ex.message.should be_kind_of(String)
+      end
+    end 
+  end
+  context "when not all input is consumed" do
+    let(:parslet) { Parslet.str('foo') }
+    
+    it "should raise with a proper error message" do
+      error = catch_failed_parse {
+        parslet.parse('foobar') }
+      
+      error.to_s.should == "Don't know what to do with \"bar\" at line 1 char 4."
+    end 
+  end
+  context "when only parsing string prefix" do
+    let(:parslet) { Parslet.str('foo') >> Parslet.str('bar') }
+    
+    it "returns the first half on a prefix parse" do
+      parslet.parse('foobarbaz', :prefix => true).should == 'foobar'
+    end 
+  end
+
+  describe ':reporter option' do
+    let(:parslet) { Parslet.str('test') >> Parslet.str('ing') }
+    let(:reporter) { flexmock(:reporter) }
+    
+    it "replaces the default reporter" do
+      cause = flexmock(:cause)
+      
+      # Two levels of the parse, calling two different error reporting
+      # methods.
+      reporter.
+        should_receive(:err_at).once
+      reporter.
+        should_receive(:err => cause).once
+      reporter.
+        should_receive(:succ).once
+      
+      # The final cause will be sent the #raise method.
+      cause.
+        should_receive(:raise).once.and_throw(:raise)
+      
+      catch(:raise) {
+        parslet.parse('testung', :reporter => reporter)
+        
+        fail "NEVER REACHED"
+      }
+    end 
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/capture_spec.rb b/spec/parslet/atoms/capture_spec.rb
new file mode 100644
index 0000000..2f3a68a
--- /dev/null
+++ b/spec/parslet/atoms/capture_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Capture do
+  include Parslet
+  
+  let(:context) { Parslet::Atoms::Context.new(nil) }
+  
+  def inject string, parser
+    source = Parslet::Source.new(string)
+    parser.apply(source, context, true)
+  end
+  
+  it "should capture simple results" do
+    inject 'a', str('a').capture(:a)
+    context.captures[:a].should == 'a'
+  end 
+  it "should capture complex results" do
+    inject 'a', str('a').as(:b).capture(:a)
+    context.captures[:a].should == {:b => 'a'}
+  end 
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/combinations_spec.rb b/spec/parslet/atoms/combinations_spec.rb
new file mode 100644
index 0000000..5edbfff
--- /dev/null
+++ b/spec/parslet/atoms/combinations_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe "Parslet combinations" do
+  include Parslet
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/dsl_spec.rb b/spec/parslet/atoms/dsl_spec.rb
new file mode 100644
index 0000000..1915bc1
--- /dev/null
+++ b/spec/parslet/atoms/dsl_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::DSL do
+  describe "deprecated methods" do
+    let(:parslet) { Parslet.str('foo') }
+    describe "<- #absnt?" do
+      slet(:absnt) { parslet.absnt? }
+      it '#bound_parslet' do
+        absnt.bound_parslet.should == parslet
+      end
+      it 'should be a negative lookahead' do
+        absnt.positive.should == false
+      end
+    end
+    describe "<- #prsnt?" do
+      slet(:prsnt) { parslet.prsnt? }
+      it '#bound_parslet' do
+        prsnt.bound_parslet.should == parslet
+      end
+      it 'should be a positive lookahead' do
+        prsnt.positive.should == true
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/entity_spec.rb b/spec/parslet/atoms/entity_spec.rb
new file mode 100644
index 0000000..fc6b028
--- /dev/null
+++ b/spec/parslet/atoms/entity_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Entity do
+  context "when constructed with str('bar') inside" do
+    let(:named) { Parslet::Atoms::Entity.new('name', &proc { Parslet.str('bar') }) }
+
+    it "should parse 'bar' without raising exceptions" do
+      named.parse('bar')
+    end 
+    it "should raise when applied to 'foo'" do
+      lambda {
+        named.parse('foo')
+      }.should raise_error(Parslet::ParseFailed)
+    end 
+
+    describe "#inspect" do
+      it "should return the name of the entity" do
+        named.inspect.should == 'NAME'
+      end 
+    end
+  end
+  context "when constructed with empty block" do
+    let(:entity) { Parslet::Atoms::Entity.new('name', &proc { }) }
+    
+    it "should raise NotImplementedError" do
+      lambda {
+        entity.parse('some_string')
+      }.should raise_error(NotImplementedError)
+    end 
+  end
+  
+  context "recursive definition parser" do
+    class RecDefParser
+      include Parslet
+      rule :recdef do
+        str('(') >> atom >> str(')')
+      end
+      rule :atom do
+        str('a') | str('b') | recdef
+      end
+    end
+    let(:parser) { RecDefParser.new }
+    
+    it "should parse balanced parens" do
+      parser.recdef.parse("(((a)))")
+    end
+    it "should not throw 'stack level too deep' when printing errors" do
+      cause = catch_failed_parse { parser.recdef.parse('(((a))') }
+      cause.ascii_tree
+    end
+  end
+
+  context "when constructed with a label" do
+    let(:named) { Parslet::Atoms::Entity.new('name', 'label', &proc { Parslet.str('bar') }) }
+
+    it "should parse 'bar' without raising exceptions" do
+      named.parse('bar')
+    end 
+    it "should raise when applied to 'foo'" do
+      lambda {
+        named.parse('foo')
+      }.should raise_error(Parslet::ParseFailed)
+    end 
+
+    describe "#inspect" do
+      it "should return the label of the entity" do
+        named.inspect.should == 'label'
+      end 
+    end
+
+    describe "#parslet" do
+      it "should set the label on the cached parslet" do
+        named.parslet.label.should == 'label'
+      end
+    end
+  end
+end
diff --git a/spec/parslet/atoms/infix_spec.rb b/spec/parslet/atoms/infix_spec.rb
new file mode 100644
index 0000000..203a3ff
--- /dev/null
+++ b/spec/parslet/atoms/infix_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Infix do
+
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/lookahead_spec.rb b/spec/parslet/atoms/lookahead_spec.rb
new file mode 100644
index 0000000..ac54ecd
--- /dev/null
+++ b/spec/parslet/atoms/lookahead_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Lookahead do
+  include Parslet
+  
+  describe 'negative lookahead' do
+    it "influences the error tree" do
+      parser = str('f').absent? >> str('b')
+      cause = catch_failed_parse { parser.parse('f') }
+      
+      cause.ascii_tree.should == "Failed to match sequence (!'f' 'b') at line 1 char 1.\n`- Input should not start with 'f' at line 1 char 1.\n"
+    end 
+  end
+  describe 'positive lookahead' do
+    it "influences the error tree" do
+      parser = str('f').present? >> str('b')
+      cause = catch_failed_parse { parser.parse('b') }
+      
+      cause.ascii_tree.should == "Failed to match sequence (&'f' 'b') at line 1 char 1.\n`- Input should start with 'f' at line 1 char 1.\n"
+    end 
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/named_spec.rb b/spec/parslet/atoms/named_spec.rb
new file mode 100644
index 0000000..98ce14d
--- /dev/null
+++ b/spec/parslet/atoms/named_spec.rb
@@ -0,0 +1,4 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Named do
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/re_spec.rb b/spec/parslet/atoms/re_spec.rb
new file mode 100644
index 0000000..44f9cd7
--- /dev/null
+++ b/spec/parslet/atoms/re_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Re do
+  describe "construction" do
+    include Parslet
+    
+    it "should allow match(str) form" do
+      match('[a]').should be_a(Parslet::Atoms::Re)
+    end 
+    it "should allow match[str] form" do
+      match['a'].should be_a(Parslet::Atoms::Re)
+    end 
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/repetition_spec.rb b/spec/parslet/atoms/repetition_spec.rb
new file mode 100644
index 0000000..0f6a8c1
--- /dev/null
+++ b/spec/parslet/atoms/repetition_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Repetition do
+  include Parslet
+
+  describe "repeat" do
+    let(:parslet) { str('a') }
+    
+    describe "(min, max)" do
+      subject { parslet.repeat(1,2) }
+      
+      it { should_not parse("") }
+      it { should parse("a") }
+      it { should parse("aa") }
+    end
+    describe "0 times" do
+      it "raises an ArgumentError" do
+        expect {
+          parslet.repeat(0,0)
+        }.to raise_error(ArgumentError)
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/scope_spec.rb b/spec/parslet/atoms/scope_spec.rb
new file mode 100644
index 0000000..2664bc8
--- /dev/null
+++ b/spec/parslet/atoms/scope_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Scope do
+  include Parslet
+  include Parslet::Atoms::DSL
+  
+  
+  let(:context) { Parslet::Atoms::Context.new(nil) }
+  let(:captures) { context.captures }
+  
+  def inject string, parser
+    source = Parslet::Source.new(string)
+    parser.apply(source, context, true)
+  end
+  
+  let(:aabb) { 
+    scope {
+      match['ab'].capture(:f) >> dynamic { |s,c| str(c.captures[:f]) }
+    }
+  }
+  it "keeps values of captures outside" do
+    captures[:f] = 'old_value'
+    inject 'aa', aabb
+    captures[:f].should == 'old_value'
+  end 
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/sequence_spec.rb b/spec/parslet/atoms/sequence_spec.rb
new file mode 100644
index 0000000..c2cec13
--- /dev/null
+++ b/spec/parslet/atoms/sequence_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Parslet::Atoms::Sequence do
+  include Parslet
+  
+  let(:sequence) { described_class.new }
+  
+  describe '>> shortcut' do
+    let(:sequence) { str('a') >> str('b') }
+    
+    context "when chained with different atoms" do
+      before(:each) { 
+        # Chain something else to the sequence parslet. If it modifies the
+        # parslet atom in place, we'll notice: 
+        
+        sequence >> str('d')
+      }
+      let!(:chained) { sequence >> str('c') }
+      
+      
+      it "is side-effect free" do
+        chained.should parse('abc')
+        chained.should_not parse('abdc')
+      end 
+    end
+    
+  end
+end
diff --git a/spec/parslet/atoms/str_spec.rb b/spec/parslet/atoms/str_spec.rb
new file mode 100644
index 0000000..455f82d
--- /dev/null
+++ b/spec/parslet/atoms/str_spec.rb
@@ -0,0 +1,15 @@
+# Encoding: UTF-8
+
+require 'spec_helper'
+
+describe Parslet::Atoms::Str do
+  def str(s)
+    described_class.new(s)
+  end
+  
+  describe 'regression #1: multibyte characters' do
+    it "parses successfully (length check works)" do
+      str('あああ').should parse('あああ')
+    end 
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms/visitor_spec.rb b/spec/parslet/atoms/visitor_spec.rb
new file mode 100644
index 0000000..c195cee
--- /dev/null
+++ b/spec/parslet/atoms/visitor_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Parslet::Atoms do
+  include Parslet
+  let(:visitor) { flexmock(:visitor) }
+  
+  describe Parslet::Atoms::Str do
+    let(:parslet) { str('foo') }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_str).with('foo').once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Re do
+    let(:parslet) { match['abc'] }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_re).with('[abc]').once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Sequence do
+    let(:parslet) { str('a') >> str('b') }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_sequence).with(Array).once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Repetition do
+    let(:parslet) { str('a').repeat(1,2) }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_repetition).with(:repetition, 1, 2, Parslet::Atoms::Base).once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Alternative do
+    let(:parslet) { str('a') | str('b') }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_alternative).with(Array).once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Named do
+    let(:parslet) { str('a').as(:a) }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_named).with(:a, Parslet::Atoms::Base).once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Entity do
+    let(:parslet) { Parslet::Atoms::Entity.new('foo', &lambda {}) }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_entity).with('foo', Proc).once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe Parslet::Atoms::Lookahead do
+    let(:parslet) { str('a').absent? }
+    it "should call back visitor" do
+      visitor.should_receive(:visit_lookahead).with(false, Parslet::Atoms::Base).once
+      
+      parslet.accept(visitor)
+    end 
+  end
+  describe "< Parslet::Parser" do
+    let(:parslet) { Parslet::Parser.new }
+    it "calls back to visitor" do
+      visitor.should_receive(:visit_parser).with(:root).once
+      
+      flexmock(parslet, :root => :root)
+      parslet.accept(visitor)
+    end 
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/atoms_spec.rb b/spec/parslet/atoms_spec.rb
new file mode 100644
index 0000000..5810280
--- /dev/null
+++ b/spec/parslet/atoms_spec.rb
@@ -0,0 +1,429 @@
+require 'spec_helper'
+
+require 'timeout'
+require 'parslet'
+
+describe Parslet do
+  def not_parse
+    raise_error(Parslet::ParseFailed)
+  end
+  
+  include Parslet
+  extend Parslet
+
+  def src(str); Parslet::Source.new str; end
+  let(:context) { Parslet::Atoms::Context.new }
+  
+  describe "match('[abc]')" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = match('[abc]')
+    end
+    
+    it "should parse {a,b,c}" do
+      parslet.parse('a')
+      parslet.parse('b')
+      parslet.parse('c')
+    end 
+    it "should not parse d" do
+      cause = catch_failed_parse {
+        parslet.parse('d')
+      }
+      cause.to_s.should == "Failed to match [abc] at line 1 char 1."
+    end 
+    it "should print as [abc]" do
+      parslet.inspect.should == "[abc]"
+    end 
+  end
+  describe "match(['[a]').repeat(3)" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = match('[a]').repeat(3)
+    end
+    
+    context "when failing on input 'aa'" do
+      let!(:cause) {
+        catch_failed_parse { parslet.parse('aa') }
+      }
+      it "should have a relevant cause" do
+        cause.to_s.should == "Expected at least 3 of [a] at line 1 char 1."
+      end 
+      it "should have a tree with 2 nodes" do
+        cause.children.size.should == 1
+      end 
+    end
+    it "should succeed on 'aaa'" do
+      parslet.parse('aaa')
+    end 
+    it "should succeed on many 'a'" do
+      parslet.parse('a'*100)
+    end 
+    it "should inspect as [a]{3, }" do
+      parslet.inspect.should == "[a]{3, }"
+    end
+  end
+  describe "str('foo')" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = str('foo')
+    end
+    
+    it "should parse 'foo'" do
+      parslet.parse('foo')
+    end
+    it "should not parse 'bar'"  do
+      cause = catch_failed_parse { parslet.parse('bar') }
+      cause.to_s.should == 
+        "Expected \"foo\", but got \"bar\" at line 1 char 1."
+    end
+    it "should inspect as 'foo'" do
+      parslet.inspect.should == "'foo'"
+    end 
+  end
+  describe "str('foo').maybe" do
+    let(:parslet) { str('foo').maybe }
+
+    it "should parse a foo" do
+      parslet.parse('foo')
+    end
+    it "should leave pos untouched if there is no foo" do
+      source = src('bar')
+      parslet.apply(source, context)
+      source.pos.charpos.should == 0
+    end
+    it "should inspect as 'foo'?" do
+      parslet.inspect.should == "'foo'?"
+    end 
+    context "when parsing 'foo'" do
+      subject { parslet.parse('foo') }
+      
+      it { should == 'foo' }
+    end
+    context "when parsing ''" do
+      subject { parslet.parse('') } 
+      
+      it { should == '' } 
+    end
+  end
+  describe "str('foo') >> str('bar')" do
+    let(:parslet) { str('foo') >> str('bar') }
+    
+    context "when it fails on input 'foobaz'" do
+      let!(:cause) {
+        catch_failed_parse { parslet.parse('foobaz') }
+      }
+
+      it "should not parse 'foobaz'" do
+        cause.to_s.should == "Failed to match sequence ('foo' 'bar') at line 1 char 4."
+      end
+      it "should have 2 nodes in error tree" do
+        cause.children.size.should == 1
+      end 
+    end
+    it "should parse 'foobar'" do
+      parslet.parse('foobar')
+    end
+    it "should inspect as ('foo' 'bar')" do
+      parslet.inspect.should == "'foo' 'bar'"
+    end 
+  end
+  describe "str('foo') | str('bar')" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = str('foo') | str('bar')
+    end
+    
+    context "when failing on input 'baz'" do
+      let!(:cause) {
+        catch_failed_parse { parslet.parse('baz') }
+      }
+
+      it "should have a sensible cause" do
+        cause.to_s.should == "Expected one of ['foo', 'bar'] at line 1 char 1."
+      end   
+      it "should have an error tree with 3 nodes" do
+        cause.children.size.should == 2
+      end 
+    end
+    
+    it "should accept 'foo'" do
+      parslet.parse('foo')
+    end
+    it "should accept 'bar'" do
+      parslet.parse('bar')
+    end
+    it "should inspect as ('foo' / 'bar')" do
+      parslet.inspect.should == "'foo' / 'bar'"
+    end 
+  end
+  describe "str('foo').present? (positive lookahead)" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = str('foo').present?
+    end
+    
+    it "should inspect as &'foo'" do
+      parslet.inspect.should == "&'foo'"
+    end 
+    context "when fed 'foo'" do
+      it "should parse" do
+        success, _ = parslet.apply(src('foo'), context)
+        success.should == true
+      end
+      it "should not change input position" do
+        source = src('foo')
+        parslet.apply(source, context)
+        source.pos.charpos.should == 0
+      end
+    end
+    context "when fed 'bar'" do
+      it "should not parse" do
+        lambda { parslet.parse('bar') }.should not_parse
+      end
+    end
+    describe "<- #parse" do
+      it "should return nil" do
+        parslet.apply(src('foo'), context).should == [true, nil]
+      end 
+    end
+  end
+  describe "str('foo').absent? (negative lookahead)" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = str('foo').absent?
+    end
+    
+    it "should inspect as !'foo'" do
+      parslet.inspect.should == "!'foo'"
+    end 
+    context "when fed 'bar'" do
+      it "should parse" do
+        parslet.apply(src('bar'), context).should == [true, nil]
+      end
+      it "should not change input position" do
+        source = src('bar')
+        parslet.apply(source, context)
+        source.pos.charpos.should == 0
+      end
+    end
+    context "when fed 'foo'" do
+      it "should not parse" do
+        lambda { parslet.parse('foo') }.should not_parse
+      end
+    end
+  end
+  describe "non greedy matcher combined with greedy matcher (possible loop)" do
+    attr_reader :parslet
+    before(:each) do
+      # repeat will always succeed, since it has a minimum of 0. It will not
+      # modify input position in that case. absent? will, depending on
+      # implementation, match as much as possible and call its inner element
+      # again. This leads to an infinite loop. This example tests for the 
+      # absence of that loop. 
+      @parslet = str('foo').repeat.maybe
+    end
+    
+    it "should not loop infinitely" do
+      lambda {
+        timeout(1) { parslet.parse('bar') }
+      }.should raise_error(Parslet::ParseFailed)
+    end 
+  end
+  describe "any" do
+    attr_reader :parslet
+    before(:each) do
+      @parslet = any
+    end
+    
+    it "should match" do
+      parslet.parse('.')
+    end 
+    it "should consume one char" do
+      source = src('foo')
+      parslet.apply(source, context)
+      source.pos.charpos.should == 1
+    end 
+  end
+  describe "eof behaviour" do
+    context "when the pattern just doesn't consume the input" do
+      let (:parslet) { any }
+
+      it "should fail the parse" do
+        cause = catch_failed_parse { parslet.parse('..') }
+        cause.to_s.should == "Don't know what to do with \".\" at line 1 char 2."
+      end 
+    end
+    context "when the pattern doesn't match the input" do
+      let (:parslet) { (str('a')).repeat(1) }
+      attr_reader :exception
+      before(:each) do
+        begin 
+          parslet.parse('a.')
+        rescue => @exception
+        end
+      end
+
+      it "raises Parslet::ParseFailed" do
+        # ParseFailed here, because the input doesn't match the parser grammar. 
+        exception.should be_kind_of(Parslet::ParseFailed)
+      end 
+      it "has the correct error message" do
+        exception.message.should == \
+          "Extra input after last repetition at line 1 char 2."
+      end 
+    end
+  end
+  
+  describe "<- #as(name)" do
+    context "str('foo').as(:bar)" do
+      it "should return :bar => 'foo'" do
+        str('foo').as(:bar).parse('foo').should == { :bar => 'foo' }
+      end 
+    end
+    context "match('[abc]').as(:name)" do
+      it "should return :name => 'b'" do
+        match('[abc]').as(:name).parse('b').should == { :name => 'b' }
+      end 
+    end
+    context "match('[abc]').repeat.as(:name)" do
+      it "should return collated result ('abc')" do
+        match('[abc]').repeat.as(:name).
+          parse('abc').should == { :name => 'abc' }
+      end
+    end
+    context "(str('a').as(:a) >> str('b').as(:b)).as(:c)" do
+      it "should return a hash of hashes" do
+        (str('a').as(:a) >> str('b').as(:b)).as(:c).
+          parse('ab').should == {
+            :c => {
+              :a => 'a', 
+              :b => 'b'
+            }
+          }
+      end 
+    end
+    context "(str('a').as(:a) >> str('ignore') >> str('b').as(:b))" do
+      it "should correctly flatten (leaving out 'ignore')" do
+        (str('a').as(:a) >> str('ignore') >> str('b').as(:b)).
+          parse('aignoreb').should == 
+          {
+            :a => 'a', 
+            :b => 'b'
+          }
+      end
+    end
+    
+    context "(str('a') >> str('ignore') >> str('b')) (no .as(...))" do
+      it "should return simply the original string" do
+        (str('a') >> str('ignore') >> str('b')).
+          parse('aignoreb').should == 'aignoreb'
+      end 
+    end
+    context "str('a').as(:a) >> str('b').as(:a)" do
+      attr_reader :parslet
+      before(:each) do
+        @parslet = str('a').as(:a) >> str('b').as(:a)
+      end
+      
+      it "should issue a warning that a key is being overwritten in merge" do
+        flexmock(parslet).
+          should_receive(:warn).once
+        parslet.parse('ab').should == { :a => 'b' }
+      end
+      it "should return :a => 'b'" do
+        flexmock(parslet).
+          should_receive(:warn)
+          
+        parslet.parse('ab').should == { :a => 'b' }
+      end  
+    end
+    context "str('a').absent?" do
+      it "should return something in merge, even though it is nil" do
+        (str('a').absent? >> str('b').as(:b)).
+          parse('b').should == {:b => 'b'}
+      end
+    end
+    context "str('a').as(:a).repeat" do
+      it "should return an array of subtrees" do
+        str('a').as(:a).repeat.
+          parse('aa').should == [{:a=>'a'}, {:a=>'a'}]
+      end 
+    end
+  end
+  describe "<- #flatten(val)" do
+    def call(val)
+      dummy = str('a')
+      flexmock(dummy, :warn => nil)
+      dummy.flatten(val)
+    end
+    
+    [
+      # In absence of named subtrees: ----------------------------------------
+      # Sequence or Repetition
+      [ [:sequence, 'a', 'b'], 'ab' ], 
+      [ [:repetition, 'a', 'a'], 'aa' ],
+            
+      # Nested inside another node
+      [ [:sequence, [:sequence, 'a', 'b']], 'ab' ],
+      # Combined with lookahead (nil)
+      [ [:sequence, nil, 'a'], 'a' ],
+                  
+      # Including named subtrees ---------------------------------------------
+      # Atom: A named subtree
+      [ {:a=>'a'}, {:a=>'a'} ],
+      # Composition of subtrees
+      [ [:sequence, {:a=>'a'},{:b=>'b'}], {:a=>'a',:b=>'b'} ],
+      # Mixed subtrees :sequence of :repetition yields []
+      [ [:sequence, [:repetition, {:a => 'a'}], {:a => 'a'} ], [{:a=>'a'}, {:a=>'a'}]],
+      [ [:sequence, {:a => 'a'},[:repetition, {:a => 'a'}] ], [{:a=>'a'}, {:a=>'a'}]],
+      [ [:sequence, [:repetition, {:a => 'a'}],[:repetition, {:a => 'a'}] ], [{:a=>'a'}, {:a=>'a'}]],
+      # Repetition
+      [ [:repetition, [:repetition, {:a=>'a'}], [:repetition, {:a=>'a'}]], 
+        [{:a => 'a'}, {:a => 'a'}]],
+      [ [:repetition, {:a=>'a'}, 'a', {:a=>'a'}], [{:a=>'a'}, {:a=>'a'}]],
+      [ [:repetition, {:a=>'a'}, [:repetition, {:b=>'b'}]], [{:a=>'a'}] ],
+      
+      # Some random samples --------------------------------------------------
+      [ [:sequence, {:a => :b, :b => :c}], {:a=>:b, :b=>:c} ], 
+      [ [:sequence, {:a => :b}, 'a', {:c=>:d}], {:a => :b, :c=>:d} ], 
+      [ [:repetition, {:a => :b}, 'a', {:c=>:d}], [{:a => :b}, {:c=>:d}] ], 
+      [ [:sequence, {:a => :b}, {:a=>:d}], {:a => :d} ], 
+      [ [:sequence, {:a=>:b}, [:sequence, [:sequence, "\n", nil]]], {:a=>:b} ], 
+      [ [:sequence, nil, " "], ' ' ], 
+    ].each do |input, output|
+      it "should transform #{input.inspect} to #{output.inspect}" do
+        call(input).should == output
+      end
+    end
+  end
+
+  describe "combinations thereof (regression)" do
+    success=[
+      [(str('a').repeat >> str('b').repeat), 'aaabbb'] 
+    ].each do |(parslet, input)|
+      describe "#{parslet.inspect} applied to #{input.inspect}" do
+        it "should parse successfully" do
+          parslet.parse(input)
+        end
+      end 
+    end
+
+    inspection=[
+      [str('a'),                              "'a'"                 ], 
+      [(str('a') | str('b')).maybe,           "('a' / 'b')?"        ], 
+      [(str('a') >> str('b')).maybe,          "('a' 'b')?"          ], 
+      [str('a').maybe.maybe,                  "'a'??"               ], 
+      [(str('a')>>str('b')).maybe.maybe,      "('a' 'b')??"         ], 
+      [(str('a') >> (str('b') | str('c'))),   "'a' ('b' / 'c')"], 
+      
+      [str('a') >> str('b').repeat,           "'a' 'b'{0, }"        ], 
+      [(str('a')>>str('b')).repeat,           "('a' 'b'){0, }"      ]  
+    ].each do |(parslet, inspect_output)|
+      context "regression for #{parslet.inspect}" do
+        it "should inspect correctly as #{inspect_output}" do
+          parslet.inspect.should == inspect_output
+        end 
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/convenience_spec.rb b/spec/parslet/convenience_spec.rb
new file mode 100644
index 0000000..8ba95ec
--- /dev/null
+++ b/spec/parslet/convenience_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe 'parslet/convenience' do
+  require 'parslet/convenience'
+  include Parslet
+  
+  class FooParser < Parslet::Parser
+    rule(:foo) { str('foo') }
+    root(:foo)
+  end
+  
+  describe 'parse_with_debug' do
+    let(:parser) { flexmock FooParser.new }
+    context 'internal' do
+      before(:each) do
+        # Suppress output.
+        #
+        parser.should_receive(:puts)
+      end
+      it 'should exist' do
+        lambda { parser.parse_with_debug('anything') }.should_not raise_error
+      end
+      it 'should catch ParseFailed exceptions' do
+        lambda { parser.parse_with_debug('bar') }.should_not raise_error
+      end
+      it 'should parse correct input like #parse' do
+        lambda { parser.parse_with_debug('foo') }.should_not raise_error
+      end
+    end
+    context 'output' do
+      it 'should puts once for tree output' do
+        parser.should_receive(:puts).once
+        
+        parser.parse_with_debug('incorrect')
+      end
+      it "should puts once for the error on unconsumed input" do
+        parser.should_receive(:puts).once
+
+        parser.parse_with_debug('foobar')
+      end 
+    end
+    
+    it "should work for all parslets" do
+      str('foo').parse_with_debug('foo')
+      match['bar'].parse_with_debug('a')
+    end 
+  end
+end
diff --git a/spec/parslet/error_reporter/contextual_spec.rb b/spec/parslet/error_reporter/contextual_spec.rb
new file mode 100644
index 0000000..c7320d5
--- /dev/null
+++ b/spec/parslet/error_reporter/contextual_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+describe Parslet::ErrorReporter::Contextual do
+
+  let(:reporter) { described_class.new }
+  let(:fake_source) { flexmock('source') }
+  let(:fake_atom) { flexmock('atom') }
+  let(:fake_cause) { flexmock('cause') }
+
+  describe '#err' do
+    before(:each) { fake_source.should_receive(
+      :pos => 13,
+      :line_and_column => [1,1]) }
+
+    it "returns the deepest cause" do
+      flexmock(reporter).
+        should_receive(:deepest).and_return(:deepest)
+      reporter.err('parslet', fake_source, 'message').
+        should == :deepest
+    end
+  end
+  describe '#err_at' do
+    before(:each) { fake_source.should_receive(
+      :pos => 13,
+      :line_and_column => [1,1]) }
+
+    it "returns the deepest cause" do
+      flexmock(reporter).
+        should_receive(:deepest).and_return(:deepest)
+      reporter.err('parslet', fake_source, 'message', 13).
+        should == :deepest
+    end
+  end
+  describe '#deepest(cause)' do
+    def fake_cause(pos=13, children=nil)
+      flexmock('cause' + pos.to_s, :pos => pos, :children => children)
+    end
+
+    context "when there is no deepest cause yet" do
+      let(:cause) { fake_cause }
+      it "returns the given cause" do
+        reporter.deepest(cause).should == cause
+      end
+    end
+    context "when the previous cause is deeper (no relationship)" do
+      let(:previous) { fake_cause }
+      before(:each) {
+        reporter.deepest(previous) }
+
+      it "returns the previous cause" do
+        reporter.deepest(fake_cause(12)).
+          should == previous
+      end
+    end
+    context "when the previous cause is deeper (child)" do
+      let(:previous) { fake_cause }
+      before(:each) {
+        reporter.deepest(previous) }
+
+      it "returns the given cause" do
+        given = fake_cause(12, [previous])
+        reporter.deepest(given).should == given
+      end
+    end
+    context "when the previous cause is shallower" do
+      before(:each) {
+        reporter.deepest(fake_cause) }
+
+      it "stores the cause as deepest" do
+        deeper = fake_cause(14)
+        reporter.deepest(deeper)
+        reporter.deepest_cause.should == deeper
+      end
+    end
+  end
+  describe '#reset' do
+    before(:each) { fake_source.should_receive(
+      :pos => Parslet::Position.new('source', 13),
+      :line_and_column => [1,1]) }
+
+    it "resets deepest cause on success of sibling expression" do
+      flexmock(reporter).
+        should_receive(:deepest).and_return(:deepest)
+      reporter.err('parslet', fake_source, 'message').
+        should == :deepest
+      flexmock(reporter).
+        should_receive(:reset).once
+      reporter.succ(fake_source)
+    end
+  end
+
+  describe 'label' do
+    before(:each) { fake_source.should_receive(
+      :pos => Parslet::Position.new('source', 13),
+      :line_and_column => [1,1]) }
+
+    it "sets label if atom has one" do
+      fake_atom.should_receive(:label).once.and_return('label')
+      fake_cause.should_receive(:set_label).once
+      flexmock(reporter).
+        should_receive(:deepest).and_return(fake_cause)
+      reporter.err(fake_atom, fake_source, 'message').
+        should == fake_cause
+    end
+
+    it 'does not set label if atom does not have one' do
+      flexmock(reporter).
+        should_receive(:deepest).and_return(:deepest)
+      fake_atom.should_receive(:update_label).never
+      reporter.err(fake_atom, fake_source, 'message').
+        should == :deepest
+    end
+  end
+
+end
diff --git a/spec/parslet/error_reporter/deepest_spec.rb b/spec/parslet/error_reporter/deepest_spec.rb
new file mode 100644
index 0000000..fbf957e
--- /dev/null
+++ b/spec/parslet/error_reporter/deepest_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Parslet::ErrorReporter::Deepest do
+  let(:reporter) { described_class.new }
+  let(:fake_source) { flexmock('source') }
+  
+  describe '#err' do
+    before(:each) { fake_source.should_receive(
+      :pos => 13, 
+      :line_and_column => [1,1]) }
+    
+    it "returns the deepest cause" do
+      flexmock(reporter).
+        should_receive(:deepest).and_return(:deepest)
+      reporter.err('parslet', fake_source, 'message').
+        should == :deepest
+    end 
+  end
+  describe '#err_at' do
+    before(:each) { fake_source.should_receive(
+      :pos => 13, 
+      :line_and_column => [1,1]) }
+
+    it "returns the deepest cause" do
+      flexmock(reporter).
+        should_receive(:deepest).and_return(:deepest)
+      reporter.err('parslet', fake_source, 'message', 13).
+        should == :deepest
+    end
+  end
+  describe '#deepest(cause)' do
+    def fake_cause(pos=13, children=nil)
+      flexmock('cause' + pos.to_s, :pos => pos, :children => children)
+    end
+    
+    context "when there is no deepest cause yet" do
+      let(:cause) { fake_cause }
+      it "returns the given cause" do
+        reporter.deepest(cause).should == cause
+      end
+    end
+    context "when the previous cause is deeper (no relationship)" do
+      let(:previous) { fake_cause }
+      before(:each) { 
+        reporter.deepest(previous) }
+      
+      it "returns the previous cause" do
+        reporter.deepest(fake_cause(12)).
+          should == previous
+      end 
+    end
+    context "when the previous cause is deeper (child)" do
+      let(:previous) { fake_cause }
+      before(:each) { 
+        reporter.deepest(previous) }
+        
+      it "returns the given cause" do
+        given = fake_cause(12, [previous])
+        reporter.deepest(given).should == given
+      end 
+    end
+    context "when the previous cause is shallower" do
+      before(:each) { 
+        reporter.deepest(fake_cause) }
+      
+      it "stores the cause as deepest" do
+        deeper = fake_cause(14)
+        reporter.deepest(deeper)
+        reporter.deepest_cause.should == deeper
+      end
+    end
+  end
+end
diff --git a/spec/parslet/error_reporter/tree_spec.rb b/spec/parslet/error_reporter/tree_spec.rb
new file mode 100644
index 0000000..a8b08ab
--- /dev/null
+++ b/spec/parslet/error_reporter/tree_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+require 'parslet/error_reporter'
+
+describe Parslet::ErrorReporter::Tree do
+  
+end
\ No newline at end of file
diff --git a/spec/parslet/export_spec.rb b/spec/parslet/export_spec.rb
new file mode 100644
index 0000000..8f12152
--- /dev/null
+++ b/spec/parslet/export_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Parslet::Parser, "exporting to other lingos" do
+  class MiniLisp < Parslet::Parser
+    root :expression
+    rule(:expression) {
+      space? >> str('(') >> space? >> body >> str(')')
+    }
+    
+    rule(:body) {
+      (expression | identifier | float | integer | string).repeat.as(:exp)
+    }
+    
+    rule(:space) {
+      match('\s').repeat(1)
+    }
+    rule(:space?) {
+      space.maybe
+    }
+    
+    rule(:identifier) { 
+      (match('[a-zA-Z=*]') >> match('[a-zA-Z=*_]').repeat).as(:identifier) >> space?
+    }
+    
+    rule(:float) { 
+      (
+        integer >> (
+          str('.') >> match('[0-9]').repeat(1) |
+          str('e') >> match('[0-9]').repeat(1)
+        ).as(:e)
+      ).as(:float) >> space?
+    }
+    
+    rule(:integer) {
+      ((str('+') | str('-')).maybe >> match("[0-9]").repeat(1)).as(:integer) >> space?
+    }
+    
+    rule(:string) {
+      str('"') >> (
+        str('\\') >> any |
+        str('"').absent? >> any 
+      ).repeat.as(:string) >> str('"') >> space?
+    }
+  end
+
+  # I only update the files once I've verified the new syntax to work with 
+  # the respective tools. This is more an acceptance test than a real spec. 
+
+  describe "<- #to_citrus" do
+    let(:citrus) { File.read(
+      File.join(File.dirname(__FILE__), 'minilisp.citrus'))
+    }
+    it "should be valid citrus syntax" do
+      # puts MiniLisp.new.to_citrus
+      MiniLisp.new.to_citrus.should == citrus 
+    end 
+  end
+  describe "<- #to_treetop" do
+    let(:treetop) { File.read(
+      File.join(File.dirname(__FILE__), 'minilisp.tt'))
+    }
+    it "should be valid treetop syntax" do
+      # puts MiniLisp.new.to_treetop
+      MiniLisp.new.to_treetop.should == treetop
+    end 
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/expression/treetop_spec.rb b/spec/parslet/expression/treetop_spec.rb
new file mode 100644
index 0000000..0c0340f
--- /dev/null
+++ b/spec/parslet/expression/treetop_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+require 'parslet'
+
+describe Parslet::Expression::Treetop do
+  include Parslet
+
+  describe "positive samples" do
+    [ # pattern             # input
+      "'abc'",              'abc', 
+      "...",                'abc', 
+      "[1-4]",              '3',
+      
+      "'abc'?",             'abc', 
+      "'abc'?",             '', 
+      
+      "('abc')",            'abc', 
+      
+      "'a' 'b'",            'ab', 
+      "'a' ('b')",          'ab', 
+      
+      "'a' / 'b'",          'a', 
+      "'a' / 'b'",          'b', 
+      
+      "'a'*",               'aaa', 
+      "'a'*",               '', 
+
+      "'a'+",               'aa', 
+      "'a'+",               'a', 
+      
+      "'a'{1,2}",           'a',
+      "'a'{1,2}",           'aa',
+
+      "'a'{1,}",            'a',
+      "'a'{1,}",            'aa',
+
+      "'a'{,2}",            '',
+      "'a'{,2}",            'a',
+      "'a'{,2}",            'aa',
+    ].each_slice(2) do |pattern, input|
+      context "exp(#{pattern.inspect})" do
+        let(:parslet) { exp(pattern) }
+        subject { parslet }
+        it { should parse(input) }
+        context "string representation" do
+          subject { exp(parslet.to_s) }
+          it { should parse(input, :trace => true) }
+        end
+      end
+    end
+  end
+  describe "negative samples" do
+    [ # pattern             # input
+      "'abc'",              'cba', 
+      "[1-4]",              '5',
+      
+      "'a' / 'b'",          'c', 
+      
+      "'a'+",               '',
+      
+      "'a'{1,2}",           '',
+      "'a'{1,2}",           'aaa',
+      
+      "'a'{1,}",            '',
+
+      "'a'{,2}",            'aaa',
+    ].each_slice(2) do |pattern, input|
+      context "exp(#{pattern.inspect})" do
+        subject { exp(pattern) }
+        it { should_not parse(input) }
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/minilisp.citrus b/spec/parslet/minilisp.citrus
new file mode 100644
index 0000000..fea3d2e
--- /dev/null
+++ b/spec/parslet/minilisp.citrus
@@ -0,0 +1,29 @@
+grammar MiniLisp
+  rule root
+    (expression)
+  end
+  rule expression
+    ((space_p) "(" (space_p) (body) ")")
+  end
+  rule space_p
+    (space)0*1
+  end
+  rule body
+    ((expression) | (identifier) | (float) | (integer) | (string))0*
+  end
+  rule space
+    \s1*
+  end
+  rule identifier
+    (([a-zA-Z=*] [a-zA-Z=*_]0*) (space_p))
+  end
+  rule float
+    (((integer) (("." [0-9]1*) | ("e" [0-9]1*))) (space_p))
+  end
+  rule integer
+    ((("+" | "-")0*1 [0-9]1*) (space_p))
+  end
+  rule string
+    ("\"" (("\\" .) | (!"\"" .))0* "\"" (space_p))
+  end
+end
diff --git a/spec/parslet/minilisp.tt b/spec/parslet/minilisp.tt
new file mode 100644
index 0000000..e0ada6d
--- /dev/null
+++ b/spec/parslet/minilisp.tt
@@ -0,0 +1,29 @@
+grammar MiniLisp
+  rule root
+    (expression)
+  end
+  rule expression
+    ((space_p) "(" (space_p) (body) ")")
+  end
+  rule space_p
+    (space)0..1
+  end
+  rule body
+    ((expression) / (identifier) / (float) / (integer) / (string))0..
+  end
+  rule space
+    \s1..
+  end
+  rule identifier
+    (([a-zA-Z=*] [a-zA-Z=*_]0..) (space_p))
+  end
+  rule float
+    (((integer) (("." [0-9]1..) / ("e" [0-9]1..))) (space_p))
+  end
+  rule integer
+    ((("+" / "-")0..1 [0-9]1..) (space_p))
+  end
+  rule string
+    ("\"" (("\\" .) / (!"\"" .))0.. "\"" (space_p))
+  end
+end
diff --git a/spec/parslet/parser_spec.rb b/spec/parslet/parser_spec.rb
new file mode 100644
index 0000000..c7e67b8
--- /dev/null
+++ b/spec/parslet/parser_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Parslet::Parser do
+  include Parslet
+  class FooParser < Parslet::Parser
+    rule(:foo) { str('foo') }
+    root(:foo)
+  end
+  
+  describe "<- .root" do
+    parser = Class.new(Parslet::Parser)
+    parser.root :root_parslet
+    
+    it "should have defined a 'root' method, returning the root" do
+      parser_instance = parser.new
+      flexmock(parser_instance).should_receive(:root_parslet => :answer)
+      
+      parser_instance.root.should == :answer
+    end 
+  end
+  it "should parse 'foo'" do
+    FooParser.new.parse('foo').should == 'foo'
+  end 
+  context "composition" do
+    let(:parser) { FooParser.new }
+    it "should allow concatenation" do
+      composite = parser >> str('bar')
+      composite.should parse('foobar')
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/parslet_spec.rb b/spec/parslet/parslet_spec.rb
new file mode 100644
index 0000000..aea9ec4
--- /dev/null
+++ b/spec/parslet/parslet_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Parslet do
+  include Parslet
+  
+  describe Parslet::ParseFailed do
+    it "should be caught by an empty rescue" do
+      begin
+        raise Parslet::ParseFailed
+      rescue
+        # Success! Ignore this.
+      end
+    end 
+  end
+  describe "<- .rule" do
+    # Rules define methods. This can be easily tested by defining them right 
+    # here. 
+    context "empty rule" do
+      rule(:empty) { }
+      
+      it "should raise a NotImplementedError" do
+        lambda {
+          empty.parslet
+        }.should raise_error(NotImplementedError)
+      end 
+    end
+    
+    context "containing 'any'" do
+      rule(:any_rule) { any }
+      subject { any_rule }
+      
+      it { should be_a Parslet::Atoms::Entity }
+      it "should memoize the returned instance" do
+        any_rule.object_id.should == any_rule.object_id
+      end 
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/pattern_spec.rb b/spec/parslet/pattern_spec.rb
new file mode 100644
index 0000000..c7df7f4
--- /dev/null
+++ b/spec/parslet/pattern_spec.rb
@@ -0,0 +1,272 @@
+require 'spec_helper'
+
+require 'parslet'
+
+describe Parslet::Pattern do
+  include Parslet
+  
+  # These two factory methods help make the specs more robust to interface
+  # changes. They also help to label trees (t) and patterns (p).
+  def p(pattern)
+    Parslet::Pattern.new(pattern)
+  end
+  def t(obj)
+    obj
+  end
+  
+  # Tries to match pattern to the tree, and verifies the bindings hash. Don't
+  # use this for new examples.
+  #
+  RSpec::Matchers.define :match_with_bind do |pattern, exp_bindings|
+    unless respond_to?(:failure_message)
+      alias_method :failure_message_for_should, :failure_message
+    end
+    
+    failure_message do |tree|
+      "expected #{pattern.inspect} to match #{tree.inspect}, but didn't. (block wasn't called or not correctly)"
+    end
+    match do |tree|
+      bindings = Parslet::Pattern.new(pattern).match(tree)
+      bindings && bindings == exp_bindings
+    end
+  end
+
+  # This is the more modern version of verifying a match: (uses 'exp'
+  # implicitly). Checks for a match of pattern in +exp+ and yields the
+  # matched variables.
+  #
+  def with_match_locals(pattern, &block) 
+    bindings = p(pattern).match(exp)
+    bindings.should_not be_nil
+    
+    block.call(bindings) if block
+  end
+  
+  # Can't use #match here, so I went to the Thesaurus.
+  #
+  RSpec::Matchers.define :detect do |pattern|
+    match do |tree|
+      bindings = Parslet::Pattern.new(pattern).match(tree)
+
+      bindings ? true : false
+    end
+  end
+  
+  describe "<- #match" do
+    context "injecting bindings" do
+      let(:pattern) { p(simple(:x)) }
+      
+      it "should not modify the original bindings hash" do
+        h = {}
+        b=pattern.match('a', h)
+        h.size.should == 0
+        b.size.should == 1
+      end
+      it "should return nil when no match succeeds" do
+        pattern.match([], :foo => :bar).should be_nil
+      end
+      context "when matching simple(:x) against 'a'" do
+        let(:bindings) { pattern.match(t('a'), :foo => :bar) }
+        
+        before(:each) { bindings.should_not be_nil }
+        it "should return the injected bindings" do
+          bindings[:foo].should == :bar
+        end
+        it "should return the new bindings" do  
+          bindings[:x].should == 'a'
+        end
+      end
+    end
+    context "simple strings" do
+      let(:exp) { 'aaaa' }
+
+      it "should match simple strings" do
+        exp.should match_with_bind(simple(:x), :x => 'aaaa')
+      end 
+    end
+    context "simple hash {:a => 'b'}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t(:a => 'b')
+      end
+
+      it "should not match {:a => simple(:x), :b => simple(:y)}" do
+        exp.should_not detect(:a => simple(:x), :b => simple(:y))
+      end
+      it "should match {:a => simple(:x)}, binding 'x' to the first argument" do
+        exp.should match_with_bind({:a => simple(:x)}, :x => 'b')
+      end 
+      it "should match {:a => 'b'} with no binds" do
+        exp.should match_with_bind({:a => 'b'}, {})
+      end 
+    end
+    context "a more complex hash {:a => {:b => 'c'}}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t(:a => {:b => 'c'})
+      end
+      
+      it "should match wholly with {:a => {:b => simple(:x)}}" do
+        exp.should match_with_bind({:a => {:b => simple(:x)}}, :x => 'c')
+      end
+      it "should match wholly with {:a => subtree(:t)}" do
+        with_match_locals(:a => subtree(:t)) do |dict|
+          dict[:t].should == {:b => 'c'}
+        end
+      end
+      it "should not bind subtrees to variables in {:a => simple(:x)}" do
+        p(:a => simple(:x)).should_not detect(exp)
+      end
+    end
+    context "a more complex hash {:a => 'a', :b => 'b'}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t({:a => 'a', :b => 'b'})
+      end
+
+      it "should not match partially" do
+        Parslet::Pattern.new(:a => simple(:x)).match(exp).should be_nil
+      end 
+      it "should match completely" do
+        exp.should match_with_bind({:a => simple(:x), :b => simple(:y)}, 
+          :x => 'a', 
+          :y => 'b')
+      end 
+    end
+    context "an array of 'a', 'b', 'c'" do
+      let(:exp) { ['a', 'b', 'c'] }
+
+      it "should match all elements at once" do
+        exp.should match_with_bind(
+          [simple(:x), simple(:y), simple(:z)], 
+          :x => 'a', :y => 'b', :z => 'c')
+      end 
+    end
+    context "{:a => 'a', :b => 'b'}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t(:a => 'a', :b => 'b')
+      end
+
+      it "should match both elements simple(:x), simple(:y)" do
+        exp.should match_with_bind(
+          {:a => simple(:x), :b => simple(:y)}, 
+          :x => 'a', :y => 'b')
+      end
+      it "should not match a constrained match (simple(:x) != simple(:y))"  do
+        exp.should_not detect({:a => simple(:x), :b => simple(:x)})
+      end
+    end
+    context "{:a => 'a', :b => 'a'}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t(:a => 'a', :b => 'a')
+      end
+
+      it "should match constrained pattern" do
+        exp.should match_with_bind(
+          {:a => simple(:x), :b => simple(:x)}, 
+          :x => 'a')
+      end
+    end
+    context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'a'}}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t({
+          :sub1 => {:a => 'a'}, 
+          :sub2 => {:a => 'a'} 
+        })
+      end
+
+      it "should verify constraints over several subtrees" do
+        exp.should match_with_bind({
+          :sub1 => {:a => simple(:x)}, 
+          :sub2 => {:a => simple(:x)} 
+        }, :x => 'a')
+      end
+      it "should return both bind variables simple(:x), simple(:y)" do
+        exp.should match_with_bind({
+          :sub1 => {:a => simple(:x)}, 
+          :sub2 => {:a => simple(:y)} 
+        }, :x => 'a', :y => 'a')
+      end  
+    end
+    context "{:sub1 => {:a => 'a'}, :sub2 => {:a => 'b'}}" do
+      attr_reader :exp
+      before(:each) do
+        @exp = t({
+          :sub1 => {:a => 'a'}, 
+          :sub2 => {:a => 'b'} 
+        })
+      end
+
+      it "should verify constraints over several subtrees" do
+        exp.should_not match_with_bind({
+          :sub1 => {:a => simple(:x)}, 
+          :sub2 => {:a => simple(:x)} 
+        }, :x => 'a')
+      end
+      it "should return both bind variables simple(:x), simple(:y)" do
+        exp.should match_with_bind({
+          :sub1 => {:a => simple(:x)}, 
+          :sub2 => {:a => simple(:y)} 
+        }, :x => 'a', :y => 'b')
+      end  
+    end
+    context "[{:a => 'x'}, {:a => 'y'}]" do
+      attr_reader :exp  
+      before(:each) do
+        @exp = t([{:a => 'x'}, {:a => 'y'}])
+      end
+      
+      it "should not match sequence(:x) (as a whole)" do
+        exp.should_not detect(sequence(:x))
+      end
+    end
+    context "['x', 'y', 'z']" do
+      attr_reader :exp  
+      before(:each) do
+        @exp = t(['x', 'y', 'z'])
+      end
+
+      it "should match [simple(:x), simple(:y), simple(:z)]" do
+        with_match_locals([simple(:x), simple(:y), simple(:z)]) do |dict|
+          dict[:x].should == 'x'
+          dict[:y].should == 'y'
+          dict[:z].should == 'z'
+        end
+      end
+      it "should match %w(x y z)" do
+        exp.should match_with_bind(%w(x y z), { })
+      end 
+      it "should not match [simple(:x), simple(:y), simple(:x)]" do
+        exp.should_not detect([simple(:x), simple(:y), simple(:x)])
+      end
+      it "should not match [simple(:x), simple(:y)]" do
+        exp.should_not detect([simple(:x), simple(:y), simple(:x)])
+      end
+      it "should match sequence(:x) (as array)" do
+        exp.should match_with_bind(sequence(:x), :x => ['x', 'y', 'z'])
+      end
+    end
+    context "{:a => [1,2,3]}" do
+      attr_reader :exp  
+      before(:each) do
+        @exp = t(:a => [1,2,3])
+      end
+
+      it "should match :a => sequence(:x) (binding x to the whole array)" do
+        exp.should match_with_bind({:a => sequence(:x)}, {:x => [1,2,3]})
+      end
+    end
+    context "with differently ordered hashes" do
+      it "should still match" do
+        t(:a => 'a', :b => 'b').should detect(:a => 'a', :b => 'b')
+        t(:a => 'a', :b => 'b').should detect(:b => 'b', :a => 'a')
+
+        t(:b => 'b', :a => 'a').should detect(:b => 'b', :a => 'a')
+        t(:b => 'b', :a => 'a').should detect(:a => 'a', :b => 'b')
+      end 
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/position_spec.rb b/spec/parslet/position_spec.rb
new file mode 100644
index 0000000..99b7a4b
--- /dev/null
+++ b/spec/parslet/position_spec.rb
@@ -0,0 +1,14 @@
+# Encoding: UTF-8
+
+require 'spec_helper'
+
+describe Parslet::Position do
+  slet(:position) { described_class.new('öäüö', 4) }
+
+  it 'should have a charpos of 2' do
+    position.charpos.should == 2
+  end
+  it 'should have a bytepos of 4' do
+    position.bytepos.should == 4
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/rig/rspec_spec.rb b/spec/parslet/rig/rspec_spec.rb
new file mode 100644
index 0000000..4174502
--- /dev/null
+++ b/spec/parslet/rig/rspec_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+require 'parslet/rig/rspec'
+
+describe 'rspec integration' do
+  include Parslet
+  subject { str('example') }
+
+  it { should parse('example') }
+  it { should_not parse('foo') }
+  it { should parse('example').as('example') }
+  it { should_not parse('foo').as('example') }
+  it { should_not parse('example').as('foo') }
+
+  it { str('foo').as(:bar).should parse('foo').as({:bar => 'foo'}) }
+  it { str('foo').as(:bar).should_not parse('foo').as({:b => 'f'}) }
+
+  it 'accepts a block to assert more specific details about the parsing output' do
+    str('foo').as(:bar).should(parse('foo').as { |output|
+      output.should have_key(:bar)
+      output.values.first.should == 'foo'
+    })
+  end
+
+  # Uncomment to test error messages manually: 
+  # it { str('foo').should parse('foo', :trace => true).as('bar') }
+  # it { str('foo').should parse('food', :trace => true) }
+  # it { str('foo').should_not parse('foo', :trace => true).as('foo') }
+  # it { str('foo').should_not parse('foo', :trace => true) }
+  # it 'accepts a block to assert more specific details about the parsing output' do
+  #   str('foo').as(:bar).should(parse('foo', :trace => true).as { |output|
+  #     output.should_not have_key(:bar)
+  #   })
+  # end
+  
+end
+
+describe 'rspec3 syntax' do
+  include Parslet
+
+  let(:s) { str('example') }
+
+  it { expect(s).to parse('example') }
+  it { expect(s).not_to parse('foo') }
+  it { expect(s).to parse('example').as('example') }
+  it { expect(s).not_to parse('foo').as('example') }
+
+  it { expect(s).not_to parse('example').as('foo') }
+
+  # Uncomment to test error messages manually: 
+  # it { expect(str('foo')).to parse('foo', :trace => true).as('bar') }
+  # it { expect(str('foo')).to parse('food', :trace => true) }
+  # it { expect(str('foo')).not_to parse('foo', :trace => true).as('foo') }
+  # it { expect(str('foo')).not_to parse('foo', :trace => true) }
+end
diff --git a/spec/parslet/scope_spec.rb b/spec/parslet/scope_spec.rb
new file mode 100644
index 0000000..398c3be
--- /dev/null
+++ b/spec/parslet/scope_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Parslet::Scope do
+  let(:scope) { described_class.new }
+  
+  describe 'simple store/retrieve' do
+    before(:each) { scope[:foo] = :bar }
+    it "allows storing objects" do
+      scope[:obj] = 42
+    end 
+    it "raises on access of empty slots" do
+      expect {
+        scope[:empty]
+      }.to raise_error(Parslet::Scope::NotFound)
+    end 
+    it "allows retrieval of stored values" do
+      scope[:foo].should == :bar
+    end 
+  end
+  
+  describe 'scoping' do
+    before(:each) { scope[:depth] = 1 }
+    before(:each) { scope.push }
+    
+    let(:depth) { scope[:depth] }
+    subject { depth }
+    
+    it { should == 1 }
+    describe 'after a push' do
+      before(:each) { scope.push }
+      it { should == 1 }
+      
+      describe 'and reassign' do
+        before(:each) { scope[:depth] = 2 }
+        
+        it { should == 2 }
+
+        describe 'and a pop' do
+          before(:each) { scope.pop }
+          it { should == 1 }
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/slice_spec.rb b/spec/parslet/slice_spec.rb
new file mode 100644
index 0000000..f6f8bd2
--- /dev/null
+++ b/spec/parslet/slice_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+
+describe Parslet::Slice do
+  def cslice string, offset, cache=nil
+    described_class.new(
+      Parslet::Position.new(string, offset), 
+      string, cache)
+  end
+
+  describe "construction" do
+    it "should construct from an offset and a string" do
+      cslice('foobar', 40)
+    end
+  end
+  context "('foobar', 40, 'foobar')" do
+    let(:slice) { cslice('foobar', 40) }
+    describe "comparison" do
+      it "should be equal to other slices with the same attributes" do
+        other = cslice('foobar', 40)
+        slice.should == other
+        other.should == slice
+      end 
+      it "should be equal to other slices (offset is irrelevant for comparison)" do
+        other = cslice('foobar', 41)
+        slice.should == other
+        other.should == slice
+      end 
+      it "should be equal to a string with the same content" do
+        slice.should == 'foobar'
+      end
+      it "should be equal to a string (inversed operands)" do
+        'foobar'.should == slice
+      end 
+      it "should not be equal to a string" do
+        slice.should_not equal('foobar')
+      end 
+      it "should not be eql to a string" do
+        slice.should_not eql('foobar')
+      end 
+      it "should not hash to the same number" do
+        slice.hash.should_not == 'foobar'.hash
+      end 
+    end
+    describe "offset" do
+      it "should return the associated offset" do
+        slice.offset.should == 6
+      end
+      it "should fail to return a line and column" do
+        lambda {
+          slice.line_and_column
+        }.should raise_error(ArgumentError)
+      end 
+      
+      context "when constructed with a source" do
+        let(:slice) { cslice(
+          'foobar', 40,  
+          flexmock(:cache, :line_and_column => [13, 14])) }
+        it "should return proper line and column" do
+          slice.line_and_column.should == [13, 14]
+        end
+      end
+    end
+    describe "string methods" do
+      describe "matching" do
+        it "should match as a string would" do
+          slice.should match(/bar/)
+          slice.should match(/foo/)
+
+          md = slice.match(/f(o)o/)
+          md.captures.first.should == 'o'
+        end
+      end
+      describe "<- #size" do
+        subject { slice.size }
+        it { should == 6 } 
+      end
+      describe "<- #length" do
+        subject { slice.length }
+        it { should == 6 } 
+      end
+      describe "<- #+" do
+        let(:other) { cslice('baz', 10) }
+        subject { slice + other }
+        
+        it "should concat like string does" do
+          subject.size.should == 9
+          subject.should == 'foobarbaz'
+          subject.offset.should == 6
+        end 
+      end
+    end
+    describe "conversion" do
+      describe "<- #to_slice" do
+        it "should return self" do
+          slice.to_slice.should eq(slice)
+        end 
+      end
+      describe "<- #to_sym" do
+        it "should return :foobar" do
+          slice.to_sym.should == :foobar
+        end 
+      end
+      describe "cast to Float" do
+        it "should return a float" do
+          Float(cslice('1.345', 11)).should == 1.345
+        end 
+      end
+      describe "cast to Integer" do
+        it "should cast to integer as a string would" do
+          s = cslice('1234', 40)
+          Integer(s).should == 1234
+          s.to_i.should == 1234
+        end 
+        it "should fail when Integer would fail on a string" do
+          lambda { Integer(slice) }.should raise_error
+        end 
+        it "should turn into zero when a string would" do
+          slice.to_i.should == 0
+        end 
+      end
+    end
+    describe "inspection and string conversion" do
+      describe "#inspect" do
+        subject { slice.inspect }
+        it { should == '"foobar"@6' }
+      end
+      describe "#to_s" do
+        subject { slice.to_s }
+        it { should == 'foobar' }
+      end
+    end
+    describe "serializability" do
+      it "should serialize" do
+        Marshal.dump(slice)
+      end
+      context "when storing a line cache" do
+        let(:slice) { cslice('foobar', 40, Parslet::Source::LineCache.new()) }
+        it "should serialize" do
+          Marshal.dump(slice)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/parslet/source/line_cache_spec.rb b/spec/parslet/source/line_cache_spec.rb
new file mode 100644
index 0000000..b1fa978
--- /dev/null
+++ b/spec/parslet/source/line_cache_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe Parslet::Source::RangeSearch do
+  describe "<- #lbound" do
+    context "for a simple array" do
+      let(:ary) { [10, 20, 30, 40, 50] }
+      before(:each) { ary.extend Parslet::Source::RangeSearch }
+
+      it "should return correct answers for numbers not in the array" do
+        ary.lbound(5).should == 0
+        ary.lbound(15).should == 1
+        ary.lbound(25).should == 2
+        ary.lbound(35).should == 3
+        ary.lbound(45).should == 4
+      end
+      it "should return correct answers for numbers in the array" do
+        ary.lbound(10).should == 1
+        ary.lbound(20).should == 2
+        ary.lbound(30).should == 3
+        ary.lbound(40).should == 4
+      end
+      it "should cover right edge case" do
+        ary.lbound(50).should be_nil
+        ary.lbound(51).should be_nil
+      end 
+      it "should cover left edge case" do
+        ary.lbound(0).should == 0
+      end
+    end
+    context "for an empty array" do
+      let(:ary) { [] }
+      before(:each) { ary.extend Parslet::Source::RangeSearch }
+
+      it "should return nil" do
+        ary.lbound(1).should be_nil
+      end 
+    end
+  end
+end
+
+describe Parslet::Source::LineCache do
+  describe "<- scan_for_line_endings" do
+    context "calculating the line_and_columns" do
+      let(:str) { "foo\nbar\nbazd" }
+
+      it "should return the first line if we have no line ends" do
+        subject.scan_for_line_endings(0, nil)
+        subject.line_and_column(3).should == [1, 4]
+
+        subject.scan_for_line_endings(0, "")
+        subject.line_and_column(5).should == [1, 6]
+      end
+
+      it "should find the right line starting from pos 0" do
+        subject.scan_for_line_endings(0, str)
+        subject.line_and_column(5).should == [2, 2]
+        subject.line_and_column(9).should == [3, 2]
+      end
+
+      it "should find the right line starting from pos 5" do
+        subject.scan_for_line_endings(5, str)
+        subject.line_and_column(11).should == [2, 3]
+      end
+
+      it "should find the right line if scannning the string multiple times" do
+        subject.scan_for_line_endings(0, str)
+        subject.scan_for_line_endings(0, "#{str}\nthe quick\nbrown fox")
+        subject.line_and_column(10).should == [3,3]
+        subject.line_and_column(24).should == [5,2]
+      end
+    end
+  end
+end
+
diff --git a/spec/parslet/source_spec.rb b/spec/parslet/source_spec.rb
new file mode 100644
index 0000000..3480078
--- /dev/null
+++ b/spec/parslet/source_spec.rb
@@ -0,0 +1,168 @@
+# Encoding: UTF-8
+
+require 'spec_helper'
+
+describe Parslet::Source do
+  describe "using simple input" do
+    let(:str)     { "a"*100 + "\n" + "a"*100 + "\n" }
+    let(:source)  { described_class.new(str) }
+  
+    describe "<- #read(n)" do
+      it "should not raise error when the return value is nil" do
+        described_class.new('').consume(1)
+      end 
+      it "should return 100 'a's when reading 100 chars" do
+        source.consume(100).should == 'a'*100
+      end
+    end
+    describe "<- #chars_left" do
+      subject { source.chars_left }
+  
+      it { should == 202 }
+      context "after depleting the source" do
+        before(:each) { source.consume(10000) }
+  
+        it { should == 0 }
+      end
+    end
+    describe "<- #pos" do
+      subject { source.pos.charpos }
+  
+      it { should == 0 }
+      context "after reading a few bytes" do
+        it "should still be correct" do
+          pos = 0
+          10.times do
+            pos += (n = rand(10)+1)
+            source.consume(n)
+  
+            source.pos.charpos.should == pos
+          end
+        end 
+      end
+    end
+    describe "<- #pos=(n)" do
+      subject { source.pos.charpos }
+      10.times do
+        pos = rand(200)
+        context "setting position #{pos}" do
+          before(:each) { source.bytepos = pos }
+  
+          it { should == pos }
+        end
+      end
+    end
+    describe '#chars_until' do
+      it 'should return 100 chars before line end' do
+        source.chars_until("\n").should == 100
+      end
+    end
+    describe "<- #column & #line" do
+      subject { source.line_and_column }
+  
+      it { should == [1,1] }
+  
+      context "on the first line" do
+        it "should increase column with every read" do
+          10.times do |i|
+            source.line_and_column.last.should == 1+i
+            source.consume(1)
+          end
+        end 
+      end
+      context "on the second line" do
+        before(:each) { source.consume(101) }
+        it { should == [2, 1]}
+      end
+      context "after reading everything" do
+        before(:each) { source.consume(10000) }
+  
+        context "when seeking to 9" do
+          before(:each) { source.bytepos = 9 }
+          it { should == [1, 10] }
+        end
+        context "when seeking to 100" do
+          before(:each) { source.bytepos = 100 }
+          it { should == [1, 101] }
+        end
+        context "when seeking to 101" do
+          before(:each) { source.bytepos = 101 }
+          it { should == [2, 1] }
+        end
+        context "when seeking to 102" do
+          before(:each) { source.bytepos = 102 }
+          it { should == [2, 2] }
+        end
+        context "when seeking beyond eof" do
+          it "should not throw an error" do
+            source.bytepos = 1000
+          end 
+        end
+      end
+      context "reading char by char, storing the results" do
+        attr_reader :results
+        before(:each) { 
+          @results = {}
+          while source.chars_left>0
+            pos = source.pos.charpos
+            @results[pos] = source.line_and_column
+            source.consume(1)
+          end
+  
+          @results.entries.size.should == 202
+          @results
+        }
+  
+        context "when using pos argument" do
+          it "should return the same results" do
+            results.each do |pos, result|
+              source.line_and_column(pos).should == result
+            end
+          end 
+        end
+        it "should give the same results when seeking" do
+          results.each do |pos, result|
+            source.bytepos = pos
+            source.line_and_column.should == result
+          end
+        end
+        it "should give the same results when reading" do
+          cur = source.bytepos = 0
+          while source.chars_left>0
+            source.line_and_column.should == results[cur]
+            cur += 1
+            source.consume(1)
+          end
+        end 
+      end
+    end
+    
+  end
+  
+  describe "reading encoded input" do
+    let(:source) { described_class.new("éö変わる") }
+
+    def r str
+      Regexp.new(Regexp.escape(str))
+    end
+  
+    it "should read characters, not bytes" do
+      source.should match(r("é"))
+      source.consume(1)
+      source.pos.charpos.should == 1
+      source.bytepos.should == 2
+      
+      source.should match(r("ö"))
+      source.consume(1)
+      source.pos.charpos.should == 2
+      source.bytepos.should == 4
+      
+      source.should match(r("変"))
+      source.consume(1)
+      
+      source.consume(2)
+      source.chars_left.should == 0
+      source.chars_left.should == 0
+    end 
+  end
+end
diff --git a/spec/parslet/transform/context_spec.rb b/spec/parslet/transform/context_spec.rb
new file mode 100644
index 0000000..d6f6ddb
--- /dev/null
+++ b/spec/parslet/transform/context_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Parslet::Context do
+  def context(*args)
+    described_class.new(*args)
+  end
+  
+  it "binds hash keys as variable like things" do
+    context(:a => 'value').instance_eval { a }.
+      should == 'value'
+  end
+  describe 'when a method in BlankSlate is inherited from the environment somehow' do
+    before(:each) { BlankSlate.send(:define_method, :a) { 'c' } }
+    after(:each) { BlankSlate.send(:undef_method, :a) }
+    
+    it "masks what is already on blank slate" do
+      context(:a => 'b').instance_eval { a }.
+        should == 'b'
+    end  
+  end
+  it "should not reveal define_singleton_method for all users of blankslate, just for us" do
+    expect {
+      BlankSlate.new.instance_eval {
+        define_singleton_method(:foo) { 'foo' }
+      }
+    }.to raise_error(NoMethodError)
+  end 
+  it "one contexts variables aren't the next ones" do
+    ca = context(:a => 'b')
+    cb = context(:b => 'c')
+
+    ca.methods.should_not include(:b)
+    cb.methods.should_not include(:a)
+  end
+end
\ No newline at end of file
diff --git a/spec/parslet/transform_spec.rb b/spec/parslet/transform_spec.rb
new file mode 100644
index 0000000..297b757
--- /dev/null
+++ b/spec/parslet/transform_spec.rb
@@ -0,0 +1,165 @@
+require 'spec_helper'
+
+require 'parslet'
+
+describe Parslet::Transform do
+  include Parslet
+  
+  let(:transform) { Parslet::Transform.new }
+  attr_reader :transform
+  before(:each) do
+    @transform = Parslet::Transform.new
+  end
+  
+  class A < Struct.new(:elt); end
+  class B < Struct.new(:elt); end
+  class C < Struct.new(:elt); end
+  class Bi < Struct.new(:a, :b); end
+
+  describe "delayed construction" do
+    context "given simple(:x) => A.new(x)" do
+      before(:each) do
+        transform.rule(simple(:x)) { |d| A.new(d[:x]) }
+      end
+
+      it "should transform 'a' into A.new('a')" do
+        transform.apply('a').should == A.new('a')
+      end 
+      it "should transform ['a', 'b'] into [A.new('a'), A.new('b')]" do
+        transform.apply(['a', 'b']).should == 
+          [A.new('a'), A.new('b')]
+      end
+    end
+    context "given rules on {:a => simple(:x)} and {:b => :_x}" do
+      before(:each) do
+        transform.rule(:a => simple(:x)) { |d| A.new(d[:x]) }
+        transform.rule(:b => simple(:x)) { |d| B.new(d[:x]) }
+      end
+
+      it "should transform {:d=>{:b=>'c'}} into d => B('c')" do
+        transform.apply({:d=>{:b=>'c'}}).should == {:d => B.new('c')}
+      end
+      it "should transform {:a=>{:b=>'c'}} into A(B('c'))" do
+        transform.apply({:a=>{:b=>'c'}}).should == A.new(B.new('c'))
+      end
+    end
+    describe "pulling out subbranches" do
+      before(:each) do
+        transform.rule(:a => {:b => simple(:x)}, :d => {:e => simple(:y)}) { |d|
+          Bi.new(*d.values_at(:x, :y))
+        }
+      end
+
+      it "should yield Bi.new('c', 'f')" do
+        transform.apply(:a => {:b => 'c'}, :d => {:e => 'f'}).should ==
+          Bi.new('c', 'f')
+      end 
+    end
+  end
+  describe "dsl construction" do
+    let(:transform) { Parslet::Transform.new do
+        rule(simple(:x)) { A.new(x) }
+      end 
+    }
+    
+    it "should still evaluate rules correctly" do
+      transform.apply('a').should == A.new('a')
+    end 
+  end
+  describe "class construction" do
+    class OptimusPrime < Parslet::Transform 
+      rule(:a => simple(:x)) { A.new(x) }
+      rule(:b => simple(:x)) { B.new(x) }
+    end
+    let(:transform) { OptimusPrime.new }
+    
+    it "should evaluate rules" do
+      transform.apply(:a => 'a').should == A.new('a')
+    end
+
+    context "with inheritance" do
+      class OptimusPrimeJunior < OptimusPrime
+        rule(:b => simple(:x)) { B.new(x.upcase) }
+        rule(:c => simple(:x)) { C.new(x) }
+      end
+      let(:transform) { OptimusPrimeJunior.new }
+
+      it "should inherit rules from its parent" do
+        transform.apply(:a => 'a').should == A.new('a')
+      end
+
+      it "should be able to override rules from its parent" do
+        transform.apply(:b => 'b').should == B.new('B')
+      end
+
+      it "should be able to define new rules" do
+        transform.apply(:c => 'c').should == C.new('c')
+      end
+    end
+  end
+  describe "<- #call_on_match" do
+    let(:bindings) { { :foo => 'test' } }
+    context "when given a block of arity 1" do
+      it "should call the block" do
+        called = false
+        transform.call_on_match(bindings, lambda do |dict|
+          called = true
+        end)
+        
+        called.should == true
+      end 
+      it "should yield the bindings" do
+        transform.call_on_match(bindings, lambda do |dict|
+          dict.should == bindings
+        end)
+      end
+      it "should execute in the current context"  do
+        foo = 'test'
+        transform.call_on_match(bindings, lambda do |dict|
+          foo.should == 'test'
+        end)
+      end
+    end
+    context "when given a block of arity 0" do
+      it "should call the block" do
+        called = false
+        transform.call_on_match(bindings, proc do 
+          called = true
+        end)
+        
+        called.should == true
+      end 
+      it "should have bindings as local variables" do
+        transform.call_on_match(bindings, proc do
+          foo.should == 'test'
+        end)
+      end
+      it "should execute in its own context" do
+        @bar = 'test'
+        transform.call_on_match(bindings, proc do
+          @bar.should_not == 'test'
+        end)
+      end
+    end
+  end
+  
+  context "various transformations (regression)" do
+    context "hashes" do
+      it "are matched completely" do
+        transform.rule(:a => simple(:x)) { fail }
+        transform.apply(:a => 'a', :b => 'b')
+      end 
+    end
+  end
+  
+  context "when not using the bindings as hash, but as local variables" do
+    it "should access the variables" do
+      transform.rule(simple(:x)) { A.new(x) }
+      transform.apply('a').should == A.new('a')
+    end
+    it "should allow context as local variable" do
+      transform.rule(simple(:x)) { foo }
+      transform.apply('a', :foo => 'bar').should == 'bar'
+    end
+  end
+end
\ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..ee7d528
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,38 @@
+
+require 'parslet'
+
+require 'parslet/rig/rspec'
+require 'parslet/atoms/visitor'
+require 'parslet/export'
+
+RSpec.configure do |config|
+  config.mock_with :flexmock
+
+  begin
+    # Here's to the worst idea ever, rspec. This is why we'll be leaving you soon.
+    config.expect_with :rspec do |c|
+      c.syntax = [:should, :expect]
+    end
+  rescue NoMethodError
+    # If the feature is missing, ignore it. 
+  end
+  
+  # Exclude other ruby versions by giving :ruby => 1.8 or :ruby => 1.9
+  #
+  config.filter_run_excluding :ruby => lambda { |version|
+    RUBY_VERSION.to_s !~ /^#{Regexp.escape(version.to_s)}/
+  }
+end
+
+def catch_failed_parse
+  begin
+    yield
+  rescue Parslet::ParseFailed => exception
+  end
+  exception.cause
+end
+
+def slet name, &block
+  let(name, &block)
+  subject(&block)
+end
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-parslet.git



More information about the Pkg-ruby-extras-commits mailing list