tree_haver 5.0.4 → 7.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/lib/tree_haver/backend_context.rb +28 -0
- data/lib/tree_haver/backend_registry.rb +19 -432
- data/lib/tree_haver/contracts.rb +460 -0
- data/lib/tree_haver/kaitai_backend.rb +30 -0
- data/lib/tree_haver/language_pack.rb +190 -0
- data/lib/tree_haver/peg_backends.rb +76 -0
- data/lib/tree_haver/version.rb +1 -12
- data/lib/tree_haver.rb +7 -1316
- data.tar.gz.sig +0 -0
- metadata +34 -245
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -1366
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -359
- data/FUNDING.md +0 -74
- data/LICENSE.txt +0 -21
- data/README.md +0 -2347
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/tree_haver/backend_api.rb +0 -349
- data/lib/tree_haver/backends/citrus.rb +0 -487
- data/lib/tree_haver/backends/ffi.rb +0 -1009
- data/lib/tree_haver/backends/java.rb +0 -893
- data/lib/tree_haver/backends/mri.rb +0 -362
- data/lib/tree_haver/backends/parslet.rb +0 -560
- data/lib/tree_haver/backends/prism.rb +0 -471
- data/lib/tree_haver/backends/psych.rb +0 -375
- data/lib/tree_haver/backends/rust.rb +0 -239
- data/lib/tree_haver/base/language.rb +0 -98
- data/lib/tree_haver/base/node.rb +0 -322
- data/lib/tree_haver/base/parser.rb +0 -24
- data/lib/tree_haver/base/point.rb +0 -48
- data/lib/tree_haver/base/tree.rb +0 -128
- data/lib/tree_haver/base.rb +0 -12
- data/lib/tree_haver/citrus_grammar_finder.rb +0 -218
- data/lib/tree_haver/compat.rb +0 -43
- data/lib/tree_haver/grammar_finder.rb +0 -374
- data/lib/tree_haver/language.rb +0 -295
- data/lib/tree_haver/language_registry.rb +0 -190
- data/lib/tree_haver/library_path_utils.rb +0 -80
- data/lib/tree_haver/node.rb +0 -579
- data/lib/tree_haver/parser.rb +0 -438
- data/lib/tree_haver/parslet_grammar_finder.rb +0 -224
- data/lib/tree_haver/path_validator.rb +0 -353
- data/lib/tree_haver/point.rb +0 -27
- data/lib/tree_haver/rspec/dependency_tags.rb +0 -1392
- data/lib/tree_haver/rspec/testable_node.rb +0 -217
- data/lib/tree_haver/rspec.rb +0 -33
- data/lib/tree_haver/tree.rb +0 -258
- data/sig/tree_haver/backends.rbs +0 -352
- data/sig/tree_haver/grammar_finder.rbs +0 -29
- data/sig/tree_haver/path_validator.rbs +0 -32
- data/sig/tree_haver.rbs +0 -234
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module TreeHaver
|
|
4
|
-
module Backends
|
|
5
|
-
# Psych backend using Ruby's built-in YAML parser
|
|
6
|
-
#
|
|
7
|
-
# This backend wraps Psych, Ruby's standard library YAML parser.
|
|
8
|
-
# Psych provides AST access via Psych.parse_stream which returns
|
|
9
|
-
# Psych::Nodes::* objects (Stream, Document, Mapping, Sequence, Scalar, Alias).
|
|
10
|
-
#
|
|
11
|
-
# @note This backend only parses YAML source code
|
|
12
|
-
# @see https://ruby-doc.org/stdlib/libdoc/psych/rdoc/Psych.html Psych documentation
|
|
13
|
-
#
|
|
14
|
-
# @example Basic usage
|
|
15
|
-
# parser = TreeHaver::Parser.new
|
|
16
|
-
# parser.language = TreeHaver::Backends::Psych::Language.yaml
|
|
17
|
-
# tree = parser.parse(yaml_source)
|
|
18
|
-
# root = tree.root_node
|
|
19
|
-
# puts root.type # => "stream"
|
|
20
|
-
module Psych
|
|
21
|
-
@load_attempted = false
|
|
22
|
-
@loaded = false
|
|
23
|
-
|
|
24
|
-
# Check if the Psych backend is available
|
|
25
|
-
#
|
|
26
|
-
# Psych is part of Ruby stdlib, so it should always be available.
|
|
27
|
-
#
|
|
28
|
-
# @return [Boolean] true if psych is available
|
|
29
|
-
class << self
|
|
30
|
-
def available?
|
|
31
|
-
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
32
|
-
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
33
|
-
begin
|
|
34
|
-
require "psych"
|
|
35
|
-
@loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
36
|
-
rescue LoadError
|
|
37
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
38
|
-
rescue StandardError
|
|
39
|
-
# :nocov: defensive code - StandardError during require is extremely rare
|
|
40
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
41
|
-
# :nocov:
|
|
42
|
-
end
|
|
43
|
-
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Reset the load state (primarily for testing)
|
|
47
|
-
#
|
|
48
|
-
# @return [void]
|
|
49
|
-
# @api private
|
|
50
|
-
def reset!
|
|
51
|
-
@load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
52
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Get capabilities supported by this backend
|
|
56
|
-
#
|
|
57
|
-
# @return [Hash{Symbol => Object}] capability map
|
|
58
|
-
def capabilities
|
|
59
|
-
return {} unless available?
|
|
60
|
-
{
|
|
61
|
-
backend: :psych,
|
|
62
|
-
query: false, # Psych doesn't have tree-sitter-style queries
|
|
63
|
-
bytes_field: false, # Psych uses line/column, not byte offsets
|
|
64
|
-
incremental: false, # Psych doesn't support incremental parsing
|
|
65
|
-
pure_ruby: false, # Psych has native libyaml C extension
|
|
66
|
-
yaml_only: true, # Psych only parses YAML
|
|
67
|
-
error_tolerant: false, # Psych raises on syntax errors
|
|
68
|
-
}
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Psych language wrapper
|
|
73
|
-
#
|
|
74
|
-
# Unlike tree-sitter which supports many languages via grammar files,
|
|
75
|
-
# Psych only parses YAML. This class exists for API compatibility with
|
|
76
|
-
# other tree_haver backends.
|
|
77
|
-
#
|
|
78
|
-
# @example
|
|
79
|
-
# language = TreeHaver::Backends::Psych::Language.yaml
|
|
80
|
-
# parser.language = language
|
|
81
|
-
class Language < TreeHaver::Base::Language
|
|
82
|
-
# Create a new Psych language instance
|
|
83
|
-
#
|
|
84
|
-
# @param name [Symbol] Language name (should be :yaml)
|
|
85
|
-
def initialize(name = :yaml)
|
|
86
|
-
super(name, backend: :psych, options: {})
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class << self
|
|
90
|
-
# Create a YAML language instance
|
|
91
|
-
#
|
|
92
|
-
# @return [Language] YAML language
|
|
93
|
-
def yaml
|
|
94
|
-
new(:yaml)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Load language from library path (API compatibility)
|
|
98
|
-
#
|
|
99
|
-
# Psych only supports YAML, so path and symbol parameters are ignored.
|
|
100
|
-
#
|
|
101
|
-
# @param _path [String] Ignored - Psych doesn't load external grammars
|
|
102
|
-
# @param symbol [String, nil] Ignored - Psych only supports YAML
|
|
103
|
-
# @param name [String, nil] Language name hint (defaults to :yaml)
|
|
104
|
-
# @return [Language] YAML language
|
|
105
|
-
# @raise [TreeHaver::NotAvailable] if requested language is not YAML
|
|
106
|
-
def from_library(_path = nil, symbol: nil, name: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
107
|
-
lang_name = name || :yaml
|
|
108
|
-
|
|
109
|
-
unless lang_name == :yaml
|
|
110
|
-
raise TreeHaver::NotAvailable,
|
|
111
|
-
"Psych backend only supports YAML, not #{lang_name}. " \
|
|
112
|
-
"Use a tree-sitter backend for #{lang_name} support."
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
yaml
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Psych parser wrapper
|
|
121
|
-
#
|
|
122
|
-
# Wraps Psych.parse_stream to provide TreeHaver-compatible parsing.
|
|
123
|
-
#
|
|
124
|
-
# @example
|
|
125
|
-
# parser = TreeHaver::Backends::Psych::Parser.new
|
|
126
|
-
# parser.language = Language.yaml
|
|
127
|
-
# tree = parser.parse(yaml_source)
|
|
128
|
-
class Parser < TreeHaver::Base::Parser
|
|
129
|
-
# Parse YAML source code
|
|
130
|
-
#
|
|
131
|
-
# @param source [String] YAML source to parse
|
|
132
|
-
# @return [Tree] Parsed tree
|
|
133
|
-
# @raise [::Psych::SyntaxError] on syntax errors
|
|
134
|
-
def parse(source)
|
|
135
|
-
raise "Language not set" unless language
|
|
136
|
-
Psych.available? or raise "Psych not available"
|
|
137
|
-
|
|
138
|
-
ast = ::Psych.parse_stream(source)
|
|
139
|
-
Tree.new(ast, source)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Alias for compatibility with tree-sitter API
|
|
143
|
-
#
|
|
144
|
-
# @param _old_tree [nil] Ignored (Psych doesn't support incremental parsing)
|
|
145
|
-
# @param source [String] YAML source to parse
|
|
146
|
-
# @return [Tree] Parsed tree
|
|
147
|
-
def parse_string(_old_tree, source)
|
|
148
|
-
parse(source)
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Psych tree wrapper
|
|
153
|
-
#
|
|
154
|
-
# Wraps a Psych::Nodes::Stream to provide TreeHaver-compatible tree interface.
|
|
155
|
-
class Tree < TreeHaver::Base::Tree
|
|
156
|
-
# @return [::Psych::Nodes::Stream] The underlying Psych stream
|
|
157
|
-
attr_reader :inner_tree
|
|
158
|
-
|
|
159
|
-
# Create a new tree wrapper
|
|
160
|
-
#
|
|
161
|
-
# @param stream [::Psych::Nodes::Stream] Psych stream node
|
|
162
|
-
# @param source [String] Original source
|
|
163
|
-
def initialize(stream, source)
|
|
164
|
-
super(stream, source: source)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Get the root node
|
|
168
|
-
#
|
|
169
|
-
# @return [Node] Root node
|
|
170
|
-
def root_node
|
|
171
|
-
Node.new(inner_tree, source: source, lines: lines)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Human-readable representation
|
|
175
|
-
def inspect
|
|
176
|
-
"#<TreeHaver::Backends::Psych::Tree documents=#{inner_tree.children&.size || 0}>"
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Psych node wrapper
|
|
181
|
-
#
|
|
182
|
-
# Wraps Psych::Nodes::* classes to provide TreeHaver::Node-compatible interface.
|
|
183
|
-
#
|
|
184
|
-
# Psych node types:
|
|
185
|
-
# - Stream: Root container
|
|
186
|
-
# - Document: YAML document (multiple per stream possible)
|
|
187
|
-
# - Mapping: Hash/object
|
|
188
|
-
# - Sequence: Array/list
|
|
189
|
-
# - Scalar: Primitive value (string, number, boolean, null)
|
|
190
|
-
# - Psych::Nodes::Alias: YAML anchor reference
|
|
191
|
-
class Node < TreeHaver::Base::Node
|
|
192
|
-
# Get the node type as a string
|
|
193
|
-
#
|
|
194
|
-
# @return [String] Node type
|
|
195
|
-
def type
|
|
196
|
-
inner_node.class.name.split("::").last.downcase
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Alias for type (API compatibility)
|
|
200
|
-
# @return [String] node type
|
|
201
|
-
def kind
|
|
202
|
-
type
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Get the text content of this node
|
|
206
|
-
#
|
|
207
|
-
# @return [String] Node text
|
|
208
|
-
def text
|
|
209
|
-
case inner_node
|
|
210
|
-
when ::Psych::Nodes::Scalar
|
|
211
|
-
inner_node.value.to_s
|
|
212
|
-
when ::Psych::Nodes::Alias
|
|
213
|
-
"*#{inner_node.anchor}"
|
|
214
|
-
else
|
|
215
|
-
# For container nodes, extract from source using location
|
|
216
|
-
extract_text_from_location
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Get child nodes
|
|
221
|
-
#
|
|
222
|
-
# @return [Array<Node>] Child nodes
|
|
223
|
-
def children
|
|
224
|
-
return [] unless inner_node.respond_to?(:children) && inner_node.children
|
|
225
|
-
|
|
226
|
-
inner_node.children.map { |child| Node.new(child, source: source, lines: lines) }
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# Get start byte offset
|
|
230
|
-
#
|
|
231
|
-
# @return [Integer] Start byte offset
|
|
232
|
-
def start_byte
|
|
233
|
-
return 0 unless inner_node.respond_to?(:start_line)
|
|
234
|
-
|
|
235
|
-
line = inner_node.start_line || 0
|
|
236
|
-
col = inner_node.start_column || 0
|
|
237
|
-
calculate_byte_offset(line, col)
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Get end byte offset
|
|
241
|
-
#
|
|
242
|
-
# @return [Integer] End byte offset
|
|
243
|
-
def end_byte
|
|
244
|
-
return start_byte + text.bytesize unless inner_node.respond_to?(:end_line)
|
|
245
|
-
|
|
246
|
-
line = inner_node.end_line || 0
|
|
247
|
-
col = inner_node.end_column || 0
|
|
248
|
-
calculate_byte_offset(line, col)
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# Get start point (row, column) - 0-based
|
|
252
|
-
#
|
|
253
|
-
# @return [TreeHaver::Base::Point] Start position
|
|
254
|
-
def start_point
|
|
255
|
-
row = (inner_node.respond_to?(:start_line) ? inner_node.start_line : 0) || 0
|
|
256
|
-
col = (inner_node.respond_to?(:start_column) ? inner_node.start_column : 0) || 0
|
|
257
|
-
TreeHaver::Base::Point.new(row, col)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
# Get end point (row, column) - 0-based
|
|
261
|
-
#
|
|
262
|
-
# @return [TreeHaver::Base::Point] End position
|
|
263
|
-
def end_point
|
|
264
|
-
row = (inner_node.respond_to?(:end_line) ? inner_node.end_line : 0) || 0
|
|
265
|
-
col = (inner_node.respond_to?(:end_column) ? inner_node.end_column : 0) || 0
|
|
266
|
-
TreeHaver::Base::Point.new(row, col)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Psych-specific: Get the anchor name for Alias/anchored nodes
|
|
270
|
-
#
|
|
271
|
-
# @return [String, nil] Anchor name
|
|
272
|
-
def anchor
|
|
273
|
-
inner_node.anchor if inner_node.respond_to?(:anchor)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Psych-specific: Get the tag for tagged nodes
|
|
277
|
-
#
|
|
278
|
-
# @return [String, nil] Tag
|
|
279
|
-
def tag
|
|
280
|
-
inner_node.tag if inner_node.respond_to?(:tag)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Psych-specific: Get the scalar value
|
|
284
|
-
#
|
|
285
|
-
# @return [String, nil] Value for scalar nodes
|
|
286
|
-
def value
|
|
287
|
-
inner_node.value if inner_node.respond_to?(:value)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Psych-specific: Check if this is a mapping (hash)
|
|
291
|
-
#
|
|
292
|
-
# @return [Boolean]
|
|
293
|
-
def mapping?
|
|
294
|
-
inner_node.is_a?(::Psych::Nodes::Mapping)
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# Psych-specific: Check if this is a sequence (array)
|
|
298
|
-
#
|
|
299
|
-
# @return [Boolean]
|
|
300
|
-
def sequence?
|
|
301
|
-
inner_node.is_a?(::Psych::Nodes::Sequence)
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
# Psych-specific: Check if this is a scalar (primitive)
|
|
305
|
-
#
|
|
306
|
-
# @return [Boolean]
|
|
307
|
-
def scalar?
|
|
308
|
-
inner_node.is_a?(::Psych::Nodes::Scalar)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Psych-specific: Check if this is an alias
|
|
312
|
-
#
|
|
313
|
-
# @return [Boolean]
|
|
314
|
-
def alias?
|
|
315
|
-
inner_node.is_a?(::Psych::Nodes::Alias)
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# Psych-specific: Get mapping entries as key-value pairs
|
|
319
|
-
#
|
|
320
|
-
# For Mapping nodes, children alternate key, value, key, value...
|
|
321
|
-
#
|
|
322
|
-
# @return [Array<Array(Node, Node)>] Key-value pairs
|
|
323
|
-
def mapping_entries
|
|
324
|
-
return [] unless mapping?
|
|
325
|
-
|
|
326
|
-
pairs = []
|
|
327
|
-
children.each_slice(2) do |key, val|
|
|
328
|
-
pairs << [key, val] if key && val
|
|
329
|
-
end
|
|
330
|
-
pairs
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
private
|
|
334
|
-
|
|
335
|
-
# Extract text from source using location
|
|
336
|
-
#
|
|
337
|
-
# @return [String] Extracted text
|
|
338
|
-
def extract_text_from_location
|
|
339
|
-
return "" unless inner_node.respond_to?(:start_line) && inner_node.respond_to?(:end_line)
|
|
340
|
-
|
|
341
|
-
start_ln = inner_node.start_line || 0
|
|
342
|
-
end_ln = inner_node.end_line || start_ln
|
|
343
|
-
start_col = inner_node.start_column || 0
|
|
344
|
-
end_col = inner_node.end_column || 0
|
|
345
|
-
|
|
346
|
-
if start_ln == end_ln
|
|
347
|
-
line = lines[start_ln] || ""
|
|
348
|
-
line[start_col...end_col] || ""
|
|
349
|
-
else
|
|
350
|
-
result = []
|
|
351
|
-
(start_ln..end_ln).each do |ln|
|
|
352
|
-
line = lines[ln] || ""
|
|
353
|
-
result << if ln == start_ln
|
|
354
|
-
line[start_col..]
|
|
355
|
-
elsif ln == end_ln
|
|
356
|
-
line[0...end_col]
|
|
357
|
-
else
|
|
358
|
-
line
|
|
359
|
-
end
|
|
360
|
-
end
|
|
361
|
-
result.compact.join
|
|
362
|
-
end
|
|
363
|
-
end
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
# Alias Point to the base class for compatibility
|
|
367
|
-
Point = TreeHaver::Base::Point
|
|
368
|
-
|
|
369
|
-
# Register the availability checker for RSpec dependency tags
|
|
370
|
-
TreeHaver::BackendRegistry.register_availability_checker(:psych) do
|
|
371
|
-
available?
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
end
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module TreeHaver
|
|
4
|
-
module Backends
|
|
5
|
-
# Rust backend using the tree_stump gem
|
|
6
|
-
#
|
|
7
|
-
# This backend wraps the tree_stump gem, which provides Ruby bindings to
|
|
8
|
-
# tree-sitter written in Rust. It offers native performance with Rust's
|
|
9
|
-
# safety guarantees and includes precompiled binaries for common platforms.
|
|
10
|
-
#
|
|
11
|
-
# tree_stump supports incremental parsing and the Query API, making it
|
|
12
|
-
# suitable for editor/IDE use cases where performance is critical.
|
|
13
|
-
#
|
|
14
|
-
# == Tree/Node Architecture
|
|
15
|
-
#
|
|
16
|
-
# This backend (like all tree-sitter backends: MRI, Rust, FFI, Java) does NOT
|
|
17
|
-
# define its own Tree or Node classes. Instead:
|
|
18
|
-
#
|
|
19
|
-
# - Parser#parse returns raw `::TreeStump::Tree` objects
|
|
20
|
-
# - These are wrapped by `TreeHaver::Tree` (inherits from `Base::Tree`)
|
|
21
|
-
# - `TreeHaver::Tree#root_node` wraps raw nodes in `TreeHaver::Node`
|
|
22
|
-
#
|
|
23
|
-
# This differs from pure-Ruby backends (Citrus, Prism, Psych) which define
|
|
24
|
-
# their own `Backend::X::Tree` and `Backend::X::Node` classes.
|
|
25
|
-
#
|
|
26
|
-
# @see TreeHaver::Tree The wrapper class for tree-sitter Tree objects
|
|
27
|
-
# @see TreeHaver::Node The wrapper class for tree-sitter Node objects
|
|
28
|
-
# @see TreeHaver::Base::Tree Base class documenting the Tree API contract
|
|
29
|
-
# @see TreeHaver::Base::Node Base class documenting the Node API contract
|
|
30
|
-
#
|
|
31
|
-
# == Platform Compatibility
|
|
32
|
-
#
|
|
33
|
-
# - MRI Ruby: ✓ Full support
|
|
34
|
-
# - JRuby: ✗ Cannot load native extensions (runs on JVM)
|
|
35
|
-
# - TruffleRuby: ✗ magnus/rb-sys incompatible with TruffleRuby's C API emulation
|
|
36
|
-
#
|
|
37
|
-
# @see https://github.com/joker1007/tree_stump tree_stump
|
|
38
|
-
module Rust
|
|
39
|
-
@load_attempted = false
|
|
40
|
-
@loaded = false
|
|
41
|
-
|
|
42
|
-
# Check if the Rust backend is available
|
|
43
|
-
#
|
|
44
|
-
# Attempts to require tree_stump on first call and caches the result.
|
|
45
|
-
#
|
|
46
|
-
# @return [Boolean] true if tree_stump is available
|
|
47
|
-
# @example
|
|
48
|
-
# if TreeHaver::Backends::Rust.available?
|
|
49
|
-
# puts "Rust backend is ready"
|
|
50
|
-
# end
|
|
51
|
-
class << self
|
|
52
|
-
def available?
|
|
53
|
-
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
54
|
-
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
55
|
-
begin
|
|
56
|
-
# tree_stump uses magnus which requires MRI's C API
|
|
57
|
-
# It doesn't work on JRuby or TruffleRuby
|
|
58
|
-
if RUBY_ENGINE == "ruby"
|
|
59
|
-
require "tree_stump"
|
|
60
|
-
@loaded = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
61
|
-
else
|
|
62
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
63
|
-
end
|
|
64
|
-
rescue LoadError
|
|
65
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
66
|
-
rescue StandardError
|
|
67
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
68
|
-
end
|
|
69
|
-
@loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Reset the load state (primarily for testing)
|
|
73
|
-
#
|
|
74
|
-
# @return [void]
|
|
75
|
-
# @api private
|
|
76
|
-
def reset!
|
|
77
|
-
@load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
78
|
-
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Get capabilities supported by this backend
|
|
82
|
-
#
|
|
83
|
-
# @return [Hash{Symbol => Object}] capability map
|
|
84
|
-
# @example
|
|
85
|
-
# TreeHaver::Backends::Rust.capabilities
|
|
86
|
-
# # => { backend: :rust, query: true, bytes_field: true, incremental: false }
|
|
87
|
-
def capabilities
|
|
88
|
-
return {} unless available?
|
|
89
|
-
{
|
|
90
|
-
backend: :rust,
|
|
91
|
-
query: true,
|
|
92
|
-
bytes_field: true,
|
|
93
|
-
incremental: false, # TreeStump doesn't currently expose incremental parsing to Ruby
|
|
94
|
-
}
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Wrapper for tree_stump Language
|
|
99
|
-
#
|
|
100
|
-
# Provides TreeHaver-compatible interface to tree_stump's language loading.
|
|
101
|
-
# tree_stump uses a registration-based API where languages are registered
|
|
102
|
-
# by name, then referenced by that name when setting parser language.
|
|
103
|
-
class Language
|
|
104
|
-
include Comparable
|
|
105
|
-
|
|
106
|
-
# The registered language name
|
|
107
|
-
# @return [String]
|
|
108
|
-
attr_reader :name
|
|
109
|
-
|
|
110
|
-
# The backend this language is for
|
|
111
|
-
# @return [Symbol]
|
|
112
|
-
attr_reader :backend
|
|
113
|
-
|
|
114
|
-
# The path this language was loaded from (if known)
|
|
115
|
-
# @return [String, nil]
|
|
116
|
-
attr_reader :path
|
|
117
|
-
|
|
118
|
-
# @api private
|
|
119
|
-
# @param name [String] the registered language name
|
|
120
|
-
# @param path [String, nil] path language was loaded from
|
|
121
|
-
def initialize(name, path: nil)
|
|
122
|
-
@name = name
|
|
123
|
-
@backend = :rust
|
|
124
|
-
@path = path
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Compare languages for equality
|
|
128
|
-
#
|
|
129
|
-
# Rust languages are equal if they have the same backend and name.
|
|
130
|
-
# Name uniquely identifies a registered language in TreeStump.
|
|
131
|
-
#
|
|
132
|
-
# @param other [Object] object to compare with
|
|
133
|
-
# @return [Integer, nil] -1, 0, 1, or nil if not comparable
|
|
134
|
-
def <=>(other)
|
|
135
|
-
return unless other.is_a?(Language)
|
|
136
|
-
return unless other.backend == @backend
|
|
137
|
-
|
|
138
|
-
@name <=> other.name
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Hash value for this language (for use in Sets/Hashes)
|
|
142
|
-
# @return [Integer]
|
|
143
|
-
def hash
|
|
144
|
-
[@backend, @name].hash
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Alias eql? to ==
|
|
148
|
-
alias_method :eql?, :==
|
|
149
|
-
|
|
150
|
-
# Load a language from a shared library path
|
|
151
|
-
#
|
|
152
|
-
# @param path [String] absolute path to the language shared library
|
|
153
|
-
# @param symbol [String, nil] the symbol name (accepted for API consistency, but tree_stump derives it from name)
|
|
154
|
-
# @param name [String, nil] logical name for the language (optional, derived from path if not provided)
|
|
155
|
-
# @return [Language] a wrapper holding the registered language name
|
|
156
|
-
# @raise [TreeHaver::NotAvailable] if tree_stump is not available
|
|
157
|
-
# @example
|
|
158
|
-
# lang = TreeHaver::Backends::Rust::Language.from_library("/usr/local/lib/libtree-sitter-toml.so")
|
|
159
|
-
class << self
|
|
160
|
-
def from_library(path, symbol: nil, name: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
161
|
-
raise TreeHaver::NotAvailable, "tree_stump not available" unless Rust.available?
|
|
162
|
-
|
|
163
|
-
# Validate the path exists before calling register_lang to provide a clear error
|
|
164
|
-
raise TreeHaver::NotAvailable, "Language library not found: #{path}" unless File.exist?(path)
|
|
165
|
-
|
|
166
|
-
# tree_stump uses TreeStump.register_lang(name, path) to register languages
|
|
167
|
-
# The name is used to derive the symbol automatically (tree_sitter_<name>)
|
|
168
|
-
# Use shared utility for consistent path parsing across backends
|
|
169
|
-
lang_name = name || LibraryPathUtils.derive_language_name_from_path(path)
|
|
170
|
-
::TreeStump.register_lang(lang_name, path)
|
|
171
|
-
new(lang_name, path: path)
|
|
172
|
-
rescue RuntimeError => e
|
|
173
|
-
raise TreeHaver::NotAvailable, "Failed to load language from #{path}: #{e.message}"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# Backward-compatible alias for from_library
|
|
177
|
-
alias_method :from_path, :from_library
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Wrapper for tree_stump Parser
|
|
182
|
-
#
|
|
183
|
-
# Provides TreeHaver-compatible interface to tree_stump's parser.
|
|
184
|
-
class Parser
|
|
185
|
-
# Create a new parser instance
|
|
186
|
-
#
|
|
187
|
-
# @raise [TreeHaver::NotAvailable] if tree_stump is not available
|
|
188
|
-
def initialize
|
|
189
|
-
raise TreeHaver::NotAvailable, "tree_stump not available" unless Rust.available?
|
|
190
|
-
@parser = ::TreeStump::Parser.new
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# Set the language for this parser
|
|
194
|
-
#
|
|
195
|
-
# Note: TreeHaver::Parser unwraps language objects before calling this method.
|
|
196
|
-
# When called from TreeHaver::Parser, receives String (language name).
|
|
197
|
-
# For backward compatibility and backend tests, also handles Language wrapper.
|
|
198
|
-
#
|
|
199
|
-
# @param lang [Language, String] the language wrapper or name string
|
|
200
|
-
# @return [Language, String] the language that was set
|
|
201
|
-
def language=(lang)
|
|
202
|
-
# Extract language name (handle both wrapper and raw string)
|
|
203
|
-
lang_name = lang.respond_to?(:name) ? lang.name : lang.to_s
|
|
204
|
-
# tree_stump uses set_language with a string name
|
|
205
|
-
@parser.set_language(lang_name)
|
|
206
|
-
lang # rubocop:disable Lint/Void (intentional return value)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Parse source code
|
|
210
|
-
#
|
|
211
|
-
# @param source [String] the source code to parse
|
|
212
|
-
# @return [TreeStump::Tree] raw backend tree (wrapping happens in TreeHaver::Parser)
|
|
213
|
-
def parse(source)
|
|
214
|
-
# Return raw tree_stump tree - TreeHaver::Parser will wrap it
|
|
215
|
-
@parser.parse(source)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Parse source code with optional incremental parsing
|
|
219
|
-
#
|
|
220
|
-
# Note: TreeStump does not currently expose incremental parsing to Ruby.
|
|
221
|
-
# The parse method always does a full parse, ignoring old_tree.
|
|
222
|
-
#
|
|
223
|
-
# @param old_tree [TreeHaver::Tree, nil] previous tree for incremental parsing (ignored)
|
|
224
|
-
# @param source [String] the source code to parse
|
|
225
|
-
# @return [TreeStump::Tree] raw backend tree (wrapping happens in TreeHaver::Parser)
|
|
226
|
-
def parse_string(old_tree, source) # rubocop:disable Lint/UnusedMethodArgument
|
|
227
|
-
# TreeStump's parse method only accepts source as a single argument
|
|
228
|
-
# and internally always passes None for the old tree (no incremental parsing support)
|
|
229
|
-
@parser.parse(source)
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# Register the availability checker for RSpec dependency tags
|
|
234
|
-
TreeHaver::BackendRegistry.register_availability_checker(:rust) do
|
|
235
|
-
available?
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|