ruby_grammar_builder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @abstract Subclass GrammarLinter or GrammarTransform to implement a plugin
4
+ class GrammarPlugin
5
+ # The options this plugin supports
6
+ # @return [Array<Symbol>] a list of symbols that represent keys that can
7
+ # be read by the plugin
8
+ # @note the keys :grammar and :repository are reserved for passing grammar information
9
+ def self.options
10
+ []
11
+ end
12
+
13
+ # display the options as they would appear in the source
14
+ # @abstract override this to display the options as they would be entered into
15
+ # source code
16
+ # @param indent [String] the spaces to indent each line width
17
+ # @param options [Hash] all options passed to the pattern
18
+ # @return [String] the options as a string
19
+ # @note each option should be prepended with +"\\n,#{indent}"+
20
+ # @note only display the options that are unique to this plugin
21
+ def self.display_options(indent, options) # rubocop:disable Lint/UnusedMethodArgument
22
+ raise "Internal error: display_options called with no provided options" if options.empty?
23
+
24
+ if self.options.empty?
25
+ raise "Internal error: display_options called on a plugin that provides no options"
26
+ end
27
+
28
+ raise "GrammarPlugin::options implemented but GrammarPlugin::display_options has not been"
29
+ end
30
+ end
31
+
32
+ # @abstract Subclass and override {#pre_lint} and/or {#post_lint}
33
+ # to implement a linter
34
+ class GrammarLinter < GrammarPlugin
35
+ #
36
+ # Runs the linter on each pattern
37
+ #
38
+ # @param pattern [PatternBase, Symbol, Hash] the pattern to lint
39
+ # @param options [Hash] hash of any of the option keys provided by self.options.
40
+ # options will only be populated when pattern is a PatternBase
41
+ #
42
+ # @return [Boolean] the result of the lint
43
+ def pre_lint(pattern, options = {}) # rubocop:disable Lint/UnusedMethodArgument
44
+ true
45
+ end
46
+
47
+ #
48
+ # Runs the linter on the entire grammar
49
+ #
50
+ # @param [Hash] grammar_hash The entire grammar
51
+ #
52
+ # @return [Boolean] the result og the lint
53
+ #
54
+ def post_lint(grammar_hash) # rubocop:disable Lint/UnusedMethodArgument
55
+ true
56
+ end
57
+ end
58
+
59
+ # @abstract Subclass and override {#pre_transform} and/or {#post_transform}
60
+ # to implement a transformation
61
+ class GrammarTransform < GrammarPlugin
62
+ #
63
+ # Preforms the transformation on each pattern
64
+ #
65
+ # @param pattern [PatternBase, Symbol, Hash] the pattern to transform
66
+ # @param options [Hash] hash of any of the option keys provided by self.options.
67
+ # options will only be populated when pattern is a PatternBase
68
+ #
69
+ # @return [PatternBase, Symbol, Hash] The transformed pattern. The return type should
70
+ # match the type of pattern
71
+ #
72
+ # @note pattern should not be modified
73
+ #
74
+ def pre_transform(pattern, options) # rubocop:disable Lint/UnusedMethodArgument
75
+ pattern
76
+ end
77
+
78
+ #
79
+ # Performs the transformation on the whole grammar
80
+ #
81
+ # @param [Hash] grammar_hash The entire grammar
82
+ #
83
+ # @return [Hash] The transformed grammar
84
+ #
85
+ # @note grammar_hash should not be modified
86
+ #
87
+ def post_transform(grammar_hash)
88
+ grammar_hash
89
+ end
90
+ end
91
+
92
+ class Grammar
93
+ @@linters = []
94
+ @@transforms = {
95
+ before_pre_linter: [],
96
+ after_pre_linter: [],
97
+ before_post_linter: [],
98
+ after_post_linter: [],
99
+ }
100
+
101
+ #
102
+ # Register a linter plugin
103
+ #
104
+ # @param [GrammarLinter] linter the linter plugin
105
+ #
106
+ # @return [void] nothing
107
+ #
108
+ def self.register_linter(linter)
109
+ @@linters << linter
110
+ end
111
+
112
+ #
113
+ # Register a transformation plugin
114
+ #
115
+ # @param [GrammarTransform] transform the transformation plugin
116
+ # @param [Numeric] priority an optional priority
117
+ #
118
+ # @note The priority controls when a transformation runs in relation to
119
+ # other events in addition to ordering transformations
120
+ # priorities < 100 have their pre transform run before pre linters
121
+ # priorities >= 100 have their pre transform run after pre linters
122
+ # priorities >= 200 do not have their pre_transform function ran
123
+ # priorities < 300 have their post transorm run before post linters
124
+ # priorities >= 300 have their post transorm run before post linters
125
+ #
126
+ # @return [void] nothing
127
+ #
128
+ def self.register_transform(transform, priority = 150)
129
+ key = if priority < 100 then :before_pre_linter
130
+ elsif priority < 200 then :after_pre_linter
131
+ elsif priority < 300 then :before_post_linter
132
+ else :after_pre_linter
133
+ end
134
+
135
+ @@transforms[key] << {
136
+ priority: priority,
137
+ transform: transform,
138
+ }
139
+ end
140
+
141
+ #
142
+ # Gets all registered plugins
143
+ #
144
+ # @api private
145
+ #
146
+ # @return [Array<GrammarPlugin>] A list of all plugins
147
+ #
148
+ def self.plugins
149
+ @@linters + @@transforms.values.flatten.map { |v| v[:transform] }
150
+ end
151
+
152
+ #
153
+ # Removes a plugin whose classname matches plugin
154
+ #
155
+ # @param [#to_s] plugin The plugin name to remove
156
+ #
157
+ # @return [void]
158
+ #
159
+ def self.remove_plugin(plugin)
160
+ @@linters.delete_if { |linter| linter.class.to_s == plugin.to_s }
161
+ @@transforms[:before_pre_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
162
+ @@transforms[:after_pre_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
163
+ @@transforms[:before_post_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
164
+ @@transforms[:after_post_linter].delete_if { |t| t[:transform].class.to_s == plugin.to_s }
165
+ end
166
+ end
167
+
168
+ #
169
+ # Filters a {PatternBase#original_arguments} to just the options required for a plugin
170
+ #
171
+ # @api private
172
+ #
173
+ # @param [GrammarPlugin] plugin The plugin to filter options
174
+ # @param [PatternBase, Symbol, Hash] pattern the pattern with options to filter
175
+ # @param [Hash] default the default options to supply to the plugin
176
+ #
177
+ # @return [Hash] the filtered options
178
+ #
179
+ def filter_options(plugin, pattern, default)
180
+ options = {}
181
+ if pattern.is_a? PatternBase
182
+ options = pattern.original_arguments.select { |k| plugin.class.options.include? k }
183
+ end
184
+ options.merge(default)
185
+ end
186
+
187
+ # load default linters and transforms
188
+ Dir[File.join(__dir__, 'linters', '*.rb')].each { |file| require file }
189
+ Dir[File.join(__dir__, 'transforms', '*.rb')].each { |file| require file }
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deep_clone'
4
+ require 'yaml'
5
+ require 'ruby_grammar_builder/grammar_plugin'
6
+ require 'ruby_grammar_builder/util'
7
+ require 'ruby_grammar_builder/regex_operator'
8
+ require 'ruby_grammar_builder/regex_operators/concat'
9
+ require 'ruby_grammar_builder/tokens'
10
+
11
+ # import Pattern, LegacyPattern, and PatternRange
12
+ Dir[File.join(__dir__, 'pattern_variations', '*.rb')].each { |file| require file }
13
+ # import .or(), .maybe(), .zeroOrMoreOf(), etc
14
+ Dir[File.join(__dir__, 'pattern_extensions', '*.rb')].each { |file| require file }
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_grammar_builder/util'
4
+
5
+ #
6
+ # Internal check, ensures that :includes is nil or a flat array
7
+ #
8
+ class FlatIncludes < GrammarLinter
9
+ #
10
+ # (see GrammarLinter#pre_lint)
11
+ #
12
+ def pre_lint(pattern, options)
13
+ return true unless pattern.is_a? PatternBase
14
+ return true if pattern.arguments[:includes].nil?
15
+
16
+ if pattern.arguments[:includes].is_a?(Array) &&
17
+ pattern.arguments[:includes].none? { |v| v.is_a? Array }
18
+ flat = true
19
+ pattern.arguments[:includes].map do |s|
20
+ flat = false unless pre_lint(s, options)
21
+ end
22
+ return flat
23
+ end
24
+
25
+ puts "The pattern `#{pattern.name}' does not have a flat includes array."
26
+ puts "This is an internal error"
27
+
28
+ false
29
+ end
30
+ end
31
+
32
+ Grammar.register_linter(FlatIncludes.new)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_grammar_builder/util'
4
+
5
+ #
6
+ # A pattern that has an includes cannot have any tag_as in @match
7
+ #
8
+ class IncludesThenTagAs < GrammarLinter
9
+ #
10
+ # Does pattern or any of its children / siblings have a tag_as
11
+ #
12
+ # @param [PatternBase, String] pattern the pattern to check
13
+ #
14
+ # @return [Boolean] if any of the patterns have a tag_as
15
+ #
16
+ def tag_as?(pattern)
17
+ return false unless pattern.is_a? PatternBase
18
+
19
+ pattern.each do |s|
20
+ puts s.arguments[:tag_as] if s.arguments[:tag_as]
21
+ return true if s.arguments[:tag_as]
22
+ end
23
+
24
+ false
25
+ end
26
+
27
+ #
28
+ # (see GrammarLinter#pre_lint)
29
+ #
30
+ def pre_lint(pattern, options)
31
+ return true unless pattern.is_a? PatternBase
32
+ return true if pattern.is_a? PatternRange
33
+
34
+ return false unless pre_lint(pattern.match, options)
35
+
36
+ return true unless pattern.arguments[:includes].is_a? Array
37
+ return true unless tag_as?(pattern.match)
38
+
39
+ name = pattern.name
40
+ puts "The pattern `#{name}' has both an includes argument and a match argument that,"
41
+ puts "it or a sub pattern has a tag_as argument."
42
+ puts "this is not supported"
43
+
44
+ true # TODO: fix issue
45
+ end
46
+ end
47
+
48
+ Grammar.register_linter(IncludesThenTagAs.new)
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Checks for names to match expected naming format
5
+ # see https://www.sublimetext.com/docs/3/scope_naming.html
6
+ #
7
+
8
+ class StandardNaming < GrammarLinter
9
+ # This is a summarization of the rules on the sublime text website for scope name
10
+ # process:
11
+ # 1. start at root
12
+ # 2. for each component in name
13
+ # 1. if there exists a key with that name
14
+ # 1. if the value is a hash: repeat with next component and that hash
15
+ # 2. otherwise return the value
16
+ # 2. else
17
+ # 1. if there exists a key of "*" return true, otherwise false
18
+ #
19
+ # to ease parsing, there should only every be one valid path
20
+ # if "a" exists, dont add "a.b" to the same key
21
+
22
+ # @return [Hash] a summarization of expected names
23
+ EXPECTED_NAMES = {
24
+ "comment" => {
25
+ "line" => true,
26
+ "block" => true,
27
+ }.freeze,
28
+ "constant" => {
29
+ "numeric" => true,
30
+ "language" => true,
31
+ "character" => {"escape" => true}.freeze,
32
+ "other" => true,
33
+ }.freeze,
34
+ "entity" => {
35
+ "name" => {
36
+ "class" => true,
37
+ "struct" => true,
38
+ "enum" => true,
39
+ "union" => true,
40
+ "trait" => true,
41
+ "interface" => true,
42
+ "impl" => true,
43
+ "type" => true,
44
+ "function" => true,
45
+ "namespace" => true,
46
+ "constant" => true,
47
+ "label" => true,
48
+ "section" => true,
49
+ "scope-resolution" => true,
50
+ "tag" => true,
51
+ "attribute-name" => true,
52
+ "other" => true,
53
+ }.freeze,
54
+ "other" => true,
55
+ }.freeze,
56
+ "invalid" => {
57
+ "illegal" => true,
58
+ "deprecated" => true,
59
+ "unknown" => true,
60
+ }.freeze,
61
+ "keyword" => {
62
+ "control" => true,
63
+ "operator" => true,
64
+ "other" => true,
65
+ "declaration" => true,
66
+ }.freeze,
67
+ "markup" => {
68
+ "heading" => true,
69
+ "list" => {
70
+ "numbered" => true,
71
+ "unnumbered" => true,
72
+ }.freeze,
73
+ "bold" => true,
74
+ "italic" => true,
75
+ "inline" => true,
76
+ "underline" => true,
77
+ "inserted" => true,
78
+ "deleted" => true,
79
+ "quote" => true,
80
+ "raw" => {
81
+ "inline" => true,
82
+ "block" => true,
83
+ }.freeze,
84
+ "other" => true,
85
+ }.freeze,
86
+ "meta" => {
87
+ "asm" => true,
88
+ "class" => true,
89
+ "struct" => true,
90
+ "enum" => true,
91
+ "union" => true,
92
+ "trait" => true,
93
+ "interface" => true,
94
+ "declaration" => true,
95
+ "impl" => true,
96
+ "initialization" => true,
97
+ "type" => true,
98
+ "qualified_type" => true,
99
+ "function" => true,
100
+ "parameter" => true,
101
+ "namespace" => true,
102
+ "using-namespace" => true,
103
+ "preprocessor" => true,
104
+ "conditional" => true,
105
+ "annotation" => true,
106
+ "path" => true,
107
+ "function-call" => true,
108
+ "block" => true,
109
+ "group" => true,
110
+ "braces" => true,
111
+ "parens" => true,
112
+ "brackets" => true,
113
+ "generic" => true,
114
+ "template" => true,
115
+ "tag" => true,
116
+ "paragraph" => true,
117
+ "string" => true,
118
+ "interpolation" => true,
119
+ "toc-list" => true,
120
+ "banner" => true,
121
+ }.freeze,
122
+ "punctuation" => {
123
+ "definition" => true,
124
+ "separator" => true,
125
+ "terminator" => true,
126
+ "accessor" => true,
127
+ "section" => true,
128
+ "vararg-ellipses" => true,
129
+ }.freeze,
130
+ "source" => true,
131
+ "storage" => {
132
+ "type" => true,
133
+ "modifier" => true,
134
+ }.freeze,
135
+ "string" => {
136
+ "quoted" => {
137
+ "single" => true,
138
+ "double" => true,
139
+ "other" => true,
140
+ }.freeze,
141
+ "unquoted" => true,
142
+ "regexp" => true,
143
+ }.freeze,
144
+ "support" => {
145
+ "constant" => true,
146
+ "function" => true,
147
+ "module" => true,
148
+ "type" => true,
149
+ "class" => true,
150
+ "other" => true,
151
+ }.freeze,
152
+ "text" => true,
153
+ "variable" => {
154
+ "other" => true,
155
+ "language" => true,
156
+ "function" => true,
157
+ "annotation" => true,
158
+ "parameter" => true,
159
+ }.freeze,
160
+ }.freeze
161
+
162
+ #
163
+ # Checks the tag keys at this level
164
+ #
165
+ # @param [Array<string>] tag an array of tag components
166
+ # @param [Numeric] index The index into tag to check
167
+ # @param [Hash] root the hash to check against
168
+ #
169
+ # @return [Boolean] If this is a valid tag
170
+ #
171
+ def recursive_check_tag(tag, index = 0, root = EXPECTED_NAMES)
172
+ if root.has_key?(tag[index])
173
+ next_part = root[tag[index]]
174
+ return recursive_check_tag(tag, index+1, next_part) if next_part.is_a? Hash
175
+
176
+ return [next_part, index, root]
177
+ elsif root.has_key? "*"
178
+ return [root["*"], index, root]
179
+ end
180
+
181
+ [false, index, root]
182
+ end
183
+
184
+ #
185
+ # Checks a tag for standard naming scheme
186
+ #
187
+ # @param [String] tag the tag to check
188
+ #
189
+ # @return [void] nothing
190
+ #
191
+ def check_tag(tag)
192
+ result, pos, root = recursive_check_tag(tag)
193
+ return if result
194
+
195
+ valid_prefix = (pos > 0) ? tag[0..(pos-1)].join(".") + "." : ""
196
+
197
+ puts "The prefix `#{tag[0..pos].join('.')}' does not follow the standard format"
198
+ puts "The expected prefixes at this level are:"
199
+ root.keys.each do |key|
200
+ if root[:key] == false
201
+ puts "- #{valid_prefix}#{key}"
202
+ else
203
+ puts " #{valid_prefix}#{key}"
204
+ end
205
+ end
206
+ end
207
+
208
+ #
209
+ # Checks for names to match expected naming format
210
+ #
211
+ # @return [True] warnings to not return false
212
+ #
213
+ def pre_lint(pattern, _options)
214
+ return true unless pattern.is_a? PatternBase
215
+
216
+ pattern.each(true) do |pat|
217
+ next unless pat.arguments[:tag_as]
218
+
219
+ pat.arguments[:tag_as].split(" ").each { |tag| check_tag(tag.split(".")) }
220
+ end
221
+
222
+ true
223
+ end
224
+ end
225
+
226
+ Grammar.register_linter(StandardNaming.new)