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
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
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_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.
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.
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.
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.}" 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_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
201 202 203 204 |
# File 'lib/tree_haver/backends/ffi.rb', line 201 def ts_node_class ensure_ffi_extended! const_get(:TSNode) end |