Module: TreeHaver::Backends::FFI

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

Overview

Note:

Requires the ffi gem and libtree-sitter shared library to be installed

FFI-based backend for calling libtree-sitter directly

This backend uses Ruby FFI (JNR-FFI on JRuby) to call the native tree-sitter
C library without requiring MRI C extensions.

The FFI backend currently supports:

  • Parsing source code
  • AST node traversal
  • Accessing node types and children

Not yet supported:

  • Query API (tree-sitter queries/patterns)

== Tree/Node Architecture

This backend defines raw FFI::Tree and FFI::Node wrapper classes that
provide minimal FFI bindings to the tree-sitter C structs. These are not
intended for direct use by application code.

The wrapping hierarchy is:
FFI::Tree/Node (raw FFI wrappers) → TreeHaver::Tree/Node → Base::Tree/Node

When you use TreeHaver::Parser#parse:

  1. FFI::Parser#parse returns an FFI::Tree (raw pointer wrapper)
  2. TreeHaver::Parser wraps it in TreeHaver::Tree (adds source storage)
  3. TreeHaver::Tree#root_node wraps FFI::Node in TreeHaver::Node

The TreeHaver::Tree and TreeHaver::Node wrappers provide the full unified
API including #children, #text, #source, #source_position, etc.

This differs from pure-Ruby backends (Citrus, Parslet, Prism, Psych) which
define Tree/Node classes that directly inherit from Base::Tree/Base::Node.

== Platform Compatibility

  • MRI Ruby: ✓ Full support
  • JRuby: ✓ Full support (uses JNR-FFI)
  • TruffleRuby: ✗ TruffleRuby’s FFI doesn’t support STRUCT_BY_VALUE return types
    (used by ts_tree_root_node, ts_node_child, ts_node_start_point, etc.)

Defined Under Namespace

Modules: Native Classes: Language, Node, Parser, Tree

Class Method Summary collapse

Class Method Details

.available?Boolean

Check if the FFI backend is available

The FFI backend requires:

  • The ffi gem to be installed
  • NOT running on TruffleRuby (STRUCT_BY_VALUE limitation)
  • MRI backend (ruby_tree_sitter) not already loaded (symbol conflicts)

Examples:

if TreeHaver::Backends::FFI.available?
  puts "FFI backend is ready"
end

Returns:

  • (Boolean)

    true if FFI backend can be used



70
71
72
73
74
75
# File 'lib/tree_haver/backends/ffi.rb', line 70

def available?
  return false unless ffi_gem_available?

  # Check if MRI backend has been loaded (which blocks FFI)
  !defined?(::TreeSitter::Parser)
end

.capabilitiesHash{Symbol => Object}

Get capabilities supported by this backend

Examples:

TreeHaver::Backends::FFI.capabilities
# => { backend: :ffi, parse: true, query: false, bytes_field: true }

Returns:

  • (Hash{Symbol => Object})

    capability map



123
124
125
126
127
128
129
130
131
132
# File 'lib/tree_haver/backends/ffi.rb', line 123

def capabilities
  return {} unless available?
  {
    backend: :ffi,
    parse: true,
    query: false, # Query API not yet implemented in FFI backend
    bytes_field: true,
    incremental: false,
  }
end

.ffi_gem_available?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.

Note:

Returns false on TruffleRuby because TruffleRuby’s FFI doesn’t support
STRUCT_BY_VALUE return types (used by ts_tree_root_node, ts_node_child, etc.)

Check if the FFI gem can be loaded and is usable for tree-sitter

Returns:

  • (Boolean)

    true if FFI gem can be loaded and works with tree-sitter



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/tree_haver/backends/ffi.rb', line 83

def ffi_gem_available?
  return @loaded if @load_attempted # rubocop:disable ThreadSafety/ClassInstanceVariable
  @load_attempted = true # rubocop:disable ThreadSafety/ClassInstanceVariable

  @loaded = begin # rubocop:disable ThreadSafety/ClassInstanceVariable
    # TruffleRuby's FFI doesn't support STRUCT_BY_VALUE return types
    # which tree-sitter uses extensively (ts_tree_root_node, ts_node_child, etc.)
    # :nocov: TruffleRuby returns false early - subsequent FFI code paths unreachable on TruffleRuby
    if RUBY_ENGINE == "truffleruby"
      false
    # :nocov:
    else
      require "ffi"
      true
    end
  rescue LoadError
    false
    # :nocov: defensive code - StandardError during require is extremely rare
  rescue StandardError
    false
    # :nocov:
  end
  @loaded # rubocop:disable ThreadSafety/ClassInstanceVariable
end

.reset!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.

Reset the load state (primarily for testing)



112
113
114
115
# File 'lib/tree_haver/backends/ffi.rb', line 112

def reset!
  @load_attempted = false # rubocop:disable ThreadSafety/ClassInstanceVariable
  @loaded = false # rubocop:disable ThreadSafety/ClassInstanceVariable
end