Skip to content

Commit

Permalink
Keep a master offset in Match instead of slicing.
Browse files Browse the repository at this point in the history
Instead of successively slicing strings in matches, we keep the
parsed text under @source, and an offset in the later in @offset.

Btw, that @offset might be very useful for keeping trace of the
localisation of the match in the source text (e.g. for subsequent
semantic passes; semantic error detection for instance).
  • Loading branch information
blambeau committed Feb 21, 2012
1 parent aaac09b commit 13ccfb3
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 20 deletions.
44 changes: 25 additions & 19 deletions lib/citrus.rb
Expand Up @@ -636,7 +636,7 @@ def parse(string, options={})
raise ParseError, input
end

Match.new(string.slice(opts[:offset], length), events)
Match.new(string, events, opts[:offset])
end

# Tests whether or not this rule matches on the given +string+. Returns the
Expand Down Expand Up @@ -1239,14 +1239,11 @@ def to_citrus # :nodoc:
# instantiated as needed. This class provides several convenient tree
# traversal methods that help when examining and interpreting parse results.
class Match
def initialize(string, events=[])
@string = string
def initialize(source, events=[], offset = 0)
@source = source
@offset = offset

if events.length > 0
if events[-1] != string.length
raise ArgumentError, "Invalid events for length #{string.length}"
end

elisions = []

while events[0].elide?
Expand All @@ -1261,18 +1258,29 @@ def initialize(string, events=[])
end
else
# Create a default stream of events for the given string.
events = [Rule.for(string), CLOSE, string.length]
events = [Rule.for(source), CLOSE, source.length]
end

@events = events
end

# The main parsed text.
attr_reader :source

# The index of this match in the source text.
attr_reader :offset

# The array of events for this match.
attr_reader :events

# Returns the length of this match.
def length
@string.length
@events.last
end

# Returns the slice of the source text that this match captures.
def string
@string ||= @source[offset, length]
end

# Returns a hash of capture names to arrays of matches with that name,
Expand All @@ -1296,16 +1304,14 @@ def first
# Allows methods of this match's string to be called directly and provides
# a convenient interface for retrieving the first match with a given name.
def method_missing(sym, *args, &block)
if @string.respond_to?(sym)
@string.__send__(sym, *args, &block)
if string.respond_to?(sym)
string.__send__(sym, *args, &block)
else
captures[sym].first
end
end

def to_s
@string
end
alias_method :to_s, :string

# This alias allows strings to be compared to the string value of Match
# objects. It is most useful in assertions in unit tests, e.g.:
Expand Down Expand Up @@ -1339,9 +1345,9 @@ def [](key, *args)
def ==(other)
case other
when String
@string == other
string == other
when Match
@string == other.to_s
string == other.to_s
else
super
end
Expand All @@ -1350,7 +1356,7 @@ def ==(other)
alias_method :eql?, :==

def inspect
@string.inspect
string.inspect
end

# Prints the entire subtree of this match using the given +indent+ to
Expand All @@ -1372,7 +1378,7 @@ def dump(indent=' ')
rule = stack.pop

space = indent * (stack.size / 3)
string = @string.slice(os, event)
string = self.string.slice(os, event)
lines[start] = "#{space}#{string.inspect} rule=#{rule}, offset=#{os}, length=#{event}"

last_length = event unless last_length
Expand Down Expand Up @@ -1425,7 +1431,7 @@ def process_events!
os = stack.pop
start = stack.pop

match = Match.new(@string.slice(os, event), @events[start..index])
match = Match.new(source, @events[start..index], @offset + os)
capture!(rule, match)

if stack.size == 1
Expand Down
17 changes: 16 additions & 1 deletion test/match_test.rb
Expand Up @@ -20,6 +20,14 @@ def test_match_inequality
assert_equal(false, match2 == match1)
end

def test_string
match1 = Match.new('abcdef')
assert_equal 'abcdef', match1.string

match2 = Match.new('abcdef', [Rule.for('bcd'), -1, 3], 1)
assert_equal 'bcd', match2.string
end

def test_matches
a = Rule.for('a')
b = Rule.for('b')
Expand Down Expand Up @@ -59,11 +67,18 @@ def test_matches
CLOSE, 3
]

match.matches.each do |m|
match.matches.each_with_index do |m, i|
assert_equal(sub_events, m.events)
assert_equal(i*3, m.offset)
assert_equal(3, m.length)
assert_equal("abc", m.string)
assert_equal("abc", m)
assert(m.matches)
assert_equal(3, m.matches.length)
m.matches.each_with_index do |m2,i2|
assert_equal(i*3+i2, m2.offset)
assert_equal(1, m2.length)
end
end
end

Expand Down

0 comments on commit 13ccfb3

Please sign in to comment.