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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.md +13 -0
  4. data/README.md +1959 -0
  5. data/lib/tree_haver/backend_api.rb +392 -0
  6. data/lib/tree_haver/backend_registry.rb +153 -3
  7. data/lib/tree_haver/backends/citrus.rb +489 -0
  8. data/lib/tree_haver/backends/ffi.rb +1013 -0
  9. data/lib/tree_haver/backends/java.rb +909 -0
  10. data/lib/tree_haver/backends/mri.rb +367 -0
  11. data/lib/tree_haver/backends/parslet.rb +565 -0
  12. data/lib/tree_haver/backends/prism.rb +568 -0
  13. data/lib/tree_haver/backends/psych.rb +379 -0
  14. data/lib/tree_haver/backends/rust.rb +243 -0
  15. data/lib/tree_haver/backends/tslp.rb +274 -0
  16. data/lib/tree_haver/base/comment.rb +320 -0
  17. data/lib/tree_haver/base/language.rb +98 -0
  18. data/lib/tree_haver/base/node.rb +330 -0
  19. data/lib/tree_haver/base/parser.rb +28 -0
  20. data/lib/tree_haver/base/point.rb +48 -0
  21. data/lib/tree_haver/base/tree.rb +128 -0
  22. data/lib/tree_haver/citrus_grammar_finder.rb +213 -0
  23. data/lib/tree_haver/contracts.rb +661 -96
  24. data/lib/tree_haver/grammar_finder.rb +429 -0
  25. data/lib/tree_haver/kaitai_backend.rb +2 -2
  26. data/lib/tree_haver/language.rb +294 -0
  27. data/lib/tree_haver/language_pack.rb +17 -166
  28. data/lib/tree_haver/language_registry.rb +221 -0
  29. data/lib/tree_haver/library_path_utils.rb +80 -0
  30. data/lib/tree_haver/node.rb +588 -0
  31. data/lib/tree_haver/parser.rb +445 -0
  32. data/lib/tree_haver/parslet_grammar_finder.rb +217 -0
  33. data/lib/tree_haver/path_validator.rb +356 -0
  34. data/lib/tree_haver/peg_backends.rb +7 -7
  35. data/lib/tree_haver/point.rb +27 -0
  36. data/lib/tree_haver/rspec/dependency_tags.rb +52 -0
  37. data/lib/tree_haver/rspec.rb +3 -0
  38. data/lib/tree_haver/tree.rb +267 -0
  39. data/lib/tree_haver/version.rb +5 -3
  40. data/lib/tree_haver.rb +613 -8
  41. data/sig/tree_haver.rbs +6 -0
  42. data.tar.gz.sig +0 -0
  43. metadata +314 -13
  44. 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