Class: RepeatablePattern

Inherits:
PatternBase show all
Defined in:
lib/textmate_grammar/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

    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/textmate_grammar/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/textmate_grammar/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/textmate_grammar/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



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/textmate_grammar/pattern_variations/repeatable_pattern.rb', line 137

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



173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/textmate_grammar/pattern_variations/repeatable_pattern.rb', line 173

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



158
159
160
# File 'lib/textmate_grammar/pattern_variations/repeatable_pattern.rb', line 158

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
78
79
80
81
# File 'lib/textmate_grammar/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?]
    if @arguments[:greedy?]
        @arguments[:dont_back_track?] = false
        @arguments[:as_few_as_possible?] = false
    end
    # 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



166
167
168
# File 'lib/textmate_grammar/pattern_variations/repeatable_pattern.rb', line 166

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



196
197
198
199
200
201
202
203
# File 'lib/textmate_grammar/pattern_variations/repeatable_pattern.rb', line 196

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:



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
124
125
126
127
# File 'lib/textmate_grammar/pattern_variations/repeatable_pattern.rb', line 89

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