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
|
@@ -33,21 +33,21 @@ module TreeHaver
|
|
|
33
33
|
return unless path
|
|
34
34
|
|
|
35
35
|
# Extract filename without extension: "libtree-sitter-toml" or "toml"
|
|
36
|
-
filename = File.basename(path,
|
|
36
|
+
filename = File.basename(path, '.*')
|
|
37
37
|
|
|
38
38
|
# Handle multi-part extensions like .so.0.24
|
|
39
|
-
filename = filename.sub(/\.so(\.\d+)*\z/,
|
|
39
|
+
filename = filename.sub(/\.so(\.\d+)*\z/, '')
|
|
40
40
|
|
|
41
41
|
# Match patterns and normalize to tree_sitter_<lang>
|
|
42
42
|
case filename
|
|
43
43
|
when /\Alib[-_]?tree[-_]sitter[-_](.+)\z/
|
|
44
|
-
"tree_sitter_#{Regexp.last_match(1).tr(
|
|
44
|
+
"tree_sitter_#{Regexp.last_match(1).tr('-', '_')}"
|
|
45
45
|
when /\Atree[-_]sitter[-_](.+)\z/
|
|
46
|
-
"tree_sitter_#{Regexp.last_match(1).tr(
|
|
46
|
+
"tree_sitter_#{Regexp.last_match(1).tr('-', '_')}"
|
|
47
47
|
else
|
|
48
48
|
# Assume filename is just the language name (e.g., "toml.so" -> "tree_sitter_toml")
|
|
49
49
|
# Also strip "lib" prefix if present (e.g., "libtoml.so" -> "tree_sitter_toml")
|
|
50
|
-
lang = filename.sub(/\Alib/,
|
|
50
|
+
lang = filename.sub(/\Alib/, '').tr('-', '_')
|
|
51
51
|
"tree_sitter_#{lang}"
|
|
52
52
|
end
|
|
53
53
|
end
|
|
@@ -64,7 +64,7 @@ module TreeHaver
|
|
|
64
64
|
return unless symbol
|
|
65
65
|
|
|
66
66
|
# Strip the "tree_sitter_" prefix to get the language name
|
|
67
|
-
symbol.sub(/\Atree_sitter_/,
|
|
67
|
+
symbol.sub(/\Atree_sitter_/, '')
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# Derive language name from a symbol
|
|
@@ -74,7 +74,7 @@ module TreeHaver
|
|
|
74
74
|
def derive_language_name_from_symbol(symbol)
|
|
75
75
|
return unless symbol
|
|
76
76
|
|
|
77
|
-
symbol.sub(/\Atree_sitter_/,
|
|
77
|
+
symbol.sub(/\Atree_sitter_/, '')
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
end
|
data/lib/tree_haver/node.rb
CHANGED
|
@@ -106,7 +106,7 @@ module TreeHaver
|
|
|
106
106
|
elsif @inner_node.respond_to?(:kind)
|
|
107
107
|
@inner_node.kind.to_s
|
|
108
108
|
else
|
|
109
|
-
raise TreeHaver::Error,
|
|
109
|
+
raise TreeHaver::Error, 'Backend node does not support type/kind'
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
@@ -151,7 +151,7 @@ module TreeHaver
|
|
|
151
151
|
Point.new(point.row, point.column)
|
|
152
152
|
end
|
|
153
153
|
else
|
|
154
|
-
raise TreeHaver::Error,
|
|
154
|
+
raise TreeHaver::Error, 'Backend node does not support start_point/start_position'
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -176,7 +176,7 @@ module TreeHaver
|
|
|
176
176
|
Point.new(point.row, point.column)
|
|
177
177
|
end
|
|
178
178
|
else
|
|
179
|
-
raise TreeHaver::Error,
|
|
179
|
+
raise TreeHaver::Error, 'Backend node does not support end_point/end_position'
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
|
|
@@ -213,7 +213,7 @@ module TreeHaver
|
|
|
213
213
|
start_line: start_line,
|
|
214
214
|
end_line: end_line,
|
|
215
215
|
start_column: start_point.column,
|
|
216
|
-
end_column: end_point.column
|
|
216
|
+
end_column: end_point.column
|
|
217
217
|
}
|
|
218
218
|
end
|
|
219
219
|
|
|
@@ -234,7 +234,7 @@ module TreeHaver
|
|
|
234
234
|
# Some backends (like TreeStump) require source as argument
|
|
235
235
|
# Check arity to determine how to call
|
|
236
236
|
arity = @inner_node.method(:text).arity
|
|
237
|
-
if
|
|
237
|
+
if [0, -1].include?(arity)
|
|
238
238
|
# No required arguments, or optional arguments only
|
|
239
239
|
@inner_node.text
|
|
240
240
|
elsif arity >= 1 && @source
|
|
@@ -242,15 +242,15 @@ module TreeHaver
|
|
|
242
242
|
@inner_node.text(@source)
|
|
243
243
|
elsif @source
|
|
244
244
|
# Fallback to byte extraction
|
|
245
|
-
@source[start_byte...end_byte] ||
|
|
245
|
+
@source[start_byte...end_byte] || ''
|
|
246
246
|
else
|
|
247
|
-
raise TreeHaver::Error,
|
|
247
|
+
raise TreeHaver::Error, 'Cannot extract text: backend requires source but none provided'
|
|
248
248
|
end
|
|
249
249
|
elsif @source
|
|
250
250
|
# Fallback: extract from source using byte positions
|
|
251
|
-
@source[start_byte...end_byte] ||
|
|
251
|
+
@source[start_byte...end_byte] || ''
|
|
252
252
|
else
|
|
253
|
-
raise TreeHaver::Error,
|
|
253
|
+
raise TreeHaver::Error, 'Cannot extract text: node has no text method and no source provided'
|
|
254
254
|
end
|
|
255
255
|
end
|
|
256
256
|
|
|
@@ -311,6 +311,7 @@ module TreeHaver
|
|
|
311
311
|
def child(index)
|
|
312
312
|
child_node = @inner_node.child(index)
|
|
313
313
|
return if child_node.nil?
|
|
314
|
+
|
|
314
315
|
Node.new(child_node, source: @source)
|
|
315
316
|
rescue IndexError
|
|
316
317
|
# Some backends (e.g., MRI w/ ruby_tree_sitter) raise IndexError for out of bounds
|
|
@@ -329,6 +330,7 @@ module TreeHaver
|
|
|
329
330
|
if @inner_node.respond_to?(:named_child)
|
|
330
331
|
child_node = @inner_node.named_child(index)
|
|
331
332
|
return if child_node.nil?
|
|
333
|
+
|
|
332
334
|
return Node.new(child_node, source: @source)
|
|
333
335
|
end
|
|
334
336
|
|
|
@@ -340,20 +342,20 @@ module TreeHaver
|
|
|
340
342
|
|
|
341
343
|
# Check if this child is named
|
|
342
344
|
is_named = if child_node.respond_to?(:named?)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
345
|
+
child_node.named?
|
|
346
|
+
elsif child_node.respond_to?(:is_named?)
|
|
347
|
+
child_node.is_named?
|
|
348
|
+
else
|
|
349
|
+
true # Assume named if we can't determine
|
|
350
|
+
end
|
|
349
351
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
352
|
+
next unless is_named
|
|
353
|
+
return Node.new(child_node, source: @source) if named_count == index
|
|
354
|
+
|
|
355
|
+
named_count += 1
|
|
354
356
|
end
|
|
355
357
|
|
|
356
|
-
nil
|
|
358
|
+
nil # Index out of bounds
|
|
357
359
|
end
|
|
358
360
|
|
|
359
361
|
# Get the count of named children
|
|
@@ -363,9 +365,7 @@ module TreeHaver
|
|
|
363
365
|
# @return [Integer] Number of named children
|
|
364
366
|
def named_child_count
|
|
365
367
|
# Try native implementation first
|
|
366
|
-
if @inner_node.respond_to?(:named_child_count)
|
|
367
|
-
return @inner_node.named_child_count
|
|
368
|
-
end
|
|
368
|
+
return @inner_node.named_child_count if @inner_node.respond_to?(:named_child_count)
|
|
369
369
|
|
|
370
370
|
# Fallback: count named children manually
|
|
371
371
|
count = 0
|
|
@@ -375,12 +375,12 @@ module TreeHaver
|
|
|
375
375
|
|
|
376
376
|
# Check if this child is named
|
|
377
377
|
is_named = if child_node.respond_to?(:named?)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
378
|
+
child_node.named?
|
|
379
|
+
elsif child_node.respond_to?(:is_named?)
|
|
380
|
+
child_node.is_named?
|
|
381
|
+
else
|
|
382
|
+
true # Assume named if we can't determine
|
|
383
|
+
end
|
|
384
384
|
|
|
385
385
|
count += 1 if is_named
|
|
386
386
|
end
|
|
@@ -408,6 +408,7 @@ module TreeHaver
|
|
|
408
408
|
# @return [Enumerator, nil]
|
|
409
409
|
def each(&block)
|
|
410
410
|
return to_enum(__method__) unless block_given?
|
|
411
|
+
|
|
411
412
|
children.each(&block)
|
|
412
413
|
end
|
|
413
414
|
|
|
@@ -419,6 +420,7 @@ module TreeHaver
|
|
|
419
420
|
if @inner_node.respond_to?(:child_by_field_name)
|
|
420
421
|
child_node = @inner_node.child_by_field_name(name.to_s)
|
|
421
422
|
return if child_node.nil?
|
|
423
|
+
|
|
422
424
|
Node.new(child_node, source: @source)
|
|
423
425
|
else
|
|
424
426
|
# Not all backends support field names
|
|
@@ -427,15 +429,17 @@ module TreeHaver
|
|
|
427
429
|
end
|
|
428
430
|
|
|
429
431
|
# Alias for child_by_field_name
|
|
430
|
-
|
|
432
|
+
alias field child_by_field_name
|
|
431
433
|
|
|
432
434
|
# Get the parent node
|
|
433
435
|
#
|
|
434
436
|
# @return [Node, nil] The parent node
|
|
435
437
|
def parent
|
|
436
438
|
return unless @inner_node.respond_to?(:parent)
|
|
439
|
+
|
|
437
440
|
parent_node = @inner_node.parent
|
|
438
441
|
return if parent_node.nil?
|
|
442
|
+
|
|
439
443
|
Node.new(parent_node, source: @source)
|
|
440
444
|
end
|
|
441
445
|
|
|
@@ -444,8 +448,10 @@ module TreeHaver
|
|
|
444
448
|
# @return [Node, nil]
|
|
445
449
|
def next_sibling
|
|
446
450
|
return unless @inner_node.respond_to?(:next_sibling)
|
|
451
|
+
|
|
447
452
|
sibling = @inner_node.next_sibling
|
|
448
453
|
return if sibling.nil?
|
|
454
|
+
|
|
449
455
|
Node.new(sibling, source: @source)
|
|
450
456
|
end
|
|
451
457
|
|
|
@@ -454,8 +460,10 @@ module TreeHaver
|
|
|
454
460
|
# @return [Node, nil]
|
|
455
461
|
def prev_sibling
|
|
456
462
|
return unless @inner_node.respond_to?(:prev_sibling)
|
|
463
|
+
|
|
457
464
|
sibling = @inner_node.prev_sibling
|
|
458
465
|
return if sibling.nil?
|
|
466
|
+
|
|
459
467
|
Node.new(sibling, source: @source)
|
|
460
468
|
end
|
|
461
469
|
|
|
@@ -512,11 +520,12 @@ module TreeHaver
|
|
|
512
520
|
# @return [Boolean] true if both nodes wrap the same inner_node
|
|
513
521
|
def ==(other)
|
|
514
522
|
return false unless other.is_a?(Node)
|
|
523
|
+
|
|
515
524
|
@inner_node == other.inner_node
|
|
516
525
|
end
|
|
517
526
|
|
|
518
527
|
# Alias for == to support both styles
|
|
519
|
-
|
|
528
|
+
alias eql? ==
|
|
520
529
|
|
|
521
530
|
# Generate hash value for this node
|
|
522
531
|
#
|
data/lib/tree_haver/parser.rb
CHANGED
|
@@ -82,7 +82,7 @@ module TreeHaver
|
|
|
82
82
|
# @example Explicit backend
|
|
83
83
|
# parser = TreeHaver::Parser.new(backend: :citrus)
|
|
84
84
|
def initialize(backend: nil)
|
|
85
|
-
super()
|
|
85
|
+
super() # Initialize @language from Base::Parser
|
|
86
86
|
|
|
87
87
|
# Convert string backend names to symbols for consistency
|
|
88
88
|
backend = backend.to_sym if backend.is_a?(String)
|
|
@@ -90,20 +90,19 @@ module TreeHaver
|
|
|
90
90
|
mod = TreeHaver.resolve_backend_module(backend)
|
|
91
91
|
|
|
92
92
|
if mod.nil?
|
|
93
|
-
if backend
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
end
|
|
93
|
+
raise NotAvailable, "Requested backend #{backend.inspect} is not available" if backend
|
|
94
|
+
|
|
95
|
+
raise NotAvailable, 'No TreeHaver backend is available'
|
|
96
|
+
|
|
98
97
|
end
|
|
99
98
|
|
|
100
99
|
# Try to create the parser, with fallback to pure Ruby if tree-sitter fails
|
|
101
100
|
# This enables auto-fallback when tree-sitter runtime isn't available
|
|
102
101
|
begin
|
|
103
102
|
@impl = mod::Parser.new
|
|
104
|
-
@explicit_backend = backend
|
|
103
|
+
@explicit_backend = backend # Remember for introspection (always a Symbol or nil)
|
|
105
104
|
rescue NoMethodError, LoadError => e
|
|
106
|
-
#
|
|
105
|
+
# NOTE: FFI::NotFoundError inherits from LoadError, so it's caught here too
|
|
107
106
|
handle_parser_creation_failure(e, backend)
|
|
108
107
|
end
|
|
109
108
|
end
|
|
@@ -117,22 +116,21 @@ module TreeHaver
|
|
|
117
116
|
def handle_parser_creation_failure(error, backend)
|
|
118
117
|
# Tree-sitter backend failed (likely missing runtime library)
|
|
119
118
|
# Try Citrus or Parslet as fallback if we weren't explicitly asked for a specific backend
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# No fallback available, re-raise original error
|
|
129
|
-
raise NotAvailable, "Tree-sitter backend failed: #{error.message}. " \
|
|
130
|
-
"Citrus/Parslet fallback not available. Install tree-sitter runtime, citrus gem, or parslet gem."
|
|
131
|
-
end
|
|
119
|
+
raise error unless backend.nil? || backend == :auto
|
|
120
|
+
|
|
121
|
+
if Backends::Citrus.available?
|
|
122
|
+
@impl = Backends::Citrus::Parser.new
|
|
123
|
+
@explicit_backend = :citrus
|
|
124
|
+
elsif Backends::Parslet.available?
|
|
125
|
+
@impl = Backends::Parslet::Parser.new
|
|
126
|
+
@explicit_backend = :parslet
|
|
132
127
|
else
|
|
133
|
-
#
|
|
134
|
-
raise error
|
|
128
|
+
# No fallback available, re-raise original error
|
|
129
|
+
raise NotAvailable, "Tree-sitter backend failed: #{error.message}. " \
|
|
130
|
+
'Citrus/Parslet fallback not available. Install tree-sitter runtime, citrus gem, or parslet gem.'
|
|
135
131
|
end
|
|
132
|
+
|
|
133
|
+
# Explicit backend was requested, don't fallback
|
|
136
134
|
end
|
|
137
135
|
|
|
138
136
|
# Get the backend this parser is using (for introspection)
|
|
@@ -234,20 +232,27 @@ module TreeHaver
|
|
|
234
232
|
# @example First parse (no old tree)
|
|
235
233
|
# tree = parser.parse_string(nil, "x = 1")
|
|
236
234
|
# @example Incremental parse
|
|
237
|
-
# tree.edit(
|
|
235
|
+
# tree.edit(
|
|
236
|
+
# start_byte: 4,
|
|
237
|
+
# old_end_byte: 5,
|
|
238
|
+
# new_end_byte: 6,
|
|
239
|
+
# start_point: {row: 0, column: 4},
|
|
240
|
+
# old_end_point: {row: 0, column: 5},
|
|
241
|
+
# new_end_point: {row: 0, column: 6}
|
|
242
|
+
# )
|
|
238
243
|
# new_tree = parser.parse_string(tree, "x = 42")
|
|
239
244
|
def parse_string(old_tree, source)
|
|
240
245
|
# Pass through to backend if it supports incremental parsing
|
|
241
246
|
if old_tree && @impl.respond_to?(:parse_string)
|
|
242
247
|
# Extract the underlying implementation from our Tree wrapper
|
|
243
248
|
old_impl = if old_tree.respond_to?(:inner_tree)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
249
|
+
old_tree.inner_tree
|
|
250
|
+
elsif old_tree.respond_to?(:instance_variable_get)
|
|
251
|
+
# Fallback for compatibility
|
|
252
|
+
old_tree.instance_variable_get(:@inner_tree) || old_tree.instance_variable_get(:@impl) || old_tree
|
|
253
|
+
else
|
|
254
|
+
old_tree
|
|
255
|
+
end
|
|
251
256
|
tree_impl = @impl.parse_string(old_impl, source)
|
|
252
257
|
# Wrap backend tree with source so Node#text works
|
|
253
258
|
Tree.new(tree_impl, source: source)
|
|
@@ -335,9 +340,9 @@ module TreeHaver
|
|
|
335
340
|
else
|
|
336
341
|
# Couldn't reload - this is an error
|
|
337
342
|
raise TreeHaver::Error,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
343
|
+
"Language backend mismatch: language is for #{lang.backend}, parser is #{current_backend}. " \
|
|
344
|
+
'Cannot reload language for correct backend. ' \
|
|
345
|
+
"Create a new language with TreeHaver::Language.from_library when backend is #{current_backend}."
|
|
341
346
|
end
|
|
342
347
|
end
|
|
343
348
|
|
|
@@ -358,17 +363,19 @@ module TreeHaver
|
|
|
358
363
|
# This shouldn't happen - all our wrappers have backend attribute
|
|
359
364
|
# If we get here, it's likely a raw backend object that was passed directly
|
|
360
365
|
raise TreeHaver::Error,
|
|
361
|
-
|
|
362
|
-
|
|
366
|
+
"Expected TreeHaver Language wrapper with backend attribute, got #{lang.class}. " \
|
|
367
|
+
'Use TreeHaver::Language.from_library to create language objects.'
|
|
363
368
|
end
|
|
364
369
|
|
|
365
370
|
case lang.backend
|
|
366
371
|
when :mri
|
|
367
372
|
return lang.to_language if lang.respond_to?(:to_language)
|
|
368
373
|
return lang.inner_language if lang.respond_to?(:inner_language)
|
|
374
|
+
|
|
369
375
|
lang
|
|
370
376
|
when :rust
|
|
371
377
|
return lang.name if lang.respond_to?(:name)
|
|
378
|
+
|
|
372
379
|
lang
|
|
373
380
|
when :ffi
|
|
374
381
|
lang # FFI needs wrapper for to_ptr
|
|
@@ -422,9 +429,9 @@ module TreeHaver
|
|
|
422
429
|
return Language.from_library(
|
|
423
430
|
lang.path,
|
|
424
431
|
symbol: lang.respond_to?(:symbol) ? lang.symbol : nil,
|
|
425
|
-
name: lang.respond_to?(:name) ? lang.name : nil
|
|
432
|
+
name: lang.respond_to?(:name) ? lang.name : nil
|
|
426
433
|
)
|
|
427
|
-
rescue => e
|
|
434
|
+
rescue StandardError => e
|
|
428
435
|
# Reload failed, continue with original
|
|
429
436
|
warn("TreeHaver: Failed to reload language for backend #{target_backend}: #{e.message}") if $VERBOSE
|
|
430
437
|
return
|
|
@@ -68,7 +68,7 @@ module TreeHaver
|
|
|
68
68
|
return @available if @load_attempted
|
|
69
69
|
|
|
70
70
|
@load_attempted = true
|
|
71
|
-
debug = ENV[
|
|
71
|
+
debug = ENV['TREE_HAVER_DEBUG']
|
|
72
72
|
|
|
73
73
|
# Guard against nil require_path (can happen if gem_name was nil)
|
|
74
74
|
if @require_path.nil? || @require_path.empty?
|
|
@@ -96,38 +96,34 @@ module TreeHaver
|
|
|
96
96
|
|
|
97
97
|
@available = true
|
|
98
98
|
rescue LoadError => e
|
|
99
|
-
# :
|
|
99
|
+
# simplecov:disable defensive - requires gem to not be installed
|
|
100
100
|
if debug
|
|
101
101
|
warn("ParsletGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
|
|
102
102
|
warn("ParsletGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
103
103
|
end
|
|
104
104
|
@available = false
|
|
105
|
-
# :
|
|
105
|
+
# simplecov:enable
|
|
106
106
|
rescue NameError => e
|
|
107
|
-
# :
|
|
107
|
+
# simplecov:disable defensive - requires gem with missing constant
|
|
108
108
|
if debug
|
|
109
109
|
warn("ParsletGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
|
|
110
110
|
warn("ParsletGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
111
111
|
end
|
|
112
112
|
@available = false
|
|
113
|
-
# :
|
|
113
|
+
# simplecov:enable
|
|
114
114
|
rescue TypeError => e
|
|
115
|
-
# :
|
|
115
|
+
# simplecov:disable defensive - TruffleRuby-specific edge case
|
|
116
116
|
warn("ParsletGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
|
|
117
|
-
warn(
|
|
118
|
-
if debug
|
|
119
|
-
warn("ParsletGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
120
|
-
end
|
|
117
|
+
warn('ParsletGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue')
|
|
118
|
+
warn("ParsletGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
|
|
121
119
|
@available = false
|
|
122
|
-
# :
|
|
123
|
-
rescue => e
|
|
124
|
-
# :
|
|
120
|
+
# simplecov:enable
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
# simplecov:disable defensive - catch-all for unexpected errors
|
|
125
123
|
warn("ParsletGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
|
|
126
|
-
if debug
|
|
127
|
-
warn("ParsletGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
128
|
-
end
|
|
124
|
+
warn("ParsletGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
|
|
129
125
|
@available = false
|
|
130
|
-
# :
|
|
126
|
+
# simplecov:enable
|
|
131
127
|
end
|
|
132
128
|
|
|
133
129
|
@available
|
|
@@ -151,16 +147,15 @@ module TreeHaver
|
|
|
151
147
|
# @raise [NotAvailable] if grammar not available and raise_on_missing is true
|
|
152
148
|
def register!(raise_on_missing: false)
|
|
153
149
|
unless available?
|
|
154
|
-
if raise_on_missing
|
|
155
|
-
|
|
156
|
-
end
|
|
150
|
+
raise NotAvailable, not_found_message if raise_on_missing
|
|
151
|
+
|
|
157
152
|
return false
|
|
158
153
|
end
|
|
159
154
|
|
|
160
155
|
TreeHaver.register_language(
|
|
161
156
|
@language_name,
|
|
162
157
|
grammar_class: @grammar_class,
|
|
163
|
-
gem_name: @gem_name
|
|
158
|
+
gem_name: @gem_name
|
|
164
159
|
)
|
|
165
160
|
true
|
|
166
161
|
end
|
|
@@ -175,7 +170,7 @@ module TreeHaver
|
|
|
175
170
|
grammar_const: @grammar_const,
|
|
176
171
|
require_path: @require_path,
|
|
177
172
|
available: available?,
|
|
178
|
-
grammar_class: @grammar_class&.name
|
|
173
|
+
grammar_class: @grammar_class&.name
|
|
179
174
|
}
|
|
180
175
|
end
|
|
181
176
|
|
|
@@ -195,7 +190,7 @@ module TreeHaver
|
|
|
195
190
|
# @return [Object] the constant
|
|
196
191
|
# @raise [NameError] if constant not found
|
|
197
192
|
def resolve_constant(const_path)
|
|
198
|
-
const_path.split(
|
|
193
|
+
const_path.split('::').reduce(Object) do |mod, const_name|
|
|
199
194
|
mod.const_get(const_name)
|
|
200
195
|
end
|
|
201
196
|
end
|
|
@@ -208,9 +203,7 @@ module TreeHaver
|
|
|
208
203
|
return false unless klass.respond_to?(:new)
|
|
209
204
|
|
|
210
205
|
# Check if it's a Parslet::Parser subclass
|
|
211
|
-
if defined?(::Parslet::Parser)
|
|
212
|
-
return true if klass < ::Parslet::Parser
|
|
213
|
-
end
|
|
206
|
+
return true if defined?(::Parslet::Parser) && (klass < ::Parslet::Parser)
|
|
214
207
|
|
|
215
208
|
# Fallback: check if it can create an instance that responds to parse
|
|
216
209
|
begin
|