tree_haver 5.0.5 → 7.1.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/LICENSE.md +13 -0
- data/README.md +299 -660
- data/lib/tree_haver/backend_api.rb +78 -35
- data/lib/tree_haver/backend_context.rb +28 -0
- data/lib/tree_haver/backend_registry.rb +119 -382
- data/lib/tree_haver/backends/citrus.rb +55 -53
- data/lib/tree_haver/backends/ffi.rb +105 -101
- data/lib/tree_haver/backends/java.rb +92 -76
- data/lib/tree_haver/backends/mri.rb +44 -39
- data/lib/tree_haver/backends/parslet.rb +53 -48
- data/lib/tree_haver/backends/prism.rb +140 -43
- data/lib/tree_haver/backends/psych.rb +30 -26
- data/lib/tree_haver/backends/rust.rb +21 -17
- data/lib/tree_haver/backends/tslp.rb +274 -0
- data/lib/tree_haver/base/comment.rb +320 -0
- data/lib/tree_haver/base/language.rb +2 -2
- data/lib/tree_haver/base/node.rb +39 -31
- data/lib/tree_haver/base/parser.rb +4 -0
- data/lib/tree_haver/base/point.rb +3 -3
- data/lib/tree_haver/base/tree.rb +1 -1
- data/lib/tree_haver/citrus_grammar_finder.rb +21 -26
- data/lib/tree_haver/contracts.rb +1025 -0
- data/lib/tree_haver/grammar_finder.rb +125 -70
- data/lib/tree_haver/kaitai_backend.rb +30 -0
- data/lib/tree_haver/language.rb +36 -37
- data/lib/tree_haver/language_pack.rb +41 -0
- data/lib/tree_haver/language_registry.rb +34 -3
- data/lib/tree_haver/library_path_utils.rb +7 -7
- data/lib/tree_haver/node.rb +40 -31
- data/lib/tree_haver/parser.rb +44 -37
- data/lib/tree_haver/parslet_grammar_finder.rb +19 -26
- data/lib/tree_haver/path_validator.rb +39 -36
- data/lib/tree_haver/peg_backends.rb +76 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +23 -1363
- data/lib/tree_haver/rspec.rb +1 -31
- data/lib/tree_haver/tree.rb +16 -7
- data/lib/tree_haver/version.rb +5 -14
- data/lib/tree_haver.rb +536 -1240
- data/sig/tree_haver.rbs +1 -229
- data.tar.gz.sig +0 -0
- metadata +154 -70
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -1393
- 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/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/tree_haver/base.rb +0 -12
- data/lib/tree_haver/compat.rb +0 -43
- data/lib/tree_haver/rspec/testable_node.rb +0 -217
- 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
|
@@ -81,28 +81,29 @@ module TreeHaver
|
|
|
81
81
|
# @note Returns false on TruffleRuby because TruffleRuby's FFI doesn't support
|
|
82
82
|
# STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)
|
|
83
83
|
def ffi_gem_available?
|
|
84
|
-
return @loaded if @load_attempted
|
|
85
|
-
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
84
|
+
return @loaded if @load_attempted
|
|
86
85
|
|
|
87
|
-
@
|
|
86
|
+
@load_attempted = true
|
|
87
|
+
|
|
88
|
+
@loaded = begin
|
|
88
89
|
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
89
90
|
# which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
|
|
90
|
-
# :
|
|
91
|
-
if RUBY_ENGINE ==
|
|
91
|
+
# simplecov:disable TruffleRuby returns false early - subsequent FFI code paths unreachable on TruffleRuby
|
|
92
|
+
if RUBY_ENGINE == 'truffleruby'
|
|
92
93
|
false
|
|
93
|
-
# :
|
|
94
|
+
# simplecov:enable
|
|
94
95
|
else
|
|
95
|
-
require
|
|
96
|
+
require 'ffi'
|
|
96
97
|
true
|
|
97
98
|
end
|
|
98
99
|
rescue LoadError
|
|
99
100
|
false
|
|
100
|
-
# :
|
|
101
|
+
# simplecov:disable defensive code - StandardError during require is extremely rare
|
|
101
102
|
rescue StandardError
|
|
102
103
|
false
|
|
103
|
-
# :
|
|
104
|
+
# simplecov:enable
|
|
104
105
|
end
|
|
105
|
-
@loaded
|
|
106
|
+
@loaded
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
# Reset the load state (primarily for testing)
|
|
@@ -110,8 +111,8 @@ module TreeHaver
|
|
|
110
111
|
# @return [void]
|
|
111
112
|
# @api private
|
|
112
113
|
def reset!
|
|
113
|
-
@load_attempted = false
|
|
114
|
-
@loaded = false
|
|
114
|
+
@load_attempted = false
|
|
115
|
+
@loaded = false
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# Get capabilities supported by this backend
|
|
@@ -119,15 +120,17 @@ module TreeHaver
|
|
|
119
120
|
# @return [Hash{Symbol => Object}] capability map
|
|
120
121
|
# @example
|
|
121
122
|
# TreeHaver::Backends::FFI.capabilities
|
|
122
|
-
# # => { backend: :ffi, parse: true, query: false, bytes_field: true }
|
|
123
|
+
# # => { backend: :ffi, parse: true, query: false, bytes_field: true, comment_support: :nodes_only }
|
|
123
124
|
def capabilities
|
|
124
125
|
return {} unless available?
|
|
126
|
+
|
|
125
127
|
{
|
|
126
128
|
backend: :ffi,
|
|
127
129
|
parse: true,
|
|
128
130
|
query: false, # Query API not yet implemented in FFI backend
|
|
129
131
|
bytes_field: true,
|
|
130
132
|
incremental: false,
|
|
133
|
+
comment_support: :nodes_only
|
|
131
134
|
}
|
|
132
135
|
end
|
|
133
136
|
end
|
|
@@ -149,9 +152,7 @@ module TreeHaver
|
|
|
149
152
|
def ensure_ffi_extended!
|
|
150
153
|
return true if @ffi_extended
|
|
151
154
|
|
|
152
|
-
unless FFI.ffi_gem_available?
|
|
153
|
-
raise TreeHaver::NotAvailable, "FFI gem is not available"
|
|
154
|
-
end
|
|
155
|
+
raise TreeHaver::NotAvailable, 'FFI gem is not available' unless FFI.ffi_gem_available?
|
|
155
156
|
|
|
156
157
|
extend(::FFI::Library)
|
|
157
158
|
|
|
@@ -169,9 +170,9 @@ module TreeHaver
|
|
|
169
170
|
# Mirrors the C struct layout: struct { uint32_t row; uint32_t column; }
|
|
170
171
|
ts_point_class = Class.new(::FFI::Struct) do
|
|
171
172
|
layout :row,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
:uint32,
|
|
174
|
+
:column,
|
|
175
|
+
:uint32
|
|
175
176
|
end
|
|
176
177
|
const_set(:TSPoint, ts_point_class)
|
|
177
178
|
typedef(ts_point_class.by_value, :ts_point)
|
|
@@ -186,11 +187,11 @@ module TreeHaver
|
|
|
186
187
|
# Mirrors the C struct layout used by tree-sitter
|
|
187
188
|
ts_node_class = Class.new(::FFI::Struct) do
|
|
188
189
|
layout :context,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
[:uint32, 4],
|
|
191
|
+
:id,
|
|
192
|
+
:pointer,
|
|
193
|
+
:tree,
|
|
194
|
+
:pointer
|
|
194
195
|
end
|
|
195
196
|
const_set(:TSNode, ts_node_class)
|
|
196
197
|
typedef(ts_node_class.by_value, :ts_node)
|
|
@@ -212,12 +213,12 @@ module TreeHaver
|
|
|
212
213
|
# @return [Array<String>] list of library names to try
|
|
213
214
|
def lib_candidates
|
|
214
215
|
[
|
|
215
|
-
ENV[
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
216
|
+
ENV['TREE_SITTER_RUNTIME_LIB'],
|
|
217
|
+
'tree-sitter',
|
|
218
|
+
'libtree-sitter.so.0',
|
|
219
|
+
'libtree-sitter.so',
|
|
220
|
+
'libtree-sitter.dylib',
|
|
221
|
+
'libtree-sitter.dll'
|
|
221
222
|
].compact
|
|
222
223
|
end
|
|
223
224
|
|
|
@@ -234,11 +235,11 @@ module TreeHaver
|
|
|
234
235
|
ensure_ffi_extended!
|
|
235
236
|
|
|
236
237
|
# Warn about potential conflicts with MRI backend
|
|
237
|
-
if defined?(::TreeSitter) && defined?(::TreeSitter::Parser)
|
|
238
|
-
warn(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
if defined?(::TreeSitter) && defined?(::TreeSitter::Parser) && $VERBOSE
|
|
239
|
+
warn('TreeHaver: FFI backend loading after ruby_tree_sitter (MRI backend). ' \
|
|
240
|
+
'This may cause symbol conflicts due to different libtree-sitter versions. ' \
|
|
241
|
+
'Consider using only one backend per process, or set TREE_SITTER_RUNTIME_LIB ' \
|
|
242
|
+
'to match the version used by ruby_tree_sitter.')
|
|
242
243
|
end
|
|
243
244
|
|
|
244
245
|
last_error = nil
|
|
@@ -249,21 +250,21 @@ module TreeHaver
|
|
|
249
250
|
lib_loaded = true
|
|
250
251
|
break
|
|
251
252
|
rescue LoadError => e
|
|
252
|
-
#
|
|
253
|
+
# NOTE: FFI::NotFoundError inherits from LoadError, so it's caught here too
|
|
253
254
|
last_error = e
|
|
254
255
|
end
|
|
255
256
|
|
|
256
257
|
unless lib_loaded
|
|
257
|
-
# :
|
|
258
|
-
tried = candidates.join(
|
|
259
|
-
env_hint = ENV[
|
|
258
|
+
# simplecov:disable
|
|
259
|
+
tried = candidates.join(', ')
|
|
260
|
+
env_hint = ENV['TREE_SITTER_RUNTIME_LIB'] ? " TREE_SITTER_RUNTIME_LIB=#{ENV['TREE_SITTER_RUNTIME_LIB']}." : ''
|
|
260
261
|
msg = if last_error
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
"Could not load libtree-sitter (tried: #{tried}).#{env_hint} #{last_error.class}: #{last_error.message}"
|
|
263
|
+
else
|
|
264
|
+
"Could not load libtree-sitter (tried: #{tried}).#{env_hint}"
|
|
265
|
+
end
|
|
265
266
|
raise TreeHaver::NotAvailable, msg
|
|
266
|
-
# :
|
|
267
|
+
# simplecov:enable
|
|
267
268
|
end
|
|
268
269
|
|
|
269
270
|
# Attach functions after lib is selected
|
|
@@ -271,16 +272,16 @@ module TreeHaver
|
|
|
271
272
|
# so these attach_function calls will fail on TruffleRuby.
|
|
272
273
|
attach_function(:ts_parser_new, [], :pointer)
|
|
273
274
|
attach_function(:ts_parser_delete, [:pointer], :void)
|
|
274
|
-
attach_function(:ts_parser_set_language, [
|
|
275
|
-
attach_function(:ts_parser_parse_string, [
|
|
275
|
+
attach_function(:ts_parser_set_language, %i[pointer pointer], :bool)
|
|
276
|
+
attach_function(:ts_parser_parse_string, %i[pointer pointer string uint32], :pointer)
|
|
276
277
|
|
|
277
278
|
attach_function(:ts_tree_delete, [:pointer], :void)
|
|
278
279
|
attach_function(:ts_tree_root_node, [:pointer], :ts_node)
|
|
279
280
|
|
|
280
281
|
attach_function(:ts_node_type, [:ts_node], :string)
|
|
281
282
|
attach_function(:ts_node_child_count, [:ts_node], :uint32)
|
|
282
|
-
attach_function(:ts_node_child, [
|
|
283
|
-
attach_function(:ts_node_child_by_field_name, [
|
|
283
|
+
attach_function(:ts_node_child, %i[ts_node uint32], :ts_node)
|
|
284
|
+
attach_function(:ts_node_child_by_field_name, %i[ts_node string uint32], :ts_node)
|
|
284
285
|
attach_function(:ts_node_start_byte, [:ts_node], :uint32)
|
|
285
286
|
attach_function(:ts_node_end_byte, [:ts_node], :uint32)
|
|
286
287
|
attach_function(:ts_node_start_point, [:ts_node], :ts_point)
|
|
@@ -296,14 +297,14 @@ module TreeHaver
|
|
|
296
297
|
attach_function(:ts_node_prev_sibling, [:ts_node], :ts_node)
|
|
297
298
|
attach_function(:ts_node_next_named_sibling, [:ts_node], :ts_node)
|
|
298
299
|
attach_function(:ts_node_prev_named_sibling, [:ts_node], :ts_node)
|
|
299
|
-
attach_function(:ts_node_named_child, [
|
|
300
|
+
attach_function(:ts_node_named_child, %i[ts_node uint32], :ts_node)
|
|
300
301
|
attach_function(:ts_node_named_child_count, [:ts_node], :uint32)
|
|
301
302
|
|
|
302
303
|
# Descendant lookup functions
|
|
303
|
-
attach_function(:ts_node_descendant_for_byte_range, [
|
|
304
|
-
attach_function(:ts_node_descendant_for_point_range, [
|
|
305
|
-
attach_function(:ts_node_named_descendant_for_byte_range, [
|
|
306
|
-
attach_function(:ts_node_named_descendant_for_point_range, [
|
|
304
|
+
attach_function(:ts_node_descendant_for_byte_range, %i[ts_node uint32 uint32], :ts_node)
|
|
305
|
+
attach_function(:ts_node_descendant_for_point_range, %i[ts_node ts_point ts_point], :ts_node)
|
|
306
|
+
attach_function(:ts_node_named_descendant_for_byte_range, %i[ts_node uint32 uint32], :ts_node)
|
|
307
|
+
attach_function(:ts_node_named_descendant_for_point_range, %i[ts_node ts_point ts_point], :ts_node)
|
|
307
308
|
|
|
308
309
|
# Only mark as fully loaded after all attach_function calls succeed
|
|
309
310
|
@loaded = true
|
|
@@ -368,10 +369,10 @@ module TreeHaver
|
|
|
368
369
|
return unless other.backend == @backend
|
|
369
370
|
|
|
370
371
|
# Compare by path first, then symbol
|
|
371
|
-
cmp = (@path ||
|
|
372
|
+
cmp = (@path || '') <=> (other.path || '')
|
|
372
373
|
return cmp if cmp.nonzero?
|
|
373
374
|
|
|
374
|
-
(@symbol ||
|
|
375
|
+
(@symbol || '') <=> (other.symbol || '')
|
|
375
376
|
end
|
|
376
377
|
|
|
377
378
|
# Hash value for this language (for use in Sets/Hashes)
|
|
@@ -381,7 +382,7 @@ module TreeHaver
|
|
|
381
382
|
end
|
|
382
383
|
|
|
383
384
|
# Alias eql? to ==
|
|
384
|
-
|
|
385
|
+
alias eql? ==
|
|
385
386
|
|
|
386
387
|
# Get the language name
|
|
387
388
|
#
|
|
@@ -391,7 +392,7 @@ module TreeHaver
|
|
|
391
392
|
def language_name
|
|
392
393
|
# Try to derive from symbol (e.g., "tree_sitter_toml" -> :toml)
|
|
393
394
|
if @symbol
|
|
394
|
-
name = @symbol.to_s.sub(/^tree_sitter_/,
|
|
395
|
+
name = @symbol.to_s.sub(/^tree_sitter_/, '')
|
|
395
396
|
return name.to_sym
|
|
396
397
|
end
|
|
397
398
|
|
|
@@ -405,7 +406,7 @@ module TreeHaver
|
|
|
405
406
|
end
|
|
406
407
|
|
|
407
408
|
# Alias for language_name (API compatibility)
|
|
408
|
-
|
|
409
|
+
alias name language_name
|
|
409
410
|
|
|
410
411
|
# Convert to FFI pointer for passing to native functions
|
|
411
412
|
#
|
|
@@ -434,20 +435,20 @@ module TreeHaver
|
|
|
434
435
|
# )
|
|
435
436
|
class << self
|
|
436
437
|
def from_library(path, symbol: nil, name: nil)
|
|
437
|
-
raise TreeHaver::NotAvailable,
|
|
438
|
+
raise TreeHaver::NotAvailable, 'FFI not available' unless Backends::FFI.available?
|
|
438
439
|
|
|
439
440
|
# Check for MRI backend conflict BEFORE loading the grammar
|
|
440
441
|
# If ruby_tree_sitter has already loaded this grammar file, the dynamic
|
|
441
442
|
# linker will return the cached library with symbols resolved against
|
|
442
443
|
# MRI's statically-linked tree-sitter, causing segfaults when FFI
|
|
443
444
|
# tries to use the pointer with its dynamically-linked libtree-sitter.
|
|
444
|
-
if
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
warn(
|
|
449
|
-
|
|
450
|
-
|
|
445
|
+
# MRI backend has been loaded - check if it might have loaded this grammar
|
|
446
|
+
# We can't reliably detect which grammars MRI loaded, so we warn and
|
|
447
|
+
# attempt to proceed. The segfault will occur when setting language on parser.
|
|
448
|
+
if defined?(::TreeSitter::Language) && $VERBOSE
|
|
449
|
+
warn('TreeHaver: FFI backend loading grammar after ruby_tree_sitter (MRI backend). ' \
|
|
450
|
+
'This may cause segfaults due to tree-sitter symbol conflicts. ' \
|
|
451
|
+
'For reliable operation, use only one backend per process.')
|
|
451
452
|
end
|
|
452
453
|
|
|
453
454
|
# Ensure the core libtree-sitter runtime is loaded first so
|
|
@@ -462,10 +463,10 @@ module TreeHaver
|
|
|
462
463
|
# (RTLD_NOW | RTLD_GLOBAL). If those constants are not present
|
|
463
464
|
# fall back to RTLD_LAZY for maximum compatibility.
|
|
464
465
|
flags = if defined?(::FFI::DynamicLibrary::RTLD_NOW) && defined?(::FFI::DynamicLibrary::RTLD_GLOBAL)
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
466
|
+
::FFI::DynamicLibrary::RTLD_NOW | ::FFI::DynamicLibrary::RTLD_GLOBAL
|
|
467
|
+
else
|
|
468
|
+
::FFI::DynamicLibrary::RTLD_LAZY
|
|
469
|
+
end
|
|
469
470
|
|
|
470
471
|
dl = ::FFI::DynamicLibrary.open(path, flags)
|
|
471
472
|
rescue LoadError, RuntimeError => e
|
|
@@ -473,16 +474,16 @@ module TreeHaver
|
|
|
473
474
|
raise TreeHaver::NotAvailable, "Could not open language library at #{path}: #{e.message}"
|
|
474
475
|
end
|
|
475
476
|
|
|
476
|
-
requested = symbol || ENV[
|
|
477
|
+
requested = symbol || ENV['TREE_SITTER_LANG_SYMBOL']
|
|
477
478
|
# Use shared utility for consistent symbol derivation across backends
|
|
478
479
|
guessed_symbol = LibraryPathUtils.derive_symbol_from_path(path)
|
|
479
480
|
# If an override was provided (arg or ENV), treat it as strict and do not fall back.
|
|
480
481
|
# Only when no override is provided do we attempt guessed and default candidates.
|
|
481
482
|
candidates = if requested && !requested.to_s.empty?
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
483
|
+
[requested]
|
|
484
|
+
else
|
|
485
|
+
[guessed_symbol, 'tree_sitter_toml'].compact.uniq
|
|
486
|
+
end
|
|
486
487
|
|
|
487
488
|
func = nil
|
|
488
489
|
last_err = nil
|
|
@@ -495,22 +496,24 @@ module TreeHaver
|
|
|
495
496
|
end
|
|
496
497
|
unless func
|
|
497
498
|
env_used = []
|
|
498
|
-
env_used << "TREE_SITTER_LANG_SYMBOL=#{ENV[
|
|
499
|
-
detail = env_used.empty? ?
|
|
500
|
-
raise TreeHaver::NotAvailable,
|
|
499
|
+
env_used << "TREE_SITTER_LANG_SYMBOL=#{ENV['TREE_SITTER_LANG_SYMBOL']}" if ENV['TREE_SITTER_LANG_SYMBOL']
|
|
500
|
+
detail = env_used.empty? ? '' : " Env overrides: #{env_used.join(', ')}."
|
|
501
|
+
raise TreeHaver::NotAvailable,
|
|
502
|
+
"Could not resolve language symbol in #{path} (tried: #{candidates.join(', ')}).#{detail} #{last_err&.message}"
|
|
501
503
|
end
|
|
502
504
|
|
|
503
505
|
# Only ensure the core lib is loaded when we actually need to interact with it
|
|
504
506
|
# (e.g., during parsing). Creating the Language handle does not require core to be loaded.
|
|
505
507
|
ptr = func.call
|
|
506
508
|
raise TreeHaver::NotAvailable, "Language factory returned NULL for #{path}" if ptr.null?
|
|
509
|
+
|
|
507
510
|
# Pass the opened DynamicLibrary into the Language instance so the
|
|
508
511
|
# library handle remains alive for the lifetime of the Language.
|
|
509
512
|
new(ptr, dl, path: path, symbol: symbol)
|
|
510
513
|
end
|
|
511
514
|
|
|
512
515
|
# Backward-compatible alias
|
|
513
|
-
|
|
516
|
+
alias from_path from_library
|
|
514
517
|
end
|
|
515
518
|
end
|
|
516
519
|
|
|
@@ -522,13 +525,13 @@ module TreeHaver
|
|
|
522
525
|
#
|
|
523
526
|
# @raise [TreeHaver::NotAvailable] if FFI not available or parser creation fails
|
|
524
527
|
def initialize
|
|
525
|
-
raise TreeHaver::NotAvailable,
|
|
528
|
+
raise TreeHaver::NotAvailable, 'FFI not available' unless Backends::FFI.available?
|
|
526
529
|
|
|
527
530
|
Native.try_load!
|
|
528
531
|
@parser = Native.ts_parser_new
|
|
529
|
-
raise TreeHaver::NotAvailable,
|
|
532
|
+
raise TreeHaver::NotAvailable, 'Failed to create ts_parser' if @parser.null?
|
|
530
533
|
|
|
531
|
-
#
|
|
534
|
+
# NOTE: We intentionally do NOT register a finalizer here because:
|
|
532
535
|
# 1. ts_parser_delete can segfault if called during certain GC scenarios
|
|
533
536
|
# 2. The native library may be unloaded before finalizers run
|
|
534
537
|
# 3. Parser cleanup happens automatically on process exit
|
|
@@ -552,17 +555,17 @@ module TreeHaver
|
|
|
552
555
|
# Defensive check: ensure we received an FFI Language wrapper
|
|
553
556
|
unless lang.is_a?(Language)
|
|
554
557
|
raise TreeHaver::NotAvailable,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
+
"FFI backend expected FFI::Language wrapper, got #{lang.class}. " \
|
|
559
|
+
'This usually means TreeHaver::Parser#unwrap_language passed the wrong type. ' \
|
|
560
|
+
'Check that language caching respects backend boundaries.'
|
|
558
561
|
end
|
|
559
562
|
|
|
560
563
|
# Additional check: verify the language is actually for FFI backend
|
|
561
564
|
if lang.respond_to?(:backend) && lang.backend != :ffi
|
|
562
565
|
raise TreeHaver::NotAvailable,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
+
"FFI backend received Language for wrong backend: #{lang.backend}. " \
|
|
567
|
+
"Expected :ffi backend. Class: #{lang.class}. " \
|
|
568
|
+
"Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}"
|
|
566
569
|
end
|
|
567
570
|
|
|
568
571
|
# Verify the DynamicLibrary is still valid (not GC'd)
|
|
@@ -570,8 +573,8 @@ module TreeHaver
|
|
|
570
573
|
lib = lang.instance_variable_get(:@library)
|
|
571
574
|
if lib.nil?
|
|
572
575
|
raise TreeHaver::NotAvailable,
|
|
573
|
-
|
|
574
|
-
|
|
576
|
+
'FFI Language has no library reference. The dynamic library may have been unloaded. ' \
|
|
577
|
+
"Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}"
|
|
575
578
|
end
|
|
576
579
|
|
|
577
580
|
# Verify the language has a valid pointer
|
|
@@ -580,9 +583,9 @@ module TreeHaver
|
|
|
580
583
|
# Check ptr is actually an FFI::Pointer
|
|
581
584
|
unless ptr.is_a?(::FFI::Pointer)
|
|
582
585
|
raise TreeHaver::NotAvailable,
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
+
"FFI Language#to_ptr returned #{ptr.class}, expected FFI::Pointer. " \
|
|
587
|
+
"Language class: #{lang.class}. " \
|
|
588
|
+
"Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}"
|
|
586
589
|
end
|
|
587
590
|
|
|
588
591
|
ptr_address = ptr.address
|
|
@@ -590,25 +593,25 @@ module TreeHaver
|
|
|
590
593
|
# Check for NULL (0x0)
|
|
591
594
|
if ptr.nil? || ptr_address.zero?
|
|
592
595
|
raise TreeHaver::NotAvailable,
|
|
593
|
-
|
|
594
|
-
|
|
596
|
+
'FFI Language has NULL pointer. Language may not have loaded correctly. ' \
|
|
597
|
+
"Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}"
|
|
595
598
|
end
|
|
596
599
|
|
|
597
600
|
# Check for small invalid addresses (< 4KB are typically unmapped memory)
|
|
598
601
|
# Common invalid addresses like 0x40 (64) indicate corrupted or uninitialized pointers
|
|
599
602
|
if ptr_address < 4096
|
|
600
603
|
raise TreeHaver::NotAvailable,
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
+
"FFI Language has invalid pointer (address 0x#{ptr_address.to_s(16)}). " \
|
|
605
|
+
'This usually indicates the language library was unloaded or never loaded correctly. ' \
|
|
606
|
+
"Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}"
|
|
604
607
|
end
|
|
605
608
|
|
|
606
|
-
#
|
|
609
|
+
# NOTE: MRI backend conflict is now handled by TreeHaver::BackendConflict
|
|
607
610
|
# at a higher level (in TreeHaver.resolve_backend_module)
|
|
608
611
|
|
|
609
612
|
# lang is a wrapped FFI::Language that has to_ptr method
|
|
610
613
|
ok = Native.ts_parser_set_language(@parser, ptr)
|
|
611
|
-
raise TreeHaver::NotAvailable,
|
|
614
|
+
raise TreeHaver::NotAvailable, 'Failed to set language on parser' unless ok
|
|
612
615
|
|
|
613
616
|
lang # rubocop:disable Lint/Void (intentional return value)
|
|
614
617
|
end
|
|
@@ -621,7 +624,7 @@ module TreeHaver
|
|
|
621
624
|
def parse(source)
|
|
622
625
|
src = String(source)
|
|
623
626
|
tree_ptr = Native.ts_parser_parse_string(@parser, ::FFI::Pointer::NULL, src, src.bytesize)
|
|
624
|
-
raise TreeHaver::NotAvailable,
|
|
627
|
+
raise TreeHaver::NotAvailable, 'Parse returned NULL' if tree_ptr.null?
|
|
625
628
|
|
|
626
629
|
# Return raw FFI::Tree - TreeHaver::Parser will wrap it
|
|
627
630
|
Tree.new(tree_ptr)
|
|
@@ -739,6 +742,7 @@ module TreeHaver
|
|
|
739
742
|
# @return [Node, nil] child node or nil if index out of bounds
|
|
740
743
|
def child(index)
|
|
741
744
|
return if index >= child_count || index < 0
|
|
745
|
+
|
|
742
746
|
child_node = Native.ts_node_child(@val, index)
|
|
743
747
|
Node.new(child_node)
|
|
744
748
|
end
|