Module: TreeHaver
- Defined in:
- lib/tree_haver.rb,
lib/tree_haver.rb,
lib/tree_haver/node.rb,
lib/tree_haver/tree.rb,
lib/tree_haver/point.rb,
lib/tree_haver/parser.rb,
lib/tree_haver/version.rb,
lib/tree_haver/language.rb,
lib/tree_haver/base/node.rb,
lib/tree_haver/base/tree.rb,
lib/tree_haver/contracts.rb,
lib/tree_haver/base/point.rb,
lib/tree_haver/backend_api.rb,
lib/tree_haver/base/parser.rb,
lib/tree_haver/backends/ffi.rb,
lib/tree_haver/backends/mri.rb,
lib/tree_haver/base/comment.rb,
lib/tree_haver/peg_backends.rb,
lib/tree_haver/backends/java.rb,
lib/tree_haver/backends/rust.rb,
lib/tree_haver/backends/tslp.rb,
lib/tree_haver/base/language.rb,
lib/tree_haver/language_pack.rb,
lib/tree_haver/backends/prism.rb,
lib/tree_haver/backends/psych.rb,
lib/tree_haver/grammar_finder.rb,
lib/tree_haver/kaitai_backend.rb,
lib/tree_haver/path_validator.rb,
lib/tree_haver/backend_context.rb,
lib/tree_haver/backends/citrus.rb,
lib/tree_haver/backend_registry.rb,
lib/tree_haver/backends/parslet.rb,
lib/tree_haver/language_registry.rb,
lib/tree_haver/library_path_utils.rb,
lib/tree_haver/citrus_grammar_finder.rb,
lib/tree_haver/rspec/dependency_tags.rb,
lib/tree_haver/parslet_grammar_finder.rb,
sig/tree_haver.rbs
Defined Under Namespace
Modules: BackendAPI, BackendRegistry, Backends, Base, Language, LanguageRegistry, LibraryPathUtils, PathValidator, RSpec, Version
Classes: AdapterInfo, AppliedEditProjectionOperation, BackendAvailabilityCheck, BackendAvailabilityReport, BackendCapability, BackendConflict, BackendReference, BinaryDiagnostic, BinaryMergeReport, BinaryNestedDispatch, BinaryPayloadRegion, BinaryRawPayload, BinaryRenderPolicy, BinaryScalarValue, ByteEditSpan, ByteRange, CitrusGrammarFinder, EditProjectionExecutionRequest, EditProjectionExecutionResult, EditProjectionOperationRequest, EditProjectionProviderMatrix, EditProjectionProviderMatrixEntry, EditProjectionProviderOperation, EditProjectionSupport, Error, FeatureProfile, GrammarFinder, KaitaiByteSpan, KaitaiTreeAnalysis, KaitaiTreeNode, LanguageVersion, LibraryPathValidation, NativeParserProvider, NativeProviderMetadata, Node, NormalizedParseResult, NormalizedTreeNode, NotAvailable, OrderedSiblingEdge, OrderedTreePrimitives, ParseErrorNode, ParseErrorTolerance, Parser, ParserDiagnostics, ParserIdentity, ParserRequest, ParsletGrammarFinder, ProviderDiagnostic, ProviderDiagnosticsReport, SourceFragment, SourcePoint, SourceSpan, Tree, TreeHaverProfile, ZipArchiveEntry, ZipArchiveInfo, ZipFamilyReport, ZipMemberDecision, ZipUnsafeEntry
Constant Summary
collapse
- PACKAGE_NAME =
'tree_haver'
- NATIVE_BACKENDS =
%i[mri rust ffi java].freeze
- RUBY_BACKENDS =
%i[citrus parslet prism psych commonmarker markly rbs].freeze
- VALID_NATIVE_BACKENDS =
NATIVE_BACKENDS.map(&:to_s).freeze
- VALID_RUBY_BACKENDS =
RUBY_BACKENDS.map(&:to_s).freeze
- VALID_BACKENDS =
(VALID_NATIVE_BACKENDS + VALID_RUBY_BACKENDS + %w[auto none tslp kreuzberg-language-pack]).freeze
- DEFAULT_BACKEND_ID =
'tslp'
- NATIVE_BACKEND_REFERENCES =
NATIVE_BACKENDS.to_h do |backend_name|
[
backend_name,
BackendReference.new(id: backend_name.to_s, family: 'tree-sitter').freeze
]
end.freeze
- Point =
Point class that works as both a Hash and an object with row/column accessors
This provides compatibility with code expecting either:
- Hash access: point, point
- Method access: point.row, point.column
TreeHaver::Point is an alias for TreeHaver::Base::Point, which is a Struct
providing all the necessary functionality.
Base::Point
- VERSION =
Current gem version exposed at the traditional constant location.
Version::VERSION
- NODE_ROLES =
%w[
structural
token
trivia
comment
delimiter
separator
virtual
error
opaque
].freeze
- MAX_LIBRARY_PATH_LENGTH =
4096
- VALID_LIBRARY_FILENAME_PATTERN =
/\A[A-Za-z0-9][A-Za-z0-9._-]*\z/
- VALID_LANGUAGE_NAME_PATTERN =
/\A[a-z][a-z0-9_]*\z/
- VALID_SYMBOL_NAME_PATTERN =
/\A[A-Za-z_][A-Za-z0-9_]*\z/
- VERSIONED_SHARED_OBJECT_PATTERN =
/\.so\.\d+\z/
- CITRUS_BACKEND =
BackendReference.new(
id: 'citrus',
family: 'peg'
).freeze
- PARSLET_BACKEND =
BackendReference.new(
id: 'parslet',
family: 'peg'
).freeze
- TSLP_BACKEND =
BackendReference.new(
id: 'tslp',
family: 'tree-sitter'
).freeze
- KREUZBERG_LANGUAGE_PACK_BACKEND =
BackendReference.new(
id: 'kreuzberg-language-pack',
family: 'tree-sitter'
).freeze
- KAITAI_STRUCT_BACKEND =
BackendReference.new(
id: 'kaitai-struct',
family: 'kaitai'
).freeze
- BACKEND_CONTEXT_KEY =
:structured_merge_tree_haver_backend
Class Method Summary
collapse
-
.allowed_native_backends ⇒ Object
-
.allowed_ruby_backends ⇒ Object
-
.backend ⇒ Object
-
.backend=(name) ⇒ Object
-
.backend_allowed?(backend_name) ⇒ Boolean
-
.backend_module ⇒ Object
-
.backend_protect ⇒ Object
-
.backend_protect=(value) ⇒ Object
-
.backend_protect? ⇒ Boolean
-
.backends_used ⇒ Object
-
.build_backend_availability_report(backend_ref, checks) ⇒ Object
-
.build_edit_projection_execution_result(source, applied_operations, diagnostics) ⇒ Object
-
.build_edit_projection_provider_matrix(operations, providers, diagnostics) ⇒ Object
-
.build_provider_diagnostics_report(provider_id, backend_ref, language, diagnostics) ⇒ Object
-
.byte_offset_for_point(source, point) ⇒ Object
-
.capabilities ⇒ Object
-
.check_backend_conflict!(backend_name) ⇒ Object
-
.conflicting_backends_for(backend_name) ⇒ Object
-
.current_backend_id ⇒ Object
-
.default_backend_id ⇒ Object
-
.effective_backend ⇒ Object
-
.extract_source_fragment(source, span, strategy) ⇒ Object
-
.kaitai_adapter_info ⇒ Object
-
.kaitai_feature_profile ⇒ Object
-
.language_pack_adapter_info ⇒ Object
-
.language_pack_feature_profile ⇒ Object
-
.library_path_errors(path) ⇒ Object
-
.node_roles ⇒ Object
-
.parse_with_citrus(source, grammar_module:) ⇒ Object
-
.parse_with_parslet(source, grammar_class:) ⇒ Object
-
.parser_for(language_name, library_path: nil, symbol: nil, citrus_config: nil, parslet_config: nil, backend_type: nil, contract: nil, preferred: nil) ⇒ Object
-
.peg_adapter_info(backend_ref) ⇒ Object
-
.peg_feature_profile(backend_ref) ⇒ Object
-
.record_backend_usage(backend_name) ⇒ Object
-
.register_backend(name, mod) ⇒ Object
-
.register_language(name, path: nil, symbol: nil, grammar_module: nil, grammar_class: nil, backend_module: nil, backend_type: nil, gem_name: nil, contract: nil) ⇒ Object
-
.register_standard_dependency_tags! ⇒ Object
-
.registered_backend(name) ⇒ Object
-
.registered_language(name) ⇒ Object
-
.registered_languages(name) ⇒ Object
-
.reset_backend!(to: :auto) ⇒ Object
-
.resolve_backend_module(explicit_backend = nil) ⇒ Object
-
.resolve_effective_backend(explicit_backend = nil) ⇒ Object
-
.resolve_native_backend_module(explicit_backend = nil) ⇒ Object
-
.ruby_reference_parser_backend_contract_report ⇒ Object
-
.safe_backend_name?(name) ⇒ Boolean
-
.safe_language_name?(name) ⇒ Boolean
-
.safe_symbol_name?(symbol) ⇒ Boolean
-
.sanitize_language_name(name) ⇒ Object
-
.slice_byte_range(source, byte_range) ⇒ Object
-
.validate_library_path(path) ⇒ Object
-
.with_backend(backend_id) ⇒ Object
-
.with_language_registration(name, backend_type, **config, &block) ⇒ Object
Class Method Details
.allowed_native_backends ⇒ Object
147
148
149
|
# File 'lib/tree_haver.rb', line 147
def allowed_native_backends
@allowed_native_backends ||= parse_backend_list_env('TREE_HAVER_NATIVE_BACKEND', VALID_NATIVE_BACKENDS)
end
|
.allowed_ruby_backends ⇒ Object
151
152
153
|
# File 'lib/tree_haver.rb', line 151
def allowed_ruby_backends
@allowed_ruby_backends ||= parse_backend_list_env('TREE_HAVER_RUBY_BACKEND', VALID_RUBY_BACKENDS)
end
|
.backend ⇒ Object
85
86
87
|
# File 'lib/tree_haver.rb', line 85
def backend
@backend ||= parse_single_backend_env
end
|
.backend=(name) ⇒ Object
89
90
91
|
# File 'lib/tree_haver.rb', line 89
def backend=(name)
@backend = name&.to_sym
end
|
.backend_allowed?(backend_name) ⇒ Boolean
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# File 'lib/tree_haver.rb', line 155
def backend_allowed?(backend_name)
backend_sym = backend_name.to_sym
if VALID_NATIVE_BACKENDS.include?(backend_sym.to_s)
allowed = allowed_native_backends
return true if allowed == [:auto]
return false if allowed == [:none]
return allowed.include?(backend_sym)
end
if VALID_RUBY_BACKENDS.include?(backend_sym.to_s)
allowed = allowed_ruby_backends
return true if allowed == [:auto]
return false if allowed == [:none]
return allowed.include?(backend_sym)
end
true
end
|
.backend_module ⇒ Object
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
# File 'lib/tree_haver.rb', line 220
def backend_module
requested = effective_backend
return backend_module_for(requested) if requested != :auto && backend_module_available?(requested)
backend_priority.each do |backend_name|
next unless backend_allowed?(backend_name)
mod = backend_module_for(backend_name)
next unless mod
next if mod.respond_to?(:available?) && !mod.available?
return mod
rescue BackendConflict
next
end
nil
end
|
.backend_protect ⇒ Object
110
111
112
|
# File 'lib/tree_haver.rb', line 110
def backend_protect
backend_protect?
end
|
.backend_protect=(value) ⇒ Object
100
101
102
|
# File 'lib/tree_haver.rb', line 100
def backend_protect=(value)
backend_protect_mutex.synchronize { @backend_protect = value }
end
|
.backend_protect? ⇒ Boolean
104
105
106
107
108
|
# File 'lib/tree_haver.rb', line 104
def backend_protect?
return @backend_protect if defined?(@backend_protect)
true
end
|
.backends_used ⇒ Object
114
115
116
|
# File 'lib/tree_haver.rb', line 114
def backends_used
@backends_used ||= Set.new
end
|
.build_backend_availability_report(backend_ref, checks) ⇒ Object
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
|
# File 'lib/tree_haver/contracts.rb', line 652
def build_backend_availability_report(backend_ref, checks)
if checks.empty?
return BackendAvailabilityReport.new(
backend_ref: backend_ref,
status: 'unknown',
checks: [],
diagnostics: ['backend availability unknown: no checks supplied']
)
end
diagnostics = []
status = 'available'
checks.each do |check|
next unless check.required && check.status != 'available'
status = 'unavailable'
diagnostics << "backend unavailable: required check #{check.name} is #{check.status}"
end
BackendAvailabilityReport.new(backend_ref: backend_ref, status: status, checks: checks, diagnostics: diagnostics)
end
|
.build_edit_projection_execution_result(source, applied_operations, diagnostics) ⇒ Object
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
|
# File 'lib/tree_haver/contracts.rb', line 693
def build_edit_projection_execution_result(source, applied_operations, diagnostics)
if diagnostics.any?(&:blocking)
return EditProjectionExecutionResult.new(
ok: false,
status: 'rejected',
source: source,
applied_operations: [],
diagnostics: diagnostics
)
end
EditProjectionExecutionResult.new(
ok: true,
status: 'applied',
source: source,
applied_operations: applied_operations,
diagnostics: diagnostics
)
end
|
.build_edit_projection_provider_matrix(operations, providers, diagnostics) ⇒ Object
714
715
716
717
718
719
720
|
# File 'lib/tree_haver/contracts.rb', line 714
def build_edit_projection_provider_matrix(operations, providers, diagnostics)
EditProjectionProviderMatrix.new(
operations: operations || [],
providers: providers || [],
diagnostics: diagnostics || []
)
end
|
.build_provider_diagnostics_report(provider_id, backend_ref, language, diagnostics) ⇒ Object
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
|
# File 'lib/tree_haver/contracts.rb', line 674
def build_provider_diagnostics_report(provider_id, backend_ref, language, diagnostics)
status = 'clean'
diagnostics.each do |diagnostic|
if diagnostic.blocking
status = 'blocked'
break
end
status = 'warning' if diagnostic.severity == 'warning'
end
ProviderDiagnosticsReport.new(
provider_id: provider_id,
backend_ref: backend_ref,
language: language,
status: status,
diagnostics: diagnostics
)
end
|
.byte_offset_for_point(source, point) ⇒ Object
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
|
# File 'lib/tree_haver/contracts.rb', line 958
def self.byte_offset_for_point(source, point)
if point.row.to_i.negative? || point.column.to_i.negative?
raise RangeError,
"invalid source point (#{point.row}, #{point.column})"
end
row = 0
column = 0
source.to_s.bytes.each_with_index do |byte, offset|
return offset if row == point.row.to_i && column == point.column.to_i
if byte == 10
row += 1
column = 0
else
column += 1
end
end
return source.to_s.bytesize if row == point.row.to_i && column == point.column.to_i
raise RangeError, "source point (#{point.row}, #{point.column}) is outside source"
end
|
.capabilities ⇒ Object
239
240
241
|
# File 'lib/tree_haver.rb', line 239
def capabilities
backend_module&.capabilities || {}
end
|
.check_backend_conflict!(backend_name) ⇒ Object
137
138
139
140
141
142
143
144
145
|
# File 'lib/tree_haver.rb', line 137
def check_backend_conflict!(backend_name)
return unless backend_protect?
conflicts = conflicting_backends_for(backend_name)
return if conflicts.empty?
raise BackendConflict,
"Cannot use #{backend_name} backend: it is blocked by previously used backend(s): #{conflicts.join(', ')}."
end
|
.conflicting_backends_for(backend_name) ⇒ Object
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# File 'lib/tree_haver.rb', line 123
def conflicting_backends_for(backend_name)
blockers = {
mri: [],
rust: [],
ffi: [:mri],
java: [],
citrus: [],
parslet: [],
prism: [],
psych: []
}.fetch(backend_name.to_sym, [])
blockers & backends_used.to_a
end
|
.current_backend_id ⇒ Object
8
9
10
|
# File 'lib/tree_haver/backend_context.rb', line 8
def current_backend_id
Thread.current[BACKEND_CONTEXT_KEY]
end
|
.default_backend_id ⇒ Object
81
82
83
|
# File 'lib/tree_haver.rb', line 81
def default_backend_id
DEFAULT_BACKEND_ID
end
|
.effective_backend ⇒ Object
176
177
178
179
180
181
|
# File 'lib/tree_haver.rb', line 176
def effective_backend
contextual = current_backend_id
return contextual.to_sym if contextual && !contextual.empty?
backend || :auto
end
|
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
|
# File 'lib/tree_haver/contracts.rb', line 937
def self.(source, span, strategy)
text = slice_byte_range(source, span.range)
SourceFragment.new(
text: text,
span: span,
available: true,
strategy: strategy,
byte_length: text.bytesize,
diagnostics: []
)
rescue RangeError => e
SourceFragment.new(
text: '',
span: span,
available: false,
strategy: strategy,
byte_length: 0,
diagnostics: [e.message]
)
end
|
.kaitai_adapter_info ⇒ Object
.kaitai_feature_profile ⇒ Object
.language_pack_adapter_info ⇒ Object
24
25
26
27
28
29
30
31
|
# File 'lib/tree_haver/language_pack.rb', line 24
def language_pack_adapter_info
AdapterInfo.new(
backend: TSLP_BACKEND.id,
backend_ref: TSLP_BACKEND,
supports_dialects: false,
supported_policies: []
)
end
|
.language_pack_feature_profile ⇒ Object
33
34
35
36
37
38
39
40
|
# File 'lib/tree_haver/language_pack.rb', line 33
def language_pack_feature_profile
FeatureProfile.new(
backend: TSLP_BACKEND.id,
backend_ref: TSLP_BACKEND,
supports_dialects: false,
supported_policies: []
)
end
|
.library_path_errors(path) ⇒ Object
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
|
# File 'lib/tree_haver/contracts.rb', line 610
def library_path_errors(path)
value = path.to_s
errors = []
errors << 'path_empty' if value.empty?
errors << 'path_too_long' if value.length > MAX_LIBRARY_PATH_LENGTH
errors << 'path_contains_null_byte' if value.include?("\0")
errors << 'path_not_absolute' unless value.start_with?('/') || windows_absolute_path?(value)
segments = value.split(%r{[/\\]})
errors << 'path_contains_parent_traversal' if segments.include?('..')
errors << 'path_contains_current_directory_traversal' if segments.include?('.')
errors << 'path_extension_not_allowed' unless allowed_library_extension?(value)
unless VALID_LIBRARY_FILENAME_PATTERN.match?(library_filename(value))
errors << 'filename_contains_invalid_characters'
end
errors
end
|
.node_roles ⇒ Object
593
594
595
|
# File 'lib/tree_haver/contracts.rb', line 593
def node_roles
NODE_ROLES.dup
end
|
.parse_with_citrus(source, grammar_module:) ⇒ Object
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
# File 'lib/tree_haver/peg_backends.rb', line 37
def parse_with_citrus(source, grammar_module:)
raw = grammar_module.parse(source)
if raw.respond_to?(:captures)
{
ok: true,
backend_ref: CITRUS_BACKEND,
raw: raw,
diagnostics: []
}
else
{
ok: false,
backend_ref: CITRUS_BACKEND,
diagnostics: [{ severity: 'error', category: 'parse_error', message: 'Citrus parse failed.' }]
}
end
rescue StandardError => e
{
ok: false,
backend_ref: CITRUS_BACKEND,
diagnostics: [{ severity: 'error', category: 'parse_error', message: e.message }]
}
end
|
.parse_with_parslet(source, grammar_class:) ⇒ Object
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
# File 'lib/tree_haver/peg_backends.rb', line 61
def parse_with_parslet(source, grammar_class:)
raw = grammar_class.new.parse(source)
{
ok: true,
backend_ref: PARSLET_BACKEND,
raw: raw,
diagnostics: []
}
rescue StandardError => e
{
ok: false,
backend_ref: PARSLET_BACKEND,
diagnostics: [{ severity: 'error', category: 'parse_error', message: e.message }]
}
end
|
.parser_for(language_name, library_path: nil, symbol: nil, citrus_config: nil, parslet_config: nil, backend_type: nil, contract: nil, preferred: nil) ⇒ Object
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
|
# File 'lib/tree_haver.rb', line 291
def parser_for(language_name, library_path: nil, symbol: nil, citrus_config: nil, parslet_config: nil,
backend_type: nil, contract: nil, preferred: nil)
name = language_name.to_sym
return parser_for_tree_sitter(name, library_path, symbol) if library_path
registrations = filter_language_registrations(
LanguageRegistry.registered(name) || {},
contract || backend_type
)
if (backend_type = requested_backend_type(registrations))
return parser_for_registered_backend(name, backend_type, registrations)
end
Array(preferred).each do |preferred_backend_type|
type = preferred_backend_type.to_sym
next unless registrations.key?(type)
next unless registered_backend_type_available?(type, registrations.fetch(type))
return parser_for_registered_backend(name, type, registrations)
end
if (config = registrations[:psych])
return parser_for_backend_module(config.fetch(:backend_module), name)
end
if (config = registrations[:prism])
return parser_for_backend_module(config.fetch(:backend_module), name)
end
if (config = registrations[:citrus]) || citrus_config
module_config = config || citrus_config
return parser_for_citrus(module_config.fetch(:grammar_module))
end
if (config = registrations[:parslet]) || parslet_config
class_config = config || parslet_config
return parser_for_parslet(class_config.fetch(:grammar_class))
end
if (config = registrations[:rbs])
return parser_for_backend_module(config.fetch(:backend_module), name)
end
if (config = registrations[:tree_sitter])
return parser_for_tree_sitter(name, config[:path], config[:symbol])
end
if (backend_type = first_available_registered_backend_type(registrations))
return parser_for_registered_backend(name, backend_type, registrations)
end
raise NotAvailable, "No parser registered for #{name}"
end
|
.peg_adapter_info(backend_ref) ⇒ Object
19
20
21
22
23
24
25
26
|
# File 'lib/tree_haver/peg_backends.rb', line 19
def peg_adapter_info(backend_ref)
AdapterInfo.new(
backend: backend_ref.id,
backend_ref: backend_ref,
supports_dialects: false,
supported_policies: []
)
end
|
.peg_feature_profile(backend_ref) ⇒ Object
28
29
30
31
32
33
34
35
|
# File 'lib/tree_haver/peg_backends.rb', line 28
def peg_feature_profile(backend_ref)
FeatureProfile.new(
backend: backend_ref.id,
backend_ref: backend_ref,
supports_dialects: false,
supported_policies: []
)
end
|
.record_backend_usage(backend_name) ⇒ Object
118
119
120
121
|
# File 'lib/tree_haver.rb', line 118
def record_backend_usage(backend_name)
backends_used << backend_name.to_sym
nil
end
|
.register_backend(name, mod) ⇒ Object
243
244
245
246
|
# File 'lib/tree_haver.rb', line 243
def register_backend(name, mod)
backend_module_registry[name.to_sym] = mod
nil
end
|
.register_language(name, path: nil, symbol: nil, grammar_module: nil, grammar_class: nil, backend_module: nil, backend_type: nil, gem_name: nil, contract: nil) ⇒ Object
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
|
# File 'lib/tree_haver.rb', line 256
def register_language(name, path: nil, symbol: nil, grammar_module: nil, grammar_class: nil, backend_module: nil,
backend_type: nil, gem_name: nil, contract: nil)
LanguageRegistry.register(name, :tree_sitter, path: path, symbol: symbol, contract: contract) if path
if grammar_module
LanguageRegistry.register(name, :citrus, grammar_module: grammar_module, gem_name: gem_name, contract: contract)
end
if grammar_class
LanguageRegistry.register(name, :parslet, grammar_class: grammar_class, gem_name: gem_name, contract: contract)
end
if backend_module
LanguageRegistry.register(name, backend_type || backend_module.name.split('::').last.downcase.to_sym,
backend_module: backend_module, gem_name: gem_name, contract: contract)
register_backend(backend_type || backend_module.name.split('::').last.downcase.to_sym, backend_module)
end
if path.nil? && grammar_module.nil? && grammar_class.nil? && backend_module.nil?
raise ArgumentError, 'Provide path:, grammar_module:, grammar_class:, or backend_module:'
end
nil
end
|
.registered_backend(name) ⇒ Object
248
249
250
|
# File 'lib/tree_haver.rb', line 248
def registered_backend(name)
backend_module_registry[name.to_sym]
end
|
.registered_language(name) ⇒ Object
.registered_languages(name) ⇒ Object
.reset_backend!(to: :auto) ⇒ Object
93
94
95
96
97
98
|
# File 'lib/tree_haver.rb', line 93
def reset_backend!(to: :auto)
@backend = to&.to_sym
@allowed_native_backends = nil
@allowed_ruby_backends = nil
nil
end
|
.resolve_backend_module(explicit_backend = nil) ⇒ Object
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
# File 'lib/tree_haver.rb', line 189
def resolve_backend_module(explicit_backend = nil)
requested = resolve_effective_backend(explicit_backend)
return backend_module if requested == :auto
return if %i[tslp kreuzberg-language-pack].include?(requested)
return unless backend_allowed?(requested)
mod = backend_module_for(requested)
return unless mod
check_backend_conflict!(requested)
return if mod.respond_to?(:available?) && !mod.available?
record_backend_usage(requested)
mod
end
|
.resolve_effective_backend(explicit_backend = nil) ⇒ Object
183
184
185
186
187
|
# File 'lib/tree_haver.rb', line 183
def resolve_effective_backend(explicit_backend = nil)
return explicit_backend.to_sym if explicit_backend
effective_backend
end
|
.resolve_native_backend_module(explicit_backend = nil) ⇒ Object
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
# File 'lib/tree_haver.rb', line 205
def resolve_native_backend_module(explicit_backend = nil)
requested = resolve_effective_backend(explicit_backend)
return resolve_backend_module(requested) if NATIVE_BACKENDS.include?(requested)
return if explicit_backend
native_backend_priority.each do |backend_name|
mod = resolve_backend_module(backend_name)
return mod if mod
rescue BackendConflict
next
end
nil
end
|
.ruby_reference_parser_backend_contract_report ⇒ Object
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
|
# File 'lib/tree_haver.rb', line 346
def ruby_reference_parser_backend_contract_report
{
report_id: 'ruby-tree-haver-parser-backend-contract',
reference_runtime: 'ruby',
contract_layer: 'tree_haver',
proves: [
{
capability: 'stable_node_spans',
fixture_roles: %w[portable_byte_location_contract node_span_source_fragment],
ruby_surface: 'TreeHaver::ByteRange and TreeHaver::SourceSpan',
portability: 'portable_contract'
},
{
capability: 'source_fragments',
fixture_roles: %w[source_fragment_extraction node_span_source_fragment],
ruby_surface: 'TreeHaver.extract_source_fragment',
portability: 'portable_contract'
},
{
capability: 'comments_when_backend_supports_them',
fixture_roles: %w[comment_capability],
ruby_surface: 'TreeHaver::Base::Comment',
portability: 'backend_restricted_contract'
},
{
capability: 'parser_diagnostics',
fixture_roles: %w[parse_error_tolerance parser_diagnostics],
ruby_surface: 'TreeHaver::ParseErrorTolerance and TreeHaver::ParserDiagnostics',
portability: 'portable_contract'
},
{
capability: 'backend_capability_reports',
fixture_roles: %w[backend_capability_report backend_availability provider_diagnostics],
ruby_surface: 'TreeHaver::BackendCapability',
portability: 'portable_contract'
},
{
capability: 'backend_selection_context',
fixture_roles: %w[backend_selection_context backend_registry],
ruby_surface: 'TreeHaver.with_backend and TreeHaver.current_backend_id',
portability: 'portable_contract_runtime_local_mechanism'
}
],
release_status: 'ruby_reference_ready',
diagnostics: [
{
severity: 'info',
category: 'ruby_reference_contract',
message: 'Ruby tree_haver proves the parser/backend substrate needed by source-region extraction.'
}
]
}
end
|
.safe_backend_name?(name) ⇒ Boolean
647
648
649
|
# File 'lib/tree_haver/contracts.rb', line 647
def safe_backend_name?(name)
name.to_s == 'auto' || !BackendRegistry.fetch(name.to_s).nil?
end
|
.safe_language_name?(name) ⇒ Boolean
631
632
633
|
# File 'lib/tree_haver/contracts.rb', line 631
def safe_language_name?(name)
VALID_LANGUAGE_NAME_PATTERN.match?(name.to_s)
end
|
.safe_symbol_name?(symbol) ⇒ Boolean
642
643
644
|
# File 'lib/tree_haver/contracts.rb', line 642
def safe_symbol_name?(symbol)
VALID_SYMBOL_NAME_PATTERN.match?(symbol.to_s)
end
|
.sanitize_language_name(name) ⇒ Object
636
637
638
639
|
# File 'lib/tree_haver/contracts.rb', line 636
def sanitize_language_name(name)
sanitized = name.to_s.downcase.gsub(/[^a-z0-9_]/, '')
safe_language_name?(sanitized) ? sanitized : nil
end
|
.slice_byte_range(source, byte_range) ⇒ Object
927
928
929
930
931
932
933
934
935
|
# File 'lib/tree_haver/contracts.rb', line 927
def self.slice_byte_range(source, byte_range)
source_bytesize = source.to_s.bytesize
unless byte_range.valid? && byte_range.end_byte.to_i <= source_bytesize
raise RangeError,
"invalid byte range [#{byte_range.start_byte}, #{byte_range.end_byte}) for source length #{source_bytesize}"
end
source.to_s.byteslice(byte_range.start_byte.to_i...byte_range.end_byte.to_i)
end
|
.validate_library_path(path) ⇒ Object
604
605
606
607
|
# File 'lib/tree_haver/contracts.rb', line 604
def validate_library_path(path)
errors = library_path_errors(path)
LibraryPathValidation.new(path: path, valid: errors.empty?, errors: errors)
end
|
.with_backend(backend_id) ⇒ Object
12
13
14
15
16
17
18
19
20
|
# File 'lib/tree_haver/backend_context.rb', line 12
def with_backend(backend_id)
validate_backend_id!(backend_id)
previous_backend = Thread.current[BACKEND_CONTEXT_KEY]
Thread.current[BACKEND_CONTEXT_KEY] = backend_id.to_s
yield
ensure
Thread.current[BACKEND_CONTEXT_KEY] = previous_backend
end
|
.with_language_registration(name, backend_type, **config, &block) ⇒ Object
281
282
283
284
285
|
# File 'lib/tree_haver.rb', line 281
def with_language_registration(name, backend_type, **config, &block)
raise ArgumentError, 'Block required' unless block
LanguageRegistry.with_registration(name, backend_type, **config, &block)
end
|