Class: TreeHaver::Backends::Java::Language Private

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/tree_haver/backends/java.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Java backend language wrapper (raw backend language)

This is a **raw backend language** that wraps a jtreesitter Language object via JRuby’s Java interop. It is used to configure the parser for a specific grammar (e.g., TOML, JSON, etc.).

Unlike TreeHaver::Language (which is a module with factory methods), this class holds the actual loaded language data from a grammar shared library.

:nocov: All Java backend implementation classes require JRuby and cannot be tested on MRI/CRuby. JRuby-specific CI jobs would test this code.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(impl, path: nil, symbol: nil) ⇒ Language

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.

Returns a new instance of Language.



274
275
276
277
278
279
# File 'lib/tree_haver/backends/java.rb', line 274

def initialize(impl, path: nil, symbol: nil)
  @impl = impl
  @backend = :java
  @path = path
  @symbol = symbol
end

Instance Attribute Details

#backendSymbol (readonly)

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.

The backend this language is for

Returns:

  • (Symbol)


263
264
265
# File 'lib/tree_haver/backends/java.rb', line 263

def backend
  @backend
end

#implObject (readonly)

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.



259
260
261
# File 'lib/tree_haver/backends/java.rb', line 259

def impl
  @impl
end

#pathString? (readonly)

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.

The path this language was loaded from (if known)

Returns:

  • (String, nil)


267
268
269
# File 'lib/tree_haver/backends/java.rb', line 267

def path
  @path
end

#symbolString? (readonly)

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.

The symbol name (if known)

Returns:

  • (String, nil)


271
272
273
# File 'lib/tree_haver/backends/java.rb', line 271

def symbol
  @symbol
end

Class Method Details

.from_library(path, symbol: nil, name: nil) ⇒ Object Also known as: from_path

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.



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/tree_haver/backends/java.rb', line 336

def from_library(path, symbol: nil, name: nil)
  raise TreeHaver::NotAvailable, "Java backend not available" unless Java.available?

  # Use shared utility for consistent symbol derivation across backends
  # If symbol not provided, derive from name or path
  sym = symbol || LibraryPathUtils.derive_symbol_from_path(path)
  # If name was provided, use it to override the derived symbol
  sym = "tree_sitter_#{name}" if name && !symbol

  begin
    arena = ::Java::JavaLangForeign::Arena.global
    symbol_lookup_class = ::Java::JavaLangForeign::SymbolLookup

    # IMPORTANT: Load libtree-sitter.so FIRST by name so its symbols are available
    # Grammar libraries need symbols like ts_language_version from the runtime
    # We cache this lookup at the module level
    unless Java.runtime_lookup
      # Use libraryLookup(String, Arena) to search LD_LIBRARY_PATH
      Java.runtime_lookup = symbol_lookup_class.libraryLookup("libtree-sitter.so", arena)
    end

    # Now load the grammar library
    if File.exist?(path)
      # Explicit path provided - use libraryLookup(Path, Arena)
      java_path = ::Java::JavaNioFile::Paths.get(path)
      grammar_lookup = symbol_lookup_class.libraryLookup(java_path, arena)
    else
      # Library name provided - use libraryLookup(String, Arena) to search
      # LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / PATH
      grammar_lookup = symbol_lookup_class.libraryLookup(path, arena)
    end

    # Chain the lookups: grammar first, then runtime library for ts_* symbols
    # This makes ts_language_version available when Language.load() needs it
    combined_lookup = grammar_lookup.or(Java.runtime_lookup)

    java_lang = Java.java_classes[:Language].load(combined_lookup, sym)
    new(java_lang, path: path, symbol: symbol)
  rescue ::Java::JavaLang::RuntimeException => e
    cause = e.cause
    root_cause = cause&.cause || cause

    error_msg = "Failed to load language '#{sym}' from #{path}: #{e.message}"
    if root_cause.is_a?(::Java::JavaLang::UnsatisfiedLinkError)
      unresolved = root_cause.message.to_s
      if unresolved.include?("ts_language_version")
        # This specific symbol was renamed in tree-sitter 0.24
        error_msg += "\n\nVersion mismatch detected: The grammar was built against " \
          "tree-sitter < 0.24 (uses ts_language_version), but your runtime library " \
          "is tree-sitter >= 0.24 (uses ts_language_abi_version).\n\n" \
          "Solutions:\n" \
          "1. Rebuild the grammar against your version of tree-sitter\n" \
          "2. Install a matching version of tree-sitter (< 0.24)\n" \
          "3. Find a pre-built grammar compatible with tree-sitter 0.24+"
      elsif unresolved.include?("ts_language") || unresolved.include?("ts_parser")
        error_msg += "\n\nThe grammar library has unresolved tree-sitter symbols. " \
          "Ensure libtree-sitter.so is in LD_LIBRARY_PATH and version-compatible " \
          "with the grammar."
      end
    end
    raise TreeHaver::NotAvailable, error_msg
  rescue ::Java::JavaLang::UnsatisfiedLinkError => e
    raise TreeHaver::NotAvailable,
      "Native library error loading #{path}: #{e.message}. " \
        "Ensure the library is in LD_LIBRARY_PATH."
  rescue ::Java::JavaLang::IllegalArgumentException => e
    raise TreeHaver::NotAvailable,
      "Could not find library '#{path}': #{e.message}. " \
        "Ensure it's in LD_LIBRARY_PATH or provide an absolute path."
  end
end

.load_by_name(name) ⇒ Language

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.

Load a language by name from java-tree-sitter grammar JARs

This method loads grammars that are packaged as java-tree-sitter JARs from Maven Central. These JARs include the native grammar library pre-built for Java’s Foreign Function API.

Examples:

# First, add the grammar JAR to TREE_SITTER_JAVA_JARS_DIR:
# tree-sitter-toml-0.23.2.jar from Maven Central
lang = TreeHaver::Backends::Java::Language.load_by_name("toml")

Parameters:

  • name (String)

    the language name (e.g., “java”, “python”, “toml”)

Returns:

Raises:



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
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
# File 'lib/tree_haver/backends/java.rb', line 422

def load_by_name(name)
  raise TreeHaver::NotAvailable, "Java backend not available" unless Java.available?

  # Try to find the grammar library in standard locations
  # Look for library names like "tree-sitter-toml" or "libtree-sitter-toml"
  lib_names = [
    "tree-sitter-#{name}",
    "libtree-sitter-#{name}",
    "tree_sitter_#{name}",
  ]

  begin
    arena = ::Java::JavaLangForeign::Arena.global
    symbol_lookup_class = ::Java::JavaLangForeign::SymbolLookup

    # Ensure runtime lookup is available
    unless Java.runtime_lookup
      Java.runtime_lookup = symbol_lookup_class.libraryLookup("libtree-sitter.so", arena)
    end

    # Try each library name
    grammar_lookup = nil
    lib_names.each do |lib_name|
      grammar_lookup = symbol_lookup_class.libraryLookup(lib_name, arena)
      break
    rescue ::Java::JavaLang::IllegalArgumentException
      # Library not found in search path, try next name
      next
    end

    unless grammar_lookup
      raise TreeHaver::NotAvailable,
        "Failed to load language '#{name}': Library not found. " \
          "Ensure the grammar library (e.g., libtree-sitter-#{name}.so) " \
          "is in LD_LIBRARY_PATH."
    end

    combined_lookup = grammar_lookup.or(Java.runtime_lookup)
    sym = "tree_sitter_#{name}"
    java_lang = Java.java_classes[:Language].load(combined_lookup, sym)
    new(java_lang, symbol: sym)
  rescue ::Java::JavaLang::RuntimeException => e
    raise TreeHaver::NotAvailable,
      "Failed to load language '#{name}': #{e.message}. " \
        "Ensure the grammar library (e.g., libtree-sitter-#{name}.so) " \
        "is in LD_LIBRARY_PATH."
  end
end

Instance Method Details

#<=>(other) ⇒ Integer?

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.

Compare languages for equality

Java languages are equal if they have the same backend, path, and symbol. Path and symbol uniquely identify a loaded language.

Parameters:

  • other (Object)

    object to compare with

Returns:

  • (Integer, nil)

    -1, 0, 1, or nil if not comparable



288
289
290
291
292
293
294
295
296
297
# File 'lib/tree_haver/backends/java.rb', line 288

def <=>(other)
  return unless other.is_a?(Language)
  return unless other.backend == @backend

  # Compare by path first, then symbol
  cmp = (@path || "") <=> (other.path || "")
  return cmp if cmp.nonzero?

  (@symbol || "") <=> (other.symbol || "")
end

#hashInteger

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.

Hash value for this language (for use in Sets/Hashes)

Returns:

  • (Integer)


301
302
303
# File 'lib/tree_haver/backends/java.rb', line 301

def hash
  [@backend, @path, @symbol].hash
end