Class: RepeatablePattern

Inherits:
PatternBase show all
Defined in:
lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb

Overview

RepeatablePattern provides quantifiers for patterns

Instance Attribute Summary collapse

Attributes inherited from PatternBase

#arguments, #match, #next_pattern, #original_arguments

Instance Method Summary collapse

Methods inherited from PatternBase

#==, #__deep_clone__, #__deep_clone_self__, #add_capture_group_if_needed, #collect_group_attributes, #convert_group_attributes_to_captures, #convert_includes_to_patterns, #do_collect_self_groups, #do_get_to_s_name, #each, #eql?, #evaluate, #evaluate_operator, #fixup_regex_references, #groupless, #groupless?, #hash, #insert, #insert!, #inspect, #lookAheadFor, #lookAheadToAvoid, #lookAround, #lookBehindFor, #lookBehindToAvoid, #map, #map!, #map_includes!, #matchResultOf, #maybe, #name, #needs_to_capture?, #oneOf, #oneOrMoreOf, #optimize_outer_group?, #or, #placeholder, #raise_if_regex_has_capture_group, #reTag, #recursivelyMatch, #resolve, #run_self_tests, #run_tests, #self_scramble_references, #single_entity?, #start_pattern, #then, #to_r, #to_s, #to_tag, #transform_includes, #transform_tag_as, #zeroOrMoreOf

Constructor Details

#initialize(pattern) ⇒ RepeatablePattern #initialize(opts) ⇒ RepeatablePattern #initialize(opts, deep_clone, original) ⇒ RepeatablePattern

Construct a new pattern

Overloads:

  • #initialize(pattern) ⇒ RepeatablePattern

    matches an exact pattern

  • #initialize(opts) ⇒ RepeatablePattern
    Note:

    Plugins may provide additional options

    Note:

    all options except :match are optional

    Options Hash (opts):

    • :match (PatternBase, Regexp, String)

      the pattern to match

    • :tag_as (String)

      what to tag this pattern as

    • :includes (Array<PatternBase, Symbol>)

      pattern includes

    • :reference (String)

      a name for this pattern can be referred to in earlier or later parts of the pattern list, or in tag_as

    • :should_fully_match (Array<String>)

      string that this pattern should fully match

    • :should_partial_match (Array<String>)

      string that this pattern should partially match

    • :should_not_fully_match (Array<String>)

      string that this pattern should not fully match

    • :should_not_partial_match (Array<String>)

      string that this pattern should not partially match

    • :at_most (Enumerator, Integer)

      match up to N times, nil to match any number of times

    • :at_least (Enumerator, Integer)

      match no fewer than N times, nil to match any number of times

    • :how_many_times (Enumerator, Integer)

      match exactly N times

    • :word_cannot_be_any_of (Array<String>)

      list of wordlike string that the pattern should not match (this is a qualifier not a unit test)

    • :dont_back_track? (Boolean)

      can this pattern backtrack

  • #initialize(opts, deep_clone, original) ⇒ RepeatablePattern

    This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

    Note:

    this should only be called by deep_clone, however subclasses must be able to accept this form

    makes a copy of PatternBase



14
15
16
17
18
19
20
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 14

def initialize(*arguments)
    super(*arguments)

    @at_least = nil
    @at_most = nil
    process_quantifiers_from_arguments
end

Instance Attribute Details

#at_leastInteger?



10
11
12
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 10

def at_least
  @at_least
end

#at_mostInteger?



12
13
14
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 12

def at_most
  @at_most
end

Instance Method Details

#add_quantifier_options_to(match, groups) ⇒ String

Adds quantifiers to match



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 133

def add_quantifier_options_to(match, groups)
    match = match.evaluate(groups) if match.is_a? PatternBase
    quantifier = simple_quantifier
    # check if there are quantifiers
    if quantifier != ""
        # if the match is not a single entity, then it needs to be wrapped
        match = "(?:#{match})" unless string_single_entity?(match)
        # add the quantified ending
        match += quantifier
    elsif @arguments[:dont_back_track?] == true
        # make atomic, which allows arbitrary expression to be prevented from backtracking
        match = "(?>#{match})"
    end
    if @arguments[:word_cannot_be_any_of]
        word_pattern = @arguments[:word_cannot_be_any_of].map { |w| Regexp.escape w }.join "|"
        match = "(?!\\b(?:#{word_pattern})\\b)#{match}"
    end
    match
end

#do_add_attributes(indent) ⇒ String

return a string of any additional attributes that need to be added to the #to_s output indent is a string with the amount of space the parent block is indented, attributes are indented 2 more spaces called by #to_s



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 169

def do_add_attributes(indent)
    # rubocop:disable Metrics/LineLength
    output = ""
    # special #then arguments
    if quantifying_allowed?
        output += ",\n#{indent}  at_least: " + @arguments[:at_least].to_s if @arguments[:at_least]
        output += ",\n#{indent}  at_most: " + @arguments[:at_most].to_s if @arguments[:at_most]
        output += ",\n#{indent}  how_many_times?: " + @arguments[:how_many_times?].to_s if @arguments[:how_many_times?]
        output += ",\n#{indent}  word_cannot_be_any_of: " + @arguments[:word_cannot_be_any_of].to_s if @arguments[:word_cannot_be_any_of]
    end
    output += ",\n#{indent}  dont_back_track?: " + @arguments[:dont_back_track?].to_s if @arguments[:dont_back_track?]
    output
    # rubocop:enable Metrics/LineLength
end

#do_evaluate_self(groups) ⇒ String

Note:

optionally override when inheriting

Note:

by default this optionally adds a capture group

evaluates @match



154
155
156
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 154

def do_evaluate_self(groups)
    add_capture_group_if_needed(add_quantifier_options_to(@match, groups))
end

#process_quantifiers_from_argumentsvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

sets @at_least and @at_most based on arguments



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 29

def process_quantifiers_from_arguments
    # this sets the @at_most and @at_least value based on the arguments

    #
    # Simplify the quantity down to just :at_least and :at_most
    #
    attributes_clone = @arguments.clone
    # convert Enumerators to numbers
    [:at_least, :at_most, :how_many_times?].each do |each|
        if attributes_clone[each].is_a?(Enumerator)
            attributes_clone[each] = attributes_clone[each].size
        end
    end

    # canonize dont_back_track? and as_few_as_possible?
    @arguments[:dont_back_track?] ||= @arguments[:possessive?]
    @arguments[:as_few_as_possible?] ||= @arguments[:lazy?]
    # extract the data
    at_least       = attributes_clone[:at_least]
    at_most        = attributes_clone[:at_most]
    how_many_times = attributes_clone[:how_many_times?]
    # simplify to at_least and at_most
    at_least = at_most = how_many_times if how_many_times.is_a?(Integer)

    # check if quantifying is allowed
    # check after everything else in case additional quantifying options
    # are created in the future
    if quantifying_allowed?
        @at_least = at_least
        @at_most = at_most
    # if a quantifying value was set and quantifying is not allowed, raise an error
    # telling the user that its not allowed
    elsif !(at_most.nil? && at_least.nil?)
        raise "\n            Inside of the \#{name} pattern, there are some quantity arguments like:\n                :at_least\n                :at_most\n                or :how_many_times?\n            These are not allowed in this kind of \#{do_get_to_s_name}) pattern\n            If you did this intentionally please wrap it inside of a Pattern.new()\n            ex: \#{do_get_to_s_name} Pattern.new( *your_arguments* ) )\n        HEREDOC\n    end\n\n    return unless @arguments[:dont_back_track?] && @arguments[:as_few_as_possible?]\n\n    raise \":dont_back_track? and :as_few_as_possible? cannot both be provided\"\nend\n".remove_indent

#quantifying_allowed?Boolean

Note:

override when inheriting. Return false unless the subclass allow quantifying

Note:

the default implementation returns True

controls weather @arguments et. al. set @at_most et. al.



162
163
164
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 162

def quantifying_allowed?
    true
end

#self_capture_group_rematchBoolean

Note:

this is used by FixRepeatedTagAs to modify patterns

Does this pattern potentially rematch any capture groups

The answer of true is a safe, but expensive to runtime, default



192
193
194
195
196
197
198
199
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 192

def self_capture_group_rematch
    # N or more
    return true if @at_most.nil? && !@at_least.nil?
    # up to N
    return true if !@at_most.nil? && @at_most > 1

    false
end

#simple_quantifierString

converts @at_least and @at_most into the appropriate quantifier this is a simple_quantifier because it does not include atomic-ness



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 85

def simple_quantifier
    # Generate the ending based on :at_least and :at_most

    # by default assume no quantifiers
    quantifier = ""
    # if there is no at_least, at_most, or how_many_times?, then theres no quantifier
    if @at_least.nil? and @at_most.nil?
        quantifier = ""
    # if there is a quantifier
    else
        # if there's no at_least, then assume at_least = 1
        @at_least = 1 if @at_least.nil?

        quantifier =
            if @at_least == 1 and @at_most == 1
                # no qualifier
                ""
            elsif @at_least == 0 and @at_most == 1
                # this is just a different way of "maybe"
                "?"
            elsif @at_least == 0 and @at_most.nil?
                # this is just a different way of "zeroOrMoreOf"
                "*"
            elsif @at_least == 1 and @at_most.nil?
                # this is just a different way of "oneOrMoreOf"
                "+"
            elsif @at_least == @at_most
                # exactly N times
                "{#{@at_least}}"
            else
                # if it is more complicated than that, just use a range
                "{#{@at_least},#{@at_most}}"
            end
    end
    # quantifiers can be made possessive without requiring atomic groups
    quantifier += "+" if quantifier != "" && @arguments[:dont_back_track?] == true
    quantifier += "?" if quantifier != "" && @arguments[:as_few_as_possible?] == true
    quantifier
end