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 +4 -4
- data/README.md +11 -7
- data/ext/tree_sitter/language.c +1 -1
- data/ext/tree_sitter/logger.c +2 -2
- data/ext/tree_sitter/node.c +9 -4
- data/ext/tree_sitter/parser.c +1 -1
- data/lib/tree_sitter/node.rb +19 -51
- data/lib/tree_sitter/tree_sitter.so +0 -0
- data/lib/tree_sitter/version.rb +2 -2
- data/lib/tree_stand/config.rb +8 -2
- data/lib/tree_stand/node.rb +153 -18
- data/lib/tree_stand/parser.rb +161 -5
- data/lib/tree_stand/utils/printer.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfbfdfc0c297585fe24e04c4b9822ff404baa795182b7a7b144f56a3941db672
|
4
|
+
data.tar.gz: 010faa21be194e40cdf7f5f00986c16ee5d51d64f377097bfb307a4245ba61b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
[](https://faveod.github.io/ruby-tree-sitter)
|
4
|
+
[](https://rubygems.org/gems/ruby_tree_sitter)
|
5
|
+
[](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/ci.yml)
|
6
|
+
<!--
|
7
|
+
[](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/valgrind.yml)
|
8
|
+
[](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/
|
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
|
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
|
data/ext/tree_sitter/language.c
CHANGED
@@ -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
|
*/
|
data/ext/tree_sitter/logger.c
CHANGED
@@ -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
|
data/ext/tree_sitter/node.c
CHANGED
@@ -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
|
-
|
175
|
-
|
176
|
-
|
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
|
/**
|
data/ext/tree_sitter/parser.c
CHANGED
@@ -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 {
|
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
|
*
|
data/lib/tree_sitter/node.rb
CHANGED
@@ -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
|
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
|
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 {#
|
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
|
-
#
|
163
|
-
#
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
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
|
data/lib/tree_sitter/version.rb
CHANGED
data/lib/tree_stand/config.rb
CHANGED
@@ -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(
|
11
|
-
|
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
|
data/lib/tree_stand/node.rb
CHANGED
@@ -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
|
-
:
|
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
|
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
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
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
|
data/lib/tree_stand/parser.rb
CHANGED
@@ -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
|
-
@
|
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
|
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.
|
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-
|
12
|
+
date: 2024-05-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sorbet-runtime
|