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
-
.define_ts_node_struct! ⇒ Object
private
Define the TSNode struct lazily.
-
.define_ts_point_struct! ⇒ Object
private
Define the TSPoint struct lazily.
-
.ensure_ffi_extended! ⇒ Boolean
private
Lazily extend with FFI::Library only when needed.
-
.lib_candidates ⇒ Array<String>
private
Get list of candidate library names for loading libtree-sitter.
- .loaded? ⇒ Boolean private
-
.try_load! ⇒ void
private
Load the tree-sitter runtime library.
-
.ts_node_class ⇒ Class
private
Get the TSNode class, ensuring it’s defined.
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
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_candidates ⇒ Array<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.
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.
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.
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.
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.}" 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_class ⇒ Class
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
167 168 169 170 |
# File 'lib/tree_haver/backends/ffi.rb', line 167 def ts_node_class ensure_ffi_extended! const_get(:TSNode) end |