Class: TreeHaver::ParsletGrammarFinder

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

Overview

Utility for finding and registering Parslet grammar gems.

ParsletGrammarFinder provides language-agnostic discovery of Parslet 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), Parslet grammars are
Ruby classes that inherit from Parslet::Parser. This class handles the
discovery and registration of these grammars.

Examples:

Basic usage with toml gem

finder = TreeHaver::ParsletGrammarFinder.new(
  language: :toml,
  gem_name: "toml",
  grammar_const: "TOML::Parslet"
)
finder.register! if finder.available?

With custom require path

finder = TreeHaver::ParsletGrammarFinder.new(
  language: :json,
  gem_name: "json-parslet",
  grammar_const: "JsonParslet::Grammar",
  require_path: "json/parslet"
)

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Initialize a Parslet grammar finder

Parameters:

  • language (Symbol, String)

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

  • gem_name (String)

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

  • grammar_const (String)

    constant path to grammar class (e.g., “TOML::Parslet”)

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

    custom require path (defaults to gem_name as-is)



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

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_class = nil
end

Instance Attribute Details

#gem_nameString (readonly)

Returns the gem name to require.

Returns:

  • (String)

    the gem name to require



37
38
39
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 37

def gem_name
  @gem_name
end

#grammar_constString (readonly)

Returns the constant path to the grammar class (e.g., “TOML::Parslet”).

Returns:

  • (String)

    the constant path to the grammar class (e.g., “TOML::Parslet”)



40
41
42
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 40

def grammar_const
  @grammar_const
end

#language_nameSymbol (readonly)

Returns the language identifier.

Returns:

  • (Symbol)

    the language identifier



34
35
36
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 34

def language_name
  @language_name
end

#require_pathString? (readonly)

Returns custom require path (defaults to gem_name).

Returns:

  • (String, nil)

    custom require path (defaults to gem_name)



43
44
45
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 43

def require_path
  @require_path
end

Instance Method Details

#available?Boolean

Check if the Parslet 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



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
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 67

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("ParsletGrammarFinder: 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_class = resolve_constant(@grammar_const)

    # Verify it can create a parser instance with a parse method
    unless valid_grammar_class?(@grammar_class)
      if debug
        warn("ParsletGrammarFinder: #{@grammar_const} is not a valid Parslet grammar class")
        warn("ParsletGrammarFinder: #{@grammar_const}.class = #{@grammar_class.class}")
      end
      @available = false
      return false
    end

    @available = true
  rescue LoadError => e
    # :nocov: defensive - requires gem to not be installed
    if debug
      warn("ParsletGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
      warn("ParsletGrammarFinder: LoadError backtrace:\n  #{e.backtrace&.first(10)&.join("\n  ")}")
    end
    @available = false
    # :nocov:
  rescue NameError => e
    # :nocov: defensive - requires gem with missing constant
    if debug
      warn("ParsletGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
      warn("ParsletGrammarFinder: NameError backtrace:\n  #{e.backtrace&.first(10)&.join("\n  ")}")
    end
    @available = false
    # :nocov:
  rescue TypeError => e
    # :nocov: defensive - TruffleRuby-specific edge case
    warn("ParsletGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
    warn("ParsletGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue")
    if debug
      warn("ParsletGrammarFinder: TypeError backtrace:\n  #{e.backtrace&.first(10)&.join("\n  ")}")
    end
    @available = false
    # :nocov:
  rescue => e
    # :nocov: defensive - catch-all for unexpected errors
    warn("ParsletGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
    if debug
      warn("ParsletGrammarFinder: backtrace:\n  #{e.backtrace&.first(10)&.join("\n  ")}")
    end
    @available = false
    # :nocov:
  end

  @available
end

#grammar_classClass?

Get the resolved grammar class

Returns:

  • (Class, nil)

    the grammar class if available



139
140
141
142
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 139

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

#not_found_messageString

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

Returns:

  • (String)

    error message with installation hints



185
186
187
188
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 185

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

#register!(raise_on_missing: false) ⇒ Boolean

Register this Parslet 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



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 152

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_class: @grammar_class,
    gem_name: @gem_name,
  )
  true
end

#search_infoHash

Get debug information about the search

Returns:

  • (Hash)

    diagnostic information



171
172
173
174
175
176
177
178
179
180
# File 'lib/tree_haver/parslet_grammar_finder.rb', line 171

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