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



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

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



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/tree_haver/backends/ffi.rb', line 131

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



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/tree_haver/backends/ffi.rb', line 115

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



179
180
181
182
183
184
185
186
187
188
# File 'lib/tree_haver/backends/ffi.rb', line 179

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)


259
260
261
# File 'lib/tree_haver/backends/ffi.rb', line 259

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:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
# File 'lib/tree_haver/backends/ffi.rb', line 197

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 ::FFI::NotFoundError, LoadError => e
    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_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)

  # 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



167
168
169
170
# File 'lib/tree_haver/backends/ffi.rb', line 167

def ts_node_class
  ensure_ffi_extended!
  const_get(:TSNode)
end