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
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'rbconfig'
|
|
4
4
|
|
|
5
5
|
module TreeHaver
|
|
6
|
-
#
|
|
6
|
+
# Registration-first utility for finding tree-sitter grammar shared libraries.
|
|
7
7
|
#
|
|
8
|
-
# GrammarFinder
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# GrammarFinder resolves tree-sitter grammars in a constrained order:
|
|
9
|
+
#
|
|
10
|
+
# 1. explicit environment override
|
|
11
|
+
# 2. existing TreeHaver registration
|
|
12
|
+
# 3. explicit extra paths
|
|
13
|
+
# 4. tree_sitter_language_pack parser backend registration
|
|
11
14
|
#
|
|
12
15
|
# This class is designed to be used by language-specific merge gems
|
|
13
|
-
#
|
|
14
|
-
# to have knowledge of each specific language.
|
|
16
|
+
# without requiring TreeHaver to own parser- or grammar-specific policy.
|
|
15
17
|
#
|
|
16
18
|
# == Security Considerations
|
|
17
19
|
#
|
|
@@ -29,7 +31,7 @@ module TreeHaver
|
|
|
29
31
|
# @example Basic usage
|
|
30
32
|
# finder = TreeHaver::GrammarFinder.new(:toml)
|
|
31
33
|
# path = finder.find_library_path
|
|
32
|
-
# # => "/usr/lib/
|
|
34
|
+
# # => "/usr/local/lib/libtree_sitter_toml.so"
|
|
33
35
|
#
|
|
34
36
|
# @example Check availability
|
|
35
37
|
# finder = TreeHaver::GrammarFinder.new(:json)
|
|
@@ -51,15 +53,6 @@ module TreeHaver
|
|
|
51
53
|
#
|
|
52
54
|
# @see PathValidator For details on security validations
|
|
53
55
|
class GrammarFinder
|
|
54
|
-
# Common base directories where tree-sitter libraries are installed
|
|
55
|
-
# Platform-specific extensions are appended automatically
|
|
56
|
-
BASE_SEARCH_DIRS = [
|
|
57
|
-
"/usr/lib",
|
|
58
|
-
"/usr/lib64",
|
|
59
|
-
"/usr/local/lib",
|
|
60
|
-
"/opt/homebrew/lib",
|
|
61
|
-
].freeze
|
|
62
|
-
|
|
63
56
|
# @return [Symbol] the language identifier
|
|
64
57
|
attr_reader :language_name
|
|
65
58
|
|
|
@@ -77,7 +70,7 @@ module TreeHaver
|
|
|
77
70
|
|
|
78
71
|
if validate && !PathValidator.safe_language_name?(name_str)
|
|
79
72
|
raise ArgumentError, "Invalid language name: #{language_name.inspect}. " \
|
|
80
|
-
|
|
73
|
+
'Language names must start with a letter and contain only lowercase letters, numbers, and underscores.'
|
|
81
74
|
end
|
|
82
75
|
|
|
83
76
|
@language_name = name_str.to_sym
|
|
@@ -98,41 +91,56 @@ module TreeHaver
|
|
|
98
91
|
"tree_sitter_#{@language_name}"
|
|
99
92
|
end
|
|
100
93
|
|
|
101
|
-
# Get the
|
|
94
|
+
# Get the canonical tree-sitter-language-pack filename for the current platform
|
|
102
95
|
#
|
|
103
|
-
# @return [String] the library filename (e.g., "
|
|
96
|
+
# @return [String] the library filename (e.g., "libtree_sitter_toml.so")
|
|
104
97
|
def library_filename
|
|
98
|
+
library_filenames.first
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get all accepted library filenames for this language
|
|
102
|
+
#
|
|
103
|
+
# Accept both the tree-sitter-language-pack naming convention and the
|
|
104
|
+
# historical hyphenated form used by some standalone grammar builds.
|
|
105
|
+
#
|
|
106
|
+
# @return [Array<String>]
|
|
107
|
+
def library_filenames
|
|
105
108
|
ext = platform_extension
|
|
106
|
-
|
|
109
|
+
[
|
|
110
|
+
"libtree_sitter_#{@language_name}#{ext}",
|
|
111
|
+
"libtree-sitter-#{@language_name}#{ext}"
|
|
112
|
+
]
|
|
107
113
|
end
|
|
108
114
|
|
|
109
115
|
# Generate the full list of search paths for this language
|
|
110
116
|
#
|
|
111
|
-
# Order:
|
|
117
|
+
# Order: registered path, then explicit extra paths.
|
|
112
118
|
#
|
|
113
119
|
# @return [Array<String>] all paths to search
|
|
114
120
|
def search_paths
|
|
115
121
|
paths = []
|
|
116
122
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
paths << File.join(dir, library_filename)
|
|
120
|
-
end
|
|
123
|
+
registration = registered_tree_sitter_registration
|
|
124
|
+
paths << registration[:path] if registration&.dig(:path)
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
@extra_paths.each do |dir|
|
|
127
|
+
library_filenames.each do |filename|
|
|
128
|
+
paths << File.join(dir, filename)
|
|
129
|
+
end
|
|
125
130
|
end
|
|
126
131
|
|
|
127
|
-
paths
|
|
132
|
+
paths.uniq
|
|
128
133
|
end
|
|
129
134
|
|
|
130
135
|
# Find the grammar library path
|
|
131
136
|
#
|
|
132
137
|
# Searches in order:
|
|
133
138
|
# 1. Environment variable override (validated for safety)
|
|
134
|
-
# 2.
|
|
135
|
-
# 3.
|
|
139
|
+
# 2. Existing TreeHaver tree-sitter registration
|
|
140
|
+
# 3. Extra paths provided at initialization
|
|
141
|
+
# tree_sitter_language_pack is intentionally not exposed as a shared-library
|
|
142
|
+
# path fallback here. It is registered as a TreeHaver backend module when
|
|
143
|
+
# its parser API is available.
|
|
136
144
|
#
|
|
137
145
|
# @note Paths from ENV are validated using {PathValidator.safe_library_path?}
|
|
138
146
|
# to prevent path traversal and other attacks. Invalid ENV paths cause
|
|
@@ -151,18 +159,18 @@ module TreeHaver
|
|
|
151
159
|
if ENV[env_var] || ENV.key?(env_var)
|
|
152
160
|
env_path = ENV[env_var]
|
|
153
161
|
|
|
154
|
-
# :
|
|
162
|
+
# simplecov:disable defensive - ENV.key? true with nil value is rare edge case
|
|
155
163
|
if env_path.nil?
|
|
156
|
-
@env_rejection_reason =
|
|
164
|
+
@env_rejection_reason = 'explicitly disabled (set to nil)'
|
|
157
165
|
return
|
|
158
166
|
end
|
|
159
|
-
# :
|
|
167
|
+
# simplecov:enable
|
|
160
168
|
|
|
161
169
|
# Empty string means "explicitly skip this grammar"
|
|
162
170
|
# This allows users to disable tree-sitter for specific languages
|
|
163
171
|
# and fall back to alternative backends like Citrus
|
|
164
172
|
if env_path.empty?
|
|
165
|
-
@env_rejection_reason =
|
|
173
|
+
@env_rejection_reason = 'explicitly disabled (set to empty string)'
|
|
166
174
|
return
|
|
167
175
|
end
|
|
168
176
|
|
|
@@ -173,35 +181,36 @@ module TreeHaver
|
|
|
173
181
|
# to a path, that path MUST work. Don't silently fall back to auto-discovery.
|
|
174
182
|
if @env_rejection_reason
|
|
175
183
|
raise TreeHaver::NotAvailable,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
"#{env_var_name} is set to #{env_path.inspect} but #{@env_rejection_reason}. " \
|
|
185
|
+
'Either fix the path, unset the variable to use auto-discovery, ' \
|
|
186
|
+
'or set it to empty string to explicitly disable this grammar.'
|
|
179
187
|
end
|
|
180
188
|
|
|
181
189
|
return env_path
|
|
182
190
|
end
|
|
183
191
|
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
registered_path = registered_tree_sitter_path
|
|
193
|
+
return registered_path if registered_path
|
|
194
|
+
|
|
195
|
+
explicit_path = explicit_search_path
|
|
196
|
+
return explicit_path if explicit_path
|
|
197
|
+
|
|
198
|
+
nil
|
|
186
199
|
end
|
|
187
200
|
|
|
188
201
|
# Validate an environment variable path and return reason if invalid
|
|
189
202
|
# @return [String, nil] rejection reason or nil if valid
|
|
190
203
|
def validate_env_path(path)
|
|
191
204
|
# Check for leading/trailing whitespace
|
|
192
|
-
if path != path.strip
|
|
193
|
-
return "contains leading or trailing whitespace (use #{path.strip.inspect})"
|
|
194
|
-
end
|
|
205
|
+
return "contains leading or trailing whitespace (use #{path.strip.inspect})" if path != path.strip
|
|
195
206
|
|
|
196
207
|
# Check if path is safe
|
|
197
208
|
unless PathValidator.safe_library_path?(path)
|
|
198
|
-
return
|
|
209
|
+
return 'failed security validation (may contain path traversal or suspicious characters)'
|
|
199
210
|
end
|
|
200
211
|
|
|
201
212
|
# Check if file exists
|
|
202
|
-
unless File.exist?(path)
|
|
203
|
-
return "file does not exist"
|
|
204
|
-
end
|
|
213
|
+
return 'file does not exist' unless File.exist?(path)
|
|
205
214
|
|
|
206
215
|
nil # Valid!
|
|
207
216
|
end
|
|
@@ -215,7 +224,6 @@ module TreeHaver
|
|
|
215
224
|
# @return [String, nil] the path to the library, or nil if not found
|
|
216
225
|
# @see PathValidator::TRUSTED_DIRECTORIES For the list of trusted directories
|
|
217
226
|
def find_library_path_safe
|
|
218
|
-
# Environment variable is NOT checked in safe mode - only trusted system paths
|
|
219
227
|
search_paths.find do |path|
|
|
220
228
|
File.exist?(path) && PathValidator.in_trusted_directory?(path)
|
|
221
229
|
end
|
|
@@ -232,6 +240,8 @@ module TreeHaver
|
|
|
232
240
|
#
|
|
233
241
|
# @return [Boolean] true if the library can be found AND tree-sitter runtime works
|
|
234
242
|
def available?
|
|
243
|
+
return true if tree_sitter_language_pack_parser_available?
|
|
244
|
+
|
|
235
245
|
path = find_library_path
|
|
236
246
|
return false if path.nil?
|
|
237
247
|
|
|
@@ -246,7 +256,7 @@ module TreeHaver
|
|
|
246
256
|
TreeHaver::Backends::MRI,
|
|
247
257
|
TreeHaver::Backends::FFI,
|
|
248
258
|
TreeHaver::Backends::Rust,
|
|
249
|
-
TreeHaver::Backends::Java
|
|
259
|
+
TreeHaver::Backends::Java
|
|
250
260
|
].freeze
|
|
251
261
|
|
|
252
262
|
class << self
|
|
@@ -273,7 +283,7 @@ module TreeHaver
|
|
|
273
283
|
true
|
|
274
284
|
end
|
|
275
285
|
rescue NoMethodError, LoadError, NotAvailable => _e
|
|
276
|
-
#
|
|
286
|
+
# NOTE: FFI::NotFoundError inherits from LoadError, so it's caught here too
|
|
277
287
|
false
|
|
278
288
|
end
|
|
279
289
|
end
|
|
@@ -303,11 +313,20 @@ module TreeHaver
|
|
|
303
313
|
# @return [Boolean] true if registration succeeded
|
|
304
314
|
# @raise [NotAvailable] if library not found and raise_on_missing is true
|
|
305
315
|
def register!(raise_on_missing: false)
|
|
316
|
+
if tree_sitter_language_pack_parser_available?
|
|
317
|
+
TreeHaver.register_language(
|
|
318
|
+
@language_name,
|
|
319
|
+
backend_module: TreeHaver::Backends::Tslp,
|
|
320
|
+
backend_type: :tslp,
|
|
321
|
+
gem_name: 'tree_sitter_language_pack'
|
|
322
|
+
)
|
|
323
|
+
return true
|
|
324
|
+
end
|
|
325
|
+
|
|
306
326
|
path = find_library_path
|
|
307
327
|
unless path
|
|
308
|
-
if raise_on_missing
|
|
309
|
-
|
|
310
|
-
end
|
|
328
|
+
raise NotAvailable, not_found_message if raise_on_missing
|
|
329
|
+
|
|
311
330
|
return false
|
|
312
331
|
end
|
|
313
332
|
|
|
@@ -325,11 +344,13 @@ module TreeHaver
|
|
|
325
344
|
env_var: env_var_name,
|
|
326
345
|
env_value: ENV[env_var_name],
|
|
327
346
|
env_rejection_reason: @env_rejection_reason,
|
|
347
|
+
tree_sitter_language_pack_parser_available: tree_sitter_language_pack_parser_available?,
|
|
328
348
|
symbol: symbol_name,
|
|
329
349
|
library_filename: library_filename,
|
|
350
|
+
library_filenames: library_filenames,
|
|
330
351
|
search_paths: search_paths,
|
|
331
352
|
found_path: found,
|
|
332
|
-
available: !found.nil
|
|
353
|
+
available: tree_sitter_language_pack_parser_available? || !found.nil?
|
|
333
354
|
}
|
|
334
355
|
end
|
|
335
356
|
|
|
@@ -342,32 +363,66 @@ module TreeHaver
|
|
|
342
363
|
# Check if env var is set but rejected
|
|
343
364
|
env_value = ENV[env_var_name]
|
|
344
365
|
msg += if env_value && @env_rejection_reason
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
msg + " Install tree-sitter-#{@language_name} or set #{env_var_name} to a valid path."
|
|
366
|
+
" #{env_var_name} is set to #{env_value.inspect} but #{@env_rejection_reason}."
|
|
367
|
+
elsif env_value && File.exist?(env_value) && !self.class.tree_sitter_runtime_usable?
|
|
368
|
+
" #{env_var_name} is set and file exists, but no tree-sitter runtime is available. " \
|
|
369
|
+
'Add ruby_tree_sitter, ffi, or tree_stump gem to your Gemfile.'
|
|
370
|
+
else
|
|
371
|
+
" Searched: #{search_paths.join(', ')}."
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
msg + ' Register the grammar, install tree_sitter_language_pack with parser API support, ' \
|
|
375
|
+
"or set #{env_var_name} to a valid path."
|
|
356
376
|
end
|
|
357
377
|
|
|
358
378
|
private
|
|
359
379
|
|
|
380
|
+
def registered_tree_sitter_registration
|
|
381
|
+
TreeHaver::LanguageRegistry.registered(@language_name, :tree_sitter)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def registered_tree_sitter_path
|
|
385
|
+
registration = registered_tree_sitter_registration
|
|
386
|
+
path = registration&.dig(:path)
|
|
387
|
+
return unless path
|
|
388
|
+
return path if File.exist?(path)
|
|
389
|
+
|
|
390
|
+
@registered_rejection_reason = "registered path does not exist: #{path}"
|
|
391
|
+
nil
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def explicit_search_path
|
|
395
|
+
search_paths.find { |path| File.exist?(path) }
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def tree_sitter_language_pack_parser_available?
|
|
399
|
+
return @tree_sitter_language_pack_parser_available if defined?(@tree_sitter_language_pack_parser_available)
|
|
400
|
+
|
|
401
|
+
@tree_sitter_language_pack_parser_available = begin
|
|
402
|
+
require 'tree_sitter_language_pack' unless defined?(::TreeSitterLanguagePack)
|
|
403
|
+
TreeHaver::Backends::Tslp.available? &&
|
|
404
|
+
::TreeSitterLanguagePack.respond_to?(:has_language) &&
|
|
405
|
+
::TreeSitterLanguagePack.has_language(@language_name.to_s) &&
|
|
406
|
+
TreeHaver::Backends::Tslp.parser_available_for?(@language_name)
|
|
407
|
+
rescue LoadError
|
|
408
|
+
false
|
|
409
|
+
rescue StandardError => e
|
|
410
|
+
@tree_sitter_language_pack_rejection_reason = e.message
|
|
411
|
+
false
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
360
415
|
# Get the platform-appropriate shared library extension
|
|
361
416
|
#
|
|
362
417
|
# @return [String] ".so" on Linux, ".dylib" on macOS
|
|
363
418
|
def platform_extension
|
|
364
|
-
case RbConfig::CONFIG[
|
|
419
|
+
case RbConfig::CONFIG['host_os']
|
|
365
420
|
when /darwin/i
|
|
366
|
-
|
|
421
|
+
'.dylib'
|
|
367
422
|
when /mswin|mingw|cygwin/i
|
|
368
|
-
|
|
423
|
+
'.dll'
|
|
369
424
|
else
|
|
370
|
-
|
|
425
|
+
'.so'
|
|
371
426
|
end
|
|
372
427
|
end
|
|
373
428
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
KAITAI_STRUCT_BACKEND = BackendReference.new(
|
|
5
|
+
id: 'kaitai-struct',
|
|
6
|
+
family: 'kaitai'
|
|
7
|
+
).freeze
|
|
8
|
+
|
|
9
|
+
BackendRegistry.register(KAITAI_STRUCT_BACKEND)
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def kaitai_adapter_info
|
|
14
|
+
AdapterInfo.new(
|
|
15
|
+
backend: KAITAI_STRUCT_BACKEND.id,
|
|
16
|
+
backend_ref: KAITAI_STRUCT_BACKEND,
|
|
17
|
+
supports_dialects: false,
|
|
18
|
+
supported_policies: []
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def kaitai_feature_profile
|
|
23
|
+
FeatureProfile.new(
|
|
24
|
+
backend: KAITAI_STRUCT_BACKEND.id,
|
|
25
|
+
backend_ref: KAITAI_STRUCT_BACKEND,
|
|
26
|
+
supports_dialects: false,
|
|
27
|
+
supported_policies: []
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/tree_haver/language.rb
CHANGED
|
@@ -90,12 +90,12 @@ module TreeHaver
|
|
|
90
90
|
if validate
|
|
91
91
|
unless PathValidator.safe_library_path?(path)
|
|
92
92
|
errors = PathValidator.validation_errors(path)
|
|
93
|
-
raise ArgumentError, "Unsafe library path: #{path.inspect}. Errors: #{errors.join(
|
|
93
|
+
raise ArgumentError, "Unsafe library path: #{path.inspect}. Errors: #{errors.join('; ')}"
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
if symbol && !PathValidator.safe_symbol_name?(symbol)
|
|
97
97
|
raise ArgumentError, "Unsafe symbol name: #{symbol.inspect}. " \
|
|
98
|
-
|
|
98
|
+
'Symbol names must be valid C identifiers.'
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
@@ -105,19 +105,20 @@ module TreeHaver
|
|
|
105
105
|
|
|
106
106
|
if mod.nil?
|
|
107
107
|
if backend
|
|
108
|
-
raise NotAvailable,
|
|
108
|
+
raise NotAvailable,
|
|
109
|
+
"Requested backend #{backend.inspect} is not available or does not support shared libraries"
|
|
109
110
|
else
|
|
110
111
|
raise NotAvailable,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
'No native tree-sitter backend is available for loading shared libraries. ' \
|
|
113
|
+
'Available native backends (MRI, Rust, FFI, Java) require platform-specific setup. ' \
|
|
114
|
+
'For pure-Ruby parsing, use backend-specific Language classes directly (e.g., Prism, Psych, Citrus).'
|
|
114
115
|
end
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# Backend must implement .from_library; fallback to .from_path for older impls
|
|
118
119
|
# Include effective backend AND ENV vars in cache key since they affect loading
|
|
119
120
|
effective_b = TreeHaver.resolve_effective_backend(backend)
|
|
120
|
-
key = [effective_b, path, symbol, name, ENV[
|
|
121
|
+
key = [effective_b, path, symbol, name, ENV['TREE_SITTER_LANG_SYMBOL']]
|
|
121
122
|
LanguageRegistry.fetch(key) do
|
|
122
123
|
if mod::Language.respond_to?(:from_library)
|
|
123
124
|
mod::Language.from_library(path, symbol: symbol, name: name)
|
|
@@ -128,7 +129,7 @@ module TreeHaver
|
|
|
128
129
|
end
|
|
129
130
|
# Alias for {from_library}
|
|
130
131
|
# @see from_library
|
|
131
|
-
|
|
132
|
+
alias from_path from_library
|
|
132
133
|
|
|
133
134
|
# Dynamic helper to load a registered language by name
|
|
134
135
|
#
|
|
@@ -162,40 +163,38 @@ module TreeHaver
|
|
|
162
163
|
|
|
163
164
|
# Determine which backend type to use
|
|
164
165
|
backend_type = if current_backend == Backends::Citrus
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
:citrus
|
|
167
|
+
elsif current_backend == Backends::Parslet
|
|
168
|
+
:parslet
|
|
169
|
+
else
|
|
170
|
+
:tree_sitter # MRI, Rust, FFI, Java all use tree-sitter
|
|
171
|
+
end
|
|
171
172
|
|
|
172
173
|
# Get backend-specific registration
|
|
173
174
|
reg = all_backends[backend_type]
|
|
174
175
|
|
|
175
176
|
# If Citrus backend is active
|
|
176
177
|
if backend_type == :citrus
|
|
177
|
-
if reg && reg[:grammar_module]
|
|
178
|
-
return Backends::Citrus::Language.new(reg[:grammar_module])
|
|
179
|
-
end
|
|
178
|
+
return Backends::Citrus::Language.new(reg[:grammar_module]) if reg && reg[:grammar_module]
|
|
180
179
|
|
|
181
|
-
#
|
|
180
|
+
# No Citrus grammar for this language — provide actionable error
|
|
182
181
|
raise NotAvailable,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
"No Citrus grammar registered for :#{method_name}. " \
|
|
183
|
+
'This language may only be available via tree-sitter. ' \
|
|
184
|
+
'Check that the correct backend is selected (current: citrus). ' \
|
|
185
|
+
"Registered backends for :#{method_name}: #{all_backends.keys.inspect}"
|
|
186
186
|
end
|
|
187
187
|
|
|
188
188
|
# If Parslet backend is active
|
|
189
189
|
if backend_type == :parslet
|
|
190
|
-
if reg && reg[:grammar_class]
|
|
191
|
-
return Backends::Parslet::Language.new(reg[:grammar_class])
|
|
192
|
-
end
|
|
190
|
+
return Backends::Parslet::Language.new(reg[:grammar_class]) if reg && reg[:grammar_class]
|
|
193
191
|
|
|
194
|
-
#
|
|
192
|
+
# No Parslet grammar for this language — provide actionable error
|
|
195
193
|
raise NotAvailable,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
"No Parslet grammar registered for :#{method_name}. " \
|
|
195
|
+
'This language may only be available via tree-sitter. ' \
|
|
196
|
+
'Check that the correct backend is selected (current: parslet). ' \
|
|
197
|
+
"Registered backends for :#{method_name}: #{all_backends.keys.inspect}"
|
|
199
198
|
end
|
|
200
199
|
|
|
201
200
|
# For tree-sitter backends, try to load from path
|
|
@@ -204,16 +203,16 @@ module TreeHaver
|
|
|
204
203
|
path = kwargs[:path] || args.first || reg[:path]
|
|
205
204
|
# Symbol priority: kwargs override > registration > derive from method_name
|
|
206
205
|
symbol = if kwargs.key?(:symbol)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
kwargs[:symbol]
|
|
207
|
+
elsif reg[:symbol]
|
|
208
|
+
reg[:symbol]
|
|
209
|
+
else
|
|
210
|
+
"tree_sitter_#{method_name}"
|
|
211
|
+
end
|
|
213
212
|
# Name priority: kwargs override > derive from symbol (strip tree_sitter_ prefix)
|
|
214
213
|
# Using symbol-derived name ensures ruby_tree_sitter gets the correct language name
|
|
215
214
|
# e.g., "toml" not "toml_both" when symbol is "tree_sitter_toml"
|
|
216
|
-
name = kwargs[:name] || symbol&.sub(/\Atree_sitter_/,
|
|
215
|
+
name = kwargs[:name] || symbol&.sub(/\Atree_sitter_/, '')
|
|
217
216
|
|
|
218
217
|
begin
|
|
219
218
|
return from_library(path, symbol: symbol, name: name)
|
|
@@ -242,8 +241,8 @@ module TreeHaver
|
|
|
242
241
|
|
|
243
242
|
# No appropriate registration found
|
|
244
243
|
raise ArgumentError,
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
"No grammar registered for :#{method_name} compatible with #{backend_type} backend. " \
|
|
245
|
+
"Registered backends: #{all_backends.keys.inspect}"
|
|
247
246
|
end
|
|
248
247
|
|
|
249
248
|
# @api private
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
TSLP_BACKEND = BackendReference.new(
|
|
5
|
+
id: 'tslp',
|
|
6
|
+
family: 'tree-sitter'
|
|
7
|
+
).freeze
|
|
8
|
+
KREUZBERG_LANGUAGE_PACK_BACKEND = BackendReference.new(
|
|
9
|
+
id: 'kreuzberg-language-pack',
|
|
10
|
+
family: 'tree-sitter'
|
|
11
|
+
).freeze
|
|
12
|
+
|
|
13
|
+
BackendRegistry.register(TSLP_BACKEND)
|
|
14
|
+
BackendRegistry.register(KREUZBERG_LANGUAGE_PACK_BACKEND)
|
|
15
|
+
BackendRegistry.register_availability_checker(:tslp) do
|
|
16
|
+
Backends::Tslp.available?
|
|
17
|
+
end
|
|
18
|
+
BackendRegistry.register_availability_checker(:"kreuzberg-language-pack") do
|
|
19
|
+
BackendRegistry.available?(:tslp)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def language_pack_adapter_info
|
|
25
|
+
AdapterInfo.new(
|
|
26
|
+
backend: TSLP_BACKEND.id,
|
|
27
|
+
backend_ref: TSLP_BACKEND,
|
|
28
|
+
supports_dialects: false,
|
|
29
|
+
supported_policies: []
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def language_pack_feature_profile
|
|
34
|
+
FeatureProfile.new(
|
|
35
|
+
backend: TSLP_BACKEND.id,
|
|
36
|
+
backend_ref: TSLP_BACKEND,
|
|
37
|
+
supports_dialects: false,
|
|
38
|
+
supported_policies: []
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -26,7 +26,7 @@ module TreeHaver
|
|
|
26
26
|
#
|
|
27
27
|
# Registration structure:
|
|
28
28
|
# ```ruby
|
|
29
|
-
#
|
|
29
|
+
# registrations = {
|
|
30
30
|
# toml: {
|
|
31
31
|
# tree_sitter: { path: "/path/to/lib.so", symbol: "tree_sitter_toml" },
|
|
32
32
|
# citrus: { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
|
|
@@ -69,8 +69,8 @@ module TreeHaver
|
|
|
69
69
|
# @api private
|
|
70
70
|
module LanguageRegistry
|
|
71
71
|
@mutex = Mutex.new
|
|
72
|
-
@cache = {}
|
|
73
|
-
@registrations = {}
|
|
72
|
+
@cache = {}
|
|
73
|
+
@registrations = {}
|
|
74
74
|
|
|
75
75
|
module_function
|
|
76
76
|
|
|
@@ -108,6 +108,36 @@ module TreeHaver
|
|
|
108
108
|
nil
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
def with_registration(name, backend_type, **config)
|
|
112
|
+
key = name.to_sym
|
|
113
|
+
backend_key = backend_type.to_sym
|
|
114
|
+
original = nil
|
|
115
|
+
had_language = false
|
|
116
|
+
had_backend = false
|
|
117
|
+
|
|
118
|
+
@mutex.synchronize do
|
|
119
|
+
had_language = @registrations.key?(key)
|
|
120
|
+
had_backend = @registrations.fetch(key, {}).key?(backend_key)
|
|
121
|
+
original = @registrations.fetch(key, {})[backend_key]&.dup
|
|
122
|
+
@registrations[key] ||= {}
|
|
123
|
+
@registrations[key][backend_key] = config.compact
|
|
124
|
+
@cache.clear
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
yield
|
|
128
|
+
ensure
|
|
129
|
+
@mutex.synchronize do
|
|
130
|
+
if had_backend
|
|
131
|
+
@registrations[key][backend_key] = original
|
|
132
|
+
elsif had_language
|
|
133
|
+
@registrations[key].delete(backend_key)
|
|
134
|
+
else
|
|
135
|
+
@registrations.delete(key)
|
|
136
|
+
end
|
|
137
|
+
@cache.clear
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
111
141
|
# Fetch registration entries for a language
|
|
112
142
|
#
|
|
113
143
|
# Returns all backend-specific configurations for a language.
|
|
@@ -153,6 +183,7 @@ module TreeHaver
|
|
|
153
183
|
def fetch(key)
|
|
154
184
|
@mutex.synchronize do
|
|
155
185
|
return @cache[key] if @cache.key?(key)
|
|
186
|
+
|
|
156
187
|
value = yield
|
|
157
188
|
@cache[key] = value
|
|
158
189
|
end
|