Class: PatternBase

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_grammar_builder/pattern_variations/base_pattern.rb,
lib/ruby_grammar_builder/pattern_extensions/maybe.rb,
lib/ruby_grammar_builder/pattern_extensions/one_of.rb,
lib/ruby_grammar_builder/pattern_extensions/or_pattern.rb,
lib/ruby_grammar_builder/pattern_extensions/placeholder.rb,
lib/ruby_grammar_builder/pattern_extensions/look_ahead_for.rb,
lib/ruby_grammar_builder/pattern_extensions/one_or_more_of.rb,
lib/ruby_grammar_builder/pattern_extensions/look_behind_for.rb,
lib/ruby_grammar_builder/pattern_extensions/match_result_of.rb,
lib/ruby_grammar_builder/pattern_extensions/zero_or_more_of.rb,
lib/ruby_grammar_builder/pattern_extensions/recursively_match.rb,
lib/ruby_grammar_builder/pattern_extensions/lookaround_pattern.rb,
lib/ruby_grammar_builder/pattern_extensions/look_ahead_to_avoid.rb,
lib/ruby_grammar_builder/pattern_extensions/look_behind_to_avoid.rb

Overview

Note:

Users should not normally directly instantiate this class

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Construct a new pattern

Overloads:

  • #initialize(pattern) ⇒ PatternBase

    matches an exact pattern

    Parameters:

  • #initialize(opts) ⇒ PatternBase
    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) ⇒ PatternBase

    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



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 235

def initialize(*arguments)
    if arguments.length > 1 && arguments[1] == :deep_clone
        @arguments = arguments[0]
        @match = @arguments[:match]
        @arguments.delete(:match)
        @original_arguments = arguments[2]
        @next_pattern = nil
        return
    end

    if arguments.length > 1
        # PatternBase was likely constructed like `PatternBase.new(/foo/, option: bar)`
        puts "PatternBase#new() expects a single Regexp, String, or Hash"
        puts "PatternBase#new() was provided with multiple arguments"
        puts "arguments:"
        puts arguments
        raise "See error above"
    end
    @next_pattern = nil
    arg1 = arguments[0]
    arg1 = {match: arg1} unless arg1.is_a? Hash
    @original_arguments = arg1.clone
    if arg1[:match].is_a? String
        arg1[:match] = Regexp.escape(arg1[:match]).gsub("/", "\\/")
        @match = arg1[:match]
    elsif arg1[:match].is_a? Regexp
        raise_if_regex_has_capture_group arg1[:match]
        @match = arg1[:match].inspect[1..-2] # convert to string and remove the slashes
    elsif arg1[:match].is_a? PatternBase
        @match = arg1[:match]
    else
        puts <<-HEREDOC.remove_indent
            Pattern.new() must be constructed with a String, Regexp, or Pattern
            Provided arguments: #{@original_arguments}
        HEREDOC
        raise "See error above"
    end
    # ensure that includes is either nil or a flat array
    if arg1[:includes]
        arg1[:includes] = [arg1[:includes]] unless arg1[:includes].is_a? Array
        arg1[:includes] = arg1[:includes].flatten
    end
    arg1.delete(:match)
    @arguments = arg1
end

Instance Attribute Details

#argumentsHash

Returns The processed arguments.

Returns:

  • (Hash)

    The processed arguments



16
17
18
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 16

def arguments
  @arguments
end

#matchString, PatternBase

Returns The pattern to match.

Returns:



14
15
16
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 14

def match
  @match
end

#next_patternPatternBase

Returns The next pattern in the linked list of patterns.

Returns:

  • (PatternBase)

    The next pattern in the linked list of patterns



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

def next_pattern
  @next_pattern
end

#original_argumentsHash

Returns The original arguments passed into initialize.

Returns:

  • (Hash)

    The original arguments passed into initialize



18
19
20
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 18

def original_arguments
  @original_arguments
end

Instance Method Details

#==(other) ⇒ Boolean

Checks for equality A pattern is considered equal to another pattern if the result of tag_as is equivalent

Parameters:

Returns:

  • (Boolean)

    true if other is a PatternBase and to_tag is equivalent, false otherwise



529
530
531
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 529

def ==(other)
    eql? other
end

#__deep_clone__PatternBase

Deeply clone self

Returns:



847
848
849
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 847

def __deep_clone__
    __deep_clone_self__.insert! @next_pattern.__deep_clone__
end

#__deep_clone_self__PatternBase

Deeply clones self, without its next_pattern

Returns:



856
857
858
859
860
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 856

def __deep_clone_self__
    options = @arguments.__deep_clone__
    options[:match] = @match.__deep_clone__
    self.class.new(options, :deep_clone, @original_arguments)
end

#collect_group_attributes(next_group = optimize_outer_group? ? 0 : 1) ⇒ Array<Hash>

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.

Collects information about the capture groups

Parameters:

  • next_group (Integer) (defaults to: optimize_outer_group? ? 0 : 1)

    the next group number to use

Returns:

  • (Array<Hash>)

    group attributes



668
669
670
671
672
673
674
675
676
677
678
679
680
681
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 668

def collect_group_attributes(next_group = optimize_outer_group? ? 0 : 1)
    groups = do_collect_self_groups(next_group)
    next_group += groups.length
    if @match.is_a? PatternBase
        new_groups = @match.collect_group_attributes(next_group)
        groups.concat(new_groups)
        next_group += new_groups.length
    end
    if @next_pattern.is_a? PatternBase
        new_groups = @next_pattern.collect_group_attributes(next_group)
        groups.concat(new_groups)
    end
    groups
end

#convert_group_attributes_to_captures(groups) ⇒ Hash

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.

Converts group attributes into a captures hash

Parameters:

  • groups (Hash)

    group attributes

Returns:

  • (Hash)

    capture hash



792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 792

def convert_group_attributes_to_captures(groups)
    captures = {}

    groups.each do |group|
        output = {}
        output[:name] = group[:tag_as] unless group[:tag_as].nil?
        if group[:includes].is_a? Array
            output[:patterns] = convert_includes_to_patterns(group[:includes])
        elsif !group[:includes].nil?
            output[:patterns] = convert_includes_to_patterns([group[:includes]])
        end
        captures[group[:group].to_s] = output
    end
    # replace $match and $reference() with the appropriate capture number
    captures.each do |key, value|
        next if value[:name].nil?

        value[:name] = value[:name].gsub(/\$(?:match|reference\((.+)\))/) do |match|
            next ("$" + key) if match == "$match"

            reference_group = groups.detect do |group|
                group[:reference] == Regexp.last_match(1)
            end
            "$" + reference_group[:group].to_s
        end
    end
end

#convert_includes_to_patterns(includes) ⇒ Array<Hash>

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.

converts an includes array into a patterns array

Parameters:

  • includes (Array<PatternBase, Symbol>)

    an includes array

Returns:

  • (Array<Hash>)

    a patterns array



829
830
831
832
833
834
835
836
837
838
839
840
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 829

def convert_includes_to_patterns(includes)
    includes = [includes] unless includes.is_a? Array
    patterns = includes.flatten.map do |rule|
        next {include: rule} if rule.is_a?(String) && rule.start_with?("source.", "text.")
        next {include: rule.to_s} if [:$self, :$base].include? rule
        next {include: "##{rule}"} if rule.is_a? Symbol

        rule = PatternBase.new(rule) unless rule.is_a? PatternBase
        rule.to_tag
    end
    patterns
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



588
589
590
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 588

def do_add_attributes(indent) # rubocop:disable Lint/UnusedMethodArgument
    ""
end

#do_collect_self_groups(next_group) ⇒ Array<Hash>

Collect group information about self

Parameters:

  • next_group (Integer)

    The next group number to use

Returns:

  • (Array<Hash>)

    group attributes



690
691
692
693
694
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 690

def do_collect_self_groups(next_group)
    groups = []
    groups << {group: next_group}.merge(@arguments) if self.needs_to_capture?
    groups
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



558
559
560
561
562
563
564
565
566
567
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 558

def do_evaluate_self(groups)
    match = @match
    match = match.evaluate(groups) if match.is_a? PatternBase
    if self.needs_to_capture?
        match = "(#{match})"
    elsif not string_single_entity?(match)
        match = "(?:#{match})"
    end
    return match
end

#do_get_to_s_name(top_level) ⇒ String

What is the name of the method that the user would call top_level is if a freestanding or chaining function is called called by #to_s

Parameters:

  • top_level (Boolean)

    is this top_level or chained

Returns:

  • (String)

    the name of the method



601
602
603
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 601

def do_get_to_s_name(top_level)
    top_level ? "Pattern.new(" : ".then("
end

#each(each_includes = false) {|self| ... } ⇒ void

This method returns an undefined value.

Call the block for each pattern in the list

Parameters:

  • each_includes (Boolean) (defaults to: false)

    should include patterns be called?

Yields:

  • (self)

    invokes the block with self



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 121

def each(each_includes = false, &block)
    yield self
    @match.each(each_includes, &block) if @match.is_a? PatternBase
    @next_pattern.each(each_includes, &block) if @next_pattern.is_a? PatternBase

    return unless each_includes
    return unless @arguments[:includes].is_a? Array

    @arguments[:includes].each do |s|
        next unless s.is_a? Pattern

        s.each(true, &block)
    end
end

#eql?(other) ⇒ Boolean

Checks for equality A pattern is considered equal to another pattern if the result of tag_as is equivalent

Parameters:

Returns:

  • (Boolean)

    true if other is a PatternBase and to_tag is equivalent, false otherwise



522
523
524
525
526
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 522

def eql?(other)
    return false unless other.is_a? PatternBase

    to_tag == other.to_tag
end

#evaluate(groups = nil, fixup_refereces: false) ⇒ String

evaluates the pattern into a string suitable for inserting into a grammar or constructing a Regexp.

Parameters:

  • groups (Hash) (defaults to: nil)

    if groups is nil consider this PatternBase to be the top_level when a pattern is top_level, group numbers and back references are relative to that pattern

Returns:

  • (String)

    the complete pattern



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 322

def evaluate(groups = nil, fixup_refereces: false)
    top_level = groups.nil?
    groups = collect_group_attributes if top_level
    evaluate_array = ['']

    pat = self
    while pat.is_a? PatternBase
        evaluate_array << pat.evaluate_operator
        evaluate_array << pat.do_evaluate_self(groups)
        pat = pat.next_pattern
    end
    
    self_evaluate = RegexOperator.evaluate(evaluate_array)
    self_evaluate = fixup_regex_references(groups, self_evaluate) if top_level || fixup_refereces
    self_evaluate
end

#evaluate_operatorRegexOperator

Returns the operator to use when evaluating

Returns:



574
575
576
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 574

def evaluate_operator
    ConcatOperator.new
end

#fixup_regex_references(groups, self_regex) ⇒ String

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.

Convert group references into backreferences

Parameters:

  • groups (Hash)

    group information for the pattern

  • self_regex (String)

    the pattern as string

Returns:

  • (String)

    the fixed up regex_string



715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 715

def fixup_regex_references(groups, self_regex)
    # rubocop:disable Metrics/LineLength
    references = {}
    # convert all references to group numbers
    groups.each do |group|
        references[group[:reference]] = group[:group] unless group[:reference].nil?
    end

    # convert back references
    self_regex = self_regex.gsub(/\(\?\#\[:backreference:([^\\]+?):\]\)/) do
        match_reference = Regexp.last_match(1)
        if references[match_reference].nil?
            if $ruby_grammar_builder__unit_test_active
                "(?#would_be_backref_but_null_because_unit_test)A(?<=B)"
            else
                raise "groups:#{groups}\nreferences: #{references}\nWhen processing the matchResultOf:#{match_reference}, I couldn't find the group it was referencing"
            end
        else
            # if the reference does exist, then replace it with it's number
            "(?:\\#{references[match_reference]})"
        end
    end

    # check for a subroutine to the Nth group, replace it with `\N`
    self_regex = self_regex.gsub(/\(\?\#\[:subroutine:([^\\]+?):\]\)/) do
        match_reference = Regexp.last_match(1)
        if references[match_reference].nil?
            if $ruby_grammar_builder__unit_test_active
                "(?#would_be_subroutine_but_null_because_unit_test)A(?<=B)"
            else
                raise "groups:#{groups}\nreferences: #{references}\nWhen processing the recursivelyMatch:#{match_reference}, I couldn't find the group it was referencing"
            end
        else
            # if the reference does exist, then replace it with it's number
            "\\g<#{references[match_reference]}>"
        end

    end
    # rubocop:enable Metrics/LineLength
    self_regex
end

#grouplessPatternBase

create a copy of this pattern that contains no groups

Returns:



617
618
619
620
621
622
623
624
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 617

def groupless
    __deep_clone__.map! do |s|
        s.arguments.delete(:tag_as)
        s.arguments.delete(:reference)
        s.arguments.delete(:includes)
        raise "unable to remove capture" if s.needs_to_capture?
    end.freeze
end

#groupless?Boolean

does this pattern contain no capturing groups

Returns:

  • (Boolean)


611
612
613
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 611

def groupless?
    collect_group_attributes == []
end

#hashInteger

Gets the patterns Hashcode

Returns:

  • (Integer)

    the Hashcode



507
508
509
510
511
512
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 507

def hash
    # TODO: find a better hash code
    # PatternBase.new("abc") == PatternBase.new(PatternBase.new("abc"))
    # but PatternBase.new("abc").hash != PatternBase.new(PatternBase.new("abc")).hash
    @match.hash
end

#insert(pattern) ⇒ PatternBase

Append pattern to a copy of the linked list of patterns

Parameters:

Returns:

  • (PatternBase)

    a copy of self with pattern appended



71
72
73
74
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 71

def insert(pattern)
    new_pattern = __deep_clone__
    new_pattern.insert!(pattern).freeze
end

#insert!(pattern) ⇒ self

Appends pattern to the linked list of patterns

Parameters:

Returns:

  • (self)

See Also:



57
58
59
60
61
62
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 57

def insert!(pattern)
    last = self
    last = last.next_pattern while last.next_pattern
    last.next_pattern = pattern
    self
end

#inspectString

Displays the Pattern for inspection

Returns:

  • (String)

    A representation of the pattern



701
702
703
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 701

def inspect
    super.split(" ")[0] + " match:" + @match.inspect + ">"
end

#lookAheadFor(pattern) ⇒ PatternBase

Equivalent to lookAround with type set to :lookAheadFor

Parameters:

  • pattern (Hash)

    pattern constructor

Returns:



14
15
16
17
18
19
20
21
# File 'lib/ruby_grammar_builder/pattern_extensions/look_ahead_for.rb', line 14

def lookAheadFor(pattern)
    if pattern.is_a? Hash
        pattern[:type] = :lookAheadFor
    else
        pattern = {match: pattern, type: :lookAheadFor}
    end
    lookAround(pattern)
end

#lookAheadToAvoid(pattern) ⇒ PatternBase

Equivalent to lookAround with type set to :lookAheadToAvoid

Parameters:

  • pattern (Hash)

    pattern constructor

Returns:



98
99
100
101
102
103
104
105
# File 'lib/ruby_grammar_builder/pattern_extensions/lookaround_pattern.rb', line 98

def lookAheadToAvoid(pattern)
    if pattern.is_a? Hash
        pattern[:type] = :lookAheadToAvoid
    else
        pattern = {match: pattern, type: :lookAheadToAvoid}
    end
    lookAround(pattern)
end

#lookAround(pattern) ⇒ PatternBase

Looks around for the pattern

option [Symbol] :type the look-around type

can be one of :lookAheadFor, :lookAheadToAvoid, :lookBehindFor, :lookBehindToAvoid

Parameters:

  • pattern (Hash)

    pattern constructor

Returns:



55
56
57
# File 'lib/ruby_grammar_builder/pattern_extensions/lookaround_pattern.rb', line 55

def lookAround(pattern)
    insert(LookAroundPattern.new(pattern))
end

#lookBehindFor(pattern) ⇒ PatternBase

Equivalent to lookAround with type set to :lookBehindFor

Parameters:

  • pattern (Hash)

    pattern constructor

Returns:



13
14
15
16
17
18
19
20
# File 'lib/ruby_grammar_builder/pattern_extensions/look_behind_for.rb', line 13

def lookBehindFor(pattern)
    if pattern.is_a? Hash
        pattern[:type] = :lookBehindFor
    else
        pattern = {match: pattern, type: :lookBehindFor}
    end
    lookAround(pattern)
end

#lookBehindToAvoid(pattern) ⇒ PatternBase

Equivalent to lookAround with type set to :lookBehindToAvoid

Parameters:

  • pattern (Hash)

    pattern constructor

Returns:



66
67
68
69
70
71
72
73
# File 'lib/ruby_grammar_builder/pattern_extensions/lookaround_pattern.rb', line 66

def lookBehindToAvoid(pattern)
    if pattern.is_a? Hash
        pattern[:type] = :lookBehindToAvoid
    else
        pattern = {match: pattern, type: :lookBehindToAvoid}
    end
    lookAround(pattern)
end

#map(map_includes = false) {|self| ... } ⇒ self, PatternBase

Uses a block to transform all Patterns in the list

Parameters:

  • map_includes (Boolean) (defaults to: false)

    should include patterns be mapped?

Yields:

  • (self)

    invokes the block with self for modification

Returns:



109
110
111
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 109

def map(map_includes = false, &block)
    __deep_clone__.map!(map_includes, &block).freeze
end

#map!(map_includes = false) {|self| ... } ⇒ self

Uses a block to transform all Patterns in the list

Parameters:

  • map_includes (Boolean) (defaults to: false)

    should include patterns be mapped?

Yields:

  • (self)

    invokes the block with self for modification

Returns:

  • (self)


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 84

def map!(map_includes = false, &block)
    yield self
    if @match.is_a? PatternBase
        if @match.frozen?
            puts "frozen @match"
            puts @match.inspect
        end
        @match = @match.map!(map_includes, &block)
    end
    if @next_pattern.is_a? PatternBase
        if @next_pattern.frozen?
            puts "frozen @next_pattern"
            puts @next_pattern.inspect
        end
        @next_pattern = @next_pattern.map!(map_includes, &block)
    end
    map_includes!(&block) if map_includes
    self
end

#map_includes! {|self| ... } ⇒ void

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:

only for use by map!

This method returns an undefined value.

Uses a block to transform all Patterns in all includes

Yields:

  • (self)

    invokes the block with the includes for modification



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 145

def map_includes!(&block)
    return unless @arguments[:includes].is_a? Array

    @arguments[:includes].map! do |s|
        if s.is_a? PatternBase
            if s.frozen?
                puts "frozen s"
                puts s.inspect
            end
        end

        next s.map!(true, &block) if s.is_a? PatternBase

        next s
    end
end

#matchResultOf(reference) ⇒ PatternBase

Match the result of some other pattern

Parameters:

  • reference (String)

    a reference to match the result of

Returns:



59
60
61
# File 'lib/ruby_grammar_builder/pattern_extensions/match_result_of.rb', line 59

def matchResultOf(reference)
    insert(MatchResultOfPattern.new(reference))
end

#maybe(pattern) ⇒ PatternBase

Optionally match pattern

Parameters:

Returns:



42
43
44
# File 'lib/ruby_grammar_builder/pattern_extensions/maybe.rb', line 42

def maybe(pattern)
    insert(MaybePattern.new(pattern))
end

#nameString

attempts to provide a memorable name for a pattern

Returns:



283
284
285
286
287
288
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 283

def name
    return @arguments[:reference] unless @arguments[:reference].nil?
    return @arguments[:tag_as] unless @arguments[:tag_as].nil?

    to_s
end

#needs_to_capture?Boolean

does @arguments contain any attributes that require this pattern be captured?

Returns:

  • (Boolean)

    if this PatternBase needs to capture



25
26
27
28
29
30
31
32
33
34
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 25

def needs_to_capture?
    capturing_attributes = [
        :tag_as,
        :reference,
        :includes,
    ]
    puts @match.class unless @arguments.is_a? Hash

    !(@arguments.keys & capturing_attributes).empty?
end

#oneOf(patterns) ⇒ PatternBase

Match one of the supplied patterns

Parameters:

Returns:



97
98
99
# File 'lib/ruby_grammar_builder/pattern_extensions/one_of.rb', line 97

def oneOf(patterns)
    insert(OneOfPattern.new(patterns))
end

#oneOrMoreOf(pattern) ⇒ PatternBase

Match pattern one or more times

Parameters:

Returns:



34
35
36
# File 'lib/ruby_grammar_builder/pattern_extensions/one_or_more_of.rb', line 34

def oneOrMoreOf(pattern)
    insert(OneOrMoreOfPattern.new(pattern))
end

#optimize_outer_group?Boolean

Can the capture be optimized out

When the pattern has nothing after it then its capture can instead become capture group 0

Returns:

  • (Boolean)

    can this capture become capture group 0



44
45
46
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 44

def optimize_outer_group?
    self.needs_to_capture? and @next_pattern.nil?
end

#or(pattern) ⇒ PatternBase

Match either the preceding pattern chain or pattern

Parameters:

  • pattern (PatternBase, Regexp, String, Hash)

    a pattern to match instead of the previous chain

Returns:



104
105
106
# File 'lib/ruby_grammar_builder/pattern_extensions/or_pattern.rb', line 104

def or(pattern)
    insert(OrPattern.new(pattern))
end

#placeholder(placeholder) ⇒ PatternBase

Match a pattern that does not exist yet

Parameters:

  • placeholder (Symbol)

    the name of the pattern to match

Returns:



83
84
85
# File 'lib/ruby_grammar_builder/pattern_extensions/placeholder.rb', line 83

def placeholder(placeholder)
    insert(PlaceholderPattern.new(placeholder))
end

#raise_if_regex_has_capture_group(regex, check = 1) ⇒ void

This method returns an undefined value.

Raise an error if regex contains a capturing group

Parameters:

  • regex (Regexp)

    the regexp to test

  • check (Integer) (defaults to: 1)

    the group to check for



870
871
872
873
874
875
876
877
878
879
880
881
882
883
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 870

def raise_if_regex_has_capture_group(regex, check = 1)
    # this will throw a RegexpError if there are no capturing groups
    _ignore = with_no_warnings { /#{regex}#{"\\" + check.to_s}/ }
    # at this point @match contains a capture group, complain
    raise <<-HEREDOC.remove_indent

        There is a pattern that is being constructed from a regular expression
        with a capturing group. This is not allowed, as the group cannot be tracked
        The bad pattern is
        #{self}
    HEREDOC
rescue RegexpError # rubocop: disable Lint/HandleExceptions
    # no capture groups present, purposely do nothing
end

#recursivelyMatch(reference) ⇒ PatternBase

Recursively match some outer pattern

Parameters:

  • reference (String)

    a reference to an outer pattern

Returns:



68
69
70
# File 'lib/ruby_grammar_builder/pattern_extensions/recursively_match.rb', line 68

def recursivelyMatch(reference)
    insert(RecursivelyMatchPattern.new(reference))
end

#resolve(repository) ⇒ PatternBase

Resolves any placeholder patterns

Parameters:

  • repository (Hash)

    the repository to resolve patterns with

Returns:

  • (PatternBase)

    a copy of self with placeholders resolved



94
95
96
# File 'lib/ruby_grammar_builder/pattern_extensions/placeholder.rb', line 94

def resolve(repository)
    __deep_clone__.map!(true) { |s| s.resolve!(repository) if s.respond_to? :resolve! }.freeze
end

#reTag(args) ⇒ PatternBase

Retags all tags_as

Parameters:

  • args (Hash)

    retag options

  • [Boolean] (Hash)

    a customizable set of options

  • [String] (Hash)

    a customizable set of options

Returns:



638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 638

def reTag(args)
    __deep_clone__.map! do |s|
        # tags are keep unless `all: false` or `keep: false`, and append is not a string
        discard_tag = (args[:all] == false || args[:keep] == false)
        discard_tag = false if args[:append].is_a? String

        args.each do |key, tag|
            if [s.arguments[:tag_as], s.arguments[:reference]].include? key
                s.arguments[:tag_as] = tag
                discard_tag = false
            end
        end

        if args[:append].is_a?(String) && s.arguments[:tag_as]
            s.arguments[:tag_as] = s.arguments[:tag_as] + "." + args[:append]
        end

        s.arguments.delete(:tag_as) if discard_tag
    end.freeze
end

#run_self_testsBoolean

Runs the unit tests for self

Returns:

  • (Boolean)

    If all test passed return true, otherwise false



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 429

def run_self_tests
    pass = [true]

    # some patterns are not able to be evaluated
    # do not attempt to unless required
    return true unless [
        :should_fully_match,
        :should_not_fully_match,
        :should_partially_match,
        :should_not_partially_match,
    ].any? { |k| @arguments.include? k }

    copy = __deep_clone_self__
    begin
        test_regex = copy.to_r
        test_fully_regex = wrap_with_anchors(copy).to_r
    rescue => exception
        raise <<~HEREDOC
            
            
            error running unit tests for: #{copy}
            #{exception}
        HEREDOC
    end

    warn = lambda do |symbol|
        puts [
            "",
            "When testing the pattern #{test_regex.inspect}. The unit test for #{symbol} failed.",
            "The unit test has the following patterns:",
            "#{@arguments[symbol].to_yaml}",
            "The Failing pattern is below:",
            "#{self}",
        ].join("\n")
    end
    if @arguments[:should_fully_match].is_a? Array
        unless @arguments[:should_fully_match].all? { |test| test =~ test_fully_regex }
            warn.call :should_fully_match
            pass << false
        end
    end
    if @arguments[:should_not_fully_match].is_a? Array
        unless @arguments[:should_not_fully_match].none? { |test| test =~ test_fully_regex }
            warn.call :should_not_fully_match
            pass << false
        end
    end
    if @arguments[:should_partially_match].is_a? Array
        unless @arguments[:should_partially_match].all? { |test| test =~ test_regex }
            warn.call :should_partially_match
            pass << false
        end
    end
    if @arguments[:should_not_partially_match].is_a? Array
        unless @arguments[:should_not_partially_match].none? { |test| test =~ test_regex }
            warn.call :should_not_partially_match
            pass << false
        end
    end

    pass.none?(&:!)
end

#run_testsBoolean

Runs the unit tests, recursively

Returns:

  • (Boolean)

    If all test passed return true, otherwise false



405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 405

def run_tests
    original_flag_value = $ruby_grammar_builder__unit_test_active
    $ruby_grammar_builder__unit_test_active = true
    pass = [
        run_self_tests,
    ]

    # run related unit tests
    pass << @match.run_tests if @match.is_a? PatternBase
    pass << @next_pattern.run_tests if @next_pattern.is_a? PatternBase
    if @arguments[:includes].is_a? Array
        @arguments[:includes]&.each { |inc| pass << inc.run_tests if inc.is_a? PatternBase }
    elsif @arguments[:includes].is_a? PatternBase
        pass << @arguments[:includes].run_tests
    end
    $ruby_grammar_builder__unit_test_active = original_flag_value
    pass.none?(&:!)
end

#self_scramble_referencesvoid

This method returns an undefined value.

Scrambles references of self This method provides a way to rename all references both actual references and references to references will be scrambled in some one to one mapping, all references that were unique before remain unique

This must be idempotent, calling this repeatedly must have references be as if it was called only once, even if the pattern is cloned between calls

this is because it may be called a different number of times depending on the nest
level of the patterns


770
771
772
773
774
775
776
777
778
779
780
781
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 770

def self_scramble_references
    scramble = lambda do |name|
        return name if name.start_with?("__scrambled__")

        "__scrambled__" + name
    end

    tag_as = @arguments[:tag_as]
    reference = @arguments[:reference]
    @arguments[:tag_as] = scramble.call(tag_as) if tag_as.is_a? String
    @arguments[:reference] = scramble.call(reference) if reference.is_a? String
end

#single_entity?Boolean

Returns:

  • (Boolean)


606
607
608
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 606

def single_entity?
    return string_single_entity?( self.evaluate() )
end

#start_patternself

To aid in Linters all Patterns support start_pattern which return the pattern for initial match, for a single match pattern that is itself

Returns:

  • (self)

    This pattern



498
499
500
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 498

def start_pattern
    self
end

#then(pattern) ⇒ PatternBase

Construct a new pattern and append to the end

Parameters:

  • pattern (PatternBase)

    options (see #initialize for options)

Returns:

  • (PatternBase)

    a copy of self with a pattern inserted

See Also:



541
542
543
544
545
546
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 541

def then(pattern)
    unless pattern.is_a?(PatternBase) && pattern.next_pattern.nil?
        pattern = Pattern.new(pattern)
    end
    insert(pattern)
end

#to_r(groups = nil) ⇒ Regexp

converts a pattern to a Regexp

Parameters:

  • groups (Hash) (defaults to: nil)

    if groups is nil consider this PatternBase to be the top_level when a pattern is top_level, group numbers and back references are relative to that pattern

Returns:

  • (Regexp)

    the pattern as a Regexp



348
349
350
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 348

def to_r(groups = nil)
    with_no_warnings { Regexp.new(evaluate(groups)) }
end

#to_s(depth = 0, top_level = true) ⇒ String

Displays the PatternBase as you would write it in code

Parameters:

  • depth (Integer) (defaults to: 0)

    the current nesting depth

  • top_level (Boolean) (defaults to: true)

    is this a top level pattern or is it being chained

Returns:

  • (String)

    The pattern as a string



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 360

def to_s(depth = 0, top_level = true)
    # TODO: make this method easier to understand

    # rubocop:disable Metrics/LineLength
    begin
        plugins = Grammar.plugins
        plugins.reject! { |p| (@original_arguments.keys & p.class.options).empty? }

        regex_as_string =
            case @original_arguments[:match]
            when PatternBase then @original_arguments[:match].to_s(depth + 2, true)
            when Regexp then @original_arguments[:match].inspect
            when String then "/" + Regexp.escape(@original_arguments[:match]) + "/"
            end
        indent = "  " * depth
        output = indent + do_get_to_s_name(top_level)
        # basic pattern information
        output += "\n#{indent}  match: " + regex_as_string.lstrip
        output += ",\n#{indent}  tag_as: \"" + @arguments[:tag_as] + '"' if @arguments[:tag_as]
        output += ",\n#{indent}  reference: \"" + @arguments[:reference] + '"' if @arguments[:reference]
        # unit tests
        output += ",\n#{indent}  should_fully_match: " + @arguments[:should_fully_match].to_s if @arguments[:should_fully_match]
        output += ",\n#{indent}  should_not_fully_match: " + @arguments[:should_not_fully_match].to_s if @arguments[:should_not_fully_match]
        output += ",\n#{indent}  should_partially_match: " + @arguments[:should_partially_match].to_s if @arguments[:should_partially_match]
        output += ",\n#{indent}  should_not_partially_match: " + @arguments[:should_not_partially_match].to_s if @arguments[:should_not_partially_match]

        output += ",\n#{indent}  includes: " + @arguments[:includes].to_s if @arguments[:includes]
        # add any linter/transform configurations
        plugins.each { |p| output += p.display_options(indent + "  ", @original_arguments) }
        # subclass, ending and recursive
        output += do_add_attributes(indent)
        output += ",\n#{indent})"
        output += @next_pattern.to_s(depth, false).lstrip if @next_pattern
        output
    rescue
        return @original_arguments.to_s
    end
    # rubocop:enable Metrics/LineLength
end

#to_tagHash

converts a PatternBase to a Hash representing a textmate rule

Returns:

  • (Hash)

    The pattern as a textmate grammar rule



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 295

def to_tag
    output = {
        match: self.evaluate(),
    }

    output[:captures] = convert_group_attributes_to_captures(collect_group_attributes)
    if optimize_outer_group?
        # optimize captures by removing outermost
        output[:match] = output[:match][1..-2]
        output[:name] = output[:captures]["0"][:name]
        output[:captures]["0"].delete(:name)
        output[:captures].reject! { |_, v| !v || v.empty? }
    end
    output.reject! { |_, v| !v || v.empty? }
    output
end

#transform_includes {|PatternBase, Symbol, Regexp, String| ... } ⇒ PatternBase

Uses block to recursively transform includes

Yields:

  • (PatternBase, Symbol, Regexp, String)

    invokes the block with each include to transform

Returns:

  • (PatternBase)

    a copy of self with transformed includes



169
170
171
172
173
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 169

def transform_includes(&block)
    map(true) do |s|
        s.arguments[:includes].map!(&block) if s.arguments[:includes].is_a? Array
    end
end

#transform_tag_as {|String| ... } ⇒ PatternBase

Uses block to recursively transform tag_as

Yields:

  • (String)

    Invokes the block to with each tag_as to transform

Returns:

  • (PatternBase)

    a copy of self with transformed tag_as



182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ruby_grammar_builder/pattern_variations/base_pattern.rb', line 182

def transform_tag_as(&block)
    __deep_clone__.map! do |s|
        s.arguments[:tag_as] = block.call(s.arguments[:tag_as]) if s.arguments[:tag_as]
        next unless s.arguments[:includes].is_a?(Array)

        s.arguments[:includes].map! do |i|
            next i unless i.is_a? PatternBase

            i.transform_tag_as(&block)
        end
    end.freeze
end

#zeroOrMoreOf(pattern) ⇒ PatternBase

Match pattern zero or more times

Parameters:

Returns:



42
43
44
# File 'lib/ruby_grammar_builder/pattern_extensions/zero_or_more_of.rb', line 42

def zeroOrMoreOf(pattern)
    insert(ZeroOrMoreOfPattern.new(pattern))
end