Commits

Kaspar Schiess committed 213446d

Source uses StringScanner now

Comments (0)

Files changed (5)

+
+group :test do
+  gem 'rspec'
+  gem 'guard-rspec'
+end
+GEM
+  specs:
+    coderay (1.0.9)
+    diff-lcs (1.2.4)
+    ffi (1.9.0)
+    formatador (0.2.4)
+    guard (1.8.2)
+      formatador (>= 0.2.4)
+      listen (>= 1.0.0)
+      lumberjack (>= 1.0.2)
+      pry (>= 0.9.10)
+      thor (>= 0.14.6)
+    guard-rspec (3.0.2)
+      guard (>= 1.8)
+      rspec (~> 2.13)
+    listen (1.3.0)
+      rb-fsevent (>= 0.9.3)
+      rb-inotify (>= 0.9)
+      rb-kqueue (>= 0.2)
+    lumberjack (1.0.4)
+    method_source (0.8.2)
+    pry (0.9.12.2)
+      coderay (~> 1.0.5)
+      method_source (~> 0.8)
+      slop (~> 3.4)
+    rb-fsevent (0.9.3)
+    rb-inotify (0.9.1)
+      ffi (>= 0.5.0)
+    rb-kqueue (0.2.0)
+      ffi (>= 0.5.0)
+    rspec (2.14.1)
+      rspec-core (~> 2.14.0)
+      rspec-expectations (~> 2.14.0)
+      rspec-mocks (~> 2.14.0)
+    rspec-core (2.14.5)
+    rspec-expectations (2.14.2)
+      diff-lcs (>= 1.1.3, < 2.0)
+    rspec-mocks (2.14.3)
+    slop (3.4.6)
+    thor (0.18.1)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  guard-rspec
+  rspec
+require 'strscan'
+
 module Par
-  # Source consumes a string bit by bit. It defines methods to consume a string
-  # conditionally or unconditionally, never touching the original string, 
-  # instead slicing it up into small results. 
+  # Source consumes a string bit by bit. It defines methods to consume a
+  # string conditionally or unconditionally, never touching the original
+  # string,  instead slicing it up into small results.
+  #
+  # String access methods are relative to the current position, unless
+  # mentioned otherwise:
   #
-  # String access methods are relative to the current position, unless mentioned
-  # otherwise: 
-  # * #string reads a given string
-  # * #patterm matches a pattern
   # * #n reads n characters
+  #
   class Source
     attr_reader :str 
     attr_reader :pos
 
     def initialize str
-      @str = str
-      @pos = 0
+      @scanner = StringScanner.new(str)
     end
 
-    def string str
-      if head(str.size) == str
-        return self.n(str.size)
+    def match pattern
+      pattern = Regexp.new(
+        Regexp.escape(pattern)) if pattern.respond_to? :to_str
+
+      if match = @scanner.scan(pattern) 
+        return Par.result(match)
       end
 
       Par.bottom
     end
-    def pattern pat
-      idx = head.index(pat)
-      return Par.bottom unless idx == 0
-      md =head.match(pat)
-      n(md[0].size)
-    end
-
+    
     def n n
-      Par.result(head(n)).tap { @pos += n }
+      match = @scanner.scan(/.{#{n}}/)
+      match && Par.result(match) || Par.bottom
     end
 
     def conditional_rewind
-      old_pos = @pos
+      old_pos = pos
       r = yield
-      old_pos = @pos if r != Par.bottom
+      old_pos = pos if r != Par.bottom
 
       return r
     ensure
-      @pos = old_pos
+      @scanner.pos = old_pos
     end
     alias :cr :conditional_rewind
 
       Lazy.new self, &block
     end
 
-    def head n=nil
-      n && str[@pos, n] || str[@pos..-1]
+    def rest
+      @scanner.rest
+    end
+    def pos
+      @scanner.pos
     end
   end
 end

spec/lib/par/source_spec.rb

       s.n(1).should == 'ä'
       s.n(2).should == 'öü'
     end
+    it "returns empty string on end of input" do
+      s.n(3).should == 'äöü'
+      s.n(10).should == Par.bottom
+    end
   end
-  describe "#pattern" do
+  describe "#match" do
     let(:s) { source 'foobar' }
+
     it "matches a pattern and consumes it" do
-      r = s.pattern /foo/
+      r = s.match /foo/
       r == 'foo'
     end
     it "returns bottom if no match" do
-      s.pattern(/bar/).should == bottom
+      s.match(/bar/).should == bottom
     end 
   end
   describe "#cr" do
     let(:s) { source 'foobar' }
+
     it 'rewinds the source if the block exits with Par.bot' do
       s.cr { 
-        s.string 'foo'
+        s.match 'foo'
         Par.bottom
       }
 
-      s.head.should == 'foobar'
+      s.rest.should == 'foobar'
     end
     it "doesn't rewind if the block has other return values" do
       s.cr {
-        s.string 'foo'
+        s.match 'foo'
       }
-      s.head.should == 'bar'
+      s.rest.should == 'bar'
     end
   end
 end
 
   describe "sequences" do
     it "parse" do
-      (s.string('foo') >> s.string('bar')).should_not == bottom
+      (s.match('foo') >> s.match('bar')).should_not == bottom
     end
     it "or not" do
-      (s.string('bar') >> s.string('bar')).should == bottom
+      (s.match('bar') >> s.match('bar')).should == bottom
     end
   end
   describe 'maybe' do
     it "allows optional parsing" do
-      (s.string('baz').maybe >> s.string('foobar')).should == 'foobar'
+      (s.match('baz').maybe >> s.match('foobar')).should == 'foobar'
     end
     it "allows maybe on results" do
-      s.string('foo').maybe.should == 'foo'
+      s.match('foo').maybe.should == 'foo'
     end
   end
 end