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.

Examples:

Method access

point = TreeHaver::Point.new(5, 10)
point.row    # => 5
point.column # => 10

Hash-like access

point[:row]    # => 5
point[:column] # => 10

Converting to hash

point.to_h # => {row: 5, column: 10}

See Also:

Base::Point
VERSION =

Current gem version exposed at the traditional constant location.

Returns:

  • (String)
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

Class Method Details

.allowed_native_backendsObject



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_backendsObject



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

.backendObject



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

Returns:

  • (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_moduleObject



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_protectObject



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

Returns:

  • (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_usedObject



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

Raises:

  • (RangeError)


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

.capabilitiesObject



239
240
241
# File 'lib/tree_haver.rb', line 239

def capabilities
  backend_module&.capabilities || {}
end

.check_backend_conflict!(backend_name) ⇒ Object

Raises:



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_idObject



8
9
10
# File 'lib/tree_haver/backend_context.rb', line 8

def current_backend_id
  Thread.current[BACKEND_CONTEXT_KEY]
end

.default_backend_idObject



81
82
83
# File 'lib/tree_haver.rb', line 81

def default_backend_id
  DEFAULT_BACKEND_ID
end

.effective_backendObject



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

.extract_source_fragment(source, span, strategy) ⇒ Object



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.extract_source_fragment(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_infoObject



13
14
15
16
17
18
19
20
# File 'lib/tree_haver/kaitai_backend.rb', line 13

def kaitai_adapter_info
  AdapterInfo.new(
    backend: KAITAI_STRUCT_BACKEND.id,
    backend_ref: KAITAI_STRUCT_BACKEND,
    supports_dialects: false,
    supported_policies: []
  )
end

.kaitai_feature_profileObject



22
23
24
25
26
27
28
29
# File 'lib/tree_haver/kaitai_backend.rb', line 22

def kaitai_feature_profile
  FeatureProfile.new(
    backend: KAITAI_STRUCT_BACKEND.id,
    backend_ref: KAITAI_STRUCT_BACKEND,
    supports_dialects: false,
    supported_policies: []
  )
end

.language_pack_adapter_infoObject



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_profileObject



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_rolesObject



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

Raises:



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

.register_standard_dependency_tags!Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/tree_haver.rb', line 66

def register_standard_dependency_tags!
  BackendRegistry.register_tag(:mri_backend, category: :backend, backend_name: :mri) do
    Backends::MRI.available?
  end
  BackendRegistry.register_tag(:ffi_backend, category: :backend, backend_name: :ffi) do
    Backends::FFI.available?
  end
  BackendRegistry.register_tag(:rust_backend, category: :backend, backend_name: :rust) do
    Backends::Rust.available?
  end
  BackendRegistry.register_tag(:java_backend, category: :backend, backend_name: :java) do
    Backends::Java.available?
  end
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



252
253
254
# File 'lib/tree_haver.rb', line 252

def registered_language(name)
  LanguageRegistry.registered(name)
end

.registered_languages(name) ⇒ Object



287
288
289
# File 'lib/tree_haver.rb', line 287

def registered_languages(name)
  LanguageRegistry.registered(name) || {}
end

.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_reportObject



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

Returns:

  • (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

Returns:

  • (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

Returns:

  • (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

Raises:

  • (ArgumentError)


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