tree_haver 7.0.0 → 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 +1959 -0
- data/lib/tree_haver/backend_api.rb +392 -0
- data/lib/tree_haver/backend_registry.rb +153 -3
- data/lib/tree_haver/backends/citrus.rb +489 -0
- data/lib/tree_haver/backends/ffi.rb +1013 -0
- data/lib/tree_haver/backends/java.rb +909 -0
- data/lib/tree_haver/backends/mri.rb +367 -0
- data/lib/tree_haver/backends/parslet.rb +565 -0
- data/lib/tree_haver/backends/prism.rb +568 -0
- data/lib/tree_haver/backends/psych.rb +379 -0
- data/lib/tree_haver/backends/rust.rb +243 -0
- 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 +98 -0
- data/lib/tree_haver/base/node.rb +330 -0
- data/lib/tree_haver/base/parser.rb +28 -0
- data/lib/tree_haver/base/point.rb +48 -0
- data/lib/tree_haver/base/tree.rb +128 -0
- data/lib/tree_haver/citrus_grammar_finder.rb +213 -0
- data/lib/tree_haver/contracts.rb +661 -96
- data/lib/tree_haver/grammar_finder.rb +429 -0
- data/lib/tree_haver/kaitai_backend.rb +2 -2
- data/lib/tree_haver/language.rb +294 -0
- data/lib/tree_haver/language_pack.rb +17 -166
- data/lib/tree_haver/language_registry.rb +221 -0
- data/lib/tree_haver/library_path_utils.rb +80 -0
- data/lib/tree_haver/node.rb +588 -0
- data/lib/tree_haver/parser.rb +445 -0
- data/lib/tree_haver/parslet_grammar_finder.rb +217 -0
- data/lib/tree_haver/path_validator.rb +356 -0
- data/lib/tree_haver/peg_backends.rb +7 -7
- data/lib/tree_haver/point.rb +27 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +52 -0
- data/lib/tree_haver/rspec.rb +3 -0
- data/lib/tree_haver/tree.rb +267 -0
- data/lib/tree_haver/version.rb +5 -3
- data/lib/tree_haver.rb +613 -8
- data/sig/tree_haver.rbs +6 -0
- data.tar.gz.sig +0 -0
- metadata +314 -13
- metadata.gz.sig +0 -0
data/lib/tree_haver.rb
CHANGED
|
@@ -1,13 +1,618 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative "tree_haver/backend_context"
|
|
6
|
-
require_relative "tree_haver/contracts"
|
|
7
|
-
require_relative "tree_haver/peg_backends"
|
|
8
|
-
require_relative "tree_haver/kaitai_backend"
|
|
9
|
-
require_relative "tree_haver/language_pack"
|
|
3
|
+
require 'version_gem'
|
|
4
|
+
require_relative 'tree_haver/version'
|
|
10
5
|
|
|
11
6
|
module TreeHaver
|
|
12
|
-
PACKAGE_NAME =
|
|
7
|
+
PACKAGE_NAME = 'tree_haver'
|
|
8
|
+
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
class NotAvailable < Error; end
|
|
11
|
+
class BackendConflict < Error; end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require_relative 'tree_haver/backend_registry'
|
|
15
|
+
require_relative 'tree_haver/backend_context'
|
|
16
|
+
require_relative 'tree_haver/contracts'
|
|
17
|
+
require_relative 'tree_haver/backend_api'
|
|
18
|
+
require_relative 'tree_haver/base/point'
|
|
19
|
+
require_relative 'tree_haver/base/language'
|
|
20
|
+
require_relative 'tree_haver/base/parser'
|
|
21
|
+
require_relative 'tree_haver/base/tree'
|
|
22
|
+
require_relative 'tree_haver/base/node'
|
|
23
|
+
require_relative 'tree_haver/base/comment'
|
|
24
|
+
require_relative 'tree_haver/point'
|
|
25
|
+
require_relative 'tree_haver/node'
|
|
26
|
+
require_relative 'tree_haver/tree'
|
|
27
|
+
require_relative 'tree_haver/language_registry'
|
|
28
|
+
require_relative 'tree_haver/path_validator'
|
|
29
|
+
require_relative 'tree_haver/library_path_utils'
|
|
30
|
+
require_relative 'tree_haver/backends/mri'
|
|
31
|
+
require_relative 'tree_haver/backends/ffi'
|
|
32
|
+
require_relative 'tree_haver/backends/rust'
|
|
33
|
+
require_relative 'tree_haver/backends/java'
|
|
34
|
+
require_relative 'tree_haver/backends/tslp'
|
|
35
|
+
require_relative 'tree_haver/grammar_finder'
|
|
36
|
+
require_relative 'tree_haver/citrus_grammar_finder'
|
|
37
|
+
require_relative 'tree_haver/parslet_grammar_finder'
|
|
38
|
+
require_relative 'tree_haver/peg_backends'
|
|
39
|
+
require_relative 'tree_haver/kaitai_backend'
|
|
40
|
+
require_relative 'tree_haver/language_pack'
|
|
41
|
+
require_relative 'tree_haver/backends/psych'
|
|
42
|
+
require_relative 'tree_haver/backends/prism'
|
|
43
|
+
require_relative 'tree_haver/backends/citrus'
|
|
44
|
+
require_relative 'tree_haver/backends/parslet'
|
|
45
|
+
require_relative 'tree_haver/language'
|
|
46
|
+
require_relative 'tree_haver/parser'
|
|
47
|
+
|
|
48
|
+
module TreeHaver
|
|
49
|
+
NATIVE_BACKENDS = %i[mri rust ffi java].freeze
|
|
50
|
+
RUBY_BACKENDS = %i[citrus parslet prism psych commonmarker markly rbs].freeze
|
|
51
|
+
VALID_NATIVE_BACKENDS = NATIVE_BACKENDS.map(&:to_s).freeze
|
|
52
|
+
VALID_RUBY_BACKENDS = RUBY_BACKENDS.map(&:to_s).freeze
|
|
53
|
+
VALID_BACKENDS = (VALID_NATIVE_BACKENDS + VALID_RUBY_BACKENDS + %w[auto none tslp kreuzberg-language-pack]).freeze
|
|
54
|
+
DEFAULT_BACKEND_ID = 'tslp'
|
|
55
|
+
NATIVE_BACKEND_REFERENCES = NATIVE_BACKENDS.to_h do |backend_name|
|
|
56
|
+
[
|
|
57
|
+
backend_name,
|
|
58
|
+
BackendReference.new(id: backend_name.to_s, family: 'tree-sitter').freeze
|
|
59
|
+
]
|
|
60
|
+
end.freeze
|
|
61
|
+
|
|
62
|
+
NATIVE_BACKEND_REFERENCES.each_value { |backend_ref| BackendRegistry.register(backend_ref) }
|
|
63
|
+
|
|
64
|
+
module_function
|
|
65
|
+
|
|
66
|
+
def register_standard_dependency_tags!
|
|
67
|
+
BackendRegistry.register_tag(:mri_backend, category: :backend, backend_name: :mri) do
|
|
68
|
+
Backends::MRI.available?
|
|
69
|
+
end
|
|
70
|
+
BackendRegistry.register_tag(:ffi_backend, category: :backend, backend_name: :ffi) do
|
|
71
|
+
Backends::FFI.available?
|
|
72
|
+
end
|
|
73
|
+
BackendRegistry.register_tag(:rust_backend, category: :backend, backend_name: :rust) do
|
|
74
|
+
Backends::Rust.available?
|
|
75
|
+
end
|
|
76
|
+
BackendRegistry.register_tag(:java_backend, category: :backend, backend_name: :java) do
|
|
77
|
+
Backends::Java.available?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def default_backend_id
|
|
82
|
+
DEFAULT_BACKEND_ID
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def backend
|
|
86
|
+
@backend ||= parse_single_backend_env
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def backend=(name)
|
|
90
|
+
@backend = name&.to_sym
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def reset_backend!(to: :auto)
|
|
94
|
+
@backend = to&.to_sym
|
|
95
|
+
@allowed_native_backends = nil
|
|
96
|
+
@allowed_ruby_backends = nil
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def backend_protect=(value)
|
|
101
|
+
backend_protect_mutex.synchronize { @backend_protect = value }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def backend_protect?
|
|
105
|
+
return @backend_protect if defined?(@backend_protect)
|
|
106
|
+
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def backend_protect
|
|
111
|
+
backend_protect?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def backends_used
|
|
115
|
+
@backends_used ||= Set.new
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def record_backend_usage(backend_name)
|
|
119
|
+
backends_used << backend_name.to_sym
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def conflicting_backends_for(backend_name)
|
|
124
|
+
blockers = {
|
|
125
|
+
mri: [],
|
|
126
|
+
rust: [],
|
|
127
|
+
ffi: [:mri],
|
|
128
|
+
java: [],
|
|
129
|
+
citrus: [],
|
|
130
|
+
parslet: [],
|
|
131
|
+
prism: [],
|
|
132
|
+
psych: []
|
|
133
|
+
}.fetch(backend_name.to_sym, [])
|
|
134
|
+
blockers & backends_used.to_a
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def check_backend_conflict!(backend_name)
|
|
138
|
+
return unless backend_protect?
|
|
139
|
+
|
|
140
|
+
conflicts = conflicting_backends_for(backend_name)
|
|
141
|
+
return if conflicts.empty?
|
|
142
|
+
|
|
143
|
+
raise BackendConflict,
|
|
144
|
+
"Cannot use #{backend_name} backend: it is blocked by previously used backend(s): #{conflicts.join(', ')}."
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def allowed_native_backends
|
|
148
|
+
@allowed_native_backends ||= parse_backend_list_env('TREE_HAVER_NATIVE_BACKEND', VALID_NATIVE_BACKENDS)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def allowed_ruby_backends
|
|
152
|
+
@allowed_ruby_backends ||= parse_backend_list_env('TREE_HAVER_RUBY_BACKEND', VALID_RUBY_BACKENDS)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def backend_allowed?(backend_name)
|
|
156
|
+
backend_sym = backend_name.to_sym
|
|
157
|
+
if VALID_NATIVE_BACKENDS.include?(backend_sym.to_s)
|
|
158
|
+
allowed = allowed_native_backends
|
|
159
|
+
return true if allowed == [:auto]
|
|
160
|
+
return false if allowed == [:none]
|
|
161
|
+
|
|
162
|
+
return allowed.include?(backend_sym)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if VALID_RUBY_BACKENDS.include?(backend_sym.to_s)
|
|
166
|
+
allowed = allowed_ruby_backends
|
|
167
|
+
return true if allowed == [:auto]
|
|
168
|
+
return false if allowed == [:none]
|
|
169
|
+
|
|
170
|
+
return allowed.include?(backend_sym)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
true
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def effective_backend
|
|
177
|
+
contextual = current_backend_id
|
|
178
|
+
return contextual.to_sym if contextual && !contextual.empty?
|
|
179
|
+
|
|
180
|
+
backend || :auto
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def resolve_effective_backend(explicit_backend = nil)
|
|
184
|
+
return explicit_backend.to_sym if explicit_backend
|
|
185
|
+
|
|
186
|
+
effective_backend
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def resolve_backend_module(explicit_backend = nil)
|
|
190
|
+
requested = resolve_effective_backend(explicit_backend)
|
|
191
|
+
return backend_module if requested == :auto
|
|
192
|
+
return if %i[tslp kreuzberg-language-pack].include?(requested)
|
|
193
|
+
return unless backend_allowed?(requested)
|
|
194
|
+
|
|
195
|
+
mod = backend_module_for(requested)
|
|
196
|
+
return unless mod
|
|
197
|
+
|
|
198
|
+
check_backend_conflict!(requested)
|
|
199
|
+
return if mod.respond_to?(:available?) && !mod.available?
|
|
200
|
+
|
|
201
|
+
record_backend_usage(requested)
|
|
202
|
+
mod
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def resolve_native_backend_module(explicit_backend = nil)
|
|
206
|
+
requested = resolve_effective_backend(explicit_backend)
|
|
207
|
+
return resolve_backend_module(requested) if NATIVE_BACKENDS.include?(requested)
|
|
208
|
+
return if explicit_backend
|
|
209
|
+
|
|
210
|
+
native_backend_priority.each do |backend_name|
|
|
211
|
+
mod = resolve_backend_module(backend_name)
|
|
212
|
+
return mod if mod
|
|
213
|
+
rescue BackendConflict
|
|
214
|
+
next
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def backend_module
|
|
221
|
+
requested = effective_backend
|
|
222
|
+
return backend_module_for(requested) if requested != :auto && backend_module_available?(requested)
|
|
223
|
+
|
|
224
|
+
backend_priority.each do |backend_name|
|
|
225
|
+
next unless backend_allowed?(backend_name)
|
|
226
|
+
|
|
227
|
+
mod = backend_module_for(backend_name)
|
|
228
|
+
next unless mod
|
|
229
|
+
next if mod.respond_to?(:available?) && !mod.available?
|
|
230
|
+
|
|
231
|
+
return mod
|
|
232
|
+
rescue BackendConflict
|
|
233
|
+
next
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def capabilities
|
|
240
|
+
backend_module&.capabilities || {}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def register_backend(name, mod)
|
|
244
|
+
backend_module_registry[name.to_sym] = mod
|
|
245
|
+
nil
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def registered_backend(name)
|
|
249
|
+
backend_module_registry[name.to_sym]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def registered_language(name)
|
|
253
|
+
LanguageRegistry.registered(name)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def register_language(name, path: nil, symbol: nil, grammar_module: nil, grammar_class: nil, backend_module: nil,
|
|
257
|
+
backend_type: nil, gem_name: nil, contract: nil)
|
|
258
|
+
LanguageRegistry.register(name, :tree_sitter, path: path, symbol: symbol, contract: contract) if path
|
|
259
|
+
|
|
260
|
+
if grammar_module
|
|
261
|
+
LanguageRegistry.register(name, :citrus, grammar_module: grammar_module, gem_name: gem_name, contract: contract)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
if grammar_class
|
|
265
|
+
LanguageRegistry.register(name, :parslet, grammar_class: grammar_class, gem_name: gem_name, contract: contract)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
if backend_module
|
|
269
|
+
LanguageRegistry.register(name, backend_type || backend_module.name.split('::').last.downcase.to_sym,
|
|
270
|
+
backend_module: backend_module, gem_name: gem_name, contract: contract)
|
|
271
|
+
register_backend(backend_type || backend_module.name.split('::').last.downcase.to_sym, backend_module)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
if path.nil? && grammar_module.nil? && grammar_class.nil? && backend_module.nil?
|
|
275
|
+
raise ArgumentError, 'Provide path:, grammar_module:, grammar_class:, or backend_module:'
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
nil
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def with_language_registration(name, backend_type, **config, &block)
|
|
282
|
+
raise ArgumentError, 'Block required' unless block
|
|
283
|
+
|
|
284
|
+
LanguageRegistry.with_registration(name, backend_type, **config, &block)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def registered_languages(name)
|
|
288
|
+
LanguageRegistry.registered(name) || {}
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def parser_for(language_name, library_path: nil, symbol: nil, citrus_config: nil, parslet_config: nil,
|
|
292
|
+
backend_type: nil, contract: nil, preferred: nil)
|
|
293
|
+
name = language_name.to_sym
|
|
294
|
+
|
|
295
|
+
return parser_for_tree_sitter(name, library_path, symbol) if library_path
|
|
296
|
+
|
|
297
|
+
registrations = filter_language_registrations(
|
|
298
|
+
LanguageRegistry.registered(name) || {},
|
|
299
|
+
contract || backend_type
|
|
300
|
+
)
|
|
301
|
+
if (backend_type = requested_backend_type(registrations))
|
|
302
|
+
return parser_for_registered_backend(name, backend_type, registrations)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
Array(preferred).each do |preferred_backend_type|
|
|
306
|
+
type = preferred_backend_type.to_sym
|
|
307
|
+
next unless registrations.key?(type)
|
|
308
|
+
next unless registered_backend_type_available?(type, registrations.fetch(type))
|
|
309
|
+
|
|
310
|
+
return parser_for_registered_backend(name, type, registrations)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if (config = registrations[:psych])
|
|
314
|
+
return parser_for_backend_module(config.fetch(:backend_module), name)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
if (config = registrations[:prism])
|
|
318
|
+
return parser_for_backend_module(config.fetch(:backend_module), name)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
if (config = registrations[:citrus]) || citrus_config
|
|
322
|
+
module_config = config || citrus_config
|
|
323
|
+
return parser_for_citrus(module_config.fetch(:grammar_module))
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
if (config = registrations[:parslet]) || parslet_config
|
|
327
|
+
class_config = config || parslet_config
|
|
328
|
+
return parser_for_parslet(class_config.fetch(:grammar_class))
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
if (config = registrations[:rbs])
|
|
332
|
+
return parser_for_backend_module(config.fetch(:backend_module), name)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
if (config = registrations[:tree_sitter])
|
|
336
|
+
return parser_for_tree_sitter(name, config[:path], config[:symbol])
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
if (backend_type = first_available_registered_backend_type(registrations))
|
|
340
|
+
return parser_for_registered_backend(name, backend_type, registrations)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
raise NotAvailable, "No parser registered for #{name}"
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def ruby_reference_parser_backend_contract_report
|
|
347
|
+
{
|
|
348
|
+
report_id: 'ruby-tree-haver-parser-backend-contract',
|
|
349
|
+
reference_runtime: 'ruby',
|
|
350
|
+
contract_layer: 'tree_haver',
|
|
351
|
+
proves: [
|
|
352
|
+
{
|
|
353
|
+
capability: 'stable_node_spans',
|
|
354
|
+
fixture_roles: %w[portable_byte_location_contract node_span_source_fragment],
|
|
355
|
+
ruby_surface: 'TreeHaver::ByteRange and TreeHaver::SourceSpan',
|
|
356
|
+
portability: 'portable_contract'
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
capability: 'source_fragments',
|
|
360
|
+
fixture_roles: %w[source_fragment_extraction node_span_source_fragment],
|
|
361
|
+
ruby_surface: 'TreeHaver.extract_source_fragment',
|
|
362
|
+
portability: 'portable_contract'
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
capability: 'comments_when_backend_supports_them',
|
|
366
|
+
fixture_roles: %w[comment_capability],
|
|
367
|
+
ruby_surface: 'TreeHaver::Base::Comment',
|
|
368
|
+
portability: 'backend_restricted_contract'
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
capability: 'parser_diagnostics',
|
|
372
|
+
fixture_roles: %w[parse_error_tolerance parser_diagnostics],
|
|
373
|
+
ruby_surface: 'TreeHaver::ParseErrorTolerance and TreeHaver::ParserDiagnostics',
|
|
374
|
+
portability: 'portable_contract'
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
capability: 'backend_capability_reports',
|
|
378
|
+
fixture_roles: %w[backend_capability_report backend_availability provider_diagnostics],
|
|
379
|
+
ruby_surface: 'TreeHaver::BackendCapability',
|
|
380
|
+
portability: 'portable_contract'
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
capability: 'backend_selection_context',
|
|
384
|
+
fixture_roles: %w[backend_selection_context backend_registry],
|
|
385
|
+
ruby_surface: 'TreeHaver.with_backend and TreeHaver.current_backend_id',
|
|
386
|
+
portability: 'portable_contract_runtime_local_mechanism'
|
|
387
|
+
}
|
|
388
|
+
],
|
|
389
|
+
release_status: 'ruby_reference_ready',
|
|
390
|
+
diagnostics: [
|
|
391
|
+
{
|
|
392
|
+
severity: 'info',
|
|
393
|
+
category: 'ruby_reference_contract',
|
|
394
|
+
message: 'Ruby tree_haver proves the parser/backend substrate needed by source-region extraction.'
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
}
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def requested_backend_type(registrations)
|
|
401
|
+
backend_id = current_backend_id || ENV['TREE_HAVER_BACKEND']
|
|
402
|
+
return if backend_id.to_s.empty?
|
|
403
|
+
|
|
404
|
+
backend_ref = BackendRegistry.fetch(backend_id.to_s)
|
|
405
|
+
type = if registrations.key?(backend_id.to_s.to_sym)
|
|
406
|
+
backend_id.to_s.to_sym
|
|
407
|
+
elsif (registered_type = registered_backend_type_for_requested_backend(backend_id, registrations))
|
|
408
|
+
registered_type
|
|
409
|
+
elsif backend_ref&.family == 'tree-sitter'
|
|
410
|
+
:tree_sitter
|
|
411
|
+
else
|
|
412
|
+
backend_id.to_s.to_sym
|
|
413
|
+
end
|
|
414
|
+
return type if registrations.key?(type)
|
|
415
|
+
|
|
416
|
+
raise NotAvailable, "No parser registered for backend #{backend_id}"
|
|
417
|
+
end
|
|
418
|
+
private_class_method :requested_backend_type
|
|
419
|
+
|
|
420
|
+
def filter_language_registrations(registrations, contract_filter)
|
|
421
|
+
contracts = Array(contract_filter).map { |contract| contract.to_s.tr('-', '_').to_sym }
|
|
422
|
+
return registrations if contracts.empty?
|
|
423
|
+
|
|
424
|
+
registrations.select do |backend_type, config|
|
|
425
|
+
contracts.include?(registration_contract(backend_type, config))
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
private_class_method :filter_language_registrations
|
|
429
|
+
|
|
430
|
+
def registration_contract(backend_type, config)
|
|
431
|
+
explicit = config[:contract]
|
|
432
|
+
return explicit.to_s.tr('-', '_').to_sym if explicit
|
|
433
|
+
return :tree_sitter if backend_type.to_sym == :tree_sitter
|
|
434
|
+
|
|
435
|
+
backend_ref = BackendRegistry.fetch(backend_type.to_s)
|
|
436
|
+
return :tree_sitter if backend_ref&.family == 'tree-sitter'
|
|
437
|
+
|
|
438
|
+
backend_type.to_sym
|
|
439
|
+
end
|
|
440
|
+
private_class_method :registration_contract
|
|
441
|
+
|
|
442
|
+
def registered_backend_type_for_requested_backend(backend_id, registrations)
|
|
443
|
+
requested_module = backend_module_for(backend_id)
|
|
444
|
+
return unless requested_module
|
|
445
|
+
|
|
446
|
+
registrations.find do |_backend_type, config|
|
|
447
|
+
config[:backend_module].equal?(requested_module)
|
|
448
|
+
end&.first
|
|
449
|
+
end
|
|
450
|
+
private_class_method :registered_backend_type_for_requested_backend
|
|
451
|
+
|
|
452
|
+
def parser_for_registered_backend(name, backend_type, registrations)
|
|
453
|
+
config = registrations.fetch(backend_type)
|
|
454
|
+
case backend_type
|
|
455
|
+
when :psych, :prism, :commonmarker, :markly, :rbs
|
|
456
|
+
parser_for_backend_module(config.fetch(:backend_module), name)
|
|
457
|
+
when :citrus
|
|
458
|
+
parser_for_citrus(config.fetch(:grammar_module))
|
|
459
|
+
when :parslet
|
|
460
|
+
parser_for_parslet(config.fetch(:grammar_class))
|
|
461
|
+
when :tree_sitter
|
|
462
|
+
parser_for_tree_sitter(name, config[:path], config[:symbol])
|
|
463
|
+
else
|
|
464
|
+
raise NotAvailable, "No parser registered for backend #{backend_type}" unless config[:backend_module]
|
|
465
|
+
|
|
466
|
+
parser_for_backend_module(config.fetch(:backend_module), name)
|
|
467
|
+
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
private_class_method :parser_for_registered_backend
|
|
471
|
+
|
|
472
|
+
def first_available_registered_backend_type(registrations)
|
|
473
|
+
registrations.each do |backend_type, config|
|
|
474
|
+
next unless registered_backend_type_available?(backend_type, config)
|
|
475
|
+
|
|
476
|
+
return backend_type
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
nil
|
|
480
|
+
end
|
|
481
|
+
private_class_method :first_available_registered_backend_type
|
|
482
|
+
|
|
483
|
+
def registered_backend_type_available?(backend_type, config)
|
|
484
|
+
return true if backend_type.to_sym == :tree_sitter && (config[:path] || config[:backend_module])
|
|
485
|
+
return false unless config[:backend_module]
|
|
486
|
+
return false unless backend_allowed?(backend_type)
|
|
487
|
+
|
|
488
|
+
backend_module = config.fetch(:backend_module)
|
|
489
|
+
return false if backend_module.respond_to?(:available?) && !backend_module.available?
|
|
490
|
+
|
|
491
|
+
true
|
|
492
|
+
end
|
|
493
|
+
private_class_method :registered_backend_type_available?
|
|
494
|
+
|
|
495
|
+
def parser_for_backend_module(backend_module, name)
|
|
496
|
+
parser = backend_module::Parser.new
|
|
497
|
+
language_factory = backend_module::Language
|
|
498
|
+
parser.language = if language_factory.respond_to?(name)
|
|
499
|
+
language_factory.public_send(name)
|
|
500
|
+
elsif language_factory.respond_to?(:from_library)
|
|
501
|
+
language_factory.from_library(nil, name: name)
|
|
502
|
+
else
|
|
503
|
+
language_factory.new(name)
|
|
504
|
+
end
|
|
505
|
+
parser
|
|
506
|
+
end
|
|
507
|
+
private_class_method :parser_for_backend_module
|
|
508
|
+
|
|
509
|
+
def parser_for_citrus(grammar_module)
|
|
510
|
+
parser = Backends::Citrus::Parser.new
|
|
511
|
+
parser.language = Backends::Citrus::Language.new(grammar_module)
|
|
512
|
+
parser
|
|
513
|
+
end
|
|
514
|
+
private_class_method :parser_for_citrus
|
|
515
|
+
|
|
516
|
+
def parser_for_parslet(grammar_class)
|
|
517
|
+
parser = Backends::Parslet::Parser.new
|
|
518
|
+
parser.language = Backends::Parslet::Language.new(grammar_class)
|
|
519
|
+
parser
|
|
520
|
+
end
|
|
521
|
+
private_class_method :parser_for_parslet
|
|
522
|
+
|
|
523
|
+
def parser_for_tree_sitter(name, library_path, symbol)
|
|
524
|
+
symbol ||= "tree_sitter_#{name}"
|
|
525
|
+
language = Language.from_library(library_path, symbol: symbol, name: name)
|
|
526
|
+
parser = Parser.new
|
|
527
|
+
parser.language = language
|
|
528
|
+
parser
|
|
529
|
+
end
|
|
530
|
+
private_class_method :parser_for_tree_sitter
|
|
531
|
+
|
|
532
|
+
def parse_single_backend_env
|
|
533
|
+
value = ENV['TREE_HAVER_BACKEND'].to_s.strip
|
|
534
|
+
return :auto if value.empty?
|
|
535
|
+
|
|
536
|
+
VALID_BACKENDS.include?(value) ? value.to_sym : :auto
|
|
537
|
+
end
|
|
538
|
+
private_class_method :parse_single_backend_env
|
|
539
|
+
|
|
540
|
+
def parse_backend_list_env(env_name, valid_backends)
|
|
541
|
+
normalized = ENV[env_name].to_s.strip.downcase
|
|
542
|
+
return [:auto] if normalized.empty? || normalized == 'auto'
|
|
543
|
+
return [:none] if normalized == 'none'
|
|
544
|
+
|
|
545
|
+
parsed = normalized.split(',').filter_map do |name|
|
|
546
|
+
candidate = name.strip
|
|
547
|
+
valid_backends.include?(candidate) ? candidate.to_sym : nil
|
|
548
|
+
end.uniq
|
|
549
|
+
parsed.empty? ? [:auto] : parsed
|
|
550
|
+
end
|
|
551
|
+
private_class_method :parse_backend_list_env
|
|
552
|
+
|
|
553
|
+
def backend_module_for(backend_name)
|
|
554
|
+
case backend_name.to_sym
|
|
555
|
+
when :mri
|
|
556
|
+
Backends::MRI
|
|
557
|
+
when :rust
|
|
558
|
+
Backends::Rust
|
|
559
|
+
when :ffi
|
|
560
|
+
Backends::FFI
|
|
561
|
+
when :java
|
|
562
|
+
Backends::Java
|
|
563
|
+
when :tslp, :"kreuzberg-language-pack"
|
|
564
|
+
Backends::Tslp
|
|
565
|
+
when :citrus
|
|
566
|
+
Backends::Citrus
|
|
567
|
+
when :parslet
|
|
568
|
+
Backends::Parslet
|
|
569
|
+
when :prism
|
|
570
|
+
Backends::Prism
|
|
571
|
+
when :psych
|
|
572
|
+
Backends::Psych
|
|
573
|
+
else
|
|
574
|
+
registered_backend(backend_name)
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
private_class_method :backend_module_for
|
|
578
|
+
|
|
579
|
+
def backend_module_available?(backend_name)
|
|
580
|
+
mod = backend_module_for(backend_name)
|
|
581
|
+
return false unless mod
|
|
582
|
+
return false unless backend_allowed?(backend_name)
|
|
583
|
+
return false if mod.respond_to?(:available?) && !mod.available?
|
|
584
|
+
|
|
585
|
+
true
|
|
586
|
+
end
|
|
587
|
+
private_class_method :backend_module_available?
|
|
588
|
+
|
|
589
|
+
def backend_priority
|
|
590
|
+
native_backend_priority + %i[prism psych citrus parslet]
|
|
591
|
+
end
|
|
592
|
+
private_class_method :backend_priority
|
|
593
|
+
|
|
594
|
+
def native_backend_priority
|
|
595
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
596
|
+
%i[java ffi]
|
|
597
|
+
else
|
|
598
|
+
%i[mri rust ffi]
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
private_class_method :native_backend_priority
|
|
602
|
+
|
|
603
|
+
def backend_module_registry
|
|
604
|
+
@backend_module_registry ||= {}
|
|
605
|
+
end
|
|
606
|
+
private_class_method :backend_module_registry
|
|
607
|
+
|
|
608
|
+
def backend_protect_mutex
|
|
609
|
+
@backend_protect_mutex ||= Mutex.new
|
|
610
|
+
end
|
|
611
|
+
private_class_method :backend_protect_mutex
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
TreeHaver.register_standard_dependency_tags!
|
|
615
|
+
|
|
616
|
+
TreeHaver::Version.class_eval do
|
|
617
|
+
extend VersionGem::Basic
|
|
13
618
|
end
|
data/sig/tree_haver.rbs
ADDED
data.tar.gz.sig
CHANGED
|
Binary file
|