Class: TreeHaver::CitrusGrammarFinder

Inherits:
Object
  • Object
show all
Defined in:
lib/tree_haver/citrus_grammar_finder.rb

Overview

Utility for finding and registering Citrus grammar gems.

CitrusGrammarFinder provides language-agnostic discovery of Citrus grammar gems. Given a language name and gem information, it attempts to load the grammar and register it with tree_haver.

Unlike tree-sitter grammars (which are .so files), Citrus grammars are Ruby modules that respond to .parse(source). This class handles the discovery and registration of these grammars.

Examples:

Basic usage with toml-rb

finder = TreeHaver::CitrusGrammarFinder.new(
  language: :toml,
  gem_name: "toml-rb",
  grammar_const: "TomlRB::Document"
)
finder.register! if finder.available?

With custom require path

finder = TreeHaver::CitrusGrammarFinder.new(
  language: :json,
  gem_name: "json-rb",
  grammar_const: "JsonRB::Grammar",
  require_path: "json/rb"
)

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(language:, gem_name:, grammar_const:, require_path: nil) ⇒ CitrusGrammarFinder

Initialize a Citrus grammar finder

Parameters:

  • language (Symbol, String)

    the language name (e.g., :toml, :json)

  • gem_name (String)

    the gem name (e.g., “toml-rb”)

  • grammar_const (String)

    constant path to grammar (e.g., “TomlRB::Document”)

  • require_path (String, nil) (defaults to: nil)

    custom require path (defaults to gem_name with dashes→slashes)



50
51
52
53
54
55
56
57
58
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 50

def initialize(language:, gem_name:, grammar_const:, require_path: nil)
  @language_name = language.to_sym
  @gem_name = gem_name
  @grammar_const = grammar_const
  @require_path = require_path || gem_name.tr("-", "/")
  @load_attempted = false
  @available = false
  @grammar_module = nil
end

Instance Attribute Details

#gem_nameString (readonly)

Returns the gem name to require.

Returns:

  • (String)

    the gem name to require



36
37
38
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 36

def gem_name
  @gem_name
end

#grammar_constString (readonly)

Returns the constant path to the grammar (e.g., “TomlRB::Document”).

Returns:

  • (String)

    the constant path to the grammar (e.g., “TomlRB::Document”)



39
40
41
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 39

def grammar_const
  @grammar_const
end

#language_nameSymbol (readonly)

Returns the language identifier.

Returns:

  • (Symbol)

    the language identifier



33
34
35
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 33

def language_name
  @language_name
end

#require_pathString? (readonly)

Returns custom require path (defaults to gem_name with dashes to slashes).

Returns:

  • (String, nil)

    custom require path (defaults to gem_name with dashes to slashes)



42
43
44
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 42

def require_path
  @require_path
end

Instance Method Details

#available?Boolean

Check if the Citrus grammar is available

Attempts to require the gem and resolve the grammar constant. Result is cached after first call.

Returns:

  • (Boolean)

    true if grammar is available



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 66

def available?
  return @available if @load_attempted

  @load_attempted = true
  begin
    # Try to require the gem
    require @require_path

    # Try to resolve the constant
    @grammar_module = resolve_constant(@grammar_const)

    # Verify it responds to parse
    unless @grammar_module.respond_to?(:parse)
      warn("#{@grammar_const} doesn't respond to :parse")
      @available = false
      return false
    end

    @available = true
  rescue LoadError => e
    # Always show LoadError for debugging
    warn("CitrusGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
    @available = false
  rescue NameError => e
    # Always show NameError for debugging
    warn("CitrusGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
    @available = false
  rescue => e
    # Catch any other errors
    warn("CitrusGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
    warn(e.backtrace.first(3).join("\n")) if ENV["TREE_HAVER_DEBUG"]
    @available = false
  end

  @available
end

#grammar_moduleModule?

Get the resolved grammar module

Returns:

  • (Module, nil)

    the grammar module if available



106
107
108
109
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 106

def grammar_module
  available? # Ensure we've tried to load
  @grammar_module
end

#not_found_messageString

Get a human-readable error message when grammar is not found

Returns:

  • (String)

    error message with installation hints



152
153
154
155
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 152

def not_found_message
  "Citrus grammar for #{@language_name} not found. " \
    "Install #{@gem_name} gem: gem install #{@gem_name}"
end

#register!(raise_on_missing: false) ⇒ Boolean

Register this Citrus grammar with TreeHaver

After registration, the language can be used via:

TreeHaver::Language.{language_name}

Parameters:

  • raise_on_missing (Boolean) (defaults to: false)

    if true, raises when grammar not available

Returns:

  • (Boolean)

    true if registration succeeded

Raises:

  • (NotAvailable)

    if grammar not available and raise_on_missing is true



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 119

def register!(raise_on_missing: false)
  unless available?
    if raise_on_missing
      raise NotAvailable, not_found_message
    end
    return false
  end

  TreeHaver.register_language(
    @language_name,
    grammar_module: @grammar_module,
    gem_name: @gem_name,
  )
  true
end

#search_infoHash

Get debug information about the search

Returns:

  • (Hash)

    diagnostic information



138
139
140
141
142
143
144
145
146
147
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 138

def search_info
  {
    language: @language_name,
    gem_name: @gem_name,
    grammar_const: @grammar_const,
    require_path: @require_path,
    available: available?,
    grammar_module: @grammar_module&.name,
  }
end