From 13ccfb3b3bf2aa4322f3f3a146dbbb73be53e253 Mon Sep 17 00:00:00 2001 From: Bernard Lambeau Date: Tue, 21 Feb 2012 21:49:05 +0100 Subject: [PATCH] Keep a master offset in Match instead of slicing. 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). --- lib/citrus.rb | 44 +++++++++++++++++++++++++------------------- test/match_test.rb | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/lib/citrus.rb b/lib/citrus.rb index 1ce0387..ff534ac 100644 --- a/lib/citrus.rb +++ b/lib/citrus.rb @@ -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 @@ -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? @@ -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, @@ -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.: @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/test/match_test.rb b/test/match_test.rb index f45962a..0cce411 100644 --- a/test/match_test.rb +++ b/test/match_test.rb @@ -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') @@ -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