tree_haver 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.md +13 -0
- data/README.md +1959 -0
- data/lib/tree_haver/backend_api.rb +392 -0
- data/lib/tree_haver/backend_registry.rb +153 -3
- data/lib/tree_haver/backends/citrus.rb +489 -0
- data/lib/tree_haver/backends/ffi.rb +1013 -0
- data/lib/tree_haver/backends/java.rb +909 -0
- data/lib/tree_haver/backends/mri.rb +367 -0
- data/lib/tree_haver/backends/parslet.rb +565 -0
- data/lib/tree_haver/backends/prism.rb +568 -0
- data/lib/tree_haver/backends/psych.rb +379 -0
- data/lib/tree_haver/backends/rust.rb +243 -0
- data/lib/tree_haver/backends/tslp.rb +274 -0
- data/lib/tree_haver/base/comment.rb +320 -0
- data/lib/tree_haver/base/language.rb +98 -0
- data/lib/tree_haver/base/node.rb +330 -0
- data/lib/tree_haver/base/parser.rb +28 -0
- data/lib/tree_haver/base/point.rb +48 -0
- data/lib/tree_haver/base/tree.rb +128 -0
- data/lib/tree_haver/citrus_grammar_finder.rb +213 -0
- data/lib/tree_haver/contracts.rb +661 -96
- data/lib/tree_haver/grammar_finder.rb +429 -0
- data/lib/tree_haver/kaitai_backend.rb +2 -2
- data/lib/tree_haver/language.rb +294 -0
- data/lib/tree_haver/language_pack.rb +17 -166
- data/lib/tree_haver/language_registry.rb +221 -0
- data/lib/tree_haver/library_path_utils.rb +80 -0
- data/lib/tree_haver/node.rb +588 -0
- data/lib/tree_haver/parser.rb +445 -0
- data/lib/tree_haver/parslet_grammar_finder.rb +217 -0
- data/lib/tree_haver/path_validator.rb +356 -0
- data/lib/tree_haver/peg_backends.rb +7 -7
- data/lib/tree_haver/point.rb +27 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +52 -0
- data/lib/tree_haver/rspec.rb +3 -0
- data/lib/tree_haver/tree.rb +267 -0
- data/lib/tree_haver/version.rb +5 -3
- data/lib/tree_haver.rb +613 -8
- data/sig/tree_haver.rbs +6 -0
- data.tar.gz.sig +0 -0
- metadata +314 -13
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
# Utility for finding and registering Citrus grammar gems.
|
|
5
|
+
#
|
|
6
|
+
# CitrusGrammarFinder provides language-agnostic discovery of Citrus grammar
|
|
7
|
+
# gems. Given a language name and gem information, it attempts to load the
|
|
8
|
+
# grammar and register it with tree_haver.
|
|
9
|
+
#
|
|
10
|
+
# Unlike tree-sitter grammars (which are .so files), Citrus grammars are
|
|
11
|
+
# Ruby modules that respond to .parse(source). This class handles the
|
|
12
|
+
# discovery and registration of these grammars.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage with toml-rb
|
|
15
|
+
# finder = TreeHaver::CitrusGrammarFinder.new(
|
|
16
|
+
# language: :toml,
|
|
17
|
+
# gem_name: "toml-rb",
|
|
18
|
+
# grammar_const: "TomlRB::Document"
|
|
19
|
+
# )
|
|
20
|
+
# finder.register! if finder.available?
|
|
21
|
+
#
|
|
22
|
+
# @example With custom require path
|
|
23
|
+
# finder = TreeHaver::CitrusGrammarFinder.new(
|
|
24
|
+
# language: :json,
|
|
25
|
+
# gem_name: "json-rb",
|
|
26
|
+
# grammar_const: "JsonRB::Grammar",
|
|
27
|
+
# require_path: "json/rb"
|
|
28
|
+
# )
|
|
29
|
+
#
|
|
30
|
+
# @see GrammarFinder For tree-sitter grammar discovery
|
|
31
|
+
class CitrusGrammarFinder
|
|
32
|
+
# @return [Symbol] the language identifier
|
|
33
|
+
attr_reader :language_name
|
|
34
|
+
|
|
35
|
+
# @return [String] the gem name to require
|
|
36
|
+
attr_reader :gem_name
|
|
37
|
+
|
|
38
|
+
# @return [String] the constant path to the grammar (e.g., "TomlRB::Document")
|
|
39
|
+
attr_reader :grammar_const
|
|
40
|
+
|
|
41
|
+
# @return [String, nil] custom require path (defaults to gem_name with dashes to slashes)
|
|
42
|
+
attr_reader :require_path
|
|
43
|
+
|
|
44
|
+
# Initialize a Citrus grammar finder
|
|
45
|
+
#
|
|
46
|
+
# @param language [Symbol, String] the language name (e.g., :toml, :json)
|
|
47
|
+
# @param gem_name [String] the gem name (e.g., "toml-rb")
|
|
48
|
+
# @param grammar_const [String] constant path to grammar (e.g., "TomlRB::Document")
|
|
49
|
+
# @param require_path [String, nil] custom require path (defaults to gem_name as-is)
|
|
50
|
+
def initialize(language:, gem_name:, grammar_const:, require_path: nil)
|
|
51
|
+
@language_name = language.to_sym
|
|
52
|
+
@gem_name = gem_name
|
|
53
|
+
@grammar_const = grammar_const
|
|
54
|
+
@require_path = require_path || gem_name
|
|
55
|
+
@load_attempted = false
|
|
56
|
+
@available = false
|
|
57
|
+
@grammar_module = nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check if the Citrus grammar is available
|
|
61
|
+
#
|
|
62
|
+
# Attempts to require the gem and resolve the grammar constant.
|
|
63
|
+
# Result is cached after first call.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] true if grammar is available
|
|
66
|
+
def available?
|
|
67
|
+
return @available if @load_attempted
|
|
68
|
+
|
|
69
|
+
@load_attempted = true
|
|
70
|
+
debug = ENV['TREE_HAVER_DEBUG']
|
|
71
|
+
|
|
72
|
+
# Guard against nil require_path (can happen if gem_name was nil)
|
|
73
|
+
if @require_path.nil? || @require_path.empty?
|
|
74
|
+
warn("CitrusGrammarFinder: require_path is nil or empty for #{@language_name}") if debug
|
|
75
|
+
@available = false
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
# Try to require the gem
|
|
81
|
+
require @require_path
|
|
82
|
+
|
|
83
|
+
# Try to resolve the constant
|
|
84
|
+
@grammar_module = resolve_constant(@grammar_const)
|
|
85
|
+
|
|
86
|
+
# Verify it responds to parse
|
|
87
|
+
unless @grammar_module.respond_to?(:parse)
|
|
88
|
+
# simplecov:disable defensive - requires a gem with malformed grammar module
|
|
89
|
+
# Show what methods ARE available to help diagnose the issue
|
|
90
|
+
if debug
|
|
91
|
+
available_methods = @grammar_module.methods(false).sort.first(20)
|
|
92
|
+
warn("CitrusGrammarFinder: #{@grammar_const} doesn't respond to :parse")
|
|
93
|
+
warn("CitrusGrammarFinder: #{@grammar_const}.class = #{@grammar_module.class}")
|
|
94
|
+
warn("CitrusGrammarFinder: #{@grammar_const} is a #{@grammar_module.is_a?(Module) ? 'Module' : 'non-Module'}")
|
|
95
|
+
warn("CitrusGrammarFinder: Available singleton methods (first 20): #{available_methods.inspect}")
|
|
96
|
+
if @grammar_module.respond_to?(:instance_methods)
|
|
97
|
+
instance_methods = @grammar_module.instance_methods(false).sort.first(20)
|
|
98
|
+
warn("CitrusGrammarFinder: Available instance methods (first 20): #{instance_methods.inspect}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
@available = false
|
|
102
|
+
return false
|
|
103
|
+
# simplecov:enable
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@available = true
|
|
107
|
+
rescue LoadError => e
|
|
108
|
+
# simplecov:disable defensive - requires gem to not be installed
|
|
109
|
+
# Only show LoadError details when debugging
|
|
110
|
+
if debug
|
|
111
|
+
warn("CitrusGrammarFinder: Failed to load '#{@require_path}': #{e.class}: #{e.message}")
|
|
112
|
+
warn("CitrusGrammarFinder: LoadError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
113
|
+
end
|
|
114
|
+
@available = false
|
|
115
|
+
# simplecov:enable
|
|
116
|
+
rescue NameError => e
|
|
117
|
+
# simplecov:disable defensive - requires gem with missing constant
|
|
118
|
+
# Only show NameError details when debugging
|
|
119
|
+
if debug
|
|
120
|
+
warn("CitrusGrammarFinder: Failed to resolve '#{@grammar_const}': #{e.class}: #{e.message}")
|
|
121
|
+
warn("CitrusGrammarFinder: NameError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}")
|
|
122
|
+
end
|
|
123
|
+
@available = false
|
|
124
|
+
# simplecov:enable
|
|
125
|
+
rescue TypeError => e
|
|
126
|
+
# simplecov:disable defensive - TruffleRuby-specific edge case
|
|
127
|
+
# TruffleRuby's bundled_gems.rb can raise TypeError when File.path is called on nil
|
|
128
|
+
# This happens in bundled_gems.rb:124 warning? method when caller locations return nil
|
|
129
|
+
# Always warn about TypeError as it indicates a platform-specific issue
|
|
130
|
+
warn("CitrusGrammarFinder: TypeError during load of '#{@require_path}': #{e.class}: #{e.message}")
|
|
131
|
+
warn('CitrusGrammarFinder: This may be a TruffleRuby bundled_gems.rb issue')
|
|
132
|
+
warn("CitrusGrammarFinder: TypeError backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
|
|
133
|
+
@available = false
|
|
134
|
+
# simplecov:enable
|
|
135
|
+
rescue StandardError => e
|
|
136
|
+
# simplecov:disable defensive - catch-all for unexpected errors
|
|
137
|
+
# Always warn about unexpected errors
|
|
138
|
+
warn("CitrusGrammarFinder: Unexpected error: #{e.class}: #{e.message}")
|
|
139
|
+
warn("CitrusGrammarFinder: backtrace:\n #{e.backtrace&.first(10)&.join("\n ")}") if debug
|
|
140
|
+
@available = false
|
|
141
|
+
# simplecov:enable
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
@available
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get the resolved grammar module
|
|
148
|
+
#
|
|
149
|
+
# @return [Module, nil] the grammar module if available
|
|
150
|
+
def grammar_module
|
|
151
|
+
available? # Ensure we've tried to load
|
|
152
|
+
@grammar_module
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Register this Citrus grammar with TreeHaver
|
|
156
|
+
#
|
|
157
|
+
# After registration, the language can be used via:
|
|
158
|
+
# TreeHaver::Language.{language_name}
|
|
159
|
+
#
|
|
160
|
+
# @param raise_on_missing [Boolean] if true, raises when grammar not available
|
|
161
|
+
# @return [Boolean] true if registration succeeded
|
|
162
|
+
# @raise [NotAvailable] if grammar not available and raise_on_missing is true
|
|
163
|
+
def register!(raise_on_missing: false)
|
|
164
|
+
unless available?
|
|
165
|
+
raise NotAvailable, not_found_message if raise_on_missing
|
|
166
|
+
|
|
167
|
+
return false
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
TreeHaver.register_language(
|
|
171
|
+
@language_name,
|
|
172
|
+
grammar_module: @grammar_module,
|
|
173
|
+
gem_name: @gem_name
|
|
174
|
+
)
|
|
175
|
+
true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Get debug information about the search
|
|
179
|
+
#
|
|
180
|
+
# @return [Hash] diagnostic information
|
|
181
|
+
def search_info
|
|
182
|
+
{
|
|
183
|
+
language: @language_name,
|
|
184
|
+
gem_name: @gem_name,
|
|
185
|
+
grammar_const: @grammar_const,
|
|
186
|
+
require_path: @require_path,
|
|
187
|
+
available: available?,
|
|
188
|
+
grammar_module: @grammar_module&.name
|
|
189
|
+
}
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Get a human-readable error message when grammar is not found
|
|
193
|
+
#
|
|
194
|
+
# @return [String] error message with installation hints
|
|
195
|
+
def not_found_message
|
|
196
|
+
"Citrus grammar for #{@language_name} not found. " \
|
|
197
|
+
"Install #{@gem_name} gem: gem install #{@gem_name}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
# Resolve a constant path like "TomlRB::Document"
|
|
203
|
+
#
|
|
204
|
+
# @param const_path [String] constant path
|
|
205
|
+
# @return [Object] the constant
|
|
206
|
+
# @raise [NameError] if constant not found
|
|
207
|
+
def resolve_constant(const_path)
|
|
208
|
+
const_path.split('::').reduce(Object) do |mod, const_name|
|
|
209
|
+
mod.const_get(const_name)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|