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:



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.

Parameters:

  • lang (Language)

    the FFI language wrapper (not unwrapped)

Returns:

  • (Language)

    the language that was set

Raises:



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

Parameters:

  • source (String)

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

Returns:

  • (Tree)

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

Raises:



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