class Citrus::Match

The base class for all matches. Matches are organized into a tree where any match may contain any number of other matches. Nodes of the tree are lazily instantiated as needed. This class provides several convenient tree traversal methods that help when examining and interpreting parse results.

Attributes

events[R]

The array of events for this match.

input[R]

The original Input this Match was generated on.

offset[R]

The index of this match in the input.

Public Class Methods

new(input, events=[], offset=0) click to toggle source
# File lib/citrus.rb, line 1274
def initialize(input, events=[], offset=0)
  @input = input
  @offset = offset
  @captures = nil
  @matches = nil

  if events.length > 0
    elisions = []

    while events[0].elide?
      elisions.unshift(events.shift)
      events.slice!(-2, events.length)
    end

    events[0].extend_match(self)

    elisions.each do |rule|
      rule.extend_match(self)
    end
  else
    # Create a default stream of events for the given string.
    string = input.to_str
    events = [Rule.for(string), CLOSE, string.length]
  end

  @events = events
end

Public Instance Methods

==(other) click to toggle source
Calls superclass method
# File lib/citrus.rb, line 1380
def ==(other)
  case other
  when String
    string == other
  when Match
    string == other.to_s
  else
    super
  end
end
Also aliased as: eql?
[](key, *args) click to toggle source

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.

# File lib/citrus.rb, line 1371
def [](key, *args)
  case key
  when Integer, Range
    to_a[key, *args]
  else
    captures[key]
  end
end
capture(name) click to toggle source

Convenient method for captures.first.

# File lib/citrus.rb, line 1334
def capture(name)
  captures[name].first
end
captures(name = nil) click to toggle source

Returns a hash of capture names to arrays of matches with that name, in the order they appeared in the input.

# File lib/citrus.rb, line 1328
def captures(name = nil)
  process_events! unless @captures
  name ? @captures[name] : @captures
end
dump(indent=' ') click to toggle source

Prints the entire subtree of this match using the given indent to indicate nested match levels. Useful for debugging.

# File lib/citrus.rb, line 1399
def dump(indent=' ')
  lines = []
  stack = []
  offset = 0
  close = false
  index = 0
  last_length = nil

  while index < @events.size
    event = @events[index]

    if close
      os = stack.pop
      start = stack.pop
      rule = stack.pop

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

      last_length = event unless last_length

      close = false
    elsif event == CLOSE
      close = true
    else
      if last_length
        offset += last_length
        last_length = nil
      end

      stack << event
      stack << index
      stack << offset
    end

    index += 1
  end

  puts lines.compact.join("\n")
end
eql?(other)
Alias for: ==
first() click to toggle source

A shortcut for retrieving the first immediate submatch of this match.

# File lib/citrus.rb, line 1345
def first
  matches.first
end
inspect() click to toggle source
# File lib/citrus.rb, line 1393
def inspect
  string.inspect
end
length() click to toggle source

Returns the length of this match.

# File lib/citrus.rb, line 1312
def length
  events.last
end
matches() click to toggle source

Returns an array of all immediate submatches of this match.

# File lib/citrus.rb, line 1339
def matches
  process_events! unless @matches
  @matches
end
source() click to toggle source

Convenient shortcut for input.source

# File lib/citrus.rb, line 1317
def source
  (input.respond_to?(:source) && input.source) || input
end
string() click to toggle source

Returns the slice of the source text that this match captures.

# File lib/citrus.rb, line 1322
def string
  @string ||= input.to_str[offset, length]
end
Also aliased as: to_s
to_a() click to toggle source

Returns this match plus all sub matches in an array.

# File lib/citrus.rb, line 1364
def to_a
  [self] + matches
end
to_s()
Also aliased as: to_str, value
Alias for: string
to_str()

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.:

assert_equal("a string", match)
Alias for: to_s
value()

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 for: to_s

Private Instance Methods

capture!(rule, match) click to toggle source
# File lib/citrus.rb, line 1510
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
captures_hash() click to toggle source

Returns a new Hash that is to be used for @captures. This hash normalizes String keys to Symbols, returns nil for unknown Numeric keys, and an empty Array for all other unknown keys.

# File lib/citrus.rb, line 1535
def captures_hash
  Hash.new do |hash, key|
    case key
    when String
      hash[key.to_sym]
    when Numeric
      nil
    else
      []
    end
  end
end
process_events!() click to toggle source

Initializes both the @captures and @matches instance variables.

# File lib/citrus.rb, line 1444
def process_events!
  @captures = captures_hash
  @matches = []

  capture!(@events[0], self)
  @captures[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(input, @events[start..index], @offset + os)
        capture!(rule, match)

        if stack.size == 1
          @matches << match
          @captures[@matches.size] = match
        end

        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
end