ruby_tree_sitter 1.0.0-x86_64-linux → 1.2.0-x86_64-linux

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5237780e4fc6628d21e5671a2259320bbb307520a690edba10e776e57f3c886b
4
- data.tar.gz: 730d07e399f4e5aee47e8c71a8dd346d83f77d0de4d2d00311a8cca7e2f554ca
3
+ metadata.gz: cfbfdfc0c297585fe24e04c4b9822ff404baa795182b7a7b144f56a3941db672
4
+ data.tar.gz: 010faa21be194e40cdf7f5f00986c16ee5d51d64f377097bfb307a4245ba61b6
5
5
  SHA512:
6
- metadata.gz: cc2878922995a67d5e95029b135f3b9cefb5769e4bf82d5bc94f5e0a282f79429f4aedc7f2ea18301979aefceed576be52f940c357d3844e82e6f7fe872b5c36
7
- data.tar.gz: 04cb391ecddc7323a53764aafda6d60789584d6b3dad0272ea9632108d2a7d55f491811e20bb7ce4241fbbd0f193a7a5f0a92899a5a2ca7b2a1e51264f9e3e3c
6
+ metadata.gz: b98898e1a8b801de3599e6daebd2adaa27b2ea729a46ebe730095d31e053dfb84c48a83712364a09fd58bac236f1b499f16ced6dcf4640902d0d4c12ac4ac7be
7
+ data.tar.gz: 4dd45cf49030a553819febb05cc8a0755ed549c413d44ea1c88836d4e2fad3c55138db3a3079776f2e14a828c38fd7d7b11664366ea8e84083098910528950b3
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Ruby tree-sitter bindings
2
2
 
3
- [![ci](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/ci.yml/badge.svg)](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/ci.yml) [![valgrind](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/valgrind.yml/badge.svg)](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/valgrind.yml) [![asan/ubsan](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/asan.yml/badge.svg)](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/asan.yml)
3
+ [![docs](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/publish-docs.yml/badge.svg)](https://faveod.github.io/ruby-tree-sitter)
4
+ [![rubygems.org](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/publish.yml/badge.svg)](https://rubygems.org/gems/ruby_tree_sitter)
5
+ [![ci](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/ci.yml/badge.svg)](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/ci.yml)
6
+ <!--
7
+ [![valgrind](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/valgrind.yml/badge.svg)](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/valgrind.yml)
8
+ [![asan/ubsan](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/asan.yml/badge.svg)](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/asan.yml)
9
+ -->
4
10
 
5
11
  Ruby bindings for [tree-sitter](https://github.com/tree-sitter/tree-sitter).
6
12
 
@@ -63,8 +69,7 @@ This gem is a binding for `tree-sitter`. It doesn't have a version of
63
69
 
64
70
  You must install `tree-sitter` and make sure that their dynamic library is
65
71
  accessible from `$PATH`, or build the gem with `--disable-sys-libs`, which will
66
- download the latest tagged `tree-sitter` and build against it (see [Build from
67
- source](docs/Contributing.md#build-from-source) .)
72
+ download the latest tagged `tree-sitter` and build against it (see [Build from source](docs/Contributing.md#build-from-source) .)
68
73
 
69
74
  You can either install `tree-sitter` from source or through your go-to package manager.
70
75
 
@@ -133,8 +138,7 @@ bundle config set build.ruby_tree_sitter --disable-sys-libs
133
138
  If you don't want to install from `rubygems`, `git`, or if you don't want to
134
139
  compile on install, then download a native gem from this repository's
135
140
  [releases](https://github.com/Faveod/ruby-tree-sitter/releases), or you can
136
- compile it yourself (see [Build from
137
- source](docs/Contributing.md#build-from-source) .)
141
+ compile it yourself (see [Build from source](docs/Contributing.md#build-from-source) .)
138
142
 
139
143
  In that case, you'd have to point your `Gemfile` to the `gem` as such:
140
144
 
@@ -171,7 +175,7 @@ See `examples` directory.
171
175
 
172
176
  ## Development
173
177
 
174
- See [`docs/README.md`](docs/Contributing.md).
178
+ See [`docs/Contributing.md`](docs/Contributing.md).
175
179
 
176
180
  ## 🚧 👷‍♀️ Notes 👷 🚧
177
181
 
@@ -186,7 +190,7 @@ don't copy them left and right, and then expect them to work without
186
190
  `SEGFAULT`ing and creating a black-hole in your living-room. Assume that you
187
191
  have to work locally with them. If you get a `SEGFAULT`, you can debug the
188
192
  native `C` code using `gdb`. You can read more on `SEGFAULT`s
189
- [here](docs/SIGSEGV.md), and debugging [here](docs/Contributing.md#Debugging).
193
+ [here](docs/SIGSEGV.md), and debugging [here](docs/Contributing#Debugging.md).
190
194
 
191
195
  That said, we do aim at providing an idiomatic `Ruby` interface. It should also
192
196
  provide a _safer_ interface, where you don't have to worry about when and how
@@ -32,7 +32,7 @@ VALUE new_language(const TSLanguage *language) {
32
32
  * with this gem.
33
33
  *
34
34
  * @param name [String] the parser's name.
35
- * @param path [String] the parser's shared library (so, dylib) path on disk.
35
+ * @param path [String, Pathname] the parser's shared library (so, dylib) path on disk.
36
36
  *
37
37
  * @return [Language]
38
38
  */
@@ -154,9 +154,9 @@ static void logger_initialize_stderr(logger_t *logger) {
154
154
  *
155
155
  * You can provide your proper backend. You have to make sure that it
156
156
  * exposes a +printf+, +puts+, or +write+ (lookup is done in that specific
157
- * order). {StringIO} is a perfect candidate.
157
+ * order). {::StringIO} is a perfect candidate.
158
158
  *
159
- * You can also provide a format ({String}) if your backend supports a +printf+.
159
+ * You can also provide a format ({::String}) if your backend supports a +printf+.
160
160
  *
161
161
  * @example
162
162
  * backend = StringIO.new
@@ -166,14 +166,19 @@ static VALUE node_child_by_field_id(VALUE self, VALUE field_id) {
166
166
  /**
167
167
  * Get the node's child with the given field name.
168
168
  *
169
- * @param field_name [String]
169
+ * @param field_name [String, Symbol]
170
170
  *
171
171
  * @return [Node]
172
172
  */
173
173
  static VALUE node_child_by_field_name(VALUE self, VALUE field_name) {
174
- const char *name = StringValuePtr(field_name);
175
- uint32_t length = (uint32_t)RSTRING_LEN(field_name);
176
- return new_node_by_val(ts_node_child_by_field_name(SELF, name, length));
174
+ if (Qtrue == rb_funcall(self, rb_intern("field?"), 1, field_name)) {
175
+ VALUE field_str = rb_funcall(field_name, rb_intern("to_s"), 0);
176
+ const char *name = StringValuePtr(field_str);
177
+ uint32_t length = (uint32_t)RSTRING_LEN(field_str);
178
+ return new_node_by_val(ts_node_child_by_field_name(SELF, name, length));
179
+ } else {
180
+ return Qnil;
181
+ }
177
182
  }
178
183
 
179
184
  /**
@@ -208,7 +208,7 @@ static VALUE parser_set_logger(VALUE self, VALUE logger) {
208
208
  * same arguments. Or you can start parsing from scratch by first calling
209
209
  * {Parser#reset}.
210
210
  * 3. Parsing was cancelled using a cancellation flag that was set by an
211
- * earlier call to {Parsert#cancellation_flag=}. You can resume parsing
211
+ * earlier call to {Parser#cancellation_flag=}. You can resume parsing
212
212
  * from where the parser left out by calling {Parser#parse} again with
213
213
  * the same arguments.
214
214
  *
@@ -13,28 +13,17 @@ module TreeSitter
13
13
  @fields << name.to_sym if name
14
14
  end
15
15
 
16
- @fields
16
+ @fields.to_a
17
17
  end
18
18
 
19
+ # @param field [String, Symbol]
19
20
  def field?(field)
20
- fields.include?(field)
21
+ fields.include?(field.to_sym)
21
22
  end
22
23
 
23
- # FIXME: These APIs (`[]` and `fetch`) need absolute fixing.
24
- # 1. The documentation with the table doesn't work.
25
- # 1. The APIs are very confusing! Make them act similarly to Hash's
26
- # `fetch` and `[]`.
27
- # 1. `[]` should take a single input and return nil if nothing found
28
- # (no exceptions).
29
- # 1. `fetch` should should accept a single argument, potentially a
30
- # default, and raise exception if no default was provided.
31
- # Also allow for the `all:` kwarg.
32
- # 1. `values_at` takes many arguments.
33
- # And I don't think we can move to 1.0 without adressing them.
34
- #
35
24
  # Access node's named children.
36
25
  #
37
- # It's similar to {#fetch}, but differes in input type, return values, and
26
+ # It's similar to {#fetch}, but differs in input type, return values, and
38
27
  # the internal implementation.
39
28
  #
40
29
  # Both of these methods exist for separate use cases, but also because
@@ -60,15 +49,15 @@ module TreeSitter
60
49
  # @return [Node | Array<Node>]
61
50
  def [](*keys)
62
51
  case keys.length
63
- when 0 then raise "#{self.class.name}##{__method__} requires a key."
52
+ when 0 then raise ArgumentError, "#{self.class.name}##{__method__} requires a key."
64
53
  when 1
65
54
  case k = keys.first
66
- when Numeric then named_child(k)
55
+ when Integer then named_child(k)
67
56
  when String, Symbol
68
- raise "Cannot find field #{k}" unless fields.include?(k.to_sym)
57
+ raise IndexError, "Cannot find field #{k}. Available: #{fields}" unless fields.include?(k.to_sym)
69
58
 
70
59
  child_by_field_name(k.to_s)
71
- else raise <<~ERR
60
+ else raise ArgumentError, <<~ERR
72
61
  #{self.class.name}##{__method__} accepts Integer and returns named child at given index,
73
62
  or a (String | Symbol) and returns the child by given field name.
74
63
  ERR
@@ -139,7 +128,7 @@ module TreeSitter
139
128
 
140
129
  # Access node's named children.
141
130
  #
142
- # It's similar to {#fetch}, but differes in input type, return values, and
131
+ # It's similar to {#[]}, but differs in input type, return values, and
143
132
  # the internal implementation.
144
133
  #
145
134
  # Both of these methods exist for separate use cases, but also because
@@ -159,39 +148,18 @@ module TreeSitter
159
148
  # uses named_child | field_name_for_child
160
149
  # child_by_field_name | via each_node
161
150
  # ------------------------------+----------------------
162
- # @param all [Boolean] If `true`, return an array of nodes for all the
163
- # demanded keys, putting `nil` for missing ones. If `false`, return the
164
- # same array after calling `compact`. Defaults to `false`.
165
- #
166
- # See {#fetch_all}.
167
- def fetch(*keys, all: false, **_kwargs)
168
- dict = {}
169
- keys.each.with_index do |k, i|
170
- dict[k.to_s] = i
171
- end
151
+ #
152
+ # See {#[]}.
153
+ def fetch(*keys)
154
+ keys = keys.map(&:to_s)
155
+ key_set = keys.to_set
156
+ fields = {}
157
+ each_field do |f, _c|
158
+ fields[f] = self[f] if key_set.delete(f)
172
159
 
173
- res = {}
174
- each_field do |f, c|
175
- if dict.key?(f)
176
- res[f] = c
177
- dict.delete(f)
178
- end
179
- break if dict.empty?
160
+ break if key_set.empty?
180
161
  end
181
-
182
- res = keys.uniq.map { |k| res[k.to_s] }
183
- res = res.compact if !all
184
- res
185
- end
186
-
187
- # Access all named children of a node, returning `nil` for missing ones.
188
- #
189
- # Equivalent to `fetch(…, all: true)`.
190
- #
191
- # See {#fetch}.
192
- def fetch_all(*keys, **kwargs)
193
- kwargs[:all] = true
194
- fetch(*keys, **kwargs)
162
+ fields.values_at(*keys)
195
163
  end
196
164
  end
197
165
  end
Binary file
@@ -2,7 +2,7 @@
2
2
 
3
3
  module TreeSitter
4
4
  # The version of the tree-sitter library.
5
- TREESITTER_VERSION = '0.20.9'
5
+ TREESITTER_VERSION = '0.22.6'
6
6
  # The current version of the gem.
7
- VERSION = '1.0.0'
7
+ VERSION = '1.2.0'
8
8
  end
@@ -1,13 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
  # typed: true
3
3
 
4
+ require 'pathname'
5
+
4
6
  module TreeStand
5
7
  # Global configuration for the gem.
6
8
  # @api private
7
9
  class Config
8
10
  extend T::Sig
9
11
 
10
- sig { returns(String) }
11
- attr_accessor :parser_path
12
+ sig { returns(T.nilable(Pathname)) }
13
+ attr_reader :parser_path
14
+
15
+ def parser_path=(path)
16
+ @parser_path = Pathname(path)
17
+ end
12
18
  end
13
19
  end
@@ -11,16 +11,60 @@ module TreeStand
11
11
  extend Forwardable
12
12
  include Enumerable
13
13
 
14
+ # @!method changed?
15
+ # @return [Boolean] true if a syntax node has been edited.
16
+ # @!method child_count
17
+ # @return [Integer] the number of child nodes.
18
+ # @!method extra?
19
+ # @return [Boolean] true if the node is *extra* (e.g. comments).
20
+ # @!method has_error?
21
+ # @return [Boolean] true if the node is a syntax error or contains any syntax errors.
22
+ # @!method missing?
23
+ # @return [Boolean] true if the parser inserted that node to recover from error.
24
+ # @!method named?
25
+ # @return [Boolean] true if the node is not a literal in the grammar.
26
+ # @!method named_child_count
27
+ # @return [Integer] the number of *named* children.
14
28
  # @!method type
15
29
  # @return [Symbol] the type of the node in the tree-sitter grammar.
16
30
  # @!method error?
17
31
  # @return [bool] true if the node is an error node.
18
32
  def_delegators(
19
33
  :@ts_node,
20
- :type,
34
+ :changed?,
35
+ :child_count,
21
36
  :error?,
37
+ :extra?,
38
+ :has_error?,
39
+ :missing?,
40
+ :named?,
41
+ :named_child_count,
42
+ :type,
22
43
  )
23
44
 
45
+ # These are methods defined in {TreeStand::Node} but map to something
46
+ # in {TreeSitter::Node}, because we want a more idiomatic API.
47
+ THINLY_REMAPPED_METHODS = {
48
+ '[]': :[],
49
+ fetch: :fetch,
50
+ field: :child_by_field_name,
51
+ next: :next_sibling,
52
+ prev: :prev_sibling,
53
+ next_named: :next_named_sibling,
54
+ prev_named: :prev_named_sibling,
55
+ field_names: :fields,
56
+ }.freeze
57
+
58
+ # These are methods from {TreeSitter} that are thinly wrapped to create
59
+ # {TreeStand::Node} instead.
60
+ THINLY_WRAPPED_METHODS = (
61
+ %i[
62
+ child
63
+ named_child
64
+ parent
65
+ ] + THINLY_REMAPPED_METHODS.keys
66
+ ).freeze
67
+
24
68
  sig { returns(TreeStand::Tree) }
25
69
  attr_reader :tree
26
70
 
@@ -32,7 +76,6 @@ module TreeStand
32
76
  def initialize(tree, ts_node)
33
77
  @tree = tree
34
78
  @ts_node = ts_node
35
- @fields = @ts_node.each_field.to_a.map(&:first)
36
79
  end
37
80
 
38
81
  # TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
@@ -131,6 +174,74 @@ module TreeStand
131
174
  enumerator
132
175
  end
133
176
 
177
+ # Enumerate named children.
178
+ # @example
179
+ # node.text # => "3 * 4"
180
+ #
181
+ # @example Iterate over the child nodes
182
+ # node.each_named do |child|
183
+ # print child.text
184
+ # end
185
+ # # prints: 34
186
+ #
187
+ # @example Enumerable methods
188
+ # node.each_named.map(&:text) # => ["3", "4"]
189
+ #
190
+ # @yieldparam child [TreeStand::Node]
191
+ sig do
192
+ params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
193
+ .returns(T::Enumerator[TreeStand::Node])
194
+ end
195
+ def each_named(&block)
196
+ enumerator = Enumerator.new do |yielder|
197
+ @ts_node.each_named do |child|
198
+ yielder << TreeStand::Node.new(@tree, child)
199
+ end
200
+ end
201
+ enumerator.each(&block) if block_given?
202
+ enumerator
203
+ end
204
+
205
+ # Iterate of (field, child).
206
+ #
207
+ # @example
208
+ # node.text # => "3 * 4"
209
+ #
210
+ # @example Iterate over the child nodes
211
+ # node.each_field do |field, child|
212
+ # puts "#{field}: #{child.text}"
213
+ # end
214
+ # # prints:
215
+ # # left: 3
216
+ # # right: 4
217
+ #
218
+ # @example Enumerable methods
219
+ # node.each_field.map { |f, c| "#{f}: #{c}" } # => ["left: 3", "right: 4"]
220
+ #
221
+ # @yieldparam field [Symbol]
222
+ # @yieldparam child [TreeStand::Node]
223
+ sig do
224
+ params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
225
+ .returns(T::Enumerator[[Symbol, TreeStand::Node]])
226
+ end
227
+ def each_field(&block)
228
+ enumerator = Enumerator.new do |yielder|
229
+ @ts_node.each_field do |field, child|
230
+ yielder << [field.to_sym, TreeStand::Node.new(@tree, child)]
231
+ end
232
+ end
233
+ enumerator.each(&block) if block_given?
234
+ enumerator
235
+ end
236
+
237
+ # @example Enumerable methods
238
+ # node.named.map(&:text) # => ["3", "4"]
239
+ alias_method :named, :each_named
240
+
241
+ # @example Enumerable methods
242
+ # node.fields.map { |f, c| "#{f}: #{c}" } # => ["left: 3", "right: 4"]
243
+ alias_method :fields, :each_field
244
+
134
245
  # (see TreeStand::Visitors::TreeWalker)
135
246
  # Backed by {TreeStand::Visitors::TreeWalker}.
136
247
  #
@@ -154,15 +265,6 @@ module TreeStand
154
265
  enumerator
155
266
  end
156
267
 
157
- # @example
158
- # node.text # => "4"
159
- # node.parent.text # => "3 * 4"
160
- # node.parent.parent.text # => "1 + 3 * 4"
161
- sig { returns(TreeStand::Node) }
162
- def parent
163
- TreeStand::Node.new(@tree, @ts_node.parent)
164
- end
165
-
166
268
  # @example
167
269
  # node.text # => "3 * 4"
168
270
  # node.to_a.map(&:text) # => ["3", "*", "4"]
@@ -174,7 +276,7 @@ module TreeStand
174
276
  # wraps the parent {TreeStand::Tree #tree} and has access to the source document.
175
277
  sig { returns(String) }
176
278
  def text
177
- T.must(@tree.document[@ts_node.start_byte...@ts_node.end_byte])
279
+ T.must(@tree.document.byteslice(@ts_node.start_byte...@ts_node.end_byte))
178
280
  end
179
281
 
180
282
  # This class overrides the `method_missing` method to delegate to the
@@ -193,10 +295,20 @@ module TreeStand
193
295
  #
194
296
  # @overload method_missing(method_name, *args, &block)
195
297
  # @raise [NoMethodError]
196
- def method_missing(method, *args, &block)
197
- return super unless @fields.include?(method.to_s)
198
-
199
- TreeStand::Node.new(@tree, T.unsafe(@ts_node).public_send(method, *args, &block))
298
+ def method_missing(method, *args, **kwargs, &block)
299
+ if thinly_wrapped?(method)
300
+ from(
301
+ T.unsafe(@ts_node)
302
+ .public_send(
303
+ THINLY_REMAPPED_METHODS[method] || method,
304
+ *args,
305
+ **kwargs,
306
+ &block
307
+ ),
308
+ )
309
+ else
310
+ super
311
+ end
200
312
  end
201
313
 
202
314
  sig { params(other: Object).returns(T::Boolean) }
@@ -217,8 +329,31 @@ module TreeStand
217
329
 
218
330
  private
219
331
 
220
- def respond_to_missing?(method, *)
221
- @fields.include?(method.to_s) || super
332
+ def respond_to_missing?(method, *_args, **_kwargs)
333
+ thinly_wrapped?(method) || super
334
+ end
335
+
336
+ def thinly_wrapped?(method)
337
+ @ts_node.fields.include?(method) || THINLY_WRAPPED_METHODS.include?(method)
338
+ end
339
+
340
+ # FIXME: Make more generic if needed in other classes.
341
+
342
+ # 1 instance of {TreeStand} from a {TreeSitter} equivalent.
343
+ def from_a(node)
344
+ node.is_a?(TreeSitter::Node) ? TreeStand::Node.new(@tree, node) : node
345
+ end
346
+
347
+ # {TreeSitter} instance, or a collection ({Array, Hash})
348
+ def from(obj)
349
+ case obj
350
+ when Array
351
+ obj.map { |n| from(n) }
352
+ when Hash
353
+ obj.to_h { |k, v| [from(k), from(v)] }
354
+ else
355
+ from_a(obj)
356
+ end
222
357
  end
223
358
  end
224
359
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # typed: true
3
3
 
4
+ require 'pathname'
5
+
4
6
  module TreeStand
5
7
  # Wrapper around the TreeSitter parser. It looks up the parser by filename in
6
8
  # the configured parsers directory.
@@ -14,6 +16,14 @@ module TreeStand
14
16
  #
15
17
  # # Looks for a parser in `path/to/parser/folder/ruby.{so,dylib}`
16
18
  # ruby_parser = TreeStand::Parser.new("ruby")
19
+ #
20
+ # If no {TreeStand::Config#parser_path} is setup, {TreeStand} will lookup in a
21
+ # set of default paths. You can always override any configuration by passing
22
+ # the environment variable `TREE_SITTER_PARSERS` (colon-separated).
23
+ #
24
+ # @see language
25
+ # @see search_for_lib
26
+ # @see LIBDIRS
17
27
  class Parser
18
28
  extend T::Sig
19
29
 
@@ -23,14 +33,160 @@ module TreeStand
23
33
  sig { returns(TreeSitter::Parser) }
24
34
  attr_reader :ts_parser
25
35
 
36
+ # A colon-seprated list of paths pointing to directories that can contain parsers.
37
+ # Order matters.
38
+ # Takes precedence over default lookup paths.
39
+ ENV_PARSERS =
40
+ ENV['TREE_SITTER_PARSERS']
41
+ &.split(':')
42
+ &.map { |v| Pathname(v) }
43
+ .freeze
44
+
45
+ # The default paths we use to lookup parsers when no specific
46
+ # {TreeStand::Config#parser_path} is {nil}.
47
+ # Order matters.
48
+ LIBDIRS = [
49
+ 'tree-sitter-parsers',
50
+ '/opt/local/lib',
51
+ '/opt/lib',
52
+ '/usr/local/lib',
53
+ '/usr/lib',
54
+ ].map { |p| Pathname(p) }.freeze
55
+
56
+ # The library directories we need to look into.
57
+ #
58
+ # @return [Array<Pathname>] the list of candidate places to use when searching for parsers.
59
+ #
60
+ # @see ENV_PARSERS
61
+ # @see LIBDIRS
62
+ sig { returns(T::Array[Pathname]) }
63
+ def self.lib_dirs = [
64
+ *ENV_PARSERS,
65
+ *(TreeStand.config.parser_path ? [TreeStand.config.parser_path] : LIBDIRS),
66
+ ].compact
67
+
68
+ # The platform-specific extension of the parser.
69
+ # @return [String] `dylib` or `so` for mac or linux.
70
+ sig { returns(String) }
71
+ def self.ext
72
+ case Gem::Platform.local.os
73
+ in /darwin/ then 'dylib'
74
+ else 'so'
75
+ end
76
+ end
77
+
78
+ # Lookup a parser by name.
79
+ #
80
+ # Precedence:
81
+ # 1. `Env['TREE_SITTER_PARSERS]`
82
+ # 2. {TreeStand::Config#parser_path}
83
+ # 3. {LIBDIRS}
84
+ #
85
+ # If a {TreeStand::Config#parser_path} is `nil`, {LIBDIRS} is used.
86
+ # If a {TreeStand::Config#parser_path} is a {::Pathname}, {LIBDIRS} is ignored.
87
+ sig { params(name: String).returns(T.nilable(Pathname)) }
88
+ def self.search_for_lib(name)
89
+ files = [
90
+ name,
91
+ "tree-sitter-#{name}",
92
+ "libtree-sitter-#{name}",
93
+ ].map { |v| "#{v}.#{ext}" }
94
+
95
+ res = lib_dirs
96
+ .product(files)
97
+ .find do |dir, so|
98
+ path = dir / so
99
+ path = dir / name / so if !path.exist?
100
+ break path.expand_path if path.exist?
101
+ end
102
+ res.is_a?(Array) ? nil : res
103
+ end
104
+
105
+ # Generates a string message on where parser lookup happens.
106
+ #
107
+ # @return [String] A pretty message.
108
+ sig { returns(String) }
109
+ def self.search_lib_message
110
+ indent = 2
111
+ pretty = ->(arr) {
112
+ if arr
113
+ arr
114
+ .compact
115
+ .map { |v| "#{' ' * indent}#{v.expand_path}" }
116
+ .join("\n")
117
+ end
118
+ }
119
+ <<~MSG
120
+ From ENV['TREE_SITTER_PARSERS']:
121
+ #{pretty.call(ENV_PARSERS)}
122
+
123
+ From TreeStand.config.parser_path:
124
+ #{pretty.call([TreeStand.config.parser_path])}
125
+
126
+ From Defaults:
127
+ #{pretty.call(LIBDIRS)}
128
+ MSG
129
+ end
130
+
131
+ # Load a language from configuration or default lookup paths.
132
+ #
133
+ # @example Load java from default paths
134
+ # # This will look for:
135
+ # #
136
+ # # tree-sitter-parsers/(java/)?(libtree-sitter-)?java.{ext}
137
+ # # /opt/local/lib/(java/)?(libtree-sitter-)?java.{ext}
138
+ # # /opt/lib/(java/)?(libtree-sitter-)?java.{ext}
139
+ # # /usr/local/lib/(java/)?(libtree-sitter-)?java.{ext}
140
+ # # /usr/lib/(java/)?(libtree-sitter-)?java.{ext}
141
+ # #
142
+ # java = TreeStand::Parser.language('java')
143
+ #
144
+ # @example Load java from a configured path
145
+ # # This will look for:
146
+ # #
147
+ # # /my/path/(java/)?(libtree-sitter-)?java.{ext}
148
+ # #
149
+ # TreeStand.config.parser_path = '/my/path'
150
+ # java = TreeStand::Parser.language('java')
151
+ #
152
+ # @example Load java from environment variables
153
+ # # This will look for:
154
+ # #
155
+ # # /my/forced/env/path/(java/)?(libtree-sitter-)?java.{ext}
156
+ # # /my/path/(java/)?(libtree-sitter-)?java.{ext}
157
+ # #
158
+ # # … and the same works for the default paths if `TreeStand.config.parser_path`
159
+ # # was `nil`
160
+ # ENV['TREE_SITTER_PARSERS'] = '/my/forced/env/path'
161
+ # TreeStand.config.parser_path = '/my/path'
162
+ # java = TreeStand::Parser.language('java')
163
+ #
164
+ # @param name [String] the name of the parser.
165
+ # This name is used to load the symbol from the compiled parser, replacing `-` with `_`.
166
+ # @return [TreeSitter:language] a language object to use in your parsers.
167
+ # @raise [RuntimeError] if the parser was not found.
168
+ # @see search_for_lib
169
+ sig { params(name: String).returns(TreeSitter::Language) }
170
+ def self.language(name)
171
+ lib = search_for_lib(name)
172
+
173
+ if lib.nil?
174
+ raise <<~MSG
175
+ Failed to load a parser for #{name}.
176
+
177
+ #{search_lib_message}
178
+ MSG
179
+ end
180
+
181
+ # We know that the bindings will accept `lib`, but I don't know how to tell sorbet
182
+ # the types in ext/tree_sitter where `load` is defined.
183
+ TreeSitter::Language.load(name.gsub(/-/, '_'), T.unsafe(lib))
184
+ end
185
+
26
186
  # @param language [String]
27
187
  sig { params(language: String).void }
28
188
  def initialize(language)
29
- @language_string = language
30
- @ts_language = TreeSitter::Language.load(
31
- language,
32
- "#{TreeStand.config.parser_path}/#{language}.so",
33
- )
189
+ @ts_language = Parser.language(language)
34
190
  @ts_parser = TreeSitter::Parser.new.tap do |parser|
35
191
  parser.language = @ts_language
36
192
  end
@@ -33,7 +33,7 @@ module TreeStand
33
33
  next
34
34
  end
35
35
 
36
- io << "#{line.sexpr}#{' ' * (@ralign - line.sexpr.size)}| #{line.text}\n"
36
+ io << "#{line.sexpr}#{' ' * [(@ralign - line.sexpr.size), 0].max}| #{line.text}\n"
37
37
  end
38
38
 
39
39
  io
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_tree_sitter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Firas al-Khalil
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-02-26 00:00:00.000000000 Z
12
+ date: 2024-05-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sorbet-runtime