Skip to content

Commit

Permalink
+ Reenables the cache - now Context has a spec
Browse files Browse the repository at this point in the history
  • Loading branch information
kschiess committed Jan 24, 2011
1 parent 00c4cb0 commit 0d67a22
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 63 deletions.
65 changes: 4 additions & 61 deletions lib/parslet/atoms/base.rb
@@ -1,3 +1,6 @@

require 'parslet/atoms/context'

# Base class for all parslets, handles orchestration of calls and implements
# a lot of the operator and chaining methods.
#
Expand All @@ -10,7 +13,7 @@ class Parslet::Atoms::Base
#
def parse(io)
source = Parslet::Source.new(io)
context = Context.new
context = Parslet::Atoms::Context.new

message = catch(:error) {
result = apply(source, context)
Expand Down Expand Up @@ -238,66 +241,6 @@ def cause? # :nodoc:
not @last_cause.nil?
end
private
# Helper class that implements a transient cache that maps position and
# parslet object to results.
#
class Context
def initialize
@cache = Hash.new
end
# Caches a parse answer for obj at source.pos. Applying the same parslet
# at one position of input always yields the same result, unless the input
# has changed.
#
# We need the entire source here so we can ask for how many characters
# were consumed by a successful parse. Imitation of such a parse must
# advance the input pos by the same amount of bytes.
#
def cache(obj, source, &block)
yield
end
# beg = source.pos
#
# # Not in cache yet? Return early.
# unless entry = lookup(obj, beg)
# result = yield
#
# set obj, beg, [result, source.pos-beg]
# return result
# end
#
# # the condition in unless has returned true, so entry is not nil.
# result, advance = entry
#
# source.read(advance)
# return result
# end
class Item
attr_reader :obj, :pos
def initialize(obj, pos)
@obj, @pos = obj, pos
end
def hash
@obj.hash - @pos
end
def eql?(o)
o.obj == self.obj && o.pos == self.pos
end
end
private
def lookup(obj, pos)
i = Item.new(obj, pos)
@cache[i]
end
def set(obj, pos, val)
i = Item.new(obj, pos)
@cache[i] = val
end
end
# Produces an instance of Success and returns it.
#
def success(result)
Expand Down
64 changes: 64 additions & 0 deletions lib/parslet/atoms/context.rb
@@ -0,0 +1,64 @@

# Helper class that implements a transient cache that maps position and
# parslet object to results.
#
class Parslet::Atoms::Context
def initialize
@cache = Hash.new
end

# Caches a parse answer for obj at source.pos. Applying the same parslet
# at one position of input always yields the same result, unless the input
# has changed.
#
# We need the entire source here so we can ask for how many characters
# were consumed by a successful parse. Imitation of such a parse must
# advance the input pos by the same amount of bytes.
#
def cache(obj, source, &block)
beg = source.pos

# Not in cache yet? Return early.
unless entry = lookup(obj, beg)
message = catch(:error) {
result = yield
set obj, beg, [true, result, source.pos-beg]
return result
}

set obj, beg, [false, message, source.pos-beg]
throw :error, message
end

# the condition in unless has returned true, so entry is not nil.
success, obj, advance = entry

source.read(advance)

throw :error, obj unless success
return obj
end

class Item
attr_reader :obj, :pos
def initialize(obj, pos)
@obj, @pos = obj, pos
end
def hash
@obj.hash - @pos
end
def eql?(o)
o.obj == self.obj && o.pos == self.pos
end
end

private
def lookup(obj, pos)
i = Item.new(obj, pos)
@cache[i]
end
def set(obj, pos, val)
i = Item.new(obj, pos)
@cache[i] = val
end
end
2 changes: 1 addition & 1 deletion spec/parslet/atoms/base_spec.rb
Expand Up @@ -2,7 +2,7 @@

describe Parslet::Atoms::Base do
let(:parslet) { Parslet::Atoms::Base.new }
let(:context) { Parslet::Atoms::Base::Context.new }
let(:context) { Parslet::Atoms::Context.new }

describe "<- #try(io)" do
it "should raise NotImplementedError" do
Expand Down
37 changes: 37 additions & 0 deletions spec/parslet/atoms/context_spec.rb
@@ -0,0 +1,37 @@
require 'spec_helper'

require 'stringio'

describe Parslet::Atoms::Context do
let(:source) { StringIO.new('foobar') }

context "when clean" do
it "should return whatever the block returns" do
subject.cache(self, source) { 1 }.should == 1
end
it "should rethrow :error" do
lambda {
subject.cache(self, source) { throw :error, 'foo' }
}.should throw_symbol(:error, 'foo')
end
end
context "when already called for obj and input position" do
context "(storing success, 1)" do
before(:each) { subject.cache(self, source) { 1 } }

it "should return cached result" do
subject.cache(self, source) { 2 }.should == 1
end
end
context "(storing error, 'foo')" do
before(:each) { catch(:error) {
subject.cache(self, source) { throw :error, 'foo' }} }

it "should return cached result" do
lambda {
subject.cache(self, source) { 2 }
}.should throw_symbol(:error, 'foo')
end
end
end
end
2 changes: 1 addition & 1 deletion spec/parslet/atoms_spec.rb
Expand Up @@ -12,7 +12,7 @@ def not_parse
extend Parslet

def src(str); Parslet::Source.new str; end
let(:context) { Parslet::Atoms::Base::Context.new }
let(:context) { Parslet::Atoms::Context.new }

describe "match('[abc]')" do
attr_reader :parslet
Expand Down

0 comments on commit 0d67a22

Please sign in to comment.