ruby_tree_sitter 1.6.0-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +213 -0
  4. data/ext/tree_sitter/encoding.c +29 -0
  5. data/ext/tree_sitter/extconf.rb +149 -0
  6. data/ext/tree_sitter/input.c +127 -0
  7. data/ext/tree_sitter/input_edit.c +42 -0
  8. data/ext/tree_sitter/language.c +219 -0
  9. data/ext/tree_sitter/logger.c +228 -0
  10. data/ext/tree_sitter/macros.h +163 -0
  11. data/ext/tree_sitter/node.c +623 -0
  12. data/ext/tree_sitter/parser.c +398 -0
  13. data/ext/tree_sitter/point.c +26 -0
  14. data/ext/tree_sitter/quantifier.c +43 -0
  15. data/ext/tree_sitter/query.c +289 -0
  16. data/ext/tree_sitter/query_capture.c +28 -0
  17. data/ext/tree_sitter/query_cursor.c +231 -0
  18. data/ext/tree_sitter/query_error.c +41 -0
  19. data/ext/tree_sitter/query_match.c +44 -0
  20. data/ext/tree_sitter/query_predicate_step.c +83 -0
  21. data/ext/tree_sitter/range.c +35 -0
  22. data/ext/tree_sitter/repo.rb +128 -0
  23. data/ext/tree_sitter/symbol_type.c +46 -0
  24. data/ext/tree_sitter/tree.c +234 -0
  25. data/ext/tree_sitter/tree_cursor.c +269 -0
  26. data/ext/tree_sitter/tree_sitter.c +44 -0
  27. data/ext/tree_sitter/tree_sitter.h +107 -0
  28. data/lib/tree_sitter/3.0/tree_sitter.bundle +0 -0
  29. data/lib/tree_sitter/3.1/tree_sitter.bundle +0 -0
  30. data/lib/tree_sitter/3.2/tree_sitter.bundle +0 -0
  31. data/lib/tree_sitter/3.3/tree_sitter.bundle +0 -0
  32. data/lib/tree_sitter/helpers.rb +23 -0
  33. data/lib/tree_sitter/mixins/language.rb +167 -0
  34. data/lib/tree_sitter/node.rb +167 -0
  35. data/lib/tree_sitter/query.rb +191 -0
  36. data/lib/tree_sitter/query_captures.rb +30 -0
  37. data/lib/tree_sitter/query_cursor.rb +27 -0
  38. data/lib/tree_sitter/query_match.rb +100 -0
  39. data/lib/tree_sitter/query_matches.rb +39 -0
  40. data/lib/tree_sitter/query_predicate.rb +14 -0
  41. data/lib/tree_sitter/text_predicate_capture.rb +37 -0
  42. data/lib/tree_sitter/version.rb +8 -0
  43. data/lib/tree_sitter.rb +34 -0
  44. data/lib/tree_stand/ast_modifier.rb +30 -0
  45. data/lib/tree_stand/breadth_first_visitor.rb +54 -0
  46. data/lib/tree_stand/config.rb +19 -0
  47. data/lib/tree_stand/node.rb +351 -0
  48. data/lib/tree_stand/parser.rb +87 -0
  49. data/lib/tree_stand/range.rb +55 -0
  50. data/lib/tree_stand/tree.rb +123 -0
  51. data/lib/tree_stand/utils/printer.rb +73 -0
  52. data/lib/tree_stand/version.rb +7 -0
  53. data/lib/tree_stand/visitor.rb +127 -0
  54. data/lib/tree_stand/visitors/tree_walker.rb +37 -0
  55. data/lib/tree_stand.rb +48 -0
  56. data/tree_sitter.gemspec +34 -0
  57. metadata +135 -0
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # Node is a wrapper around a tree-sitter node.
5
+ class Node
6
+ include Enumerable
7
+
8
+ # @return [Array<Symbol>] the node's named fields
9
+ def fields
10
+ return @fields if @fields
11
+
12
+ @fields = Set.new
13
+ child_count.times do |i|
14
+ name = field_name_for_child(i)
15
+ @fields << name.to_sym if name
16
+ end
17
+
18
+ @fields.to_a
19
+ end
20
+
21
+ # @param field [String, Symbol]
22
+ def field?(field)
23
+ fields.include?(field.to_sym)
24
+ end
25
+
26
+ # Access node's named children.
27
+ #
28
+ # It's similar to {#fetch}, but differs in input type, return values, and
29
+ # the internal implementation.
30
+ #
31
+ # Both of these methods exist for separate use cases, but also because
32
+ # sometime tree-sitter does some monkey business and having both separate
33
+ # implementations can help.
34
+ #
35
+ # Comparison with {#fetch}:
36
+ #
37
+ # [] | fetch
38
+ # ------------------------------+----------------------
39
+ # input types Integer, String, Symbol | Array<String, Symbol>
40
+ # Array<Integer, String, Symbol>|
41
+ # ------------------------------+----------------------
42
+ # returns 1-to-1 correspondance with | unique nodes
43
+ # input |
44
+ # ------------------------------+----------------------
45
+ # uses named_child | field_name_for_child
46
+ # child_by_field_name | via each_node
47
+ # ------------------------------+----------------------
48
+ #
49
+ # @param keys [Integer | String | Symbol | Array<Integer, String, Symbol>, #read]
50
+ #
51
+ # @return [Node | Array<Node>]
52
+ def [](*keys)
53
+ case keys.length
54
+ when 0 then raise ArgumentError, "#{self.class.name}##{__method__} requires a key."
55
+ when 1
56
+ case k = keys.first
57
+ when Integer then named_child(k)
58
+ when String, Symbol
59
+ raise IndexError, "Cannot find field #{k}. Available: #{fields}" unless fields.include?(k.to_sym)
60
+
61
+ child_by_field_name(k.to_s)
62
+ else raise ArgumentError, <<~ERR
63
+ #{self.class.name}##{__method__} accepts Integer and returns named child at given index,
64
+ or a (String | Symbol) and returns the child by given field name.
65
+ ERR
66
+ end
67
+ else
68
+ keys.map { |key| self[key] }
69
+ end
70
+ end
71
+
72
+ # @!visibility private
73
+ #
74
+ # Allows access to child_by_field_name without using [].
75
+ def method_missing(method_name, *_args, &_block)
76
+ if fields.include?(method_name)
77
+ child_by_field_name(method_name.to_s)
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ # @!visibility private
84
+ #
85
+ def respond_to_missing?(*args)
86
+ args.length == 1 && fields.include?(args[0])
87
+ end
88
+
89
+ # Iterate over a node's children.
90
+ #
91
+ # @yieldparam child [Node] the child
92
+ def each(&_block)
93
+ return enum_for __method__ if !block_given?
94
+
95
+ (0...child_count).each do |i|
96
+ yield child(i)
97
+ end
98
+ end
99
+
100
+ # Iterate over a node's children assigned to a field.
101
+ #
102
+ # @yieldparam name [NilClass | String] field name.
103
+ # @yieldparam child [Node] the child.
104
+ def each_field
105
+ return enum_for __method__ if !block_given?
106
+
107
+ each.with_index do |c, i|
108
+ f = field_name_for_child(i)
109
+ next if f.nil? || f.empty?
110
+
111
+ yield f, c
112
+ end
113
+ end
114
+
115
+ # Iterate over a node's named children
116
+ #
117
+ # @yieldparam child [Node] the child
118
+ def each_named
119
+ return enum_for __method__ if !block_given?
120
+
121
+ (0...(named_child_count)).each do |i|
122
+ yield named_child(i)
123
+ end
124
+ end
125
+
126
+ # @return [Array<TreeSitter::Node>] all the node's children
127
+ def to_a
128
+ each.to_a
129
+ end
130
+
131
+ # Access node's named children.
132
+ #
133
+ # It's similar to {#[]}, but differs in input type, return values, and
134
+ # the internal implementation.
135
+ #
136
+ # Both of these methods exist for separate use cases, but also because
137
+ # sometime tree-sitter does some monkey business and having both separate
138
+ # implementations can help.
139
+ #
140
+ # Comparison with {#fetch}:
141
+ #
142
+ # [] | fetch
143
+ # ------------------------------+----------------------
144
+ # input types Integer, String, Symbol | String, Symbol
145
+ # Array<Integer, String, Symbol>| Array<String, Symbol>
146
+ # ------------------------------+----------------------
147
+ # returns 1-to-1 correspondance with | unique nodes
148
+ # input |
149
+ # ------------------------------+----------------------
150
+ # uses named_child | field_name_for_child
151
+ # child_by_field_name | via each_node
152
+ # ------------------------------+----------------------
153
+ #
154
+ # See {#[]}.
155
+ def fetch(*keys)
156
+ keys = keys.map(&:to_s)
157
+ key_set = keys.to_set
158
+ fields = {}
159
+ each_field do |f, _c|
160
+ fields[f] = self[f] if key_set.delete(f)
161
+
162
+ break if key_set.empty?
163
+ end
164
+ fields.values_at(*keys)
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers'
4
+
5
+ module TreeSitter
6
+ # Query is a wrapper around a tree-sitter query.
7
+ class Query
8
+ attr_reader :capture_names
9
+ attr_reader :capture_quantifiers
10
+ attr_reader :text_predicates
11
+ attr_reader :property_predicates
12
+ attr_reader :property_settings
13
+ attr_reader :general_predicates
14
+
15
+ private
16
+
17
+ # Called from query.c on initialize.
18
+ #
19
+ # Prepares all the predicates so we could process them in places like
20
+ # {QueryMatch#satisfies_text_predicate?}.
21
+ #
22
+ # This is translation from the [rust bindings](https://github.com/tree-sitter/tree-sitter/blob/e553578696fe86071846ed612ee476d0167369c1/lib/binding_rust/lib.rs#L1860)
23
+ # Because it's a direct translation, it's way too long and we need to shut up rubocop.
24
+ # TODO: refactor + simplify when stable.
25
+ def process(source) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
26
+ string_count = self.string_count
27
+ capture_count = self.capture_count
28
+ pattern_count = self.pattern_count
29
+
30
+ # Build a vector of strings to store the capture names.
31
+ capture_names = capture_count.times.map { |i| capture_name_for_id(i) }
32
+
33
+ # Build a vector to store capture qunatifiers.
34
+ capture_quantifiers =
35
+ pattern_count.times.map do |i|
36
+ capture_count.times.map do |j|
37
+ capture_quantifier_for_id(i, j)
38
+ end
39
+ end
40
+
41
+ # Build a vector of strings to represent literal values used in predicates.
42
+ string_values = string_count.times.map { |i| string_value_for_id(i) }
43
+
44
+ # Build a vector of predicates for each pattern.
45
+ pattern_count.times do |i| # rubocop:disable Metrics/BlockLength
46
+ predicate_steps = predicates_for_pattern(i)
47
+ byte_offset = start_byte_for_pattern(i)
48
+ row =
49
+ source.chars.map.with_index
50
+ .take_while { |_, i| i < byte_offset } # rubocop:disable Lint/ShadowingOuterLocalVariable
51
+ .filter { |c, _| c == "\n" }
52
+ .size
53
+ text_predicates = []
54
+ property_predicates = []
55
+ property_settings = []
56
+ general_predicates = []
57
+
58
+ array_split_like_rust(predicate_steps) { |s| s.type == QueryPredicateStep::DONE } # rubocop:disable Metrics/BlockLength
59
+ .each do |p|
60
+ next if p.empty?
61
+
62
+ if p[0] == QueryPredicateStep::STRING
63
+ cap = capture_names[p[0].value_id]
64
+ raise ArgumentError, <<~MSG.chomp
65
+ L#{row}: Expected predicate to start with a function name. Got @#{cap}.
66
+ MSG
67
+ end
68
+
69
+ # Build a predicate for each of the known predicate function names.
70
+ operator_name = string_values[p[0].value_id]
71
+
72
+ case operator_name
73
+ in 'any-eq?' | 'any-not-eq?' | 'eq?' | 'not-eq?'
74
+ if p.size != 3
75
+ raise ArgumentError, <<~MSG.chomp
76
+ L#{row}: Wrong number of arguments to ##{operator_name} predicate. Expected 2, got #{p.size - 1}.
77
+ MSG
78
+ end
79
+
80
+ if p[1].type != QueryPredicateStep::CAPTURE
81
+ lit = string_values[p[1].value_id]
82
+ raise ArgumentError, <<~MSG.chomp
83
+ L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
84
+ MSG
85
+ end
86
+
87
+ is_positive = %w[eq? any-eq?].include?(operator_name)
88
+ match_all = %w[eq? not-eq?].include?(operator_name)
89
+ # NOTE: in the rust impl, match_all can hit an unreachable! but I am simplifying
90
+ # for readability. Same applies for the other `in` branches.
91
+ text_predicates <<
92
+ if p[2].type == QueryPredicateStep::CAPTURE
93
+ TextPredicateCapture.eq_capture(p[1].value_id, p[2].value_id, is_positive, match_all)
94
+ else
95
+ TextPredicateCapture.eq_string(p[1].value_id, string_values[p[2].value_id], is_positive, match_all)
96
+ end
97
+
98
+ in 'match?' | 'not-match?' | 'any-match?' | 'any-not-match?'
99
+ if p.size != 3
100
+ raise ArgumentError, <<~MSG.chomp
101
+ L#{row}: Wrong number of arguments to ##{operator_name} predicate. Expected 2, got #{p.size - 1}.
102
+ MSG
103
+ end
104
+
105
+ if p[1].type != QueryPredicateStep::CAPTURE
106
+ lit = string_values[p[1].value_id]
107
+ raise ArgumentError, <<~MSG.chomp
108
+ L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
109
+ MSG
110
+ end
111
+
112
+ if p[2].type == QueryPredicateStep::CAPTURE
113
+ cap = capture_names[p[2].value_id]
114
+ raise ArgumentError, <<~MSG.chomp
115
+ L#{row}: First argument to ##{operator_name} predicate must be a literal. Got capture @#{cap}".
116
+ MSG
117
+ end
118
+
119
+ is_positive = %w[match? any-match?].include?(operator_name)
120
+ match_all = %w[match? not-match?].include?(operator_name)
121
+ regex = /#{string_values[p[2].value_id]}/
122
+
123
+ text_predicates << TextPredicateCapture.match_string(p[1].value_id, regex, is_positive, match_all)
124
+
125
+ in 'set!'
126
+ property_settings << 'todo!'
127
+
128
+ in 'is?' | 'is-not?'
129
+ property_predicates << 'todo!'
130
+
131
+ in 'any-of?' | 'not-any-of?'
132
+ if p.size < 2
133
+ raise ArgumentError, <<~MSG.chomp
134
+ L#{row}: Wrong number of arguments to ##{operator_name} predicate. Expected at least 1, got #{p.size - 1}.
135
+ MSG
136
+ end
137
+
138
+ if p[1].type != QueryPredicateStep::CAPTURE
139
+ lit = string_values[p[1].value_id]
140
+ raise ArgumentError, <<~MSG.chomp
141
+ L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
142
+ MSG
143
+ end
144
+
145
+ is_positive = operator_name == 'any_of'
146
+ values = []
147
+
148
+ p[2..].each do |arg|
149
+ if arg.type == QueryPredicateStep::CAPTURE
150
+ lit = string_values[arg.value_id]
151
+ raise ArgumentError, <<~MSG.chomp
152
+ L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
153
+ MSG
154
+ end
155
+ values << string_values[arg.value_id]
156
+ end
157
+
158
+ # TODO: is the map to to_s necessary in ruby?
159
+ text_predicates <<
160
+ TextPredicateCapture.any_string(p[1].value_id, values.map(&:to_s), is_positive, match_all)
161
+ else
162
+ general_predicates <<
163
+ QueryPredicate.new(
164
+ operator_name,
165
+ p[1..].map do |a|
166
+ if a.type == QueryPredicateStep::CAPTURE
167
+ { capture: a.value_id }
168
+ else
169
+ { string: string_values[a.value_id] }
170
+ end
171
+ end,
172
+ )
173
+ end
174
+
175
+ @text_predicates << text_predicates
176
+ @property_predicates << property_predicates
177
+ @property_settings << property_settings
178
+ @general_predicates << general_predicates
179
+ end
180
+
181
+ @capture_names = capture_names
182
+ @capture_quantifiers = capture_quantifiers
183
+ end
184
+ end
185
+
186
+ # TODO
187
+ def parse_property
188
+ # todo
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # A sequence of {TreeSitter::QueryCapture} associated with a given {TreeSitter::QueryCursor}.
5
+ class QueryCaptures
6
+ include Enumerable
7
+
8
+ def initialize(cursor, query, src)
9
+ @cursor = cursor
10
+ @query = query
11
+ @src = src
12
+ end
13
+
14
+ # Iterator over captures.
15
+ #
16
+ # @yieldparam match [TreeSitter::QueryMatch]
17
+ # @yieldparam capture_index [Integer]
18
+ def each(&_block)
19
+ return enum_for __method__ if !block_given?
20
+
21
+ while (capture_index, match = @cursor.next_capture)
22
+ next if !match.is_a?(TreeSitter::QueryMatch)
23
+
24
+ if match.satisfies_text_predicate?(@query, @src)
25
+ yield [match, capture_index]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # A Cursor for {Query}.
5
+ class QueryCursor
6
+ # Iterate over all of the matches in the order that they were found.
7
+ #
8
+ # Each match contains the index of the pattern that matched, and a list of
9
+ # captures. Because multiple patterns can match the same set of nodes,
10
+ # one match may contain captures that appear *before* some of the
11
+ # captures from a previous match.
12
+ def matches(query, node, src)
13
+ self.exec(query, node)
14
+ QueryMatches.new(self, query, src)
15
+ end
16
+
17
+ # Iterate over all of the individual captures in the order that they
18
+ # appear.
19
+ #
20
+ # This is useful if you don't care about which pattern matched, and just
21
+ # want a single, ordered sequence of captures.
22
+ def captures(query, node, src)
23
+ self.exec(query, node)
24
+ QueryCaptures.new(self, query, src)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'query_captures'
4
+
5
+ module TreeSitter
6
+ # A match for a {Query}.
7
+ class QueryMatch
8
+ # All nodes at a given capture index.
9
+ #
10
+ # @param index [Integer]
11
+ #
12
+ # @return [TreeSitter::Node]
13
+ def nodes_for_capture_index(index) = captures.filter_map { |capture| capture.node if capture.index == index }
14
+
15
+ # Whether the {QueryMatch} satisfies the text predicates in the query.
16
+ #
17
+ # This is a translation from the [rust bindings](https://github.com/tree-sitter/tree-sitter/blob/e553578696fe86071846ed612ee476d0167369c1/lib/binding_rust/lib.rs#L2502).
18
+ # Because it's a direct translation, it's way too long and we need to shut up rubocop.
19
+ # TODO: refactor + simplify when satable.
20
+ def satisfies_text_predicate?(query, src) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
21
+ return true if query.text_predicates[pattern_index].nil?
22
+
23
+ query # rubocop:disable Metrics/BlockLength
24
+ .text_predicates[pattern_index]
25
+ .all? do |predicate|
26
+ case predicate.type
27
+ in TextPredicateCapture::EQ_CAPTURE
28
+ fst_nodes = nodes_for_capture_index(predicate.fst)
29
+ snd_nodes = nodes_for_capture_index(predicate.snd)
30
+ res = nil
31
+ consumed = 0
32
+ fst_nodes.zip(snd_nodes).each do |node1, node2|
33
+ text1 = node_text(node1, src)
34
+ text2 = node_text(node2, src)
35
+ if (text1 == text2) != predicate.positive? && predicate.match_all?
36
+ res = false
37
+ break
38
+ end
39
+ if (text1 == text2) == predicate.positive? && !predicate.match_all?
40
+ res = true
41
+ break
42
+ end
43
+ consumed += 1
44
+ end
45
+ (res.nil? && consumed == fst_nodes.length && consumed == snd_nodes.length) \
46
+ || res
47
+
48
+ in TextPredicateCapture::EQ_STRING
49
+ nodes = nodes_for_capture_index(predicate.fst)
50
+ res = true
51
+ nodes.each do |node|
52
+ text = node_text(node, src)
53
+ if (predicate.snd == text) != predicate.positive? && predicate.match_all?
54
+ res = false
55
+ break
56
+ end
57
+ if (predicate.snd == text) == predicate.positive? && !predicate.match_all?
58
+ res = true
59
+ break
60
+ end
61
+ end
62
+ res
63
+
64
+ in TextPredicateCapture::MATCH_STRING
65
+ nodes = nodes_for_capture_index(predicate.fst)
66
+ res = true
67
+ nodes.each do |node|
68
+ text = node_text(node, src)
69
+ if predicate.snd.match?(text) != predicate.positive? && predicate.match_all?
70
+ res = false
71
+ break
72
+ end
73
+ if predicate.snd.match?(text) == predicate.positive? && !predicate.match_all?
74
+ res = true
75
+ break
76
+ end
77
+ end
78
+ res
79
+
80
+ in TextPredicateCapture::ANY_STRING
81
+ nodes = nodes_for_capture_index(predicate.fst)
82
+ res = true
83
+ nodes.each do |node|
84
+ text = node_text(node, src)
85
+ if predicate.snd.any? { |v| v == text } != predicate.positive?
86
+ res = false
87
+ break
88
+ end
89
+ end
90
+ res
91
+
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def node_text(node, text) = text.byteslice(node.start_byte...node.end_byte)
99
+ end
100
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # A sequence of {QueryMatch} associated with a given {QueryCursor}.
5
+ class QueryMatches
6
+ include Enumerable
7
+
8
+ def initialize(cursor, query, src)
9
+ @cursor = cursor
10
+ @query = query
11
+ @src = src
12
+ end
13
+
14
+ # Iterator over matches.
15
+ #
16
+ # @yieldparam match [TreeSitter::QueryMatch]
17
+ def each(&_block)
18
+ return enum_for __method__ if !block_given?
19
+
20
+ while match = @cursor.next_match
21
+ if match.satisfies_text_predicate?(@query, @src)
22
+ yield match
23
+ end
24
+ end
25
+ end
26
+
27
+ # Iterate over all the results presented as hashes of `capture name => node`.
28
+ #
29
+ # @yieldparam match [Hash<String, TreeSitter::Node>]
30
+ def each_capture_hash(&_block)
31
+ # TODO: should we return [Array<Hash<Symbol, TreeSitter::Node]>>] instead?
32
+ return enum_for __method__ if !block_given?
33
+
34
+ each do |match|
35
+ yield match.captures.to_h { |cap| [@query.capture_name_for_id(cap.index), cap.node] }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # A {Query} predicate generic representation.
5
+ class QueryPredicate
6
+ attr_accessor :operator
7
+ attr_accessor :args
8
+
9
+ def initialize(operator, args)
10
+ @operator = operator
11
+ @args = args
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # A representation for text predicates.
5
+ class TextPredicateCapture
6
+ EQ_CAPTURE = 0 # Equality Capture
7
+ EQ_STRING = 1 # Equality String
8
+ MATCH_STRING = 2 # Match String
9
+ ANY_STRING = 3 # Any String
10
+
11
+ attr_reader :fst
12
+ attr_reader :snd
13
+ attr_reader :type
14
+
15
+ # Create a TextPredicateCapture for {EQ_CAPTURE}.
16
+ def self.eq_capture(...) = new(EQ_CAPTURE, ...)
17
+ # Create a TextPredicateCapture for {EQ_STRING}.
18
+ def self.eq_string(...) = new(EQ_STRING, ...)
19
+ # Create a TextPredicateCapture for {MATCH_STRING}.
20
+ def self.match_string(...) = new(MATCH_STRING, ...)
21
+ # Create a TextPredicateCapture for {ANY_STRING}.
22
+ def self.any_string(...) = new(ANY_STRING, ...)
23
+
24
+ def initialize(type, fst, snd, positive, match_all)
25
+ @type = type
26
+ @fst = fst
27
+ @snd = snd
28
+ @positive = positive
29
+ @match_all = match_all
30
+ end
31
+
32
+ # `#eq` is positive, `#not-eq` is not.
33
+ def positive? = @positive
34
+ # `#any-` means don't match all.
35
+ def match_all? = @match_all
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ # The version of the tree-sitter library.
5
+ TREESITTER_VERSION = '0.22.6'
6
+ # The current version of the gem.
7
+ VERSION = '1.6.0'
8
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ begin
6
+ RUBY_VERSION =~ /(\d+\.\d+)/
7
+ require "tree_sitter/#{Regexp.last_match(1)}/tree_sitter"
8
+ rescue LoadError
9
+ require 'tree_sitter/tree_sitter'
10
+ end
11
+
12
+ require 'tree_sitter/version'
13
+
14
+ require 'tree_sitter/mixins/language'
15
+
16
+ require 'tree_sitter/node'
17
+ require 'tree_sitter/query'
18
+ require 'tree_sitter/query_captures'
19
+ require 'tree_sitter/query_cursor'
20
+ require 'tree_sitter/query_match'
21
+ require 'tree_sitter/query_matches'
22
+ require 'tree_sitter/query_predicate'
23
+ require 'tree_sitter/text_predicate_capture'
24
+
25
+ # TreeSitter is a Ruby interface to the tree-sitter parsing library.
26
+ module TreeSitter
27
+ extend Mixins::Language
28
+
29
+ class << self
30
+ alias_method :lang, :language
31
+ end
32
+ end
33
+
34
+ ObjectSpace.define_finalizer(TreeSitter::Tree.class, proc { TreeSitter::Tree.finalizer })
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module TreeStand
5
+ # An experimental class to modify the AST. It re-runs the query on the
6
+ # modified document every loop to ensure that the match is still valid.
7
+ # @see TreeStand::Tree
8
+ # @api experimental
9
+ class AstModifier
10
+ extend T::Sig
11
+
12
+ sig { params(tree: TreeStand::Tree).void }
13
+ def initialize(tree)
14
+ @tree = tree
15
+ end
16
+
17
+ # @param query [String]
18
+ # @yieldparam self [self]
19
+ # @yieldparam match [TreeStand::Match]
20
+ # @return [void]
21
+ def on_match(query)
22
+ matches = @tree.query(query)
23
+
24
+ while !matches.empty?
25
+ yield self, matches.first
26
+ matches = @tree.query(query)
27
+ end
28
+ end
29
+ end
30
+ end