Class: TreeHaver::Backends::FFI::Parser

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeParser

Create a new parser instance

Raises:



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.

Parameters:

  • lang (Language)

    the FFI language wrapper (not unwrapped)

Returns:

  • (Language)

    the language that was set

Raises:



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

Parameters:

  • source (String)

    the source code to parse (should be UTF-8)

Returns:

  • (Tree)

    raw backend tree (wrapping happens in TreeHaver::Parser)

Raises:



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