Module: TreeHaver::LanguageRegistry Private

Defined in:
lib/tree_haver/language_registry.rb

Overview

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

Thread-safe language registrations and cache for loaded Language handles

The LanguageRegistry provides two main functions:

  1. Registrations: Store mappings from language names to backend-specific configurations
  2. Cache: Memoize loaded Language objects to avoid repeated dlopen calls

The registry supports multiple backends for the same language, allowing runtime switching, benchmarking, and fallback scenarios.

Supported Backend Types

The registry is extensible and supports any backend type. Common types include:

  • :tree_sitter - Native tree-sitter grammars (.so files)
  • :citrus - Citrus PEG parser grammars (pure Ruby)
  • :prism - Ruby's Prism parser (Ruby source only)
  • :psych - Ruby's Psych parser (YAML only)
  • :commonmarker - Commonmarker gem (Markdown)
  • :markly - Markly gem (Markdown/GFM)
  • :rbs - RBS gem (RBS type signatures) - registered externally by rbs-merge

External gems can register their own backend types using the same API.

Registration structure:

registrations = {
  toml: {
    tree_sitter: { path: "/path/to/lib.so", symbol: "tree_sitter_toml" },
    citrus: { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
  },
  ruby: {
    prism: { backend_module: TreeHaver::Backends::Prism }
  },
  yaml: {
    psych: { backend_module: TreeHaver::Backends::Psych }
  },
  markdown: {
    commonmarker: { backend_module: TreeHaver::Backends::Commonmarker },
    markly: { backend_module: TreeHaver::Backends::Markly }
  },
  rbs: {
    rbs: { backend_module: Rbs::Merge::Backends::RbsBackend }  # External
  }
}
  TreeHaver::LanguageRegistry.register(:toml, :tree_sitter,
    path: "/path/to/lib.so", symbol: "tree_sitter_toml")
  TreeHaver::LanguageRegistry.register(:toml, :citrus,
    grammar_module: TomlRB::Document, gem_name: "toml-rb")
  TreeHaver::LanguageRegistry.register(:rbs, :rbs,
    backend_module: Rbs::Merge::Backends::RbsBackend,
    gem_name: "rbs")

Examples:

Register tree-sitter grammar

Register Citrus grammar

Register a pure Ruby backend (internal or external)

Class Method Summary collapse

Class Method Details

.clearvoid

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.

This method returns an undefined value.

Clear all registrations and cache

Removes all language registrations and cached Language objects. Primarily used in tests to reset state between test cases.

Examples:

LanguageRegistry.clear


213
214
215
216
217
218
219
# File 'lib/tree_haver/language_registry.rb', line 213

def clear
  @mutex.synchronize do
    @registrations.clear
    @cache.clear
  end
  nil
end

.clear_cache!void

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.

This method returns an undefined value.

Clear the language cache

Removes all cached Language objects. The next call to fetch for any key will recompute the value. Does not clear registrations.

Examples:

LanguageRegistry.clear_cache!


200
201
202
203
# File 'lib/tree_haver/language_registry.rb', line 200

def clear_cache!
  @mutex.synchronize { @cache.clear }
  nil
end

.fetch(key) ⇒ Object

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.

Fetch a cached language by key or compute and store it

This method provides thread-safe memoization for loaded Language objects. If the key exists in the cache, the cached value is returned immediately. Otherwise, the block is called to compute the value, which is then cached.

Examples:

language = LanguageRegistry.fetch(["/path/lib.so", "symbol", "toml"]) do
  expensive_language_load_operation
end

Parameters:

  • key (Array)

    cache key, typically [path, symbol, name]

Yield Returns:

  • (Object)

    the computed language handle (called only on cache miss)

Returns:

  • (Object)

    the cached or computed language handle



183
184
185
186
187
188
189
190
# File 'lib/tree_haver/language_registry.rb', line 183

def fetch(key)
  @mutex.synchronize do
    return @cache[key] if @cache.key?(key)

    value = yield
    @cache[key] = value
  end
end

.register(name, backend_type, **config) ⇒ void

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.

This method returns an undefined value.

Register a language for a specific backend

Stores backend-specific configuration for a language. Multiple backends can be registered for the same language without conflict.

Examples:

Register tree-sitter grammar

LanguageRegistry.register(:toml, :tree_sitter,
  path: "/usr/local/lib/libtree-sitter-toml.so", symbol: "tree_sitter_toml")

Register Citrus grammar

LanguageRegistry.register(:toml, :citrus,
  grammar_module: TomlRB::Document, gem_name: "toml-rb")

Register pure Ruby backend (external gem)

LanguageRegistry.register(:rbs, :rbs,
  backend_module: Rbs::Merge::Backends::RbsBackend, gem_name: "rbs")

Parameters:

  • name (Symbol, String)

    language identifier (e.g., :toml, :json, :ruby, :yaml, :rbs)

  • backend_type (Symbol)

    backend type (:tree_sitter, :citrus, :prism, :psych, :commonmarker, :markly, or custom)

  • config (Hash)

    backend-specific configuration

Options Hash (**config):

  • :path (String)

    tree-sitter library path (for tree-sitter backends)

  • :symbol (String)

    exported symbol name (for tree-sitter backends)

  • :grammar_module (Module)

    Citrus grammar module (for Citrus backend)

  • :backend_module (Module)

    backend module with Language/Parser classes (for pure Ruby backends)

  • :gem_name (String)

    gem name for error messages and availability checks



100
101
102
103
104
105
106
107
108
109
# File 'lib/tree_haver/language_registry.rb', line 100

def register(name, backend_type, **config)
  key = name.to_sym
  backend_key = backend_type.to_sym

  @mutex.synchronize do
    @registrations[key] ||= {}
    @registrations[key][backend_key] = config.compact
  end
  nil
end

.registered(name, backend_type = nil) ⇒ Hash{Symbol => Hash}, ...

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.

Fetch registration entries for a language

Returns all backend-specific configurations for a language.

Examples:

Get all backends

entries = LanguageRegistry.registered(:toml)
# => {
#   tree_sitter: { path: "/usr/local/lib/libtree-sitter-toml.so", symbol: "tree_sitter_toml" },
#   citrus: { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
# }

Get specific backend

entry = LanguageRegistry.registered(:toml, :citrus)
# => { grammar_module: TomlRB::Document, gem_name: "toml-rb" }

Parameters:

  • name (Symbol, String)

    language identifier

  • backend_type (Symbol, nil) (defaults to: nil)

    optional backend type to filter by

Returns:

  • (Hash{Symbol => Hash}, Hash, nil)

    all backends or specific backend config



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/tree_haver/language_registry.rb', line 157

def registered(name, backend_type = nil)
  @mutex.synchronize do
    lang_config = @registrations[name.to_sym]
    return unless lang_config

    if backend_type
      lang_config[backend_type.to_sym]
    else
      lang_config
    end
  end
end

.with_registration(name, backend_type, **config) ⇒ Object

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.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/tree_haver/language_registry.rb', line 111

def with_registration(name, backend_type, **config)
  key = name.to_sym
  backend_key = backend_type.to_sym
  original = nil
  had_language = false
  had_backend = false

  @mutex.synchronize do
    had_language = @registrations.key?(key)
    had_backend = @registrations.fetch(key, {}).key?(backend_key)
    original = @registrations.fetch(key, {})[backend_key]&.dup
    @registrations[key] ||= {}
    @registrations[key][backend_key] = config.compact
    @cache.clear
  end

  yield
ensure
  @mutex.synchronize do
    if had_backend
      @registrations[key][backend_key] = original
    elsif had_language
      @registrations[key].delete(backend_key)
    else
      @registrations.delete(key)
    end
    @cache.clear
  end
end