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,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
# Thread-safe language registrations and cache for loaded Language handles
|
|
5
|
+
#
|
|
6
|
+
# The LanguageRegistry provides two main functions:
|
|
7
|
+
# 1. **Registrations**: Store mappings from language names to backend-specific configurations
|
|
8
|
+
# 2. **Cache**: Memoize loaded Language objects to avoid repeated dlopen calls
|
|
9
|
+
#
|
|
10
|
+
# The registry supports multiple backends for the same language, allowing runtime
|
|
11
|
+
# switching, benchmarking, and fallback scenarios.
|
|
12
|
+
#
|
|
13
|
+
# == Supported Backend Types
|
|
14
|
+
#
|
|
15
|
+
# The registry is extensible and supports any backend type. Common types include:
|
|
16
|
+
#
|
|
17
|
+
# - `:tree_sitter` - Native tree-sitter grammars (.so files)
|
|
18
|
+
# - `:citrus` - Citrus PEG parser grammars (pure Ruby)
|
|
19
|
+
# - `:prism` - Ruby's Prism parser (Ruby source only)
|
|
20
|
+
# - `:psych` - Ruby's Psych parser (YAML only)
|
|
21
|
+
# - `:commonmarker` - Commonmarker gem (Markdown)
|
|
22
|
+
# - `:markly` - Markly gem (Markdown/GFM)
|
|
23
|
+
# - `:rbs` - RBS gem (RBS type signatures) - registered externally by rbs-merge
|
|
24
|
+
#
|
|
25
|
+
# External gems can register their own backend types using the same API.
|
|
26
|
+
#
|
|
27
|
+
# Registration structure:
|
|
28
|
+
# ```ruby
|
|
29
|
+
# registrations = {
|
|
30
|
+
# toml: {
|
|
31
|
+
# tree_sitter: { path: "/path/to/lib.so", symbol: "tree_sitter_toml" },
|
|
32
|
+
# citrus: { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
|
|
33
|
+
# },
|
|
34
|
+
# ruby: {
|
|
35
|
+
# prism: { backend_module: TreeHaver::Backends::Prism }
|
|
36
|
+
# },
|
|
37
|
+
# yaml: {
|
|
38
|
+
# psych: { backend_module: TreeHaver::Backends::Psych }
|
|
39
|
+
# },
|
|
40
|
+
# markdown: {
|
|
41
|
+
# commonmarker: { backend_module: TreeHaver::Backends::Commonmarker },
|
|
42
|
+
# markly: { backend_module: TreeHaver::Backends::Markly }
|
|
43
|
+
# },
|
|
44
|
+
# rbs: {
|
|
45
|
+
# rbs: { backend_module: Rbs::Merge::Backends::RbsBackend } # External
|
|
46
|
+
# }
|
|
47
|
+
# }
|
|
48
|
+
# ```
|
|
49
|
+
#
|
|
50
|
+
# @example Register tree-sitter grammar
|
|
51
|
+
# ```ruby
|
|
52
|
+
# TreeHaver::LanguageRegistry.register(:toml, :tree_sitter,
|
|
53
|
+
# path: "/path/to/lib.so", symbol: "tree_sitter_toml")
|
|
54
|
+
# ```
|
|
55
|
+
#
|
|
56
|
+
# @example Register Citrus grammar
|
|
57
|
+
# ```ruby
|
|
58
|
+
# TreeHaver::LanguageRegistry.register(:toml, :citrus,
|
|
59
|
+
# grammar_module: TomlRB::Document, gem_name: "toml-rb")
|
|
60
|
+
# ```
|
|
61
|
+
#
|
|
62
|
+
# @example Register a pure Ruby backend (internal or external)
|
|
63
|
+
# ```ruby
|
|
64
|
+
# TreeHaver::LanguageRegistry.register(:rbs, :rbs,
|
|
65
|
+
# backend_module: Rbs::Merge::Backends::RbsBackend,
|
|
66
|
+
# gem_name: "rbs")
|
|
67
|
+
# ```
|
|
68
|
+
#
|
|
69
|
+
# @api private
|
|
70
|
+
module LanguageRegistry
|
|
71
|
+
@mutex = Mutex.new
|
|
72
|
+
@cache = {}
|
|
73
|
+
@registrations = {}
|
|
74
|
+
|
|
75
|
+
module_function
|
|
76
|
+
|
|
77
|
+
# Register a language for a specific backend
|
|
78
|
+
#
|
|
79
|
+
# Stores backend-specific configuration for a language. Multiple backends
|
|
80
|
+
# can be registered for the same language without conflict.
|
|
81
|
+
#
|
|
82
|
+
# @param name [Symbol, String] language identifier (e.g., :toml, :json, :ruby, :yaml, :rbs)
|
|
83
|
+
# @param backend_type [Symbol] backend type (:tree_sitter, :citrus, :prism, :psych, :commonmarker, :markly, or custom)
|
|
84
|
+
# @param config [Hash] backend-specific configuration
|
|
85
|
+
# @option config [String] :path tree-sitter library path (for tree-sitter backends)
|
|
86
|
+
# @option config [String] :symbol exported symbol name (for tree-sitter backends)
|
|
87
|
+
# @option config [Module] :grammar_module Citrus grammar module (for Citrus backend)
|
|
88
|
+
# @option config [Module] :backend_module backend module with Language/Parser classes (for pure Ruby backends)
|
|
89
|
+
# @option config [String] :gem_name gem name for error messages and availability checks
|
|
90
|
+
# @return [void]
|
|
91
|
+
# @example Register tree-sitter grammar
|
|
92
|
+
# LanguageRegistry.register(:toml, :tree_sitter,
|
|
93
|
+
# path: "/usr/local/lib/libtree-sitter-toml.so", symbol: "tree_sitter_toml")
|
|
94
|
+
# @example Register Citrus grammar
|
|
95
|
+
# LanguageRegistry.register(:toml, :citrus,
|
|
96
|
+
# grammar_module: TomlRB::Document, gem_name: "toml-rb")
|
|
97
|
+
# @example Register pure Ruby backend (external gem)
|
|
98
|
+
# LanguageRegistry.register(:rbs, :rbs,
|
|
99
|
+
# backend_module: Rbs::Merge::Backends::RbsBackend, gem_name: "rbs")
|
|
100
|
+
def register(name, backend_type, **config)
|
|
101
|
+
key = name.to_sym
|
|
102
|
+
backend_key = backend_type.to_sym
|
|
103
|
+
|
|
104
|
+
@mutex.synchronize do
|
|
105
|
+
@registrations[key] ||= {}
|
|
106
|
+
@registrations[key][backend_key] = config.compact
|
|
107
|
+
end
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def with_registration(name, backend_type, **config)
|
|
112
|
+
key = name.to_sym
|
|
113
|
+
backend_key = backend_type.to_sym
|
|
114
|
+
original = nil
|
|
115
|
+
had_language = false
|
|
116
|
+
had_backend = false
|
|
117
|
+
|
|
118
|
+
@mutex.synchronize do
|
|
119
|
+
had_language = @registrations.key?(key)
|
|
120
|
+
had_backend = @registrations.fetch(key, {}).key?(backend_key)
|
|
121
|
+
original = @registrations.fetch(key, {})[backend_key]&.dup
|
|
122
|
+
@registrations[key] ||= {}
|
|
123
|
+
@registrations[key][backend_key] = config.compact
|
|
124
|
+
@cache.clear
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
yield
|
|
128
|
+
ensure
|
|
129
|
+
@mutex.synchronize do
|
|
130
|
+
if had_backend
|
|
131
|
+
@registrations[key][backend_key] = original
|
|
132
|
+
elsif had_language
|
|
133
|
+
@registrations[key].delete(backend_key)
|
|
134
|
+
else
|
|
135
|
+
@registrations.delete(key)
|
|
136
|
+
end
|
|
137
|
+
@cache.clear
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Fetch registration entries for a language
|
|
142
|
+
#
|
|
143
|
+
# Returns all backend-specific configurations for a language.
|
|
144
|
+
#
|
|
145
|
+
# @param name [Symbol, String] language identifier
|
|
146
|
+
# @param backend_type [Symbol, nil] optional backend type to filter by
|
|
147
|
+
# @return [Hash{Symbol => Hash}, Hash, nil] all backends or specific backend config
|
|
148
|
+
# @example Get all backends
|
|
149
|
+
# entries = LanguageRegistry.registered(:toml)
|
|
150
|
+
# # => {
|
|
151
|
+
# # tree_sitter: { path: "/usr/local/lib/libtree-sitter-toml.so", symbol: "tree_sitter_toml" },
|
|
152
|
+
# # citrus: { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
|
|
153
|
+
# # }
|
|
154
|
+
# @example Get specific backend
|
|
155
|
+
# entry = LanguageRegistry.registered(:toml, :citrus)
|
|
156
|
+
# # => { grammar_module: TomlRB::Document, gem_name: "toml-rb" }
|
|
157
|
+
def registered(name, backend_type = nil)
|
|
158
|
+
@mutex.synchronize do
|
|
159
|
+
lang_config = @registrations[name.to_sym]
|
|
160
|
+
return unless lang_config
|
|
161
|
+
|
|
162
|
+
if backend_type
|
|
163
|
+
lang_config[backend_type.to_sym]
|
|
164
|
+
else
|
|
165
|
+
lang_config
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Fetch a cached language by key or compute and store it
|
|
171
|
+
#
|
|
172
|
+
# This method provides thread-safe memoization for loaded Language objects.
|
|
173
|
+
# If the key exists in the cache, the cached value is returned immediately.
|
|
174
|
+
# Otherwise, the block is called to compute the value, which is then cached.
|
|
175
|
+
#
|
|
176
|
+
# @param key [Array] cache key, typically [path, symbol, name]
|
|
177
|
+
# @yieldreturn [Object] the computed language handle (called only on cache miss)
|
|
178
|
+
# @return [Object] the cached or computed language handle
|
|
179
|
+
# @example
|
|
180
|
+
# language = LanguageRegistry.fetch(["/path/lib.so", "symbol", "toml"]) do
|
|
181
|
+
# expensive_language_load_operation
|
|
182
|
+
# end
|
|
183
|
+
def fetch(key)
|
|
184
|
+
@mutex.synchronize do
|
|
185
|
+
return @cache[key] if @cache.key?(key)
|
|
186
|
+
|
|
187
|
+
value = yield
|
|
188
|
+
@cache[key] = value
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Clear the language cache
|
|
193
|
+
#
|
|
194
|
+
# Removes all cached Language objects. The next call to {fetch} for any key
|
|
195
|
+
# will recompute the value. Does not clear registrations.
|
|
196
|
+
#
|
|
197
|
+
# @return [void]
|
|
198
|
+
# @example
|
|
199
|
+
# LanguageRegistry.clear_cache!
|
|
200
|
+
def clear_cache!
|
|
201
|
+
@mutex.synchronize { @cache.clear }
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Clear all registrations and cache
|
|
206
|
+
#
|
|
207
|
+
# Removes all language registrations and cached Language objects.
|
|
208
|
+
# Primarily used in tests to reset state between test cases.
|
|
209
|
+
#
|
|
210
|
+
# @return [void]
|
|
211
|
+
# @example
|
|
212
|
+
# LanguageRegistry.clear
|
|
213
|
+
def clear
|
|
214
|
+
@mutex.synchronize do
|
|
215
|
+
@registrations.clear
|
|
216
|
+
@cache.clear
|
|
217
|
+
end
|
|
218
|
+
nil
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TreeHaver
|
|
4
|
+
# Utility methods for deriving tree-sitter symbol and language names from library paths
|
|
5
|
+
#
|
|
6
|
+
# This module provides consistent path parsing across all backends that load
|
|
7
|
+
# tree-sitter grammar libraries from shared object files (.so/.dylib/.dll).
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# TreeHaver::LibraryPathUtils.derive_symbol_from_path("/usr/lib/libtree-sitter-toml.so")
|
|
11
|
+
# # => "tree_sitter_toml"
|
|
12
|
+
#
|
|
13
|
+
# TreeHaver::LibraryPathUtils.derive_language_name_from_path("/usr/lib/libtree-sitter-toml.so")
|
|
14
|
+
# # => "toml"
|
|
15
|
+
module LibraryPathUtils
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Derive the tree-sitter symbol name from a library path
|
|
19
|
+
#
|
|
20
|
+
# Symbol names are the exported C function names (e.g., "tree_sitter_toml")
|
|
21
|
+
# that return a pointer to the TSLanguage struct.
|
|
22
|
+
#
|
|
23
|
+
# Handles various naming conventions:
|
|
24
|
+
# - libtree-sitter-toml.so → tree_sitter_toml
|
|
25
|
+
# - libtree_sitter_toml.so → tree_sitter_toml
|
|
26
|
+
# - tree-sitter-toml.so → tree_sitter_toml
|
|
27
|
+
# - tree_sitter_toml.so → tree_sitter_toml
|
|
28
|
+
# - toml.so → tree_sitter_toml (assumes simple language name)
|
|
29
|
+
#
|
|
30
|
+
# @param path [String, nil] path like "/usr/lib/libtree-sitter-toml.so"
|
|
31
|
+
# @return [String, nil] symbol like "tree_sitter_toml", or nil if path is nil
|
|
32
|
+
def derive_symbol_from_path(path)
|
|
33
|
+
return unless path
|
|
34
|
+
|
|
35
|
+
# Extract filename without extension: "libtree-sitter-toml" or "toml"
|
|
36
|
+
filename = File.basename(path, '.*')
|
|
37
|
+
|
|
38
|
+
# Handle multi-part extensions like .so.0.24
|
|
39
|
+
filename = filename.sub(/\.so(\.\d+)*\z/, '')
|
|
40
|
+
|
|
41
|
+
# Match patterns and normalize to tree_sitter_<lang>
|
|
42
|
+
case filename
|
|
43
|
+
when /\Alib[-_]?tree[-_]sitter[-_](.+)\z/
|
|
44
|
+
"tree_sitter_#{Regexp.last_match(1).tr('-', '_')}"
|
|
45
|
+
when /\Atree[-_]sitter[-_](.+)\z/
|
|
46
|
+
"tree_sitter_#{Regexp.last_match(1).tr('-', '_')}"
|
|
47
|
+
else
|
|
48
|
+
# Assume filename is just the language name (e.g., "toml.so" -> "tree_sitter_toml")
|
|
49
|
+
# Also strip "lib" prefix if present (e.g., "libtoml.so" -> "tree_sitter_toml")
|
|
50
|
+
lang = filename.sub(/\Alib/, '').tr('-', '_')
|
|
51
|
+
"tree_sitter_#{lang}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Derive the language name from a library path
|
|
56
|
+
#
|
|
57
|
+
# Language names are the short identifiers (e.g., "toml", "json", "ruby")
|
|
58
|
+
# used by some backends (like tree_stump/Rust) to register grammars.
|
|
59
|
+
#
|
|
60
|
+
# @param path [String, nil] path like "/usr/lib/libtree-sitter-toml.so"
|
|
61
|
+
# @return [String, nil] language name like "toml", or nil if path is nil
|
|
62
|
+
def derive_language_name_from_path(path)
|
|
63
|
+
symbol = derive_symbol_from_path(path)
|
|
64
|
+
return unless symbol
|
|
65
|
+
|
|
66
|
+
# Strip the "tree_sitter_" prefix to get the language name
|
|
67
|
+
symbol.sub(/\Atree_sitter_/, '')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Derive language name from a symbol
|
|
71
|
+
#
|
|
72
|
+
# @param symbol [String, nil] symbol like "tree_sitter_toml"
|
|
73
|
+
# @return [String, nil] language name like "toml", or nil if symbol is nil
|
|
74
|
+
def derive_language_name_from_symbol(symbol)
|
|
75
|
+
return unless symbol
|
|
76
|
+
|
|
77
|
+
symbol.sub(/\Atree_sitter_/, '')
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|