Module: TreeHaver::Backends::FFI::Native Private

Defined in:
lib/tree_haver/backends/ffi.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Native FFI bindings to libtree-sitter

This module handles loading the tree-sitter runtime library and defining
FFI function attachments for the core tree-sitter API.

All FFI operations are lazy - nothing is loaded until actually needed.
This prevents polluting the Ruby environment at require time.

Class Method Summary collapse

Class Method Details

.define_ts_node_struct!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Define the TSNode struct lazily



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/tree_haver/backends/ffi.rb', line 182

def define_ts_node_struct!
  return if const_defined?(:TSNode, false)

  # FFI struct representation of TSNode
  # Mirrors the C struct layout used by tree-sitter
  ts_node_class = Class.new(::FFI::Struct) do
    layout :context,
      [:uint32, 4],
      :id,
      :pointer,
      :tree,
      :pointer
  end
  const_set(:TSNode, ts_node_class)
  typedef(ts_node_class.by_value, :ts_node)
end

.define_ts_point_struct!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Define the TSPoint struct lazily



165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/tree_haver/backends/ffi.rb', line 165

def define_ts_point_struct!
  return if const_defined?(:TSPoint, false)

  # FFI struct representation of TSPoint
  # Mirrors the C struct layout: struct { uint32_t row; uint32_t column; }
  ts_point_class = Class.new(::FFI::Struct) do
    layout :row,
      :uint32,
      :column,
      :uint32
  end
  const_set(:TSPoint, ts_point_class)
  typedef(ts_point_class.by_value, :ts_point)
end

.ensure_ffi_extended!Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Lazily extend with FFI::Library only when needed

Returns:

  • (Boolean)

    true if FFI was successfully extended



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/tree_haver/backends/ffi.rb', line 149

def ensure_ffi_extended!
  return true if @ffi_extended

  unless FFI.ffi_gem_available?
    raise TreeHaver::NotAvailable, "FFI gem is not available"
  end

  extend(::FFI::Library)

  define_ts_point_struct!
  define_ts_node_struct!
  @ffi_extended = true
end

.lib_candidatesArray<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

TREE_SITTER_LIB is intentionally NOT supported

Get list of candidate library names for loading libtree-sitter

The list is built dynamically to respect environment variables set at runtime.
If TREE_SITTER_RUNTIME_LIB is set, it is tried first.

Returns:

  • (Array<String>)

    list of library names to try



213
214
215
216
217
218
219
220
221
222
# File 'lib/tree_haver/backends/ffi.rb', line 213

def lib_candidates
  [
    ENV["TREE_SITTER_RUNTIME_LIB"],
    "tree-sitter",
    "libtree-sitter.so.0",
    "libtree-sitter.so",
    "libtree-sitter.dylib",
    "libtree-sitter.dll",
  ].compact
end

.loaded?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


312
313
314
# File 'lib/tree_haver/backends/ffi.rb', line 312

def loaded?
  !!@loaded
end

.try_load!void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Load the tree-sitter runtime library

Tries each candidate library name in order until one succeeds.
After loading, attaches FFI function definitions for the tree-sitter API.

Raises:



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/tree_haver/backends/ffi.rb', line 231

def try_load!
  return if @loaded

  ensure_ffi_extended!

  # Warn about potential conflicts with MRI backend
  if defined?(::TreeSitter) && defined?(::TreeSitter::Parser)
    warn("TreeHaver: FFI backend loading after ruby_tree_sitter (MRI backend). " \
      "This may cause symbol conflicts due to different libtree-sitter versions. " \
      "Consider using only one backend per process, or set TREE_SITTER_RUNTIME_LIB " \
      "to match the version used by ruby_tree_sitter.") if $VERBOSE
  end

  last_error = nil
  candidates = lib_candidates
  lib_loaded = false
  candidates.each do |name|
    ffi_lib(name)
    lib_loaded = true
    break
  rescue LoadError => e
    # Note: FFI::NotFoundError inherits from LoadError, so it's caught here too
    last_error = e
  end

  unless lib_loaded
    # :nocov:
    tried = candidates.join(", ")
    env_hint = ENV["TREE_SITTER_RUNTIME_LIB"] ? " TREE_SITTER_RUNTIME_LIB=#{ENV["TREE_SITTER_RUNTIME_LIB"]}." : ""
    msg = if last_error
      "Could not load libtree-sitter (tried: #{tried}).#{env_hint} #{last_error.class}: #{last_error.message}"
    else
      "Could not load libtree-sitter (tried: #{tried}).#{env_hint}"
    end
    raise TreeHaver::NotAvailable, msg
    # :nocov:
  end

  # Attach functions after lib is selected
  # Note: TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types,
  # so these attach_function calls will fail on TruffleRuby.
  attach_function(:ts_parser_new, [], :pointer)
  attach_function(:ts_parser_delete, [:pointer], :void)
  attach_function(:ts_parser_set_language, [:pointer, :pointer], :bool)
  attach_function(:ts_parser_parse_string, [:pointer, :pointer, :string, :uint32], :pointer)

  attach_function(:ts_tree_delete, [:pointer], :void)
  attach_function(:ts_tree_root_node, [:pointer], :ts_node)

  attach_function(:ts_node_type, [:ts_node], :string)
  attach_function(:ts_node_child_count, [:ts_node], :uint32)
  attach_function(:ts_node_child, [:ts_node, :uint32], :ts_node)
  attach_function(:ts_node_child_by_field_name, [:ts_node, :string, :uint32], :ts_node)
  attach_function(:ts_node_start_byte, [:ts_node], :uint32)
  attach_function(:ts_node_end_byte, [:ts_node], :uint32)
  attach_function(:ts_node_start_point, [:ts_node], :ts_point)
  attach_function(:ts_node_end_point, [:ts_node], :ts_point)
  attach_function(:ts_node_is_null, [:ts_node], :bool)
  attach_function(:ts_node_is_named, [:ts_node], :bool)
  attach_function(:ts_node_is_missing, [:ts_node], :bool)
  attach_function(:ts_node_has_error, [:ts_node], :bool)

  # Node navigation functions
  attach_function(:ts_node_parent, [:ts_node], :ts_node)
  attach_function(:ts_node_next_sibling, [:ts_node], :ts_node)
  attach_function(:ts_node_prev_sibling, [:ts_node], :ts_node)
  attach_function(:ts_node_next_named_sibling, [:ts_node], :ts_node)
  attach_function(:ts_node_prev_named_sibling, [:ts_node], :ts_node)
  attach_function(:ts_node_named_child, [:ts_node, :uint32], :ts_node)
  attach_function(:ts_node_named_child_count, [:ts_node], :uint32)

  # Descendant lookup functions
  attach_function(:ts_node_descendant_for_byte_range, [:ts_node, :uint32, :uint32], :ts_node)
  attach_function(:ts_node_descendant_for_point_range, [:ts_node, :ts_point, :ts_point], :ts_node)
  attach_function(:ts_node_named_descendant_for_byte_range, [:ts_node, :uint32, :uint32], :ts_node)
  attach_function(:ts_node_named_descendant_for_point_range, [:ts_node, :ts_point, :ts_point], :ts_node)

  # Only mark as fully loaded after all attach_function calls succeed
  @loaded = true
end

.ts_node_classClass

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the TSNode class, ensuring it’s defined

Returns:

  • (Class)

    the TSNode FFI struct class



201
202
203
204
# File 'lib/tree_haver/backends/ffi.rb', line 201

def ts_node_class
  ensure_ffi_extended!
  const_get(:TSNode)
end