textmate_grammar 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/lib/textmate_grammar/generated/grammar.rb +32 -0
  4. data/lib/textmate_grammar/generated/rule.rb +144 -0
  5. data/lib/textmate_grammar/grammar.rb +670 -0
  6. data/lib/textmate_grammar/grammar_plugin.rb +189 -0
  7. data/lib/textmate_grammar/import_patterns.rb +14 -0
  8. data/lib/textmate_grammar/linters/flat_includes.rb +32 -0
  9. data/lib/textmate_grammar/linters/includes_then_tag_as.rb +48 -0
  10. data/lib/textmate_grammar/linters/standard_naming.rb +226 -0
  11. data/lib/textmate_grammar/linters/start_match_empty.rb +49 -0
  12. data/lib/textmate_grammar/linters/tests.rb +19 -0
  13. data/lib/textmate_grammar/linters/unused_unresolved.rb +9 -0
  14. data/lib/textmate_grammar/pattern_extensions/look_ahead_for.rb +32 -0
  15. data/lib/textmate_grammar/pattern_extensions/look_ahead_to_avoid.rb +31 -0
  16. data/lib/textmate_grammar/pattern_extensions/look_behind_for.rb +31 -0
  17. data/lib/textmate_grammar/pattern_extensions/look_behind_to_avoid.rb +31 -0
  18. data/lib/textmate_grammar/pattern_extensions/lookaround_pattern.rb +169 -0
  19. data/lib/textmate_grammar/pattern_extensions/match_result_of.rb +67 -0
  20. data/lib/textmate_grammar/pattern_extensions/maybe.rb +50 -0
  21. data/lib/textmate_grammar/pattern_extensions/one_of.rb +107 -0
  22. data/lib/textmate_grammar/pattern_extensions/one_or_more_of.rb +42 -0
  23. data/lib/textmate_grammar/pattern_extensions/or_pattern.rb +55 -0
  24. data/lib/textmate_grammar/pattern_extensions/placeholder.rb +102 -0
  25. data/lib/textmate_grammar/pattern_extensions/recursively_match.rb +76 -0
  26. data/lib/textmate_grammar/pattern_extensions/zero_or_more_of.rb +50 -0
  27. data/lib/textmate_grammar/pattern_variations/base_pattern.rb +870 -0
  28. data/lib/textmate_grammar/pattern_variations/legacy_pattern.rb +61 -0
  29. data/lib/textmate_grammar/pattern_variations/pattern.rb +9 -0
  30. data/lib/textmate_grammar/pattern_variations/pattern_range.rb +233 -0
  31. data/lib/textmate_grammar/pattern_variations/repeatable_pattern.rb +204 -0
  32. data/lib/textmate_grammar/regex_operator.rb +182 -0
  33. data/lib/textmate_grammar/regex_operators/alternation.rb +24 -0
  34. data/lib/textmate_grammar/regex_operators/concat.rb +23 -0
  35. data/lib/textmate_grammar/stdlib/common.rb +20 -0
  36. data/lib/textmate_grammar/tokens.rb +110 -0
  37. data/lib/textmate_grammar/transforms/add_ending.rb +25 -0
  38. data/lib/textmate_grammar/transforms/bailout.rb +92 -0
  39. data/lib/textmate_grammar/transforms/fix_repeated_tag_as.rb +75 -0
  40. data/lib/textmate_grammar/transforms/resolve_placeholders.rb +121 -0
  41. data/lib/textmate_grammar/util.rb +198 -0
  42. data/lib/textmate_grammar.rb +4 -0
  43. metadata +85 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_pattern.rb"
4
+
5
+ #
6
+ # LegacyPattern allows for a hash to be treated as a Pattern
7
+ # It implements the minimum required to be sucessfully generated by the grammar
8
+ #
9
+ class LegacyPattern < PatternBase
10
+ def initialize(hash)
11
+ super("placeholder")
12
+ @hash = hash.transform_keys(&:to_sym)
13
+ end
14
+
15
+ # LegacyPattern cannot be evaluated
16
+ def evaluate(*_ignored)
17
+ raise "LegacyPattern cannot be used as a part of a Pattern"
18
+ end
19
+
20
+ # LegacyPattern cannot be chained
21
+ def insert!(_pattern)
22
+ raise "LegacyPattern cannot be used as a part of a Pattern"
23
+ end
24
+
25
+ #
26
+ # (see PatternBase#to_tag)
27
+ #
28
+ # @return [Hash] The hash it was constructed with
29
+ #
30
+ def to_tag
31
+ @hash
32
+ end
33
+
34
+ #
35
+ # (see PatternBase#run_tests)
36
+ #
37
+ def run_tests
38
+ true
39
+ end
40
+
41
+ #
42
+ # (see PatternBase#map!)
43
+ #
44
+ def map!(*)
45
+ self
46
+ end
47
+
48
+ #
49
+ # (see PatternBase#start_pattern)
50
+ #
51
+ def start_pattern
52
+ ""
53
+ end
54
+
55
+ #
56
+ # (see PatternBase#__deep_clone__)
57
+ #
58
+ def __deep_clone__
59
+ self.class.new(@hash)
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./repeatable_pattern.rb"
4
+
5
+ #
6
+ # Pattern is a class alias for RepeatablePattern
7
+ #
8
+ # class Pattern # <- encase people ctrl+F for "class Pattern"
9
+ Pattern = RepeatablePattern
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_pattern.rb"
4
+
5
+ #
6
+ # Provides the ability to create begin/end and begin/while rules
7
+ #
8
+ class PatternRange < PatternBase
9
+ attr_reader :start_pattern
10
+
11
+ #
12
+ # Creates a new PatternRange
13
+ #
14
+ # @param [Hash] arguments options
15
+ # @option arguments [PatternBase,Regexp,String] :start_pattern the start pattern
16
+ # @option arguments [PatternBase,Regexp,String] :end_pattern the end pattern
17
+ # @option arguments [PatternBase,Regexp,String] :while_pattern the while pattern
18
+ # @option arguments [String] :tag_as the tag for this pattern
19
+ # @option arguments [String] :tag_contents_as the tag for contents of this pattern
20
+ # @option arguments [String] :tag_start_as the tag for the start pattern
21
+ # @option arguments [String] :tag_end_as the tag for the end pattern
22
+ # @option arguments [String] :tag_while_as the tag for the continuation pattern
23
+ #
24
+ # Plugins may add additional options
25
+ # @note exactly one of :end_pattern or :while_pattern is required
26
+ #
27
+ def initialize(arguments)
28
+ @original_arguments = arguments
29
+ @match = nil
30
+ @next_pattern = nil
31
+
32
+ raise "PatternRange.new() expects a hash" unless arguments.is_a? Hash
33
+
34
+ # ensure end_pattern: XOR while_pattern: is provided
35
+ if arguments[:end_pattern].nil? && arguments[:while_pattern].nil?
36
+ raise "one of `while_pattern:` or `end_pattern` must be supplied"
37
+ end
38
+
39
+ if !arguments[:end_pattern].nil? && !arguments[:while_pattern].nil?
40
+ raise "only one of `while_pattern:` or `end_pattern` must be supplied"
41
+ end
42
+
43
+ @start_pattern = arguments[:start_pattern]
44
+ @stop_pattern = arguments[:end_pattern] || arguments[:while_pattern]
45
+
46
+ # convert to patterns if needed
47
+ @start_pattern = PatternBase.new(@start_pattern) unless @start_pattern.is_a? PatternBase
48
+ @stop_pattern = PatternBase.new(@stop_pattern) unless @stop_pattern.is_a? PatternBase
49
+
50
+ # store originals for to_s
51
+ @original_start_pattern = @start_pattern
52
+ @original_stop_pattern = @stop_pattern
53
+
54
+ if arguments[:tag_start_as]
55
+ @start_pattern = PatternBase.new(
56
+ match: @start_pattern,
57
+ tag_as: arguments[:tag_start_as],
58
+ )
59
+ end
60
+
61
+ tag_stop_as = arguments[:tag_end_as] || arguments[:tag_while_as]
62
+
63
+ if tag_stop_as
64
+ @stop_pattern = PatternBase.new(
65
+ match: @stop_pattern,
66
+ tag_as: tag_stop_as,
67
+ )
68
+ end
69
+
70
+ raise "match: is not supported in a PatternRange" if arguments[:match]
71
+
72
+ @stop_type = arguments[:end_pattern] ? :end_pattern : :while_pattern
73
+
74
+ arguments.delete(:start_pattern)
75
+ arguments.delete(:end_pattern)
76
+ arguments.delete(:while_pattern)
77
+
78
+ # ensure that includes is either nil or a flat array
79
+ if arguments[:includes]
80
+ arguments[:includes] = [arguments[:includes]] unless arguments[:includes].is_a? Array
81
+ arguments[:includes] = arguments[:includes].flatten
82
+ end
83
+
84
+ #canonize end_pattern_last
85
+ arguments[:end_pattern_last] = arguments[:apply_end_pattern_last] if arguments[:apply_end_pattern_last]
86
+ arguments[:end_pattern_last] = arguments[:applyEndPatternLast] if arguments[:applyEndPatternLast]
87
+
88
+ arguments.delete(:apply_end_pattern_last)
89
+ arguments.delete(:applyEndPatternLast)
90
+
91
+ @arguments = arguments
92
+ end
93
+
94
+ #
95
+ # (see PatternBase#__deep_clone__)
96
+ #
97
+ def __deep_clone__
98
+ options = @arguments.__deep_clone__
99
+ options[:start_pattern] = @original_start_pattern.__deep_clone__
100
+ if @stop_type == :end_pattern
101
+ options[:end_pattern] = @original_stop_pattern.__deep_clone__
102
+ else
103
+ options[:while_pattern] = @original_stop_pattern.__deep_clone__
104
+ end
105
+ self.class.new(options)
106
+ end
107
+
108
+ #
109
+ # Raises an error to prevent use inside a pattern list
110
+ #
111
+ # @param _ignored ignored
112
+ #
113
+ # @return [void]
114
+ #
115
+ def evaluate(*_ignored)
116
+ raise "PatternRange cannot be used as a part of a Pattern"
117
+ end
118
+
119
+ #
120
+ # Raises an error to prevent use inside a pattern list
121
+ #
122
+ # @param _ignored ignored
123
+ #
124
+ # @return [void]
125
+ #
126
+ def do_evaluate_self(*_ignored)
127
+ raise "PatternRange cannot be used as a part of a Pattern"
128
+ end
129
+
130
+ #
131
+ # Generate a Textmate rule from the PatternRange
132
+ #
133
+ # @return [Hash] The Textmate rule
134
+ #
135
+ def to_tag
136
+ match_key = { end_pattern: "end", while_pattern: "while" }[@stop_type]
137
+ capture_key = { end_pattern: "endCaptures", while_pattern: "whileCaptures" }[@stop_type]
138
+
139
+ start_groups = @start_pattern.collect_group_attributes
140
+ stop_groups = @stop_pattern.collect_group_attributes
141
+
142
+ output = {
143
+ "begin" => @start_pattern.evaluate,
144
+ # this is supposed to be start_groups as back references in end and while
145
+ # refer to the start pattern
146
+ match_key => @stop_pattern.evaluate(start_groups, fixup_refereces: true),
147
+ "beginCaptures" => convert_group_attributes_to_captures(start_groups),
148
+ capture_key => convert_group_attributes_to_captures(stop_groups),
149
+ }
150
+
151
+ output[:name] = @arguments[:tag_as] unless @arguments[:tag_as].nil?
152
+ output[:contentName] = @arguments[:tag_content_as] unless @arguments[:tag_content_as].nil?
153
+
154
+ output["begin"] = output["begin"][1..-2] if @start_pattern.optimize_outer_group?
155
+ output[match_key] = output[match_key][1..-2] if @stop_pattern.optimize_outer_group?
156
+
157
+ if @arguments[:includes].is_a? Array
158
+ output[:patterns] = convert_includes_to_patterns(@arguments[:includes])
159
+ elsif !@arguments[:includes].nil?
160
+ output[:patterns] = convert_includes_to_patterns([@arguments[:includes]])
161
+ end
162
+
163
+ # end_pattern_last
164
+ output["applyEndPatternLast"] = 1 if @arguments[:end_pattern_last]
165
+
166
+ output
167
+ end
168
+
169
+ #
170
+ # Displays this pattern range as source code that would generate it
171
+ #
172
+ # @return [String] The PatternRange as source code
173
+ #
174
+ def to_s
175
+ start_pattern = @original_start_pattern.to_s(2, true)
176
+ stop_pattern = @original_stop_pattern.to_s(2, true)
177
+
178
+ output = "PatternRange.new("
179
+ output += "\n start_pattern: " + start_pattern.lstrip
180
+ output += ",\n #{@stop_type}: " + stop_pattern.lstrip
181
+ [:tag_as, :tag_content_as, :tag_start_as, :tag_end_as, :tag_while_as].each do |tag|
182
+ next if @arguments[tag].nil?
183
+
184
+ output += ",\n #{tag}: \"" + @arguments[tag] + "\"" if @arguments[tag].is_a? String
185
+ end
186
+ output += ",\n includes: " + @arguments[:includes].to_s if @arguments[:includes]
187
+ output += ",\n end_pattern_last: #{@arguments[:end_pattern_last]}" if @arguments[:end_pattern_last]
188
+ output += ",\n)"
189
+
190
+ output
191
+ end
192
+
193
+ #
194
+ # (see PatternBase#map!)
195
+ #
196
+ def map!(map_includes = false, &block)
197
+ yield self
198
+
199
+ @start_pattern.map!(map_includes, &block)
200
+ @stop_pattern.map!(map_includes, &block)
201
+ map_includes!(&block) if map_includes
202
+
203
+ self
204
+ end
205
+
206
+ #
207
+ # (see PatternBase#transform_includes)
208
+ #
209
+ def transform_includes(&block)
210
+ copy = __deep_clone__
211
+ copy.arguments[:includes].map!(&block) if copy.arguments[:includes].is_a? Array
212
+
213
+ copy.map!(true) do |s|
214
+ s.arguments[:includes].map!(&block) if s.arguments[:includes].is_a? Array
215
+ end.freeze
216
+ end
217
+
218
+ #
219
+ # (see PatternBase#run_tests)
220
+ #
221
+ def run_tests
222
+ s = @start_pattern.run_tests
223
+ e = @stop_pattern.run_tests
224
+ s && e
225
+ end
226
+
227
+ #
228
+ # (see PatternBase#inspect)
229
+ #
230
+ def inspect
231
+ super.split(" ")[0] + " start_pattern:" + @start_pattern.inspect + ">"
232
+ end
233
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_pattern.rb"
4
+
5
+ #
6
+ # RepeatablePattern provides quantifiers for patterns
7
+ #
8
+ class RepeatablePattern < PatternBase
9
+ # @return [Integer,nil] the minimum amount that can be matched
10
+ attr_accessor :at_least
11
+ # @return [Integer,nil] the maximum amount that can be matched
12
+ attr_accessor :at_most
13
+ # (see PatternBase#initialize)
14
+ def initialize(*arguments)
15
+ super(*arguments)
16
+
17
+ @at_least = nil
18
+ @at_most = nil
19
+ process_quantifiers_from_arguments
20
+ end
21
+
22
+ #
23
+ # sets @at_least and @at_most based on arguments
24
+ #
25
+ # @return [void]
26
+ #
27
+ # @api private
28
+ #
29
+ def process_quantifiers_from_arguments
30
+ # this sets the @at_most and @at_least value based on the arguments
31
+
32
+ #
33
+ # Simplify the quantity down to just :at_least and :at_most
34
+ #
35
+ attributes_clone = @arguments.clone
36
+ # convert Enumerators to numbers
37
+ [:at_least, :at_most, :how_many_times?].each do |each|
38
+ if attributes_clone[each].is_a?(Enumerator)
39
+ attributes_clone[each] = attributes_clone[each].size
40
+ end
41
+ end
42
+
43
+ # canonize dont_back_track? and as_few_as_possible?
44
+ @arguments[:dont_back_track?] ||= @arguments[:possessive?]
45
+ @arguments[:as_few_as_possible?] ||= @arguments[:lazy?]
46
+ if @arguments[:greedy?]
47
+ @arguments[:dont_back_track?] = false
48
+ @arguments[:as_few_as_possible?] = false
49
+ end
50
+ # extract the data
51
+ at_least = attributes_clone[:at_least]
52
+ at_most = attributes_clone[:at_most]
53
+ how_many_times = attributes_clone[:how_many_times?]
54
+ # simplify to at_least and at_most
55
+ at_least = at_most = how_many_times if how_many_times.is_a?(Integer)
56
+
57
+ # check if quantifying is allowed
58
+ # check after everything else in case additional quantifying options
59
+ # are created in the future
60
+ if quantifying_allowed?
61
+ @at_least = at_least
62
+ @at_most = at_most
63
+ # if a quantifying value was set and quantifying is not allowed, raise an error
64
+ # telling the user that its not allowed
65
+ elsif !(at_most.nil? && at_least.nil?)
66
+ raise <<-HEREDOC.remove_indent
67
+
68
+ Inside of the #{name} pattern, there are some quantity arguments like:
69
+ :at_least
70
+ :at_most
71
+ or :how_many_times?
72
+ These are not allowed in this kind of #{do_get_to_s_name}) pattern
73
+ If you did this intentionally please wrap it inside of a Pattern.new()
74
+ ex: #{do_get_to_s_name} Pattern.new( *your_arguments* ) )
75
+ HEREDOC
76
+ end
77
+
78
+ return unless @arguments[:dont_back_track?] && @arguments[:as_few_as_possible?]
79
+
80
+ raise ":dont_back_track? and :as_few_as_possible? cannot both be provided"
81
+ end
82
+
83
+ #
84
+ # converts @at_least and @at_most into the appropriate quantifier
85
+ # this is a simple_quantifier because it does not include atomic-ness
86
+ #
87
+ # @return [String] the quantifier
88
+ #
89
+ def simple_quantifier
90
+ # Generate the ending based on :at_least and :at_most
91
+
92
+ # by default assume no quantifiers
93
+ quantifier = ""
94
+ # if there is no at_least, at_most, or how_many_times, then theres no quantifier
95
+ if @at_least.nil? and @at_most.nil?
96
+ quantifier = ""
97
+ # if there is a quantifier
98
+ else
99
+ # if there's no at_least, then assume at_least = 1
100
+ @at_least = 1 if @at_least.nil?
101
+
102
+ quantifier =
103
+ if @at_least == 1 and @at_most == 1
104
+ # no qualifier
105
+ ""
106
+ elsif @at_least == 0 and @at_most == 1
107
+ # this is just a different way of "maybe"
108
+ "?"
109
+ elsif @at_least == 0 and @at_most.nil?
110
+ # this is just a different way of "zeroOrMoreOf"
111
+ "*"
112
+ elsif @at_least == 1 and @at_most.nil?
113
+ # this is just a different way of "oneOrMoreOf"
114
+ "+"
115
+ elsif @at_least == @at_most
116
+ # exactly N times
117
+ "{#{@at_least}}"
118
+ else
119
+ # if it is more complicated than that, just use a range
120
+ "{#{@at_least},#{@at_most}}"
121
+ end
122
+ end
123
+ # quantifiers can be made possessive without requiring atomic groups
124
+ quantifier += "+" if quantifier != "" && @arguments[:dont_back_track?] == true
125
+ quantifier += "?" if quantifier != "" && @arguments[:as_few_as_possible?] == true
126
+ quantifier
127
+ end
128
+
129
+ #
130
+ # Adds quantifiers to match
131
+ #
132
+ # @param [String, PatternBase] match the pattern to add a quantifier to
133
+ # @param [Array] groups group information, used for evaluating match
134
+ #
135
+ # @return [String] match with quantifiers applied
136
+ #
137
+ def add_quantifier_options_to(match, groups)
138
+ match = match.evaluate(groups) if match.is_a? PatternBase
139
+ quantifier = simple_quantifier
140
+ # check if there are quantifiers
141
+ if quantifier != ""
142
+ # if the match is not a single entity, then it needs to be wrapped
143
+ match = "(?:#{match})" unless string_single_entity?(match)
144
+ # add the quantified ending
145
+ match += quantifier
146
+ elsif @arguments[:dont_back_track?] == true
147
+ # make atomic, which allows arbitrary expression to be prevented from backtracking
148
+ match = "(?>#{match})"
149
+ end
150
+ if @arguments[:word_cannot_be_any_of]
151
+ word_pattern = @arguments[:word_cannot_be_any_of].map { |w| Regexp.escape w }.join "|"
152
+ match = "(?!\\b(?:#{word_pattern})\\b)#{match}"
153
+ end
154
+ match
155
+ end
156
+
157
+ # (see PatternBase#do_evaluate_self)
158
+ def do_evaluate_self(groups)
159
+ add_capture_group_if_needed(add_quantifier_options_to(@match, groups))
160
+ end
161
+
162
+ # controls weather @arguments[:at_most] et. al. set @at_most et. al.
163
+ # @note override when inheriting. Return false unless the subclass allow quantifying
164
+ # @return [Boolean] if quantifying is allowed
165
+ # @note the default implementation returns True
166
+ def quantifying_allowed?
167
+ true
168
+ end
169
+
170
+ #
171
+ # (see PatternBase#do_add_attributes)
172
+ #
173
+ def do_add_attributes(indent)
174
+ # rubocop:disable Metrics/LineLength
175
+ output = ""
176
+ # special #then arguments
177
+ if quantifying_allowed?
178
+ output += ",\n#{indent} at_least: " + @arguments[:at_least].to_s if @arguments[:at_least]
179
+ output += ",\n#{indent} at_most: " + @arguments[:at_most].to_s if @arguments[:at_most]
180
+ output += ",\n#{indent} how_many_times: " + @arguments[:how_many_times].to_s if @arguments[:how_many_times]
181
+ output += ",\n#{indent} word_cannot_be_any_of: " + @arguments[:word_cannot_be_any_of].to_s if @arguments[:word_cannot_be_any_of]
182
+ end
183
+ output += ",\n#{indent} dont_back_track?: " + @arguments[:dont_back_track?].to_s if @arguments[:dont_back_track?]
184
+ output
185
+ # rubocop:enable Metrics/LineLength
186
+ end
187
+
188
+ #
189
+ # Does this pattern potentially rematch any capture groups
190
+ #
191
+ # @note this is used by FixRepeatedTagAs to modify patterns
192
+ # The answer of true is a safe, but expensive to runtime, default
193
+ #
194
+ # @return [Boolean] True if this pattern potentially rematches capture groups
195
+ #
196
+ def self_capture_group_rematch
197
+ # N or more
198
+ return true if @at_most.nil? && !@at_least.nil?
199
+ # up to N
200
+ return true if !@at_most.nil? && @at_most > 1
201
+
202
+ false
203
+ end
204
+ end