lib/citrus.rb in citrus-1.2.2 vs lib/citrus.rb in citrus-1.3.0

- old
+ new

@@ -2,11 +2,11 @@ # elegance and expressiveness of the language with the simplicity and power of # parsing expressions. # # http://mjijackson.com/citrus module Citrus - VERSION = [1, 2, 2] + VERSION = [1, 3, 0] Infinity = 1.0 / 0 autoload :File, 'citrus/file' @@ -417,17 +417,17 @@ end private def extend_match(match) - match.ext = ext if ext + match.extend(ext) if ext end def create_match(data, offset) match = Match.new(data, offset) extend_match(match) - match.name = name + match.names << name if name match end end # A Proxy is a Rule that is a placeholder for another rule. It stores the @@ -435,17 +435,17 @@ # actual Rule object at runtime. This lazy evaluation permits us to create # Proxy objects for rules that we may not know the definition of yet. module Proxy include Rule - def initialize(name='<proxy>') - self.rule_name = name + def initialize(rule_name='<proxy>') + self.rule_name = rule_name end # Sets the name of the rule this rule is proxy for. - def rule_name=(name) - @rule_name = name.to_sym + def rule_name=(rule_name) + @rule_name = rule_name.to_sym end # The name of this proxy's rule. attr_reader :rule_name @@ -458,12 +458,12 @@ # +nil+ if no match can be made. def match(input, offset=0) m = input.match(rule, offset) if m extend_match(m) - # If this Proxy has a name then it should rename all of its matches. - m.name = name if name + # This proxy's name should be added to the names of the match. + m.names << name if name m end end end @@ -673,33 +673,39 @@ # label:expr # class Label include Predicate - def initialize(label='<label>', rule='') - @label = label.to_sym + def initialize(label_name='<label>', rule='') super(rule) + self.label_name = label_name end - # The symbol this rule uses to re-name all its matches. - attr_reader :label + # Sets the name of this label. + def label_name=(label_name) + @label_name = label_name.to_sym + end + # The name this rule adds to all its matches. + attr_reader :label_name + # Returns the Match for this rule on +input+ at the given +offset+, +nil+ if # no match can be made. When a Label makes a match, it re-names the match to # the value of its label. def match(input, offset=0) m = rule.match(input, offset) if m extend_match(m) - m.name = label + # This label's name should be added to the names of the match. + m.names << label_name m end end # Returns the Citrus notation of this rule as a string. def to_s - label.to_s + ':' + rule.embed + label_name.to_s + ':' + rule.embed end end # A Repeat is a Predicate that specifies a minimum and maximum number of times # its rule must match. The Citrus notation is an integer, +N+, followed by an @@ -792,11 +798,16 @@ # Returns the Match for this rule on +input+ at the given +offset+, +nil+ if # no match can be made. def match(input, offset=0) rules.each do |rule| m = input.match(rule, offset) - return create_match([m], offset) if m + if m + extend_match(m) + # This choice's name should be added to the names of the match. + m.names << name if name + return m + end end nil end # Returns the Citrus notation of this rule as a string. @@ -849,22 +860,30 @@ end @offset = offset end - # The name by which this match can be accessed from a parent match. This - # will be the name of the rule that generated the match in most cases. - # However, if the match is the result of a Label this will be the value of - # the label. - attr_accessor :name - - # A module that will be used to extend this match. - attr_accessor :ext - # The offset in the input at which this match occurred. attr_reader :offset + # An array of all names of this match. A name is added to a match object + # for each rule that returns that object when matching. These names can then + # be used to determine which rules were satisfied by a given match. + def names + @names ||= [] + end + + # The name of the lowest level rule that originally created this match. + def name + names.first + end + + # Returns +true+ if this match has the given +name+. + def has_name?(name) + names.include?(name) + end + # An array of all sub-matches of this match. def matches @matches ||= [] end @@ -895,11 +914,11 @@ # Returns an array of all sub-matches with the given +name+. If +deep+ is # +false+, returns only sub-matches that are immediate descendants of this # match. def find(name, deep=true) sym = name.to_sym - ms = matches.select {|m| sym == m.name } + ms = matches.select {|m| m.has_name?(sym) } ms.concat(matches.map {|m| m.find(name, deep) }.flatten) if deep ms end # A shortcut for retrieving the first immediate sub-match of this match. If @@ -923,25 +942,11 @@ alias eql? == # Uses #match to allow sub-matches of this match to be called by name as # instance methods. def method_missing(sym, *args) - # Extend this object only when needed and immediately redefine - # #method_missing so that the new version is used on all future calls. - extend(ext) if ext - redefine_method_missing! - __send__(sym, *args) - end - - private - - def redefine_method_missing! # :nodoc: - instance_eval(<<-RUBY, __FILE__, __LINE__ + 1) - def method_missing(sym, *args) - m = first(sym) - return m if m - raise 'No match named "%s" in %s (%s)' % [sym, self, name] - end - RUBY + m = first(sym) + return m if m + raise 'No match named "%s" in %s (%s)' % [sym, self, name] end end end