Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adds self to Match#captures

A common point of confusion for some Citrus users is that they are not
able to retrieve a match by calling its own name on itself. This commit
adds self to the internal @captures Hash in the first position.

Also, Match#to_a and Match#[] were added for convenience when retrieving
captures and submatches.
  • Loading branch information...
commit cbdce448796327782c34962859b0d1821c946ea3 1 parent 1e61125
@mjackson authored
Showing with 135 additions and 85 deletions.
  1. +132 −81 lib/citrus.rb
  2. +3 −3 lib/citrus/file.rb
  3. +0 −1  test/match_test.rb
View
213 lib/citrus.rb
@@ -1232,7 +1232,7 @@ def initialize(string, events=[])
while events[0].elide?
elisions.unshift(events.shift)
- events = events.slice(0, events.length - 2)
+ events.slice!(-2, events.length)
end
events[0].extend_match(self)
@@ -1240,6 +1240,9 @@ def initialize(string, events=[])
elisions.each do |rule|
rule.extend_match(self)
end
+ else
+ # Create a default stream of events for the given string.
+ events = [Rule.for(string), CLOSE, string.length]
end
@events = events
@@ -1256,93 +1259,21 @@ def length
# Returns a hash of capture names to arrays of matches with that name,
# in the order they appeared in the input.
def captures
- @captures ||= begin
- captures = Hash.new {|hash, key| hash[key] = [] }
- stack = []
- offset = 0
- close = false
- index = 0
- last_length = nil
- capture = true
- count = 0
-
- while index < @events.size
- event = @events[index]
-
- if close
- start = stack.pop
-
- if Rule === start
- rule = start
- os = stack.pop
- start = stack.pop
-
- match = Match.new(@string.slice(os, event), @events[start..index])
-
- # We can lookup immediate submatches by their index.
- if stack.size == 1
- captures[count] = match
- count += 1
- end
-
- # We can lookup matches that were created by proxy by the name of
- # the rule they are proxy for.
- captures[rule.rule_name] << match if Proxy === rule
-
- # We can lookup matches that were created by rules with labels by
- # that label.
- captures[rule.label] << match if rule.label
-
- capture = true
- end
-
- last_length = event unless last_length
-
- close = false
- elsif event == CLOSE
- close = true
- else
- stack << index
-
- # We can calculate the offset of this rule event by adding back the
- # last match length.
- if last_length
- offset += last_length
- last_length = nil
- end
-
- if capture && stack.size != 1
- stack << offset
- stack << event
- # We should not create captures when traversing the portion of the
- # event stream that is masked by a proxy in the original rule
- # definition.
- capture = false if Proxy === event
- end
- end
-
- index += 1
- end
-
- captures
- end
+ process_events! unless @captures
+ @captures
end
# Returns an array of all immediate submatches of this match.
def matches
- @matches ||= (0...captures.size).map {|n| captures[n] }.flatten
+ process_events! unless @matches
+ @matches
end
# A shortcut for retrieving the first immediate submatch of this match.
def first
- captures[0]
+ matches.first
end
- # The default value for a match is its string value. This method is
- # overridden in most cases to be more meaningful according to the desired
- # interpretation.
- alias_method :value, :to_s
-
# 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)
@@ -1359,6 +1290,32 @@ def to_s
alias_method :to_str, :to_s
+ # The default value for a match is its string value. This method is
+ # overridden in most cases to be more meaningful according to the desired
+ # interpretation.
+ alias_method :value, :to_s
+
+ # Returns this match plus all sub #matches in an array.
+ def to_a
+ [captures[0]] + matches
+ end
+
+ alias_method :to_ary, :to_a
+
+ # Returns the capture at the given +key+. If it is an Integer (and an
+ # optional length) or a Range, the result of #to_a with the same arguments
+ # is returned. Otherwise, the value at +key+ in #captures is returned.
+ def [](key, *args)
+ case key
+ when Integer, Range
+ to_a[key, *args]
+ else
+ captures[key]
+ end
+ end
+
+ alias_method :fetch, :[]
+
def ==(other)
case other
when String
@@ -1398,9 +1355,7 @@ def dump(indent=' ')
string = @string.slice(os, event)
lines[start] = "#{space}#{string.inspect} rule=#{rule}, offset=#{os}, length=#{event}"
- unless last_length
- last_length = event
- end
+ last_length = event unless last_length
close = false
elsif event == CLOSE
@@ -1421,6 +1376,102 @@ def dump(indent=' ')
puts lines.compact.join("\n")
end
+
+ private
+
+ # Setup both @captures and @matches instance variables.
+ def process_events!
+ # @captures should automatically convert String keys to Symbols when
+ # fetching, and return an empty Array for all other unknown keys.
+ @captures = Hash.new {|hash, key| String === key ? hash[key.to_sym] : [] }
+ @matches = []
+
+ capture!(@events[0], self)
+
+ stack = []
+ offset = 0
+ close = false
+ index = 0
+ last_length = nil
+ capture = true
+
+ while index < @events.size
+ event = @events[index]
+
+ if close
+ start = stack.pop
+
+ if Rule === start
+ rule = start
+ os = stack.pop
+ start = stack.pop
+
+ match = Match.new(@string.slice(os, event), @events[start..index])
+ capture!(rule, match)
+
+ @matches << match if stack.size == 1
+
+ capture = true
+ end
+
+ last_length = event unless last_length
+
+ close = false
+ elsif event == CLOSE
+ close = true
+ else
+ stack << index
+
+ # We can calculate the offset of this rule event by adding back the
+ # last match length.
+ if last_length
+ offset += last_length
+ last_length = nil
+ end
+
+ if capture && stack.size != 1
+ stack << offset
+ stack << event
+
+ # We should not create captures when traversing a portion of the
+ # event stream that is masked by a proxy in the original rule
+ # definition.
+ capture = false if Proxy === event
+ end
+ end
+
+ index += 1
+ end
+
+ # Add numeric indices to @captures.
+ @captures[0] = self
+
+ @matches.each_with_index do |match, index|
+ @captures[index + 1] = match
+ end
+ end
+
+ def capture!(rule, match)
+ # We can lookup matches that were created by proxy by the name of
+ # the rule they are proxy for.
+ if Proxy === rule
+ if @captures.key?(rule.rule_name)
+ @captures[rule.rule_name] << match
+ else
+ @captures[rule.rule_name] = [match]
+ end
+ end
+
+ # We can lookup matches that were created by rules with labels by
+ # that label.
+ if rule.label
+ if @captures.key?(rule.label)
+ @captures[rule.label] << match
+ else
+ @captures[rule.label] = [match]
+ end
+ end
+ end
end
end
View
6 lib/citrus/file.rb
@@ -9,7 +9,7 @@ def module_segments
end
def module_namespace
- module_segments[0..-2].inject(Object) do |namespace, constant|
+ module_segments[0...-1].inject(Object) do |namespace, constant|
constant.empty? ? namespace : namespace.const_get(constant)
end
end
@@ -311,8 +311,8 @@ def value
rule :star do
all(/[0-9]*/, '*', /[0-9]*/, :space) { |rule|
- min = captures[0] == '' ? 0 : captures[0].to_i
- max = captures[2] == '' ? Infinity : captures[2].to_i
+ min = captures[1] == '' ? 0 : captures[1].to_i
+ max = captures[3] == '' ? Infinity : captures[3].to_i
Repeat.new(rule, min, max)
}
end
View
1  test/match_test.rb
@@ -25,7 +25,6 @@ def test_matches
b = Rule.for('b')
c = Rule.for('c')
s = Rule.for([ a, b, c ])
- s.name = 's'
r = Repeat.new(s, 0, Infinity)
events = [
Please sign in to comment.
Something went wrong with that request. Please try again.