tree_haver 1.0.0 → 2.0.0
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +47 -3
- data/README.md +130 -76
- data/lib/tree_haver/backends/citrus.rb +302 -0
- data/lib/tree_haver/backends/ffi.rb +75 -17
- data/lib/tree_haver/backends/java.rb +11 -7
- data/lib/tree_haver/backends/mri.rb +10 -20
- data/lib/tree_haver/backends/rust.rb +8 -20
- data/lib/tree_haver/grammar_finder.rb +1 -1
- data/lib/tree_haver/node.rb +376 -0
- data/lib/tree_haver/path_validator.rb +18 -3
- data/lib/tree_haver/tree.rb +205 -0
- data/lib/tree_haver/version.rb +2 -2
- data/lib/tree_haver.rb +44 -229
- data/sig/tree_haver/backends.rbs +68 -1
- data/sig/tree_haver/path_validator.rbs +1 -0
- data/sig/tree_haver.rbs +95 -9
- data.tar.gz.sig +0 -0
- metadata +11 -8
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
module Backends
|
|
5
|
+
# Citrus backend using pure Ruby PEG parser
|
|
6
|
+
#
|
|
7
|
+
# This backend wraps Citrus-based parsers (like toml-rb) to provide a
|
|
8
|
+
# pure Ruby alternative to tree-sitter. Citrus is a PEG (Parsing Expression
|
|
9
|
+
# Grammar) parser generator written in Ruby.
|
|
10
|
+
#
|
|
11
|
+
# Unlike tree-sitter backends which are language-agnostic runtime parsers,
|
|
12
|
+
# Citrus parsers are grammar-specific and compiled into Ruby code. Each
|
|
13
|
+
# language needs its own Citrus grammar (e.g., toml-rb for TOML).
|
|
14
|
+
#
|
|
15
|
+
# @note This backend requires a Citrus grammar for the specific language
|
|
16
|
+
# @see https://github.com/mjackson/citrus Citrus parser generator
|
|
17
|
+
# @see https://github.com/emancu/toml-rb toml-rb (TOML Citrus grammar)
|
|
18
|
+
#
|
|
19
|
+
# @example Using with toml-rb
|
|
20
|
+
# require "toml-rb"
|
|
21
|
+
#
|
|
22
|
+
# parser = TreeHaver::Parser.new
|
|
23
|
+
# # For Citrus, "language" is actually a grammar module
|
|
24
|
+
# parser.language = TomlRB::Document
|
|
25
|
+
# tree = parser.parse(toml_source)
|
|
26
|
+
module Citrus
|
|
27
|
+
@load_attempted = false
|
|
28
|
+
@loaded = false
|
|
29
|
+
|
|
30
|
+
# Check if the Citrus backend is available
|
|
31
|
+
#
|
|
32
|
+
# Attempts to require citrus on first call and caches the result.
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean] true if citrus gem is available
|
|
35
|
+
# @example
|
|
36
|
+
# if TreeHaver::Backends::Citrus.available?
|
|
37
|
+
# puts "Citrus backend is ready"
|
|
38
|
+
# end
|
|
39
|
+
class << self
|
|
40
|
+
def available?
|
|
41
|
+
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
42
|
+
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
43
|
+
begin
|
|
44
|
+
require "citrus"
|
|
45
|
+
|
|
46
|
+
@loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
47
|
+
rescue LoadError
|
|
48
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
49
|
+
end
|
|
50
|
+
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Reset the load state (primarily for testing)
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
56
|
+
# @api private
|
|
57
|
+
def reset!
|
|
58
|
+
@load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
59
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get capabilities supported by this backend
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash{Symbol => Object}] capability map
|
|
65
|
+
# @example
|
|
66
|
+
# TreeHaver::Backends::Citrus.capabilities
|
|
67
|
+
# # => { backend: :citrus, query: false, bytes_field: true, incremental: false }
|
|
68
|
+
def capabilities
|
|
69
|
+
return {} unless available?
|
|
70
|
+
{
|
|
71
|
+
backend: :citrus,
|
|
72
|
+
query: false, # Citrus doesn't have a query API like tree-sitter
|
|
73
|
+
bytes_field: true, # Citrus::Match provides offset and length
|
|
74
|
+
incremental: false, # Citrus doesn't support incremental parsing
|
|
75
|
+
pure_ruby: true, # Citrus is pure Ruby (portable)
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Citrus grammar wrapper
|
|
81
|
+
#
|
|
82
|
+
# Unlike tree-sitter which loads compiled .so files, Citrus uses Ruby modules
|
|
83
|
+
# that define grammars. This class wraps a Citrus grammar module.
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
# # For TOML, use toml-rb's grammar
|
|
87
|
+
# language = TreeHaver::Backends::Citrus::Language.new(TomlRB::Document)
|
|
88
|
+
class Language
|
|
89
|
+
# The Citrus grammar module
|
|
90
|
+
# @return [Module] Citrus grammar module (e.g., TomlRB::Document)
|
|
91
|
+
attr_reader :grammar_module
|
|
92
|
+
|
|
93
|
+
# @param grammar_module [Module] A Citrus grammar module with a parse method
|
|
94
|
+
def initialize(grammar_module)
|
|
95
|
+
unless grammar_module.respond_to?(:parse)
|
|
96
|
+
raise TreeHaver::NotAvailable,
|
|
97
|
+
"Grammar module must respond to :parse. " \
|
|
98
|
+
"Expected a Citrus grammar module (e.g., TomlRB::Document)."
|
|
99
|
+
end
|
|
100
|
+
@grammar_module = grammar_module
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Not applicable for Citrus (tree-sitter-specific)
|
|
104
|
+
#
|
|
105
|
+
# Citrus grammars are Ruby modules, not shared libraries.
|
|
106
|
+
# This method exists for API compatibility but will raise an error.
|
|
107
|
+
#
|
|
108
|
+
# @raise [TreeHaver::NotAvailable] always raises
|
|
109
|
+
class << self
|
|
110
|
+
def from_library(path, symbol: nil, name: nil)
|
|
111
|
+
raise TreeHaver::NotAvailable,
|
|
112
|
+
"Citrus backend doesn't use shared libraries. " \
|
|
113
|
+
"Use Citrus::Language.new(GrammarModule) instead."
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
alias_method :from_path, :from_library
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Citrus parser wrapper
|
|
121
|
+
#
|
|
122
|
+
# Wraps Citrus grammar modules to provide a tree-sitter-like API.
|
|
123
|
+
class Parser
|
|
124
|
+
# Create a new Citrus parser instance
|
|
125
|
+
#
|
|
126
|
+
# @raise [TreeHaver::NotAvailable] if citrus gem is not available
|
|
127
|
+
def initialize
|
|
128
|
+
raise TreeHaver::NotAvailable, "citrus gem not available" unless Citrus.available?
|
|
129
|
+
@grammar = nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Set the grammar for this parser
|
|
133
|
+
#
|
|
134
|
+
# @param grammar [Language, Module] Citrus grammar module or Language wrapper
|
|
135
|
+
# @return [Language, Module] the grammar that was set
|
|
136
|
+
# @example
|
|
137
|
+
# require "toml-rb"
|
|
138
|
+
# parser.language = TomlRB::Document # Pass module directly
|
|
139
|
+
# # or
|
|
140
|
+
# parser.language = TreeHaver::Backends::Citrus::Language.new(TomlRB::Document)
|
|
141
|
+
def language=(grammar)
|
|
142
|
+
@grammar = if grammar.respond_to?(:grammar_module)
|
|
143
|
+
grammar.grammar_module
|
|
144
|
+
elsif grammar.respond_to?(:parse)
|
|
145
|
+
grammar
|
|
146
|
+
else
|
|
147
|
+
raise ArgumentError,
|
|
148
|
+
"Expected Citrus grammar module or Language wrapper, " \
|
|
149
|
+
"got #{grammar.class}"
|
|
150
|
+
end
|
|
151
|
+
grammar
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Parse source code
|
|
155
|
+
#
|
|
156
|
+
# @param source [String] the source code to parse
|
|
157
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
158
|
+
# @raise [TreeHaver::NotAvailable] if no grammar is set
|
|
159
|
+
# @raise [::Citrus::ParseError] if parsing fails
|
|
160
|
+
def parse(source)
|
|
161
|
+
raise TreeHaver::NotAvailable, "No grammar loaded" unless @grammar
|
|
162
|
+
|
|
163
|
+
begin
|
|
164
|
+
citrus_match = @grammar.parse(source)
|
|
165
|
+
inner_tree = Tree.new(citrus_match, source)
|
|
166
|
+
TreeHaver::Tree.new(inner_tree, source: source)
|
|
167
|
+
rescue ::Citrus::ParseError => e
|
|
168
|
+
# Re-raise with more context
|
|
169
|
+
raise TreeHaver::Error, "Parse error: #{e.message}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Parse source code (compatibility with tree-sitter API)
|
|
174
|
+
#
|
|
175
|
+
# Citrus doesn't support incremental parsing, so old_tree is ignored.
|
|
176
|
+
#
|
|
177
|
+
# @param old_tree [TreeHaver::Tree, nil] ignored (no incremental parsing support)
|
|
178
|
+
# @param source [String] the source code to parse
|
|
179
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
180
|
+
def parse_string(old_tree, source)
|
|
181
|
+
parse(source) # Citrus doesn't support incremental parsing
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Citrus tree wrapper
|
|
186
|
+
#
|
|
187
|
+
# Wraps a Citrus::Match (which represents the parse tree) to provide
|
|
188
|
+
# tree-sitter-compatible API.
|
|
189
|
+
#
|
|
190
|
+
# @api private
|
|
191
|
+
class Tree
|
|
192
|
+
attr_reader :root_match, :source
|
|
193
|
+
|
|
194
|
+
def initialize(root_match, source)
|
|
195
|
+
@root_match = root_match
|
|
196
|
+
@source = source
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def root_node
|
|
200
|
+
Node.new(@root_match, @source)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Citrus node wrapper
|
|
205
|
+
#
|
|
206
|
+
# Wraps Citrus::Match objects to provide tree-sitter-compatible node API.
|
|
207
|
+
#
|
|
208
|
+
# Citrus::Match provides:
|
|
209
|
+
# - events[0]: rule name (Symbol) - used as type
|
|
210
|
+
# - offset: byte position
|
|
211
|
+
# - length: byte length
|
|
212
|
+
# - string: matched text
|
|
213
|
+
# - matches: child matches
|
|
214
|
+
# - captures: named groups
|
|
215
|
+
#
|
|
216
|
+
# @api private
|
|
217
|
+
class Node
|
|
218
|
+
attr_reader :match, :source
|
|
219
|
+
|
|
220
|
+
def initialize(match, source)
|
|
221
|
+
@match = match
|
|
222
|
+
@source = source
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Get node type from Citrus rule name
|
|
226
|
+
#
|
|
227
|
+
# @return [String] rule name from grammar
|
|
228
|
+
def type
|
|
229
|
+
# Citrus stores the rule name in events[0]
|
|
230
|
+
return "unknown" unless @match.respond_to?(:events)
|
|
231
|
+
return "unknown" unless @match.events.is_a?(Array)
|
|
232
|
+
return "unknown" if @match.events.empty?
|
|
233
|
+
|
|
234
|
+
first = @match.events.first
|
|
235
|
+
first.is_a?(Symbol) ? first.to_s : "unknown"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def start_byte
|
|
239
|
+
@match.offset
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def end_byte
|
|
243
|
+
@match.offset + @match.length
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def start_point
|
|
247
|
+
calculate_point(@match.offset)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def end_point
|
|
251
|
+
calculate_point(@match.offset + @match.length)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def text
|
|
255
|
+
@match.string
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def child_count
|
|
259
|
+
@match.respond_to?(:matches) ? @match.matches.size : 0
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def child(index)
|
|
263
|
+
return unless @match.respond_to?(:matches)
|
|
264
|
+
return if index >= @match.matches.size
|
|
265
|
+
|
|
266
|
+
Node.new(@match.matches[index], @source)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def children
|
|
270
|
+
return [] unless @match.respond_to?(:matches)
|
|
271
|
+
@match.matches.map { |m| Node.new(m, @source) }
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def each(&block)
|
|
275
|
+
return to_enum(__method__) unless block_given?
|
|
276
|
+
children.each(&block)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def has_error?
|
|
280
|
+
false # Citrus raises on parse error, so successful parse has no errors
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def missing?
|
|
284
|
+
false # Citrus doesn't have the concept of missing nodes
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def named?
|
|
288
|
+
true # Citrus matches are typically "named" in tree-sitter terminology
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private
|
|
292
|
+
|
|
293
|
+
def calculate_point(offset)
|
|
294
|
+
lines_before = @source[0...offset].count("\n")
|
|
295
|
+
line_start = @source.rindex("\n", offset - 1) || -1
|
|
296
|
+
column = offset - line_start - 1
|
|
297
|
+
{row: lines_before, column: column}
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
@@ -14,7 +14,7 @@ module TreeHaver
|
|
|
14
14
|
module Backends
|
|
15
15
|
# FFI-based backend for calling libtree-sitter directly
|
|
16
16
|
#
|
|
17
|
-
# This backend uses Ruby FFI (JNR-FFI on JRuby) to call the native
|
|
17
|
+
# This backend uses Ruby FFI (JNR-FFI on JRuby) to call the native tree-sitter
|
|
18
18
|
# C library without requiring MRI C extensions. This makes it compatible with
|
|
19
19
|
# JRuby, TruffleRuby, and other Ruby implementations that support FFI.
|
|
20
20
|
#
|
|
@@ -24,16 +24,16 @@ module TreeHaver
|
|
|
24
24
|
# - Accessing node types and children
|
|
25
25
|
#
|
|
26
26
|
# Not yet supported:
|
|
27
|
-
# - Query API (
|
|
27
|
+
# - Query API (tree-sitter queries/patterns)
|
|
28
28
|
#
|
|
29
29
|
# @note Requires the `ffi` gem and libtree-sitter shared library to be installed
|
|
30
30
|
# @see https://github.com/ffi/ffi Ruby FFI
|
|
31
|
-
# @see https://tree-sitter.github.io/tree-sitter/
|
|
31
|
+
# @see https://tree-sitter.github.io/tree-sitter/ tree-sitter
|
|
32
32
|
module FFI
|
|
33
33
|
# Native FFI bindings to libtree-sitter
|
|
34
34
|
#
|
|
35
|
-
# This module handles loading the
|
|
36
|
-
# FFI function attachments for the core
|
|
35
|
+
# This module handles loading the tree-sitter runtime library and defining
|
|
36
|
+
# FFI function attachments for the core tree-sitter API.
|
|
37
37
|
#
|
|
38
38
|
# @api private
|
|
39
39
|
module Native
|
|
@@ -42,8 +42,8 @@ module TreeHaver
|
|
|
42
42
|
|
|
43
43
|
# FFI struct representation of TSNode
|
|
44
44
|
#
|
|
45
|
-
# Mirrors the C struct layout used by
|
|
46
|
-
# by value in the
|
|
45
|
+
# Mirrors the C struct layout used by tree-sitter. TSNode is passed
|
|
46
|
+
# by value in the tree-sitter C API.
|
|
47
47
|
#
|
|
48
48
|
# @api private
|
|
49
49
|
class TSNode < ::FFI::Struct
|
|
@@ -79,10 +79,10 @@ module TreeHaver
|
|
|
79
79
|
].compact
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
# Load the
|
|
82
|
+
# Load the tree-sitter runtime library
|
|
83
83
|
#
|
|
84
84
|
# Tries each candidate library name in order until one succeeds.
|
|
85
|
-
# After loading, attaches FFI function definitions for the
|
|
85
|
+
# After loading, attaches FFI function definitions for the tree-sitter API.
|
|
86
86
|
#
|
|
87
87
|
# @raise [TreeHaver::NotAvailable] if no library can be loaded
|
|
88
88
|
# @return [void]
|
|
@@ -189,7 +189,7 @@ module TreeHaver
|
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
-
# Represents a
|
|
192
|
+
# Represents a tree-sitter language loaded via FFI
|
|
193
193
|
#
|
|
194
194
|
# Holds a pointer to a TSLanguage struct from a loaded shared library.
|
|
195
195
|
class Language
|
|
@@ -276,7 +276,7 @@ module TreeHaver
|
|
|
276
276
|
end
|
|
277
277
|
end
|
|
278
278
|
|
|
279
|
-
# FFI-based
|
|
279
|
+
# FFI-based tree-sitter parser
|
|
280
280
|
#
|
|
281
281
|
# Wraps a TSParser pointer and manages its lifecycle with a finalizer.
|
|
282
282
|
class Parser
|
|
@@ -323,18 +323,19 @@ module TreeHaver
|
|
|
323
323
|
# Parse source code into a syntax tree
|
|
324
324
|
#
|
|
325
325
|
# @param source [String] the source code to parse (should be UTF-8)
|
|
326
|
-
# @return [Tree]
|
|
326
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
327
327
|
# @raise [TreeHaver::NotAvailable] if parsing fails
|
|
328
328
|
def parse(source)
|
|
329
329
|
src = String(source)
|
|
330
330
|
tree_ptr = Native.ts_parser_parse_string(@parser, ::FFI::Pointer::NULL, src, src.bytesize)
|
|
331
331
|
raise TreeHaver::NotAvailable, "Parse returned NULL" if tree_ptr.null?
|
|
332
332
|
|
|
333
|
-
Tree.new(tree_ptr)
|
|
333
|
+
inner_tree = Tree.new(tree_ptr)
|
|
334
|
+
TreeHaver::Tree.new(inner_tree, source: src)
|
|
334
335
|
end
|
|
335
336
|
end
|
|
336
337
|
|
|
337
|
-
# FFI-based
|
|
338
|
+
# FFI-based tree-sitter tree
|
|
338
339
|
#
|
|
339
340
|
# Wraps a TSTree pointer and manages its lifecycle with a finalizer.
|
|
340
341
|
class Tree
|
|
@@ -369,10 +370,10 @@ module TreeHaver
|
|
|
369
370
|
end
|
|
370
371
|
end
|
|
371
372
|
|
|
372
|
-
# FFI-based
|
|
373
|
+
# FFI-based tree-sitter node
|
|
373
374
|
#
|
|
374
375
|
# Wraps a TSNode by-value struct. TSNode is passed by value in the
|
|
375
|
-
#
|
|
376
|
+
# tree-sitter C API, so we store the struct value directly.
|
|
376
377
|
class Node
|
|
377
378
|
# @api private
|
|
378
379
|
# @param ts_node_value [Native::TSNode] the TSNode struct (by value)
|
|
@@ -388,6 +389,63 @@ module TreeHaver
|
|
|
388
389
|
Native.ts_node_type(@val)
|
|
389
390
|
end
|
|
390
391
|
|
|
392
|
+
# Get the number of children
|
|
393
|
+
#
|
|
394
|
+
# @return [Integer] child count
|
|
395
|
+
def child_count
|
|
396
|
+
Native.ts_node_child_count(@val)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Get a child by index
|
|
400
|
+
#
|
|
401
|
+
# @param index [Integer] child index
|
|
402
|
+
# @return [Node, nil] child node or nil if index out of bounds
|
|
403
|
+
def child(index)
|
|
404
|
+
return if index >= child_count || index < 0
|
|
405
|
+
child_node = Native.ts_node_child(@val, index)
|
|
406
|
+
Node.new(child_node)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Get start byte offset
|
|
410
|
+
#
|
|
411
|
+
# @return [Integer]
|
|
412
|
+
def start_byte
|
|
413
|
+
Native.ts_node_start_byte(@val)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Get end byte offset
|
|
417
|
+
#
|
|
418
|
+
# @return [Integer]
|
|
419
|
+
def end_byte
|
|
420
|
+
Native.ts_node_end_byte(@val)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Get start point
|
|
424
|
+
#
|
|
425
|
+
# @return [Object] with row and column
|
|
426
|
+
def start_point
|
|
427
|
+
# FFI backend would need to implement ts_node_start_point
|
|
428
|
+
# For now, return a simple struct
|
|
429
|
+
Struct.new(:row, :column).new(0, Native.ts_node_start_byte(@val))
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Get end point
|
|
433
|
+
#
|
|
434
|
+
# @return [Object] with row and column
|
|
435
|
+
def end_point
|
|
436
|
+
# FFI backend would need to implement ts_node_end_point
|
|
437
|
+
# For now, return a simple struct
|
|
438
|
+
Struct.new(:row, :column).new(0, Native.ts_node_end_byte(@val))
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Check if node has error
|
|
442
|
+
#
|
|
443
|
+
# @return [Boolean]
|
|
444
|
+
def has_error?
|
|
445
|
+
# Would need ts_node_has_error implementation
|
|
446
|
+
false
|
|
447
|
+
end
|
|
448
|
+
|
|
391
449
|
# Iterate over child nodes
|
|
392
450
|
#
|
|
393
451
|
# @yieldparam child [Node] each child node
|
|
@@ -395,7 +453,7 @@ module TreeHaver
|
|
|
395
453
|
def each
|
|
396
454
|
return enum_for(:each) unless block_given?
|
|
397
455
|
|
|
398
|
-
count =
|
|
456
|
+
count = child_count
|
|
399
457
|
i = 0
|
|
400
458
|
while i < count
|
|
401
459
|
child = Native.ts_node_child(@val, i)
|
|
@@ -7,7 +7,7 @@ module TreeHaver
|
|
|
7
7
|
# This backend integrates with java-tree-sitter JARs on JRuby,
|
|
8
8
|
# leveraging JRuby's native Java integration for optimal performance.
|
|
9
9
|
#
|
|
10
|
-
# java-tree-sitter provides Java bindings to
|
|
10
|
+
# java-tree-sitter provides Java bindings to tree-sitter and supports:
|
|
11
11
|
# - Parsing source code into syntax trees
|
|
12
12
|
# - Incremental parsing via Parser.parse(Tree, String)
|
|
13
13
|
# - The Query API for pattern matching
|
|
@@ -393,10 +393,11 @@ module TreeHaver
|
|
|
393
393
|
# Parse source code
|
|
394
394
|
#
|
|
395
395
|
# @param source [String] the source code to parse
|
|
396
|
-
# @return [Tree]
|
|
396
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
397
397
|
def parse(source)
|
|
398
398
|
java_tree = @parser.parse(source)
|
|
399
|
-
Tree.new(java_tree)
|
|
399
|
+
inner_tree = Tree.new(java_tree)
|
|
400
|
+
TreeHaver::Tree.new(inner_tree, source: source)
|
|
400
401
|
end
|
|
401
402
|
|
|
402
403
|
# Parse source code with optional incremental parsing
|
|
@@ -404,18 +405,21 @@ module TreeHaver
|
|
|
404
405
|
# When old_tree is provided and has been edited, tree-sitter will reuse
|
|
405
406
|
# unchanged nodes for better performance.
|
|
406
407
|
#
|
|
407
|
-
# @param old_tree [Tree, nil] previous tree for incremental parsing
|
|
408
|
+
# @param old_tree [TreeHaver::Tree, nil] previous tree for incremental parsing
|
|
408
409
|
# @param source [String] the source code to parse
|
|
409
|
-
# @return [Tree]
|
|
410
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
410
411
|
# @see https://tree-sitter.github.io/java-tree-sitter/io/github/treesitter/jtreesitter/Parser.html#parse(io.github.treesitter.jtreesitter.Tree,java.lang.String)
|
|
411
412
|
def parse_string(old_tree, source)
|
|
412
413
|
if old_tree
|
|
413
|
-
|
|
414
|
+
# Unwrap TreeHaver::Tree to get inner tree
|
|
415
|
+
inner_old_tree = old_tree.respond_to?(:inner_tree) ? old_tree.inner_tree : old_tree
|
|
416
|
+
java_old_tree = inner_old_tree.is_a?(Tree) ? inner_old_tree.impl : inner_old_tree
|
|
414
417
|
java_tree = @parser.parse(java_old_tree, source)
|
|
415
418
|
else
|
|
416
419
|
java_tree = @parser.parse(source)
|
|
417
420
|
end
|
|
418
|
-
Tree.new(java_tree)
|
|
421
|
+
inner_tree = Tree.new(java_tree)
|
|
422
|
+
TreeHaver::Tree.new(inner_tree, source: source)
|
|
419
423
|
end
|
|
420
424
|
end
|
|
421
425
|
|
|
@@ -5,7 +5,7 @@ module TreeHaver
|
|
|
5
5
|
# MRI backend using the ruby_tree_sitter gem
|
|
6
6
|
#
|
|
7
7
|
# This backend wraps the ruby_tree_sitter gem, which is a native C extension
|
|
8
|
-
# for MRI Ruby. It provides the most feature-complete
|
|
8
|
+
# for MRI Ruby. It provides the most feature-complete tree-sitter integration
|
|
9
9
|
# on MRI, including support for the Query API.
|
|
10
10
|
#
|
|
11
11
|
# @note This backend only works on MRI Ruby, not JRuby or TruffleRuby
|
|
@@ -96,34 +96,24 @@ module TreeHaver
|
|
|
96
96
|
# Parse source code
|
|
97
97
|
#
|
|
98
98
|
# @param source [String] the source code to parse
|
|
99
|
-
# @return [::
|
|
99
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
100
100
|
def parse(source)
|
|
101
|
-
@parser.parse(source)
|
|
101
|
+
tree = @parser.parse(source)
|
|
102
|
+
TreeHaver::Tree.new(tree, source: source)
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
# Parse source code with optional incremental parsing
|
|
105
106
|
#
|
|
106
|
-
# @param old_tree [::
|
|
107
|
+
# @param old_tree [TreeHaver::Tree, nil] previous tree for incremental parsing
|
|
107
108
|
# @param source [String] the source code to parse
|
|
108
|
-
# @return [::
|
|
109
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
109
110
|
def parse_string(old_tree, source)
|
|
110
|
-
|
|
111
|
+
# Unwrap if TreeHaver::Tree to get inner tree for incremental parsing
|
|
112
|
+
inner_old_tree = old_tree.respond_to?(:inner_tree) ? old_tree.inner_tree : old_tree
|
|
113
|
+
tree = @parser.parse_string(inner_old_tree, source)
|
|
114
|
+
TreeHaver::Tree.new(tree, source: source)
|
|
111
115
|
end
|
|
112
116
|
end
|
|
113
|
-
|
|
114
|
-
# Wrapper for ruby_tree_sitter Tree
|
|
115
|
-
#
|
|
116
|
-
# Not used directly; TreeHaver passes through ::TreeSitter::Tree objects.
|
|
117
|
-
class Tree
|
|
118
|
-
# Not used directly; we pass through ruby_tree_sitter::Tree
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Wrapper for ruby_tree_sitter Node
|
|
122
|
-
#
|
|
123
|
-
# Not used directly; TreeHaver passes through ::TreeSitter::Node objects.
|
|
124
|
-
class Node
|
|
125
|
-
# Not used directly; we pass through ruby_tree_sitter::Node
|
|
126
|
-
end
|
|
127
117
|
end
|
|
128
118
|
end
|
|
129
119
|
end
|
|
@@ -5,7 +5,7 @@ module TreeHaver
|
|
|
5
5
|
# Rust backend using the tree_stump gem
|
|
6
6
|
#
|
|
7
7
|
# This backend wraps the tree_stump gem, which provides Ruby bindings to
|
|
8
|
-
#
|
|
8
|
+
# tree-sitter written in Rust. It offers native performance with Rust's
|
|
9
9
|
# safety guarantees and includes precompiled binaries for common platforms.
|
|
10
10
|
#
|
|
11
11
|
# tree_stump supports incremental parsing and the Query API, making it
|
|
@@ -140,36 +140,24 @@ module TreeHaver
|
|
|
140
140
|
# Parse source code
|
|
141
141
|
#
|
|
142
142
|
# @param source [String] the source code to parse
|
|
143
|
-
# @return [
|
|
143
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
144
144
|
def parse(source)
|
|
145
|
-
@parser.parse(source)
|
|
145
|
+
tree = @parser.parse(source)
|
|
146
|
+
TreeHaver::Tree.new(tree, source: source)
|
|
146
147
|
end
|
|
147
148
|
|
|
148
149
|
# Parse source code with optional incremental parsing
|
|
149
150
|
#
|
|
150
|
-
# @param old_tree [
|
|
151
|
+
# @param old_tree [TreeHaver::Tree, nil] previous tree for incremental parsing
|
|
151
152
|
# @param source [String] the source code to parse
|
|
152
|
-
# @return [
|
|
153
|
+
# @return [TreeHaver::Tree] wrapped tree
|
|
153
154
|
def parse_string(old_tree, source)
|
|
154
155
|
# tree_stump doesn't have parse_string, use parse instead
|
|
155
156
|
# TODO: Check if tree_stump supports incremental parsing
|
|
156
|
-
@parser.parse(source)
|
|
157
|
+
tree = @parser.parse(source)
|
|
158
|
+
TreeHaver::Tree.new(tree, source: source)
|
|
157
159
|
end
|
|
158
160
|
end
|
|
159
|
-
|
|
160
|
-
# Wrapper for tree_stump Tree
|
|
161
|
-
#
|
|
162
|
-
# Not used directly; TreeHaver passes through tree_stump Tree objects.
|
|
163
|
-
class Tree
|
|
164
|
-
# Not used directly; we pass through tree_stump::Tree
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Wrapper for tree_stump Node
|
|
168
|
-
#
|
|
169
|
-
# Not used directly; TreeHaver passes through tree_stump::Node objects.
|
|
170
|
-
class Node
|
|
171
|
-
# Not used directly; we pass through tree_stump::Node
|
|
172
|
-
end
|
|
173
161
|
end
|
|
174
162
|
end
|
|
175
163
|
end
|
|
@@ -221,7 +221,7 @@ module TreeHaver
|
|
|
221
221
|
#
|
|
222
222
|
# @return [String] error message with installation hints
|
|
223
223
|
def not_found_message
|
|
224
|
-
"
|
|
224
|
+
"tree-sitter #{@language_name} grammar not found. " \
|
|
225
225
|
"Searched: #{search_paths.join(", ")}. " \
|
|
226
226
|
"Install tree-sitter-#{@language_name} or set #{env_var_name}."
|
|
227
227
|
end
|