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
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'lib/tree_haver/backends/ffi.rb', line 524 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.
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 |
# File 'lib/tree_haver/backends/ffi.rb', line 551 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
621 622 623 624 625 626 627 628 |
# File 'lib/tree_haver/backends/ffi.rb', line 621 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 |