ruby_tree_sitter 0.20.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +152 -0
  4. data/ext/tree_sitter/encoding.c +29 -0
  5. data/ext/tree_sitter/extconf.rb +172 -0
  6. data/ext/tree_sitter/input.c +126 -0
  7. data/ext/tree_sitter/input_edit.c +42 -0
  8. data/ext/tree_sitter/language.c +134 -0
  9. data/ext/tree_sitter/logger.c +212 -0
  10. data/ext/tree_sitter/macros.h +163 -0
  11. data/ext/tree_sitter/node.c +310 -0
  12. data/ext/tree_sitter/parser.c +203 -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 +157 -0
  16. data/ext/tree_sitter/query_capture.c +28 -0
  17. data/ext/tree_sitter/query_cursor.c +103 -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/symbol_type.c +46 -0
  23. data/ext/tree_sitter/tree.c +145 -0
  24. data/ext/tree_sitter/tree_cursor.c +97 -0
  25. data/ext/tree_sitter/tree_sitter.c +32 -0
  26. data/ext/tree_sitter/tree_sitter.h +107 -0
  27. data/lib/tree_sitter/node.rb +164 -0
  28. data/lib/tree_sitter/version.rb +5 -0
  29. data/lib/tree_sitter.rb +13 -0
  30. data/test/README.md +15 -0
  31. data/test/test_helper.rb +9 -0
  32. data/test/tree_sitter/language_test.rb +68 -0
  33. data/test/tree_sitter/logger_test.rb +69 -0
  34. data/test/tree_sitter/node_test.rb +355 -0
  35. data/test/tree_sitter/parser_test.rb +140 -0
  36. data/test/tree_sitter/query_test.rb +153 -0
  37. data/test/tree_sitter/tree_cursor_test.rb +83 -0
  38. data/test/tree_sitter/tree_test.rb +51 -0
  39. data/tree_sitter.gemspec +32 -0
  40. metadata +189 -0
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ class Node
5
+ def fields
6
+ return @fields if @fields
7
+
8
+ @fields = Set.new
9
+ child_count.times do |i|
10
+ name = field_name_for_child(i)
11
+ @fields << name.to_sym if name
12
+ end
13
+
14
+ @fields
15
+ end
16
+
17
+ def field?(field)
18
+ fields.include?(field)
19
+ end
20
+
21
+ # Access node's named children.
22
+ #
23
+ # It's similar to {#fetch}, but differes in input type, return values, and
24
+ # the internal implementation.
25
+ #
26
+ # Both of these methods exist for separate use cases, but also because
27
+ # sometime tree-sitter does some monkey business and having both separate
28
+ # implementations can help.
29
+ #
30
+ # Comparison with {#fetch}:
31
+ #
32
+ # [] | fetch
33
+ # ------------------------------+----------------------
34
+ # input types Integer, String, Symbol | Array<String, Symbol>
35
+ # Array<Integer, String, Symbol>|
36
+ # ------------------------------+----------------------
37
+ # returns 1-to-1 correspondance with | unique nodes
38
+ # input |
39
+ # ------------------------------+----------------------
40
+ # uses named_child | field_name_for_child
41
+ # child_by_field_name | via each_node
42
+ # ------------------------------+----------------------
43
+ #
44
+ # @param keys [Integer | String | Symbol | Array<Integer, String, Symbol>, #read]
45
+ #
46
+ # @return [Node | Array<Node>]
47
+ def [](*keys)
48
+ case keys.length
49
+ when 0 then raise "#{self.class.name}##{__method__} requires a key."
50
+ when 1
51
+ case k = keys.first
52
+ when Numeric then named_child(k)
53
+ when String, Symbol
54
+ if fields.include?(k.to_sym)
55
+ child_by_field_name(k.to_s)
56
+ else
57
+ raise "Cannot find field #{k}"
58
+ end
59
+ else raise <<~ERR
60
+ #{self.class.name}##{__method__} accepts Integer and returns named child at given index,
61
+ or a (String | Symbol) and returns the child by given field name.
62
+ ERR
63
+ end
64
+ else
65
+ keys.map { |key| self[key] }
66
+ end
67
+ end
68
+
69
+ # Allows access to child_by_field_name without using [].
70
+ def method_missing(method_name, *_args, &_block)
71
+ if fields.include?(method_name)
72
+ child_by_field_name(method_name.to_s)
73
+ else
74
+ super
75
+ end
76
+ end
77
+
78
+ def respond_to_missing?(*args)
79
+ init_fields
80
+ args.length == 1 && fields.include?(args[0])
81
+ end
82
+
83
+ # Iterate over a node's children.
84
+ #
85
+ # @yieldparam child [Node] the child
86
+ def each
87
+ return enum_for __method__ if !block_given?
88
+
89
+ (0...(child_count)).each do |i|
90
+ yield child(i)
91
+ end
92
+ end
93
+
94
+ # Iterate over a node's children assigned to a field.
95
+ #
96
+ # @yieldparam name [NilClass | String] field name.
97
+ # @yieldparam child [Node] the child.
98
+ def each_field
99
+ return enum_for __method__ if !block_given?
100
+
101
+ each.with_index do |c, i|
102
+ f = field_name_for_child(i)
103
+ next if f.nil? || f.empty?
104
+
105
+ yield f, c
106
+ end
107
+ end
108
+
109
+ # Iterate over a node's named children
110
+ #
111
+ # @yieldparam child [Node] the child
112
+ def each_named
113
+ return enum_for __method__ if !block_given?
114
+
115
+ (0...(named_child_count)).each do |i|
116
+ yield named_child(i)
117
+ end
118
+ end
119
+
120
+ def to_a
121
+ each.to_a
122
+ end
123
+
124
+ # Access node's named children.
125
+ #
126
+ # It's similar to {#fetch}, but differes in input type, return values, and
127
+ # the internal implementation.
128
+ #
129
+ # Both of these methods exist for separate use cases, but also because
130
+ # sometime tree-sitter does some monkey business and having both separate
131
+ # implementations can help.
132
+ #
133
+ # Comparison with {#fetch}:
134
+ #
135
+ # [] | fetch
136
+ # ------------------------------+----------------------
137
+ # input types Integer, String, Symbol | String, Symbol
138
+ # Array<Integer, String, Symbol>| Array<String, Symbol>
139
+ # ------------------------------+----------------------
140
+ # returns 1-to-1 correspondance with | unique nodes
141
+ # input |
142
+ # ------------------------------+----------------------
143
+ # uses named_child | field_name_for_child
144
+ # child_by_field_name | via each_node
145
+ # ------------------------------+----------------------
146
+ def fetch(*keys)
147
+ dict = {}
148
+ keys.each.with_index do |k, i|
149
+ dict[k.to_s] = i
150
+ end
151
+
152
+ res = {}
153
+ each_field do |f, c|
154
+ if dict.key?(f)
155
+ res[dict[f]] = c
156
+ dict.delete(f)
157
+ end
158
+ break if dict.empty?
159
+ end
160
+
161
+ res.sort.map { |_, v| v }
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ VERSION = '0.20.6.3'
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TreeSitter
4
+ end
5
+
6
+ require 'set'
7
+
8
+ require 'tree_sitter/version'
9
+
10
+ require 'tree_sitter/tree_sitter'
11
+ require 'tree_sitter/node'
12
+
13
+ ObjectSpace.define_finalizer(TreeSitter::Tree.class, proc { TreeSitter::Tree.finalizer })
data/test/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # Unit testing
2
+
3
+ Since we don't have languages bundled in ruby gems, we have to load the dynamic
4
+ libraries from disk.
5
+
6
+ We're using the `bin/get` scripts to do so.
7
+
8
+ Since not all languages have a Makefile in their root dir, and we don't want to
9
+ mess with copying Makefiles, we're going to rely on languages that do have a
10
+ Makefile in their root.
11
+
12
+ So for the time being we're sticking with `ruby`.
13
+
14
+ We might need to change this strategy in the future if we're to test some
15
+ multilang parsing.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ at_exit { GC.start }
4
+
5
+ require 'minitest/autorun'
6
+ require 'minitest/color'
7
+ require 'tree_sitter'
8
+
9
+ require_relative '../examples/helpers'
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ ruby = TreeSitter.lang('ruby')
6
+ parser = TreeSitter::Parser.new
7
+ parser.language = ruby
8
+
9
+ program = <<~RUBY
10
+ def mul(a, b)
11
+ res = a * b
12
+ puts res.inspect
13
+ return res
14
+ end
15
+ RUBY
16
+
17
+ tree = parser.parse_string(nil, program)
18
+ root = tree.root_node
19
+
20
+ # NOTE: one should be weary of testing with data structures that are owned by
21
+ # parsers. They are not reliable and we should expect them to break when these
22
+ # parsers evolve.
23
+
24
+ describe 'language' do
25
+ it 'must be able to load a library from `Pathname` (or any object that has `to_s`)' do
26
+ path =
27
+ if p = ENV.fetch('TREE_SITTER_PARSERS', nil)
28
+ Pathname(p) / "libtree-sitter-ruby.#{TreeSitter.ext}"
29
+ else
30
+ Pathname('tree-sitter-parsers') / 'ruby' / "libtree-sitter-ruby.#{TreeSitter.ext}"
31
+ end
32
+ ll = TreeSitter::Language.load('ruby', path)
33
+ assert ll.field_count.positive?
34
+ end
35
+
36
+ it 'must return symbol count' do
37
+ assert ruby.symbol_count.positive?
38
+ end
39
+
40
+ it 'must return symbol name' do
41
+ assert_equal 'end', ruby.symbol_name(0)
42
+ end
43
+
44
+ it 'must return symbol id for string name' do
45
+ assert ruby.symbol_for_name(root.type, root.named?).positive?
46
+ end
47
+
48
+ it 'must return field count' do
49
+ assert ruby.field_count.positive?
50
+ end
51
+
52
+ it 'must return field name for id' do
53
+ assert_equal 'alias', ruby.field_name_for_id(1)
54
+ end
55
+
56
+ it 'must return field name for id' do
57
+ assert_equal 1, ruby.field_id_for_name('alias')
58
+ end
59
+
60
+ it 'must return field symbol type' do
61
+ assert_equal TreeSitter::SymbolType::AUXILIARY, ruby.symbol_type(0)
62
+ end
63
+
64
+ it 'must be of correct version' do
65
+ assert ruby.version <= TreeSitter::LANGUAGE_VERSION \
66
+ && ruby.version >= TreeSitter::MIN_COMPATIBLE_LANGUAGE_VERSION
67
+ end
68
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+ require 'stringio'
5
+
6
+ ruby = TreeSitter.lang('ruby')
7
+ parser = TreeSitter::Parser.new
8
+ parser.language = ruby
9
+
10
+ program = <<~RUBY
11
+ def mul(a, b)
12
+ res = a * b
13
+ puts res.inspect
14
+ return res
15
+ end
16
+ RUBY
17
+
18
+ # Use with fork.
19
+ #
20
+ # Minitest on ruby 2.7 was failing sometimes for no particular reason,
21
+ # complaining about some random number not having a `write` method.
22
+ #
23
+ # My assumption is that the global var $stderr is not playing nice when
24
+ # we try to replace it and other minitest stuff are going alongside.
25
+ #
26
+ # If this theory holds, then forking should solve any race conditions that
27
+ # minitest could introduce. Let's wait and see.
28
+ def capture_stderr
29
+ # The output stream must be an IO-like object. In this case we capture it in
30
+ # an in-memory IO object so we can return the string value. You can assign any
31
+ # IO object here.
32
+ previous_stderr = $stderr
33
+ $stderr = StringIO.new
34
+ yield
35
+ $stderr.string
36
+ ensure
37
+ # Restore the previous value of stderr (typically equal to STDERR).
38
+ $stderr = previous_stderr
39
+ end
40
+
41
+ describe 'logging' do
42
+ it 'should log to stderr by default' do
43
+ fork do
44
+ captured_output = capture_stderr do
45
+ # Does not output anything directly.
46
+ parser.logger = TreeSitter::Logger.new
47
+ parser.parse_string(nil, program)
48
+ end
49
+ refute_equal 0, captured_output.length
50
+ end
51
+ end
52
+
53
+ it 'should log to IO objects' do
54
+ backend = StringIO.new
55
+ parser.logger = TreeSitter::Logger.new(backend)
56
+ parser.parse_string(nil, program)
57
+ refute_equal 0, backend.length
58
+ end
59
+
60
+ it 'should format output when a format string is passed' do
61
+ delim = '~~~~~'
62
+ backend = StringIO.new
63
+ parser.logger = TreeSitter::Logger.new(backend, "%s#{delim}%s")
64
+ parser.parse_string(nil, program)
65
+ backend.each_line do |l|
66
+ assert (/#{delim}/ =~ l), 'delimiter must be in every single line'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,355 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ ruby = TreeSitter.lang('ruby')
6
+ parser = TreeSitter::Parser.new
7
+ parser.language = ruby
8
+
9
+ program = <<~RUBY
10
+ def mul(a, b)
11
+ res = a * b
12
+ puts res.inspect
13
+ return res
14
+ end
15
+ RUBY
16
+
17
+ tree = parser.parse_string(nil, program)
18
+ root = tree.root_node
19
+
20
+ describe 'type' do
21
+ it 'must be a Symbol' do
22
+ assert_instance_of Symbol, root.type
23
+ end
24
+
25
+ it 'must be "program" on root' do
26
+ assert_equal :program, root.type
27
+ end
28
+ end
29
+
30
+ describe 'symbol' do
31
+ it 'must be an Integer' do
32
+ assert_instance_of Integer, root.symbol
33
+ end
34
+ end
35
+
36
+ describe 'start_byte' do
37
+ it 'must be an Integer' do
38
+ assert_instance_of Integer, root.start_byte
39
+ end
40
+
41
+ it 'must be an 0' do
42
+ assert_equal 0, root.start_byte
43
+ end
44
+ end
45
+
46
+ describe 'end_byte' do
47
+ it 'must be an Integer' do
48
+ assert_instance_of Integer, root.end_byte
49
+ end
50
+
51
+ it 'must not be 0' do
52
+ refute_equal 0, root.end_byte
53
+ end
54
+ end
55
+
56
+ describe 'start_point' do
57
+ it 'must be an instance of point' do
58
+ assert_instance_of TreeSitter::Point, root.start_point
59
+ end
60
+
61
+ it 'must be at row 0' do
62
+ assert_equal 0, root.start_point.row
63
+ end
64
+
65
+ it 'must be at column 0' do
66
+ assert_equal 0, root.start_point.row
67
+ end
68
+ end
69
+
70
+ describe 'end_point' do
71
+ it 'must be an instance of point' do
72
+ assert_instance_of TreeSitter::Point, root.end_point
73
+ end
74
+
75
+ it 'must not be at row 0' do
76
+ refute_equal 0, root.end_point.row
77
+ end
78
+
79
+ it 'must not be at column 0' do
80
+ refute_equal 0, root.end_point.row
81
+ end
82
+ end
83
+
84
+ describe 'string' do
85
+ it 'must be an instance of string' do
86
+ assert_instance_of String, root.to_s
87
+ end
88
+
89
+ it 'must be non-empty' do
90
+ refute root.to_s.empty?
91
+ end
92
+ end
93
+
94
+ describe 'predicates' do
95
+ it 'must not be null' do
96
+ refute_nil root.null?
97
+ end
98
+
99
+ it 'must be named' do
100
+ assert root.named?
101
+ end
102
+
103
+ it 'must not be missing' do
104
+ refute root.missing?
105
+ end
106
+
107
+ it 'must not be extra' do
108
+ refute root.extra?
109
+ end
110
+
111
+ it 'must not have any changes' do
112
+ # TODO: needs a more elaborate test to check for true changes?
113
+ refute root.changed?
114
+ end
115
+
116
+ it 'must not have no errors' do
117
+ refute root.error?
118
+ end
119
+ end
120
+
121
+ describe 'parent' do
122
+ # NOTE: never call parent on root. It will segfault.
123
+ #
124
+ # tree-sitter does not provide a way to check if a node has a parent.
125
+
126
+ it 'must never be nil' do
127
+ refute_nil root.child(0).parent
128
+ end
129
+
130
+ it 'must be root for its children' do
131
+ assert_equal root, root.child(0).parent
132
+ end
133
+ end
134
+
135
+ describe 'child' do
136
+ before do
137
+ @child = root.child(0)
138
+ end
139
+
140
+ it 'must return proper children count' do
141
+ assert_equal 1, root.child_count
142
+ end
143
+
144
+ it 'must return proper name child count' do
145
+ assert_equal 5, @child.named_child_count
146
+ end
147
+
148
+ it 'must return proper name child' do
149
+ assert_equal @child.child(1), @child.named_child(0)
150
+ end
151
+
152
+ it 'must return proper child by field name' do
153
+ assert_equal @child.child(1), @child.child_by_field_name('name')
154
+ end
155
+
156
+ it 'must return proper child by field id' do
157
+ assert_equal @child.child(1), @child.child_by_field_id(ruby.field_id_for_name('name'))
158
+ end
159
+
160
+ it 'must return proper child for byte' do
161
+ child = @child.child(0)
162
+ assert_equal child, @child.first_child_for_byte(child.start_byte)
163
+ end
164
+
165
+ it 'must return proper named child for byte' do
166
+ child = @child.child(1)
167
+ assert_equal child, @child.first_named_child_for_byte(child.start_byte)
168
+ end
169
+
170
+ it 'must return proper descendant for byte range' do
171
+ child = @child.child(1)
172
+ assert_equal child, @child.descendant_for_byte_range(child.start_byte, child.end_byte)
173
+ end
174
+
175
+ it 'must return proper descendant for point range' do
176
+ child = @child.child(1)
177
+ assert_equal child, @child.descendant_for_point_range(child.start_point, child.end_point)
178
+ end
179
+
180
+ it 'must return proper named descendant for byte range' do
181
+ child = @child.child(1)
182
+ assert_equal child, @child.named_descendant_for_byte_range(child.start_byte, child.end_byte)
183
+ end
184
+
185
+ it 'must return proper named descendant for point range' do
186
+ child = @child.child(1)
187
+ assert_equal child, @child.named_descendant_for_point_range(child.start_point, child.end_point)
188
+ end
189
+
190
+ it 'must raise an exception for wrong ranges' do
191
+ child = @child.child(0)
192
+ assert_raises IndexError do
193
+ @child.descendant_for_byte_range(child.end_byte, child.start_byte)
194
+ end
195
+ assert_raises IndexError do
196
+ @child.named_descendant_for_byte_range(child.end_byte, child.start_byte)
197
+ end
198
+ assert_raises IndexError do
199
+ p1 = TreeSitter::Point.new
200
+ p1.row = @child.end_point.row
201
+ p1.column = @child.end_point.column + 1
202
+ @child.named_descendant_for_point_range(@child.start_point, p1)
203
+ end
204
+ assert_raises IndexError do
205
+ p1 = TreeSitter::Point.new
206
+ p1.row = @child.end_point.row
207
+ p1.column = @child.end_point.column + 1
208
+ @child.named_descendant_for_point_range(@child.start_point, p1)
209
+ end
210
+ end
211
+ end
212
+
213
+ describe 'field_name' do
214
+ before do
215
+ @child = root.child(0)
216
+ end
217
+
218
+ it 'must return proper field name' do
219
+ assert_equal 'name', @child.field_name_for_child(1)
220
+ end
221
+ end
222
+
223
+ describe 'siblings' do
224
+ before do
225
+ @child = root.child(0).child(0)
226
+ end
227
+
228
+ it 'must return proper next/previous siblings' do
229
+ assert_equal @child, @child.next_sibling.prev_sibling
230
+ end
231
+
232
+ it 'must return proper next/previous named siblings' do
233
+ assert_equal @child.parent.child(1), @child.next_named_sibling
234
+ end
235
+ end
236
+
237
+ # TODO: edit
238
+
239
+ # Tese are High-Level Ruby APIs that we designed.
240
+ # They rely on the bindings.
241
+
242
+ describe '[]' do
243
+ before do
244
+ @child = root.child(0)
245
+ end
246
+
247
+ it 'must return a named child by index when index is an Integer' do
248
+ assert_equal @child.named_child(0), @child[0]
249
+ end
250
+
251
+ it 'must return a child by field name when index is a (String | Symbol)' do
252
+ assert_equal @child.named_child(0), @child[:name]
253
+ assert_equal @child.named_child(0), @child['name']
254
+ end
255
+
256
+ it 'must return an array of nodes when index is an Array' do
257
+ arr = [@child.named_child(0)] * 3
258
+ assert_equal arr, @child[0, :name, 'name']
259
+ end
260
+
261
+ it 'must throw an exception when out of index' do
262
+ assert_raises { @child[255] }
263
+ end
264
+
265
+ it 'must throw an exception when field is not found (NO SIGSEGV ANYMORE!)' do
266
+ assert_raises { @child[:randomzes] }
267
+ end
268
+ end
269
+
270
+ describe 'each' do
271
+ before do
272
+ @child = root.child(0)
273
+ end
274
+
275
+ it 'must iterate over all children' do
276
+ i = 0
277
+ @child.each do |_|
278
+ i += 1
279
+ end
280
+ assert @child.child_count, i
281
+ end
282
+
283
+ it 'must iterate ove named children attached to fields only' do
284
+ @child.each_field do |f, c|
285
+ refute f.nil?
286
+ refute f.empty?
287
+ assert_equal @child[f], c
288
+ end
289
+ end
290
+
291
+ it 'must iterate over named children when `each_named_child`' do
292
+ i = 0
293
+ @child.each_named do |c|
294
+ assert c.named?
295
+ i += 1
296
+ end
297
+ assert @child.named_child_count, i
298
+ end
299
+ end
300
+
301
+ describe 'method_missing' do
302
+ before do
303
+ @child = root.child(0)
304
+ end
305
+
306
+ it 'should act like the [] method when we pass (String | Symbol)' do
307
+ assert_equal @child[:name], @child.name
308
+ end
309
+ end
310
+
311
+ describe 'to_a' do
312
+ before do
313
+ @child = root.child(0)
314
+ end
315
+
316
+ it 'should return the list from each' do
317
+ ll = @child.to_a
318
+
319
+ refute ll.empty?
320
+
321
+ @child.each.with_index do |c, i|
322
+ assert_equal @child.child(i), c
323
+ end
324
+ end
325
+ end
326
+
327
+ describe 'fetch' do
328
+ before do
329
+ @child = root.child(0).child(4)
330
+ end
331
+
332
+ it 'should return all requested keys by order' do
333
+ method = @child.child(0)
334
+ arguments = @child.child(1)
335
+
336
+ m, a = @child.fetch(:method, :arguments)
337
+
338
+ assert_equal method, m
339
+ assert_equal arguments, a
340
+
341
+ a, m = @child.fetch(:arguments, :method)
342
+
343
+ assert_equal method, m
344
+ assert_equal arguments, a
345
+ end
346
+
347
+ it 'should return unique keys' do
348
+ method = @child.child(0)
349
+
350
+ m = @child.fetch(:method, :method)
351
+
352
+ assert_equal 1, m.length
353
+ assert_equal method, m.first
354
+ end
355
+ end