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__, #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

    Parameters:

  • #initialize(opts) ⇒ RepeatablePattern
    Note:

    Plugins may provide additional options

    Note:

    all options except :match are optional

    Parameters:

    • opts (Hash)

      options

    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

    Parameters:

    • opts (Hash)

      the original patterns @arguments with match

    • deep_clone (:deep_clone)

      identifies as a deep_clone construction

    • original (Hash)

      the original patterns @original_arguments



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?

Returns the minimum amount that can be matched.

Returns:

  • (Integer, nil)

    the minimum amount that can be matched



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

def at_least
  @at_least
end

#at_mostInteger?

Returns the maximum amount that can be matched.

Returns:

  • (Integer, nil)

    the maximum amount that can be matched



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

Parameters:

  • match (String, PatternBase)

    the pattern to add a quantifier to

  • groups (Array)

    group information, used for evaluating match

Returns:

  • (String)

    match with quantifiers applied



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

Parameters:

  • indent (String)

    the spaces to indent with

Returns:

  • (String)

    the attributes to add



175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 175

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

Parameters:

  • groups (Hash)

    group attributes

Returns:

  • (String)

    the result of evaluating @match



154
155
156
157
158
159
160
161
162
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 154

def do_evaluate_self(groups)
    match = add_quantifier_options_to(@match, groups)
    if self.needs_to_capture?
        match = "(#{match})"
    elsif not string_single_entity?(match)
        match = "(?:#{match})"
    end
    return match
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 <<-HEREDOC.remove_indent

            Inside of the #{name} pattern, there are some quantity arguments like:
                :at_least
                :at_most
                or :how_many_times?
            These are not allowed in this kind of #{do_get_to_s_name}) pattern
            If you did this intentionally please wrap it inside of a Pattern.new()
            ex: #{do_get_to_s_name} Pattern.new( *your_arguments* ) )
        HEREDOC
    end

    return unless @arguments[:dont_back_track?] && @arguments[:as_few_as_possible?]

    raise ":dont_back_track? and :as_few_as_possible? cannot both be provided"
end

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

Returns:

  • (Boolean)

    if quantifying is allowed



168
169
170
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 168

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

Returns:

  • (Boolean)

    True if this pattern potentially rematches capture groups



198
199
200
201
202
203
204
205
# File 'lib/ruby_grammar_builder/pattern_variations/repeatable_pattern.rb', line 198

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

Returns:



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