tree_haver 3.1.2 → 3.2.1
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 +227 -2
- data/README.md +391 -357
- data/lib/tree_haver/backends/citrus.rb +7 -1
- data/lib/tree_haver/backends/ffi.rb +80 -66
- data/lib/tree_haver/backends/java.rb +11 -4
- data/lib/tree_haver/backends/mri.rb +37 -21
- data/lib/tree_haver/backends/rust.rb +17 -5
- data/lib/tree_haver/citrus_grammar_finder.rb +57 -9
- data/lib/tree_haver/grammar_finder.rb +4 -1
- data/lib/tree_haver/language.rb +255 -0
- data/lib/tree_haver/library_path_utils.rb +80 -0
- data/lib/tree_haver/node.rb +4 -1
- data/lib/tree_haver/parser.rb +352 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +406 -226
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +128 -560
- data.tar.gz.sig +0 -0
- metadata +7 -4
- metadata.gz.sig +0 -0
|
@@ -451,8 +451,14 @@ module TreeHaver
|
|
|
451
451
|
private
|
|
452
452
|
|
|
453
453
|
def calculate_point(offset)
|
|
454
|
+
return {row: 0, column: 0} if offset <= 0
|
|
455
|
+
|
|
454
456
|
lines_before = @source[0...offset].count("\n")
|
|
455
|
-
|
|
457
|
+
# Find the newline before this offset (or -1 if we're on line 0)
|
|
458
|
+
line_start = if offset > 0
|
|
459
|
+
@source.rindex("\n", offset - 1)
|
|
460
|
+
end
|
|
461
|
+
line_start ||= -1
|
|
456
462
|
column = offset - line_start - 1
|
|
457
463
|
{row: lines_before, column: column}
|
|
458
464
|
end
|
|
@@ -5,8 +5,7 @@ module TreeHaver
|
|
|
5
5
|
# FFI-based backend for calling libtree-sitter directly
|
|
6
6
|
#
|
|
7
7
|
# This backend uses Ruby FFI (JNR-FFI on JRuby) to call the native tree-sitter
|
|
8
|
-
# C library without requiring MRI C extensions.
|
|
9
|
-
# JRuby, TruffleRuby, and other Ruby implementations that support FFI.
|
|
8
|
+
# C library without requiring MRI C extensions.
|
|
10
9
|
#
|
|
11
10
|
# The FFI backend currently supports:
|
|
12
11
|
# - Parsing source code
|
|
@@ -16,28 +15,87 @@ module TreeHaver
|
|
|
16
15
|
# Not yet supported:
|
|
17
16
|
# - Query API (tree-sitter queries/patterns)
|
|
18
17
|
#
|
|
18
|
+
# == Platform Compatibility
|
|
19
|
+
#
|
|
20
|
+
# - MRI Ruby: ✓ Full support
|
|
21
|
+
# - JRuby: ✓ Full support (uses JNR-FFI)
|
|
22
|
+
# - TruffleRuby: ✗ TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
23
|
+
# (used by ts_tree_root_node, ts_node_child, ts_node_start_point, etc.)
|
|
24
|
+
#
|
|
19
25
|
# @note Requires the `ffi` gem and libtree-sitter shared library to be installed
|
|
20
26
|
# @see https://github.com/ffi/ffi Ruby FFI
|
|
21
27
|
# @see https://tree-sitter.github.io/tree-sitter/ tree-sitter
|
|
22
28
|
module FFI
|
|
23
|
-
#
|
|
29
|
+
# Module-level availability and capability methods
|
|
24
30
|
#
|
|
25
|
-
#
|
|
26
|
-
# polluting the environment at load time.
|
|
31
|
+
# These methods provide API consistency with other backends.
|
|
27
32
|
class << self
|
|
28
|
-
# Check if the FFI
|
|
29
|
-
#
|
|
33
|
+
# Check if the FFI backend is available
|
|
34
|
+
#
|
|
35
|
+
# The FFI backend requires:
|
|
36
|
+
# - The ffi gem to be installed
|
|
37
|
+
# - NOT running on TruffleRuby (STRUCT_BY_VALUE limitation)
|
|
38
|
+
# - MRI backend (ruby_tree_sitter) not already loaded (symbol conflicts)
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean] true if FFI backend can be used
|
|
41
|
+
# @example
|
|
42
|
+
# if TreeHaver::Backends::FFI.available?
|
|
43
|
+
# puts "FFI backend is ready"
|
|
44
|
+
# end
|
|
45
|
+
def available?
|
|
46
|
+
return false unless ffi_gem_available?
|
|
47
|
+
|
|
48
|
+
# Check if MRI backend has been loaded (which blocks FFI)
|
|
49
|
+
!defined?(::TreeSitter::Parser)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Check if the FFI gem can be loaded and is usable for tree-sitter
|
|
53
|
+
#
|
|
54
|
+
# @return [Boolean] true if FFI gem can be loaded and works with tree-sitter
|
|
30
55
|
# @api private
|
|
56
|
+
# @note Returns false on TruffleRuby because TruffleRuby's FFI doesn't support
|
|
57
|
+
# STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)
|
|
31
58
|
def ffi_gem_available?
|
|
32
|
-
return @
|
|
59
|
+
return @loaded if @load_attempted
|
|
60
|
+
|
|
61
|
+
@load_attempted = true
|
|
62
|
+
@loaded = begin
|
|
63
|
+
# TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
|
|
64
|
+
# which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
|
|
65
|
+
return false if RUBY_ENGINE == "truffleruby"
|
|
33
66
|
|
|
34
|
-
@ffi_gem_available = begin
|
|
35
67
|
require "ffi"
|
|
36
68
|
true
|
|
37
69
|
rescue LoadError
|
|
38
70
|
false
|
|
39
71
|
end
|
|
40
72
|
end
|
|
73
|
+
|
|
74
|
+
# Reset the load state (primarily for testing)
|
|
75
|
+
#
|
|
76
|
+
# @return [void]
|
|
77
|
+
# @api private
|
|
78
|
+
def reset!
|
|
79
|
+
@load_attempted = false
|
|
80
|
+
@loaded = false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get capabilities supported by this backend
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash{Symbol => Object}] capability map
|
|
86
|
+
# @example
|
|
87
|
+
# TreeHaver::Backends::FFI.capabilities
|
|
88
|
+
# # => { backend: :ffi, parse: true, query: false, bytes_field: true }
|
|
89
|
+
def capabilities
|
|
90
|
+
return {} unless available?
|
|
91
|
+
{
|
|
92
|
+
backend: :ffi,
|
|
93
|
+
parse: true,
|
|
94
|
+
query: false, # Query API not yet implemented in FFI backend
|
|
95
|
+
bytes_field: true,
|
|
96
|
+
incremental: false,
|
|
97
|
+
}
|
|
98
|
+
end
|
|
41
99
|
end
|
|
42
100
|
|
|
43
101
|
# Native FFI bindings to libtree-sitter
|
|
@@ -151,15 +209,16 @@ module TreeHaver
|
|
|
151
209
|
|
|
152
210
|
last_error = nil
|
|
153
211
|
candidates = lib_candidates
|
|
212
|
+
lib_loaded = false
|
|
154
213
|
candidates.each do |name|
|
|
155
214
|
ffi_lib(name)
|
|
156
|
-
|
|
215
|
+
lib_loaded = true
|
|
157
216
|
break
|
|
158
217
|
rescue ::FFI::NotFoundError, LoadError => e
|
|
159
218
|
last_error = e
|
|
160
219
|
end
|
|
161
220
|
|
|
162
|
-
unless
|
|
221
|
+
unless lib_loaded
|
|
163
222
|
# :nocov:
|
|
164
223
|
tried = candidates.join(", ")
|
|
165
224
|
env_hint = ENV["TREE_SITTER_RUNTIME_LIB"] ? " TREE_SITTER_RUNTIME_LIB=#{ENV["TREE_SITTER_RUNTIME_LIB"]}." : ""
|
|
@@ -173,6 +232,8 @@ module TreeHaver
|
|
|
173
232
|
end
|
|
174
233
|
|
|
175
234
|
# Attach functions after lib is selected
|
|
235
|
+
# Note: TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types,
|
|
236
|
+
# so these attach_function calls will fail on TruffleRuby.
|
|
176
237
|
attach_function(:ts_parser_new, [], :pointer)
|
|
177
238
|
attach_function(:ts_parser_delete, [:pointer], :void)
|
|
178
239
|
attach_function(:ts_parser_set_language, [:pointer, :pointer], :bool)
|
|
@@ -190,6 +251,9 @@ module TreeHaver
|
|
|
190
251
|
attach_function(:ts_node_end_point, [:ts_node], :ts_point)
|
|
191
252
|
attach_function(:ts_node_is_null, [:ts_node], :bool)
|
|
192
253
|
attach_function(:ts_node_is_named, [:ts_node], :bool)
|
|
254
|
+
|
|
255
|
+
# Only mark as fully loaded after all attach_function calls succeed
|
|
256
|
+
@loaded = true
|
|
193
257
|
end
|
|
194
258
|
|
|
195
259
|
def loaded?
|
|
@@ -198,57 +262,6 @@ module TreeHaver
|
|
|
198
262
|
end
|
|
199
263
|
end
|
|
200
264
|
|
|
201
|
-
class << self
|
|
202
|
-
# Check if the FFI backend is available
|
|
203
|
-
#
|
|
204
|
-
# Returns true if:
|
|
205
|
-
# 1. The `ffi` gem is present
|
|
206
|
-
# 2. MRI backend (ruby_tree_sitter) has NOT been loaded
|
|
207
|
-
#
|
|
208
|
-
# FFI and MRI backends conflict at the libtree-sitter level.
|
|
209
|
-
# Once MRI loads, using FFI will cause segfaults.
|
|
210
|
-
#
|
|
211
|
-
# @return [Boolean] true if FFI backend can be used
|
|
212
|
-
# @example
|
|
213
|
-
# if TreeHaver::Backends::FFI.available?
|
|
214
|
-
# puts "FFI backend is ready"
|
|
215
|
-
# end
|
|
216
|
-
def available?
|
|
217
|
-
return false unless TreeHaver::Backends::FFI.ffi_gem_available?
|
|
218
|
-
|
|
219
|
-
# Check if MRI backend has been loaded (which blocks FFI)
|
|
220
|
-
!defined?(::TreeSitter::Parser)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# Reset the load state (primarily for testing)
|
|
224
|
-
#
|
|
225
|
-
# Note: FFI backend doesn't maintain load state like other backends,
|
|
226
|
-
# but this method is provided for API consistency.
|
|
227
|
-
#
|
|
228
|
-
# @return [void]
|
|
229
|
-
# @api private
|
|
230
|
-
def reset!
|
|
231
|
-
# FFI backend uses constant-time availability check, no state to reset
|
|
232
|
-
nil
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Get capabilities supported by this backend
|
|
236
|
-
#
|
|
237
|
-
# @return [Hash{Symbol => Object}] capability map
|
|
238
|
-
# @example
|
|
239
|
-
# TreeHaver::Backends::FFI.capabilities
|
|
240
|
-
# # => { backend: :ffi, parse: true, query: false, bytes_field: true }
|
|
241
|
-
def capabilities
|
|
242
|
-
return {} unless available?
|
|
243
|
-
{
|
|
244
|
-
backend: :ffi,
|
|
245
|
-
parse: true,
|
|
246
|
-
query: false,
|
|
247
|
-
bytes_field: true,
|
|
248
|
-
}
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
265
|
# Represents a tree-sitter language loaded via FFI
|
|
253
266
|
#
|
|
254
267
|
# Holds a pointer to a TSLanguage struct from a loaded shared library.
|
|
@@ -378,19 +391,20 @@ module TreeHaver
|
|
|
378
391
|
end
|
|
379
392
|
|
|
380
393
|
dl = ::FFI::DynamicLibrary.open(path, flags)
|
|
381
|
-
rescue LoadError => e
|
|
394
|
+
rescue LoadError, RuntimeError => e
|
|
395
|
+
# TruffleRuby raises RuntimeError instead of LoadError when a shared library cannot be opened
|
|
382
396
|
raise TreeHaver::NotAvailable, "Could not open language library at #{path}: #{e.message}"
|
|
383
397
|
end
|
|
384
398
|
|
|
385
399
|
requested = symbol || ENV["TREE_SITTER_LANG_SYMBOL"]
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
# Use shared utility for consistent symbol derivation across backends
|
|
401
|
+
guessed_symbol = LibraryPathUtils.derive_symbol_from_path(path)
|
|
388
402
|
# If an override was provided (arg or ENV), treat it as strict and do not fall back.
|
|
389
403
|
# Only when no override is provided do we attempt guessed and default candidates.
|
|
390
404
|
candidates = if requested && !requested.to_s.empty?
|
|
391
405
|
[requested]
|
|
392
406
|
else
|
|
393
|
-
[
|
|
407
|
+
[guessed_symbol, "tree_sitter_toml"].compact.uniq
|
|
394
408
|
end
|
|
395
409
|
|
|
396
410
|
func = nil
|
|
@@ -13,6 +13,12 @@ module TreeHaver
|
|
|
13
13
|
# - The Query API for pattern matching
|
|
14
14
|
# - Tree editing for incremental re-parsing
|
|
15
15
|
#
|
|
16
|
+
# == Platform Compatibility
|
|
17
|
+
#
|
|
18
|
+
# - MRI Ruby: ✗ Not available (no JVM)
|
|
19
|
+
# - JRuby: ✓ Full support (native Java integration)
|
|
20
|
+
# - TruffleRuby: ✗ Not available (java-tree-sitter requires JRuby's Java interop)
|
|
21
|
+
#
|
|
16
22
|
# == Installation
|
|
17
23
|
#
|
|
18
24
|
# 1. Download the JAR from Maven Central:
|
|
@@ -24,7 +30,6 @@ module TreeHaver
|
|
|
24
30
|
# 3. Use JRuby to run your code:
|
|
25
31
|
# jruby -e "require 'tree_haver'; puts TreeHaver::Backends::Java.available?"
|
|
26
32
|
#
|
|
27
|
-
# @note Only available on JRuby
|
|
28
33
|
# @see https://github.com/tree-sitter/java-tree-sitter source
|
|
29
34
|
# @see https://tree-sitter.github.io/java-tree-sitter java-tree-sitter documentation
|
|
30
35
|
# @see https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter Maven Central
|
|
@@ -311,9 +316,11 @@ module TreeHaver
|
|
|
311
316
|
def from_library(path, symbol: nil, name: nil)
|
|
312
317
|
raise TreeHaver::NotAvailable, "Java backend not available" unless Java.available?
|
|
313
318
|
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
sym = symbol ||
|
|
319
|
+
# Use shared utility for consistent symbol derivation across backends
|
|
320
|
+
# If symbol not provided, derive from name or path
|
|
321
|
+
sym = symbol || LibraryPathUtils.derive_symbol_from_path(path)
|
|
322
|
+
# If name was provided, use it to override the derived symbol
|
|
323
|
+
sym = "tree_sitter_#{name}" if name && !symbol
|
|
317
324
|
|
|
318
325
|
begin
|
|
319
326
|
arena = ::Java::JavaLangForeign::Arena.global
|
|
@@ -8,7 +8,12 @@ module TreeHaver
|
|
|
8
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
|
+
# == Platform Compatibility
|
|
12
|
+
#
|
|
13
|
+
# - MRI Ruby: ✓ Full support (fastest tree-sitter backend on MRI)
|
|
14
|
+
# - JRuby: ✗ Cannot load native C extensions (runs on JVM)
|
|
15
|
+
# - TruffleRuby: ✗ C extension not compatible with TruffleRuby
|
|
16
|
+
#
|
|
12
17
|
# @see https://github.com/Faveod/ruby-tree-sitter ruby_tree_sitter
|
|
13
18
|
module MRI
|
|
14
19
|
@load_attempted = false
|
|
@@ -37,6 +42,14 @@ module TreeHaver
|
|
|
37
42
|
def available?
|
|
38
43
|
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
39
44
|
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
45
|
+
|
|
46
|
+
# ruby_tree_sitter is a C extension that only works on MRI
|
|
47
|
+
# It doesn't work on JRuby or TruffleRuby
|
|
48
|
+
unless RUBY_ENGINE == "ruby"
|
|
49
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
50
|
+
return @loaded
|
|
51
|
+
end
|
|
52
|
+
|
|
40
53
|
begin
|
|
41
54
|
require "tree_sitter" # Note: gem is ruby_tree_sitter but requires tree_sitter
|
|
42
55
|
|
|
@@ -153,6 +166,21 @@ module TreeHaver
|
|
|
153
166
|
# lang = TreeHaver::Backends::MRI::Language.from_library("/path/to/lib.so", symbol: "tree_sitter_json")
|
|
154
167
|
class << self
|
|
155
168
|
def from_library(path, symbol: nil, name: nil)
|
|
169
|
+
# Derive symbol from path if not provided using shared utility
|
|
170
|
+
symbol ||= LibraryPathUtils.derive_symbol_from_path(path)
|
|
171
|
+
from_path(path, symbol: symbol, name: name)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
# Load a language from a shared library path (internal implementation)
|
|
177
|
+
#
|
|
178
|
+
# @param path [String] absolute path to the language shared library
|
|
179
|
+
# @param symbol [String] the exported symbol name (e.g., "tree_sitter_json")
|
|
180
|
+
# @param name [String, nil] optional language name
|
|
181
|
+
# @return [Language] wrapped language handle
|
|
182
|
+
# @api private
|
|
183
|
+
def from_path(path, symbol: nil, name: nil)
|
|
156
184
|
raise TreeHaver::NotAvailable, "ruby_tree_sitter not available" unless MRI.available?
|
|
157
185
|
|
|
158
186
|
# ruby_tree_sitter's TreeSitter::Language.load takes (language_name, path_to_so)
|
|
@@ -160,8 +188,8 @@ module TreeHaver
|
|
|
160
188
|
# NOT the full symbol name (e.g., NOT "tree_sitter_toml")
|
|
161
189
|
# and path_to_so is the full path to the .so file
|
|
162
190
|
#
|
|
163
|
-
# If name is not provided, derive it from symbol
|
|
164
|
-
language_name = name || symbol
|
|
191
|
+
# If name is not provided, derive it from symbol using shared utility
|
|
192
|
+
language_name = name || LibraryPathUtils.derive_language_name_from_symbol(symbol)
|
|
165
193
|
ts_lang = ::TreeSitter::Language.load(language_name, path)
|
|
166
194
|
new(ts_lang, path: path, symbol: symbol)
|
|
167
195
|
rescue NameError => e
|
|
@@ -177,16 +205,6 @@ module TreeHaver
|
|
|
177
205
|
raise # Re-raise if it's not a TreeSitter error
|
|
178
206
|
end
|
|
179
207
|
end
|
|
180
|
-
|
|
181
|
-
# Load a language from a shared library path (legacy method)
|
|
182
|
-
#
|
|
183
|
-
# @param path [String] absolute path to the language shared library
|
|
184
|
-
# @param symbol [String] the exported symbol name (e.g., "tree_sitter_json")
|
|
185
|
-
# @return [Language] wrapped language handle
|
|
186
|
-
# @deprecated Use {from_library} instead
|
|
187
|
-
def from_path(path, symbol: nil)
|
|
188
|
-
from_library(path, symbol: symbol)
|
|
189
|
-
end
|
|
190
208
|
end
|
|
191
209
|
end
|
|
192
210
|
|
|
@@ -216,19 +234,17 @@ module TreeHaver
|
|
|
216
234
|
|
|
217
235
|
# Set the language for this parser
|
|
218
236
|
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
# @param lang [::TreeSitter::Language] the language to use (already unwrapped)
|
|
223
|
-
# @return [::TreeSitter::Language] the language that was set
|
|
237
|
+
# @param lang [::TreeSitter::Language, TreeHaver::Backends::MRI::Language] the language to use
|
|
238
|
+
# @return [::TreeSitter::Language, TreeHaver::Backends::MRI::Language] the language that was set
|
|
224
239
|
# @raise [TreeHaver::NotAvailable] if setting language fails
|
|
225
240
|
def language=(lang)
|
|
226
|
-
#
|
|
227
|
-
|
|
241
|
+
# Unwrap if it's a TreeHaver wrapper
|
|
242
|
+
inner_lang = lang.respond_to?(:inner_language) ? lang.inner_language : lang
|
|
243
|
+
@parser.language = inner_lang
|
|
228
244
|
# Verify it was set
|
|
229
245
|
raise TreeHaver::NotAvailable, "Language not set correctly" if @parser.language.nil?
|
|
230
246
|
|
|
231
|
-
# Return the language object
|
|
247
|
+
# Return the original language object (wrapped or unwrapped)
|
|
232
248
|
lang
|
|
233
249
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
234
250
|
# TreeSitter errors inherit from Exception (not StandardError) in ruby_tree_sitter v2+
|
|
@@ -11,7 +11,12 @@ module TreeHaver
|
|
|
11
11
|
# tree_stump supports incremental parsing and the Query API, making it
|
|
12
12
|
# suitable for editor/IDE use cases where performance is critical.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# == Platform Compatibility
|
|
15
|
+
#
|
|
16
|
+
# - MRI Ruby: ✓ Full support
|
|
17
|
+
# - JRuby: ✗ Cannot load native extensions (runs on JVM)
|
|
18
|
+
# - TruffleRuby: ✗ magnus/rb-sys incompatible with TruffleRuby's C API emulation
|
|
19
|
+
#
|
|
15
20
|
# @see https://github.com/joker1007/tree_stump tree_stump
|
|
16
21
|
module Rust
|
|
17
22
|
@load_attempted = false
|
|
@@ -30,6 +35,14 @@ module TreeHaver
|
|
|
30
35
|
def available?
|
|
31
36
|
return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
32
37
|
@load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
38
|
+
|
|
39
|
+
# tree_stump uses magnus which requires MRI's C API
|
|
40
|
+
# It doesn't work on JRuby or TruffleRuby
|
|
41
|
+
unless RUBY_ENGINE == "ruby"
|
|
42
|
+
@loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
43
|
+
return @loaded
|
|
44
|
+
end
|
|
45
|
+
|
|
33
46
|
begin
|
|
34
47
|
require "tree_stump"
|
|
35
48
|
|
|
@@ -136,16 +149,15 @@ module TreeHaver
|
|
|
136
149
|
|
|
137
150
|
# tree_stump uses TreeStump.register_lang(name, path) to register languages
|
|
138
151
|
# The name is used to derive the symbol automatically (tree_sitter_<name>)
|
|
139
|
-
|
|
152
|
+
# Use shared utility for consistent path parsing across backends
|
|
153
|
+
lang_name = name || LibraryPathUtils.derive_language_name_from_path(path)
|
|
140
154
|
::TreeStump.register_lang(lang_name, path)
|
|
141
155
|
new(lang_name, path: path)
|
|
142
156
|
rescue RuntimeError => e
|
|
143
157
|
raise TreeHaver::NotAvailable, "Failed to load language from #{path}: #{e.message}"
|
|
144
158
|
end
|
|
145
159
|
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
# @see from_library
|
|
160
|
+
# Backward-compatible alias for from_library
|
|
149
161
|
alias_method :from_path, :from_library
|
|
150
162
|
end
|
|
151
163
|
end
|
|
@@ -46,12 +46,12 @@ module TreeHaver
|
|
|
46
46
|
# @param language [Symbol, String] the language name (e.g., :toml, :json)
|
|
47
47
|
# @param gem_name [String] the gem name (e.g., "toml-rb")
|
|
48
48
|
# @param grammar_const [String] constant path to grammar (e.g., "TomlRB::Document")
|
|
49
|
-
# @param require_path [String, nil] custom require path (defaults to gem_name
|
|
49
|
+
# @param require_path [String, nil] custom require path (defaults to gem_name as-is)
|
|
50
50
|
def initialize(language:, gem_name:, grammar_const:, require_path: nil)
|
|
51
51
|
@language_name = language.to_sym
|
|
52
52
|
@gem_name = gem_name
|
|
53
53
|
@grammar_const = grammar_const
|
|
54
|
-
@require_path = require_path || gem_name
|
|
54
|
+
@require_path = require_path || gem_name
|
|
55
55
|
@load_attempted = false
|
|
56
56
|
@available = false
|
|
57
57
|
@grammar_module = nil
|
|
@@ -67,6 +67,15 @@ module TreeHaver
|
|
|
67
67
|
return @available if @load_attempted
|
|
68
68
|
|
|
69
69
|
@load_attempted = true
|
|
70
|
+
debug = ENV["TREE_HAVER_DEBUG"]
|
|
71
|
+
|
|
72
|
+
# Guard against nil require_path (can happen if gem_name was nil)
|
|
73
|
+
if @require_path.nil? || @require_path.empty?
|
|
74
|
+
warn("CitrusGrammarFinder: require_path is nil or empty for #{@language_name}") if debug
|
|
75
|
+
@available = false
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
70
79
|
begin
|
|
71
80
|
# Try to require the gem
|
|
72
81
|
require @require_path
|
|
@@ -76,25 +85,64 @@ module TreeHaver
|
|
|
76
85
|
|
|
77
86
|
# Verify it responds to parse
|
|
78
87
|
unless @grammar_module.respond_to?(:parse)
|
|
79
|
-
|
|
88
|
+
# :nocov: defensive - requires a gem with malformed grammar module
|
|
89
|
+
# Show what methods ARE available to help diagnose the issue
|
|
90
|
+
if debug
|
|
91
|
+
available_methods = @grammar_module.methods(false).sort.first(20)
|
|
92
|
+
warn("CitrusGrammarFinder: #{@grammar_const} doesn't respond to :parse")
|
|
93
|
+
warn("CitrusGrammarFinder: #{@grammar_const}.class = #{@grammar_module.class}")
|
|
94
|
+
warn("CitrusGrammarFinder: #{@grammar_const} is a #{@grammar_module.is_a?(Module) ? "Module" : "non-Module"}")
|
|
95
|
+
warn("CitrusGrammarFinder: Available singleton methods (first 20): #{available_methods.inspect}")
|
|
96
|
+
if @grammar_module.respond_to?(:instance_methods)
|
|
97
|
+
instance_methods = @grammar_module.instance_methods(false).sort.first(20)
|
|
98
|
+
warn("CitrusGrammarFinder: Available instance methods (first 20): #{instance_methods.inspect}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
80
101
|
@available = false
|
|
81
102
|
return false
|
|
103
|
+
# :nocov:
|
|
82
104
|
end
|
|
83
105
|
|
|
84
106
|
@available = true
|
|
85
107
|
rescue LoadError => e
|
|
86
|
-
#
|
|
87
|
-
|
|
108
|
+
# :nocov: defensive - requires gem to not be installed
|
|
109
|
+
# Only show LoadError details when debugging
|
|
110
|
+
if debug
|
|
111
|
+
warn("CitrusGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
|
|
112
|
+
warn("CitrusGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
113
|
+
end
|
|
88
114
|
@available = false
|
|
115
|
+
# :nocov:
|
|
89
116
|
rescue NameError => e
|
|
90
|
-
#
|
|
91
|
-
|
|
117
|
+
# :nocov: defensive - requires gem with missing constant
|
|
118
|
+
# Only show NameError details when debugging
|
|
119
|
+
if debug
|
|
120
|
+
warn("CitrusGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
|
|
121
|
+
warn("CitrusGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
122
|
+
end
|
|
92
123
|
@available = false
|
|
124
|
+
# :nocov:
|
|
125
|
+
rescue TypeError => e
|
|
126
|
+
# :nocov: defensive - TruffleRuby-specific edge case
|
|
127
|
+
# TruffleRuby's bundled_gems.rb can raise TypeError when File.path is called on nil
|
|
128
|
+
# This happens in bundled_gems.rb:124 warning? method when caller locations return nil
|
|
129
|
+
# Always warn about TypeError as it indicates a platform-specific issue
|
|
130
|
+
warn("CitrusGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
|
|
131
|
+
warn("CitrusGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue")
|
|
132
|
+
if debug
|
|
133
|
+
warn("CitrusGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
134
|
+
end
|
|
135
|
+
@available = false
|
|
136
|
+
# :nocov:
|
|
93
137
|
rescue => e
|
|
94
|
-
#
|
|
138
|
+
# :nocov: defensive - catch-all for unexpected errors
|
|
139
|
+
# Always warn about unexpected errors
|
|
95
140
|
warn("CitrusGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
|
|
96
|
-
|
|
141
|
+
if debug
|
|
142
|
+
warn("CitrusGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
143
|
+
end
|
|
97
144
|
@available = false
|
|
145
|
+
# :nocov:
|
|
98
146
|
end
|
|
99
147
|
|
|
100
148
|
@available
|
|
@@ -270,7 +270,10 @@ module TreeHaver
|
|
|
270
270
|
# Try to instantiate a parser - this will fail if runtime isn't available
|
|
271
271
|
mod::Parser.new
|
|
272
272
|
true
|
|
273
|
-
rescue NoMethodError,
|
|
273
|
+
rescue NoMethodError, LoadError, NotAvailable => _e
|
|
274
|
+
false
|
|
275
|
+
rescue StandardError => _e
|
|
276
|
+
# Catch FFI::NotFoundError and other errors when FFI is loaded
|
|
274
277
|
false
|
|
275
278
|
end
|
|
276
279
|
end
|