Class: TreeHaver::Backends::FFI::Parser
- Inherits:
-
Object
- Object
- TreeHaver::Backends::FFI::Parser
- Defined in:
- lib/tree_haver/backends/ffi.rb
Overview
FFI-based tree-sitter parser
Wraps a TSParser pointer and manages its lifecycle with a finalizer.
Instance Method Summary collapse
-
#initialize ⇒ Parser
constructor
Create a new parser instance.
-
#language=(lang) ⇒ Language
Set the language for this parser.
-
#parse(source) ⇒ Tree
Parse source code into a syntax tree.
Constructor Details
#initialize ⇒ Parser
Create a new parser instance
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/tree_haver/backends/ffi.rb', line 415 def initialize raise TreeHaver::NotAvailable, "FFI not available" unless Backends::FFI.available? Native.try_load! @parser = Native.ts_parser_new raise TreeHaver::NotAvailable, "Failed to create ts_parser" if @parser.null? # Note: We intentionally do NOT register a finalizer here because: # 1. ts_parser_delete can segfault if called during certain GC scenarios # 2. The native library may be unloaded before finalizers run # 3. Parser cleanup happens automatically on process exit # 4. Long-running processes should explicitly manage parser lifecycle # # If you need explicit cleanup in long-running processes, store the # parser in an instance variable and call a cleanup method explicitly # when done, rather than relying on GC finalizers. end |
Instance Method Details
#language=(lang) ⇒ Language
Set the language for this parser
Note: FFI backend is special - it receives the wrapped Language object because it needs to call to_ptr to get the FFI pointer. TreeHaver::Parser detects FFI Language wrappers (respond_to?(:to_ptr)) and passes them through.
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/tree_haver/backends/ffi.rb', line 442 def language=(lang) # Defensive check: ensure we received an FFI Language wrapper unless lang.is_a?(Language) raise TreeHaver::NotAvailable, "FFI backend expected FFI::Language wrapper, got #{lang.class}. " \ "This usually means TreeHaver::Parser#unwrap_language passed the wrong type. " \ "Check that language caching respects backend boundaries." end # Additional check: verify the language is actually for FFI backend if lang.respond_to?(:backend) && lang.backend != :ffi raise TreeHaver::NotAvailable, "FFI backend received Language for wrong backend: #{lang.backend}. " \ "Expected :ffi backend. Class: #{lang.class}. " \ "Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}" end # Verify the DynamicLibrary is still valid (not GC'd) # The Language stores @library to prevent this, but let's verify lib = lang.instance_variable_get(:@library) if lib.nil? raise TreeHaver::NotAvailable, "FFI Language has no library reference. The dynamic library may have been unloaded. " \ "Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}" end # Verify the language has a valid pointer ptr = lang.to_ptr # Check ptr is actually an FFI::Pointer unless ptr.is_a?(::FFI::Pointer) raise TreeHaver::NotAvailable, "FFI Language#to_ptr returned #{ptr.class}, expected FFI::Pointer. " \ "Language class: #{lang.class}. " \ "Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}" end ptr_address = ptr.address # Check for NULL (0x0) if ptr.nil? || ptr_address.zero? raise TreeHaver::NotAvailable, "FFI Language has NULL pointer. Language may not have loaded correctly. " \ "Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}" end # Check for small invalid addresses (< 4KB are typically unmapped memory) # Common invalid addresses like 0x40 (64) indicate corrupted or uninitialized pointers if ptr_address < 4096 raise TreeHaver::NotAvailable, "FFI Language has invalid pointer (address 0x#{ptr_address.to_s(16)}). " \ "This usually indicates the language library was unloaded or never loaded correctly. " \ "Path: #{lang.path.inspect}, Symbol: #{lang.symbol.inspect}" end # Note: MRI backend conflict is now handled by TreeHaver::BackendConflict # at a higher level (in TreeHaver.resolve_backend_module) # lang is a wrapped FFI::Language that has to_ptr method ok = Native.ts_parser_set_language(@parser, ptr) raise TreeHaver::NotAvailable, "Failed to set language on parser" unless ok lang # rubocop:disable Lint/Void (intentional return value) end |
#parse(source) ⇒ Tree
Parse source code into a syntax tree
512 513 514 515 516 517 518 519 |
# File 'lib/tree_haver/backends/ffi.rb', line 512 def parse(source) src = String(source) tree_ptr = Native.ts_parser_parse_string(@parser, ::FFI::Pointer::NULL, src, src.bytesize) raise TreeHaver::NotAvailable, "Parse returned NULL" if tree_ptr.null? # Return raw FFI::Tree - TreeHaver::Parser will wrap it Tree.new(tree_ptr) end |