Class: TreeHaver::CitrusGrammarFinder
- Inherits:
-
Object
- Object
- TreeHaver::CitrusGrammarFinder
- 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.
Instance Attribute Summary collapse
-
#gem_name ⇒ String
readonly
The gem name to require.
-
#grammar_const ⇒ String
readonly
The constant path to the grammar (e.g., “TomlRB::Document”).
-
#language_name ⇒ Symbol
readonly
The language identifier.
-
#require_path ⇒ String?
readonly
Custom require path (defaults to gem_name with dashes to slashes).
Instance Method Summary collapse
-
#available? ⇒ Boolean
Check if the Citrus grammar is available.
-
#grammar_module ⇒ Module?
Get the resolved grammar module.
-
#initialize(language:, gem_name:, grammar_const:, require_path: nil) ⇒ CitrusGrammarFinder
constructor
Initialize a Citrus grammar finder.
-
#not_found_message ⇒ String
Get a human-readable error message when grammar is not found.
-
#register!(raise_on_missing: false) ⇒ Boolean
Register this Citrus grammar with TreeHaver.
-
#search_info ⇒ Hash
Get debug information about the search.
Constructor Details
#initialize(language:, gem_name:, grammar_const:, require_path: nil) ⇒ CitrusGrammarFinder
Initialize a Citrus grammar finder
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 @load_attempted = false @available = false @grammar_module = nil end |
Instance Attribute Details
#gem_name ⇒ String (readonly)
Returns 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_const ⇒ String (readonly)
Returns 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_name ⇒ Symbol (readonly)
Returns the language identifier.
33 34 35 |
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 33 def language_name @language_name end |
#require_path ⇒ String? (readonly)
Returns 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.
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 102 103 104 105 106 107 108 109 110 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 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 66 def available? return @available if @load_attempted @load_attempted = true debug = ENV["TREE_HAVER_DEBUG"] # Guard against nil require_path (can happen if gem_name was nil) if @require_path.nil? || @require_path.empty? warn("CitrusGrammarFinder: require_path is nil or empty for #{@language_name}") if debug @available = false return false end 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) # :nocov: defensive - requires a gem with malformed grammar module # Show what methods ARE available to help diagnose the issue if debug available_methods = @grammar_module.methods(false).sort.first(20) warn("CitrusGrammarFinder: #{@grammar_const} doesn't respond to :parse") warn("CitrusGrammarFinder: #{@grammar_const}.class = #{@grammar_module.class}") warn("CitrusGrammarFinder: #{@grammar_const} is a #{@grammar_module.is_a?(Module) ? "Module" : "non-Module"}") warn("CitrusGrammarFinder: Available singleton methods (first 20): #{available_methods.inspect}") if @grammar_module.respond_to?(:instance_methods) instance_methods = @grammar_module.instance_methods(false).sort.first(20) warn("CitrusGrammarFinder: Available instance methods (first 20): #{instance_methods.inspect}") end end @available = false return false # :nocov: end @available = true rescue LoadError => e # :nocov: defensive - requires gem to not be installed # Only show LoadError details when debugging if debug warn("CitrusGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.}") warn("CitrusGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") end @available = false # :nocov: rescue NameError => e # :nocov: defensive - requires gem with missing constant # Only show NameError details when debugging if debug warn("CitrusGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.}") warn("CitrusGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") end @available = false # :nocov: rescue TypeError => e # :nocov: defensive - TruffleRuby-specific edge case # TruffleRuby's bundled_gems.rb can raise TypeError when File.path is called on nil # This happens in bundled_gems.rb:124 warning? method when caller locations return nil # Always warn about TypeError as it indicates a platform-specific issue warn("CitrusGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.}") warn("CitrusGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue") if debug warn("CitrusGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") end @available = false # :nocov: rescue => e # :nocov: defensive - catch-all for unexpected errors # Always warn about unexpected errors warn("CitrusGrammarFinder: Unexpected error: #{e.class}: #{e.}") if debug warn("CitrusGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") end @available = false # :nocov: end @available end |
#grammar_module ⇒ Module?
Get the resolved grammar module
154 155 156 157 |
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 154 def grammar_module available? # Ensure we've tried to load @grammar_module end |
#not_found_message ⇒ String
Get a human-readable error message when grammar is not found
200 201 202 203 |
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 200 def "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
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 167 def register!(raise_on_missing: false) unless available? if raise_on_missing raise NotAvailable, end return false end TreeHaver.register_language( @language_name, grammar_module: @grammar_module, gem_name: @gem_name, ) true end |
#search_info ⇒ Hash
Get debug information about the search
186 187 188 189 190 191 192 193 194 195 |
# File 'lib/tree_haver/citrus_grammar_finder.rb', line 186 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 |