Commits

Kaspar Schiess committed 0861579

EDN parser start, some improvements

Applying this par thingy to a real parser makes real issues surface. As
usual...

Comments (0)

Files changed (6)

+
+# A (partial) implementation of EDN in par. 
+# https://github.com/edn-format/edn
+
+require 'rspec/autorun'
+
+$:.unshift File.dirname(__FILE__) + "/../lib"
+require 'par'
+
+module EDN
+
+  def nil src
+    src.match('nil').as(nil)
+  end
+  def bool src
+    src.either { src.match('true').as(true) }.
+      or { src.match('false').as(false) }.value
+  end
+  def string src
+    src.match('"') or return Par.bottom
+
+    str = src.match(/.*(?=(?<!\\)")/)
+
+    src.match('"').as(Par.result(str))
+  end
+  def char src
+    src.match('\\') or return Par.bottom  
+  
+    src.match(/./)
+  end
+
+  def value src
+    src.
+      either { self.nil(src) }.
+      or     { bool(src) }.
+      or     { string(src) }.
+      or     { char(src) }.value
+  end
+end
+
+describe EDN do
+  include EDN 
+
+  def parses str
+    source = Par.source(str)
+    result = subject.call(source)
+
+    expect(source).to be_at_eos
+    expect(result).not_to eq(Par.bottom)
+    if expected 
+      expect(result).to eq(expected)
+    end
+  end
+  def parses_into str, expected
+    source = Par.source(str)
+    result = subject.call(source)
+
+    expect(source).to be_at_eos
+    expect(result).not_to eq(Par.bottom)
+    expect(result).to eq(expected)
+  end
+  def fails str
+    expect(subject.call(Par.source(str))).to eq(Par.bottom)
+  end    
+
+  describe '#nil' do
+    subject { method(:nil) }
+
+    it { parses_into 'nil', nil }
+    it { fails 'foo' }
+  end
+  describe '#bool' do
+    subject { method(:bool) }
+
+    it { parses_into('true', true) }
+    it { parses_into('false', false) }
+    it { fails 'foo' }
+  end
+  describe '#string' do
+    subject { method(:string) }
+
+    it { fails 'foo' }
+    it { parses_into '"foo"', 'foo' }
+    it { fails '"foo' }
+  end
+  describe '#char' do
+    subject { method(:char) }
+
+    it { parses_into '\c', ?c }
+    it { parses_into '\newline', "\n" }
+  end
+
+  describe '#value' do
+    subject { method(:value) }
+
+    it { parses_into '\c', ?c }
+    it { parses_into '"foo"', 'foo' }
+    it { parses_into('true', true) }
+    it { parses_into 'nil', nil }
+  end
+end
 module Par
   class Lazy
+    class Elem
+      def initialize source, block
+        @source = source
+        @block = block
+      end        
+      def value 
+        @source.conditional_rewind {
+          @block.call
+        }
+      end
+    end
+
     def initialize source, &block
       @source = source
-      @block = block
+      @elems = [Elem.new(source, block)]
     end
 
     def or &block
-      other = Lazy.new(@source, &block)
-
-      left = self.value 
+      @elems << Elem.new(@source, block)
 
-      return left if left != Par.bottom
-      return other.value
+      self
     end
+    def value
+      @value ||= begin
+        for elem in @elems
+          result = elem.value
+          return result if result != Par.bottom
+        end
 
-    def value 
-      @source.conditional_rewind {
-        @block.call
-      }
+        Par.bottom
+      end
     end
   end
 end

lib/par/result/bottom.rb

     def as obj=nil, &block
       self
     end
+
+    def inspect
+      'BOT'
+    end
   end
 end

lib/par/source.rb

     def pos
       @scanner.pos
     end
+    def at_eos?
+      @scanner.eos?
+    end
   end
 end

spec/lib/par/lazy_spec.rb

       source.either { raise }
     end 
   end
+  describe 'chaining' do
+    let(:any) { result 'any' }
+
+    it "allows a long chain of .ors" do
+      source.either { bottom }.or { bottom }.or { bottom }.or { any }.value.should == any
+    end
+  end
   describe 'behaviour with bottom and top' do
     let(:any) { result 'any' }
 
     it "bottom or any" do
-      source.either { bottom }.or { any }.should == any
+      source.either { bottom }.or { any }.value.should == any
     end
     it "any or bottom" do
-      source.either { any }.or { bottom }.should == any
+      source.either { any }.or { bottom }.value.should == any
     end
     it "top or any" do
-      source.either { top }.or { any }.should == top
+      source.either { top }.or { any }.value.should == top
     end
     it "any or top" do
-      source.either { any }.or { top }.should == any
+      source.either { any }.or { top }.value.should == any
     end
   end
   describe 'rewinding' do

spec/lib/par/source_spec.rb

       s.rest.should == 'bar'
     end
   end
+  describe "#at_eos?" do
+    it "indicates end of string" do
+      s = source('foo')
+      expect(s).not_to be_at_eos
+
+      s.n(3)
+      expect(s).to be_at_eos
+    end
+  end
 end