tree_haver 3.0.0 → 3.1.1
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/CHANGELOG.md +152 -1
- data/CONTRIBUTING.md +46 -14
- data/README.md +425 -102
- data/lib/tree_haver/backends/citrus.rb +39 -0
- data/lib/tree_haver/backends/commonmarker.rb +491 -0
- data/lib/tree_haver/backends/ffi.rb +66 -23
- data/lib/tree_haver/backends/java.rb +3 -2
- data/lib/tree_haver/backends/markly.rb +560 -0
- data/lib/tree_haver/backends/mri.rb +41 -12
- data/lib/tree_haver/backends/prism.rb +625 -0
- data/lib/tree_haver/backends/psych.rb +622 -0
- data/lib/tree_haver/backends/rust.rb +2 -2
- data/lib/tree_haver/grammar_finder.rb +98 -7
- data/lib/tree_haver/language_registry.rb +6 -0
- data/lib/tree_haver/node.rb +73 -40
- data/lib/tree_haver/point.rb +65 -0
- data/lib/tree_haver/rspec/dependency_tags.rb +697 -0
- data/lib/tree_haver/rspec.rb +23 -0
- data/lib/tree_haver/tree.rb +1 -1
- data/lib/tree_haver/version.rb +1 -1
- data/lib/tree_haver.rb +251 -24
- data/sig/tree_haver.rbs +18 -1
- data.tar.gz.sig +0 -0
- metadata +32 -5
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# TreeHaver RSpec Integration
|
|
4
|
+
#
|
|
5
|
+
# This file provides RSpec helpers and configuration for testing
|
|
6
|
+
# code that uses TreeHaver. Require this in your spec_helper.rb:
|
|
7
|
+
#
|
|
8
|
+
# require "tree_haver/rspec"
|
|
9
|
+
#
|
|
10
|
+
# This will load:
|
|
11
|
+
# - Dependency tags for conditional test execution
|
|
12
|
+
# - (Future) Additional test helpers as needed
|
|
13
|
+
#
|
|
14
|
+
# @example spec_helper.rb
|
|
15
|
+
# require "tree_haver/rspec"
|
|
16
|
+
#
|
|
17
|
+
# RSpec.configure do |config|
|
|
18
|
+
# # Your additional configuration...
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @see TreeHaver::RSpec::DependencyTags
|
|
22
|
+
|
|
23
|
+
require_relative "rspec/dependency_tags"
|
data/lib/tree_haver/tree.rb
CHANGED
|
@@ -156,7 +156,7 @@ module TreeHaver
|
|
|
156
156
|
raise unless e.name == :edit || e.message.include?("edit")
|
|
157
157
|
raise TreeHaver::NotAvailable,
|
|
158
158
|
"Incremental parsing not supported by current backend. " \
|
|
159
|
-
"Use MRI (ruby_tree_sitter), Rust (tree_stump), or Java (java-tree-sitter) backend."
|
|
159
|
+
"Use MRI (ruby_tree_sitter), Rust (tree_stump), or Java (java-tree-sitter / jtreesitter) backend."
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
private
|
data/lib/tree_haver/version.rb
CHANGED
data/lib/tree_haver.rb
CHANGED
|
@@ -10,12 +10,17 @@ require "set"
|
|
|
10
10
|
require_relative "tree_haver/version"
|
|
11
11
|
require_relative "tree_haver/language_registry"
|
|
12
12
|
|
|
13
|
-
# TreeHaver is a cross-Ruby adapter for
|
|
13
|
+
# TreeHaver is a cross-Ruby adapter for code parsing with 10 backends.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
15
|
+
# Provides a unified API for parsing source code across MRI Ruby, JRuby, and TruffleRuby
|
|
16
|
+
# using tree-sitter grammars or language-specific native parsers.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
18
|
+
# Supports 10 backends:
|
|
19
|
+
# - Tree-sitter: MRI (C), Rust, FFI, Java
|
|
20
|
+
# - Native parsers: Prism (Ruby), Psych (YAML), Commonmarker (Markdown), Markly (GFM)
|
|
21
|
+
# - Pure Ruby: Citrus (portable fallback)
|
|
22
|
+
#
|
|
23
|
+
# @example Basic usage with tree-sitter
|
|
19
24
|
# # Load a language grammar
|
|
20
25
|
# language = TreeHaver::Language.from_library(
|
|
21
26
|
# "/usr/local/lib/libtree-sitter-toml.so",
|
|
@@ -30,8 +35,28 @@ require_relative "tree_haver/language_registry"
|
|
|
30
35
|
# tree = parser.parse("[package]\nname = \"my-app\"")
|
|
31
36
|
# root = tree.root_node
|
|
32
37
|
#
|
|
33
|
-
# #
|
|
34
|
-
# root.
|
|
38
|
+
# # Use unified Position API (works across all backends)
|
|
39
|
+
# puts root.start_line # => 1 (1-based)
|
|
40
|
+
# puts root.source_position # => {start_line:, end_line:, start_column:, end_column:}
|
|
41
|
+
#
|
|
42
|
+
# @example Using language-specific backends
|
|
43
|
+
# # Parse Ruby with Prism
|
|
44
|
+
# TreeHaver.backend = :prism
|
|
45
|
+
# parser = TreeHaver::Parser.new
|
|
46
|
+
# parser.language = TreeHaver::Backends::Prism::Language.ruby
|
|
47
|
+
# tree = parser.parse("class Example; end")
|
|
48
|
+
#
|
|
49
|
+
# # Parse YAML with Psych
|
|
50
|
+
# TreeHaver.backend = :psych
|
|
51
|
+
# parser = TreeHaver::Parser.new
|
|
52
|
+
# parser.language = TreeHaver::Backends::Psych::Language.yaml
|
|
53
|
+
# tree = parser.parse("key: value")
|
|
54
|
+
#
|
|
55
|
+
# # Parse Markdown with Commonmarker
|
|
56
|
+
# TreeHaver.backend = :commonmarker
|
|
57
|
+
# parser = TreeHaver::Parser.new
|
|
58
|
+
# parser.language = TreeHaver::Backends::Commonmarker::Language.markdown
|
|
59
|
+
# tree = parser.parse("# Heading\nParagraph")
|
|
35
60
|
#
|
|
36
61
|
# @example Using language registration
|
|
37
62
|
# TreeHaver.register_language(:toml, path: "/usr/local/lib/libtree-sitter-toml.so")
|
|
@@ -43,22 +68,21 @@ require_relative "tree_haver/language_registry"
|
|
|
43
68
|
# finder.register! if finder.available?
|
|
44
69
|
# language = TreeHaver::Language.toml
|
|
45
70
|
#
|
|
46
|
-
# @example Using GrammarFinder in a *-merge gem
|
|
47
|
-
# # Each merge gem (toml-merge, json-merge, bash-merge) uses the same pattern
|
|
48
|
-
# finder = TreeHaver::GrammarFinder.new(:toml) # or :json, :bash, etc.
|
|
49
|
-
# if finder.available?
|
|
50
|
-
# finder.register!
|
|
51
|
-
# else
|
|
52
|
-
# warn finder.not_found_message
|
|
53
|
-
# end
|
|
54
|
-
#
|
|
55
71
|
# @example Selecting a backend
|
|
56
|
-
# TreeHaver.backend = :
|
|
57
|
-
# TreeHaver.backend = :
|
|
58
|
-
# TreeHaver.backend = :
|
|
72
|
+
# TreeHaver.backend = :mri # Force MRI (ruby_tree_sitter)
|
|
73
|
+
# TreeHaver.backend = :rust # Force Rust (tree_stump)
|
|
74
|
+
# TreeHaver.backend = :ffi # Force FFI
|
|
75
|
+
# TreeHaver.backend = :java # Force Java (JRuby)
|
|
76
|
+
# TreeHaver.backend = :prism # Force Prism (Ruby)
|
|
77
|
+
# TreeHaver.backend = :psych # Force Psych (YAML)
|
|
78
|
+
# TreeHaver.backend = :commonmarker # Force Commonmarker (Markdown)
|
|
79
|
+
# TreeHaver.backend = :markly # Force Markly (GFM)
|
|
80
|
+
# TreeHaver.backend = :citrus # Force Citrus (pure Ruby)
|
|
81
|
+
# TreeHaver.backend = :auto # Auto-select (default)
|
|
59
82
|
#
|
|
60
83
|
# @see https://tree-sitter.github.io/tree-sitter/ tree-sitter documentation
|
|
61
84
|
# @see GrammarFinder For automatic grammar library discovery
|
|
85
|
+
# @see Backends For available parsing backends
|
|
62
86
|
module TreeHaver
|
|
63
87
|
# Base error class for TreeHaver exceptions
|
|
64
88
|
# @see https://github.com/Faveod/ruby-tree-sitter/pull/83 for inherit from Exception reasoning
|
|
@@ -115,12 +139,17 @@ module TreeHaver
|
|
|
115
139
|
# - {Backends::FFI} - Uses Ruby FFI to call libtree-sitter directly
|
|
116
140
|
# - {Backends::Java} - Uses JRuby's Java integration
|
|
117
141
|
# - {Backends::Citrus} - Uses Citrus PEG parser (pure Ruby, portable)
|
|
142
|
+
# - {Backends::Prism} - Uses Ruby's built-in Prism parser (Ruby-only, stdlib in 3.4+)
|
|
118
143
|
module Backends
|
|
119
144
|
autoload :MRI, File.join(__dir__, "tree_haver", "backends", "mri")
|
|
120
145
|
autoload :Rust, File.join(__dir__, "tree_haver", "backends", "rust")
|
|
121
146
|
autoload :FFI, File.join(__dir__, "tree_haver", "backends", "ffi")
|
|
122
147
|
autoload :Java, File.join(__dir__, "tree_haver", "backends", "java")
|
|
123
148
|
autoload :Citrus, File.join(__dir__, "tree_haver", "backends", "citrus")
|
|
149
|
+
autoload :Prism, File.join(__dir__, "tree_haver", "backends", "prism")
|
|
150
|
+
autoload :Psych, File.join(__dir__, "tree_haver", "backends", "psych")
|
|
151
|
+
autoload :Commonmarker, File.join(__dir__, "tree_haver", "backends", "commonmarker")
|
|
152
|
+
autoload :Markly, File.join(__dir__, "tree_haver", "backends", "markly")
|
|
124
153
|
|
|
125
154
|
# Known backend conflicts
|
|
126
155
|
#
|
|
@@ -135,6 +164,10 @@ module TreeHaver
|
|
|
135
164
|
ffi: [:mri], # FFI segfaults if MRI (ruby_tree_sitter) has been loaded
|
|
136
165
|
java: [],
|
|
137
166
|
citrus: [],
|
|
167
|
+
prism: [], # Prism has no conflicts with other backends
|
|
168
|
+
psych: [], # Psych has no conflicts with other backends
|
|
169
|
+
commonmarker: [], # Commonmarker has no conflicts with other backends
|
|
170
|
+
markly: [], # Markly has no conflicts with other backends
|
|
138
171
|
}.freeze
|
|
139
172
|
end
|
|
140
173
|
|
|
@@ -179,6 +212,9 @@ module TreeHaver
|
|
|
179
212
|
# @see CitrusGrammarFinder
|
|
180
213
|
autoload :CitrusGrammarFinder, File.join(__dir__, "tree_haver", "citrus_grammar_finder")
|
|
181
214
|
|
|
215
|
+
# Point class for position information (row, column)
|
|
216
|
+
autoload :Point, File.join(__dir__, "tree_haver", "point")
|
|
217
|
+
|
|
182
218
|
# Unified Node wrapper providing consistent API across backends
|
|
183
219
|
autoload :Node, File.join(__dir__, "tree_haver", "node")
|
|
184
220
|
|
|
@@ -201,7 +237,10 @@ module TreeHaver
|
|
|
201
237
|
# @return [Boolean]
|
|
202
238
|
# @example Disable protection for testing
|
|
203
239
|
# TreeHaver.backend_protect = false
|
|
204
|
-
|
|
240
|
+
def backend_protect=(value)
|
|
241
|
+
@backend_protect_mutex ||= Mutex.new
|
|
242
|
+
@backend_protect_mutex.synchronize { @backend_protect = value }
|
|
243
|
+
end
|
|
205
244
|
|
|
206
245
|
# Check if backend conflict protection is enabled
|
|
207
246
|
#
|
|
@@ -267,6 +306,10 @@ module TreeHaver
|
|
|
267
306
|
when "ffi" then :ffi
|
|
268
307
|
when "java" then :java
|
|
269
308
|
when "citrus" then :citrus
|
|
309
|
+
when "prism" then :prism
|
|
310
|
+
when "psych" then :psych
|
|
311
|
+
when "commonmarker" then :commonmarker
|
|
312
|
+
when "markly" then :markly
|
|
270
313
|
else :auto
|
|
271
314
|
end
|
|
272
315
|
end
|
|
@@ -463,6 +506,14 @@ module TreeHaver
|
|
|
463
506
|
Backends::Java
|
|
464
507
|
when :citrus
|
|
465
508
|
Backends::Citrus
|
|
509
|
+
when :prism
|
|
510
|
+
Backends::Prism
|
|
511
|
+
when :psych
|
|
512
|
+
Backends::Psych
|
|
513
|
+
when :commonmarker
|
|
514
|
+
Backends::Commonmarker
|
|
515
|
+
when :markly
|
|
516
|
+
Backends::Markly
|
|
466
517
|
when :auto
|
|
467
518
|
backend_module # Fall back to normal resolution for :auto
|
|
468
519
|
else
|
|
@@ -519,6 +570,14 @@ module TreeHaver
|
|
|
519
570
|
Backends::Java
|
|
520
571
|
when :citrus
|
|
521
572
|
Backends::Citrus
|
|
573
|
+
when :prism
|
|
574
|
+
Backends::Prism
|
|
575
|
+
when :psych
|
|
576
|
+
Backends::Psych
|
|
577
|
+
when :commonmarker
|
|
578
|
+
Backends::Commonmarker
|
|
579
|
+
when :markly
|
|
580
|
+
Backends::Markly
|
|
522
581
|
else
|
|
523
582
|
# auto-select: prefer native/fast backends, fall back to pure Ruby (Citrus)
|
|
524
583
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" && Backends::Java.available?
|
|
@@ -669,6 +728,111 @@ module TreeHaver
|
|
|
669
728
|
def registered_language(name)
|
|
670
729
|
LanguageRegistry.registered(name)
|
|
671
730
|
end
|
|
731
|
+
|
|
732
|
+
# Create a parser configured for a specific language
|
|
733
|
+
#
|
|
734
|
+
# This is the recommended high-level API for creating a parser. It handles:
|
|
735
|
+
# 1. Checking if the language is already registered
|
|
736
|
+
# 2. Auto-discovering tree-sitter grammar via GrammarFinder
|
|
737
|
+
# 3. Falling back to Citrus grammar if tree-sitter is unavailable
|
|
738
|
+
# 4. Creating and configuring the parser
|
|
739
|
+
#
|
|
740
|
+
# @param language_name [Symbol, String] the language to parse (e.g., :toml, :json, :bash)
|
|
741
|
+
# @param library_path [String, nil] optional explicit path to tree-sitter grammar library
|
|
742
|
+
# @param symbol [String, nil] optional tree-sitter symbol name (defaults to "tree_sitter_<name>")
|
|
743
|
+
# @param citrus_config [Hash, nil] optional Citrus fallback configuration
|
|
744
|
+
# @option citrus_config [String] :gem_name gem name for the Citrus grammar
|
|
745
|
+
# @option citrus_config [String] :grammar_const fully qualified constant name for grammar module
|
|
746
|
+
# @return [TreeHaver::Parser] configured parser with language set
|
|
747
|
+
# @raise [TreeHaver::NotAvailable] if no parser backend is available for the language
|
|
748
|
+
#
|
|
749
|
+
# @example Basic usage (auto-discovers grammar)
|
|
750
|
+
# parser = TreeHaver.parser_for(:toml)
|
|
751
|
+
# tree = parser.parse("[package]\nname = \"my-app\"")
|
|
752
|
+
#
|
|
753
|
+
# @example With explicit library path
|
|
754
|
+
# parser = TreeHaver.parser_for(:toml, library_path: "/custom/path/libtree-sitter-toml.so")
|
|
755
|
+
#
|
|
756
|
+
# @example With Citrus fallback configuration
|
|
757
|
+
# parser = TreeHaver.parser_for(:toml,
|
|
758
|
+
# citrus_config: { gem_name: "toml-rb", grammar_const: "TomlRB::Document" }
|
|
759
|
+
# )
|
|
760
|
+
def parser_for(language_name, library_path: nil, symbol: nil, citrus_config: nil)
|
|
761
|
+
name = language_name.to_sym
|
|
762
|
+
symbol ||= "tree_sitter_#{name}"
|
|
763
|
+
|
|
764
|
+
# Step 1: Try to get the language (may already be registered)
|
|
765
|
+
language = begin
|
|
766
|
+
# Check if already registered and loadable
|
|
767
|
+
if registered_language(name)
|
|
768
|
+
Language.public_send(name, path: library_path, symbol: symbol)
|
|
769
|
+
end
|
|
770
|
+
rescue NotAvailable, ArgumentError, LoadError
|
|
771
|
+
nil
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
# Step 2: If not registered, try GrammarFinder for tree-sitter
|
|
775
|
+
unless language
|
|
776
|
+
# Principle of Least Surprise: If user provides an explicit path,
|
|
777
|
+
# it MUST exist. Don't silently fall back to auto-discovery.
|
|
778
|
+
if library_path && !library_path.empty?
|
|
779
|
+
unless File.exist?(library_path)
|
|
780
|
+
raise NotAvailable,
|
|
781
|
+
"Specified parser path does not exist: #{library_path}"
|
|
782
|
+
end
|
|
783
|
+
begin
|
|
784
|
+
register_language(name, path: library_path, symbol: symbol)
|
|
785
|
+
language = Language.public_send(name)
|
|
786
|
+
rescue NotAvailable, ArgumentError, LoadError => e
|
|
787
|
+
# Re-raise with more context since user explicitly provided this path
|
|
788
|
+
raise NotAvailable,
|
|
789
|
+
"Failed to load parser from specified path #{library_path}: #{e.message}"
|
|
790
|
+
end
|
|
791
|
+
else
|
|
792
|
+
# Auto-discover via GrammarFinder (no explicit path provided)
|
|
793
|
+
begin
|
|
794
|
+
finder = GrammarFinder.new(name)
|
|
795
|
+
if finder.available?
|
|
796
|
+
finder.register!
|
|
797
|
+
language = Language.public_send(name)
|
|
798
|
+
end
|
|
799
|
+
rescue NotAvailable, ArgumentError, LoadError
|
|
800
|
+
language = nil
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
# Step 3: Try Citrus fallback if tree-sitter failed
|
|
806
|
+
unless language
|
|
807
|
+
citrus_config ||= {}
|
|
808
|
+
begin
|
|
809
|
+
citrus_finder = CitrusGrammarFinder.new(
|
|
810
|
+
language: name,
|
|
811
|
+
gem_name: citrus_config[:gem_name],
|
|
812
|
+
grammar_const: citrus_config[:grammar_const],
|
|
813
|
+
)
|
|
814
|
+
if citrus_finder.available?
|
|
815
|
+
citrus_finder.register!
|
|
816
|
+
language = Language.public_send(name)
|
|
817
|
+
end
|
|
818
|
+
rescue NotAvailable, ArgumentError, LoadError, NameError
|
|
819
|
+
language = nil
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
# Step 4: Raise if nothing worked
|
|
824
|
+
unless language
|
|
825
|
+
raise NotAvailable,
|
|
826
|
+
"No parser available for #{name}. " \
|
|
827
|
+
"Install tree-sitter-#{name} or the appropriate Ruby gem. " \
|
|
828
|
+
"Set TREE_SITTER_#{name.to_s.upcase}_PATH for custom grammar location."
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# Step 5: Create and configure parser
|
|
832
|
+
parser = Parser.new
|
|
833
|
+
parser.language = language
|
|
834
|
+
parser
|
|
835
|
+
end
|
|
672
836
|
end
|
|
673
837
|
|
|
674
838
|
# Represents a tree-sitter language grammar
|
|
@@ -827,7 +991,8 @@ module TreeHaver
|
|
|
827
991
|
"Registered backends: #{all_backends.keys.inspect}"
|
|
828
992
|
end
|
|
829
993
|
|
|
830
|
-
# For tree-sitter backends,
|
|
994
|
+
# For tree-sitter backends, try to load from path
|
|
995
|
+
# If that fails, fall back to Citrus if available
|
|
831
996
|
if reg && reg[:path]
|
|
832
997
|
path = kwargs[:path] || args.first || reg[:path]
|
|
833
998
|
# Symbol priority: kwargs override > registration > derive from method_name
|
|
@@ -842,7 +1007,30 @@ module TreeHaver
|
|
|
842
1007
|
# Using symbol-derived name ensures ruby_tree_sitter gets the correct language name
|
|
843
1008
|
# e.g., "toml" not "toml_both" when symbol is "tree_sitter_toml"
|
|
844
1009
|
name = kwargs[:name] || symbol&.sub(/\Atree_sitter_/, "")
|
|
845
|
-
|
|
1010
|
+
|
|
1011
|
+
begin
|
|
1012
|
+
return from_library(path, symbol: symbol, name: name)
|
|
1013
|
+
rescue NotAvailable, ArgumentError, LoadError, FFI::NotFoundError => _e
|
|
1014
|
+
# Tree-sitter failed to load - check for Citrus fallback
|
|
1015
|
+
# This handles cases where:
|
|
1016
|
+
# - The .so file doesn't exist or can't be loaded (NotAvailable, LoadError)
|
|
1017
|
+
# - FFI can't find required symbols like ts_parser_new (FFI::NotFoundError)
|
|
1018
|
+
# - Invalid arguments were provided (ArgumentError)
|
|
1019
|
+
citrus_reg = all_backends[:citrus]
|
|
1020
|
+
if citrus_reg && citrus_reg[:grammar_module]
|
|
1021
|
+
return Backends::Citrus::Language.new(citrus_reg[:grammar_module])
|
|
1022
|
+
end
|
|
1023
|
+
# No Citrus fallback available, re-raise the original error
|
|
1024
|
+
raise
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
# No tree-sitter path registered - check for Citrus fallback
|
|
1029
|
+
# This enables auto-fallback when tree-sitter grammar is not installed
|
|
1030
|
+
# but a Citrus grammar (pure Ruby) is available
|
|
1031
|
+
citrus_reg = all_backends[:citrus]
|
|
1032
|
+
if citrus_reg && citrus_reg[:grammar_module]
|
|
1033
|
+
return Backends::Citrus::Language.new(citrus_reg[:grammar_module])
|
|
846
1034
|
end
|
|
847
1035
|
|
|
848
1036
|
# No appropriate registration found
|
|
@@ -913,8 +1101,28 @@ module TreeHaver
|
|
|
913
1101
|
end
|
|
914
1102
|
end
|
|
915
1103
|
|
|
916
|
-
|
|
917
|
-
|
|
1104
|
+
# Try to create the parser, with fallback to Citrus if tree-sitter fails
|
|
1105
|
+
# This enables auto-fallback when tree-sitter runtime isn't available
|
|
1106
|
+
begin
|
|
1107
|
+
@impl = mod::Parser.new
|
|
1108
|
+
@explicit_backend = backend # Remember for introspection (always a Symbol or nil)
|
|
1109
|
+
rescue NoMethodError, FFI::NotFoundError, LoadError => e
|
|
1110
|
+
# Tree-sitter backend failed (likely missing runtime library)
|
|
1111
|
+
# Try Citrus as fallback if we weren't explicitly asked for a specific backend
|
|
1112
|
+
if backend.nil? || backend == :auto
|
|
1113
|
+
if Backends::Citrus.available?
|
|
1114
|
+
@impl = Backends::Citrus::Parser.new
|
|
1115
|
+
@explicit_backend = :citrus
|
|
1116
|
+
else
|
|
1117
|
+
# No fallback available, re-raise original error
|
|
1118
|
+
raise NotAvailable, "Tree-sitter backend failed: #{e.message}. " \
|
|
1119
|
+
"Citrus fallback not available. Install tree-sitter runtime or citrus gem."
|
|
1120
|
+
end
|
|
1121
|
+
else
|
|
1122
|
+
# Explicit backend was requested, don't fallback
|
|
1123
|
+
raise
|
|
1124
|
+
end
|
|
1125
|
+
end
|
|
918
1126
|
end
|
|
919
1127
|
|
|
920
1128
|
# Get the backend this parser is using (for introspection)
|
|
@@ -952,12 +1160,23 @@ module TreeHaver
|
|
|
952
1160
|
# @example
|
|
953
1161
|
# parser.language = TreeHaver::Language.from_library("/path/to/grammar.so")
|
|
954
1162
|
def language=(lang)
|
|
1163
|
+
# Check if this is a Citrus language - if so, we need a Citrus parser
|
|
1164
|
+
# This enables automatic backend switching when tree-sitter fails and
|
|
1165
|
+
# falls back to Citrus
|
|
1166
|
+
if lang.is_a?(Backends::Citrus::Language)
|
|
1167
|
+
unless @impl.is_a?(Backends::Citrus::Parser)
|
|
1168
|
+
# Switch to Citrus parser to match the Citrus language
|
|
1169
|
+
@impl = Backends::Citrus::Parser.new
|
|
1170
|
+
@explicit_backend = :citrus
|
|
1171
|
+
end
|
|
1172
|
+
end
|
|
1173
|
+
|
|
955
1174
|
# Unwrap the language before passing to backend
|
|
956
1175
|
# Backends receive raw language objects, never TreeHaver wrappers
|
|
957
1176
|
inner_lang = unwrap_language(lang)
|
|
958
1177
|
@impl.language = inner_lang
|
|
959
1178
|
# Return the original (possibly wrapped) language for consistency
|
|
960
|
-
lang
|
|
1179
|
+
lang # rubocop:disable Lint/Void (intentional return value)
|
|
961
1180
|
end
|
|
962
1181
|
|
|
963
1182
|
private
|
|
@@ -1028,6 +1247,14 @@ module TreeHaver
|
|
|
1028
1247
|
return lang.impl if lang.respond_to?(:impl)
|
|
1029
1248
|
when :citrus
|
|
1030
1249
|
return lang.grammar_module if lang.respond_to?(:grammar_module)
|
|
1250
|
+
when :prism
|
|
1251
|
+
return lang # Prism backend expects the Language wrapper
|
|
1252
|
+
when :psych
|
|
1253
|
+
return lang # Psych backend expects the Language wrapper
|
|
1254
|
+
when :commonmarker
|
|
1255
|
+
return lang # Commonmarker backend expects the Language wrapper
|
|
1256
|
+
when :markly
|
|
1257
|
+
return lang # Markly backend expects the Language wrapper
|
|
1031
1258
|
else
|
|
1032
1259
|
# Unknown backend (e.g., test backend)
|
|
1033
1260
|
# Try generic unwrapping methods for flexibility in testing
|
data/sig/tree_haver.rbs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Type definitions for TreeHaver
|
|
2
2
|
#
|
|
3
|
-
# TreeHaver is a cross-Ruby adapter for
|
|
3
|
+
# TreeHaver is a cross-Ruby adapter for code parsing with 10 backends:
|
|
4
|
+
# - Tree-sitter: MRI, Rust, FFI, Java
|
|
5
|
+
# - Native parsers: Prism (Ruby), Psych (YAML), Commonmarker (Markdown), Markly (GFM)
|
|
6
|
+
# - Pure Ruby: Citrus
|
|
4
7
|
|
|
5
8
|
module TreeHaver
|
|
6
9
|
VERSION: String
|
|
@@ -134,6 +137,20 @@ module TreeHaver
|
|
|
134
137
|
# Get the end point (row, column)
|
|
135
138
|
def end_point: () -> Point
|
|
136
139
|
|
|
140
|
+
# Position API - consistent across all backends
|
|
141
|
+
# Get 1-based line number where node starts
|
|
142
|
+
def start_line: () -> Integer
|
|
143
|
+
|
|
144
|
+
# Get 1-based line number where node ends
|
|
145
|
+
def end_line: () -> Integer
|
|
146
|
+
|
|
147
|
+
# Get complete position information as hash
|
|
148
|
+
# Returns {start_line:, end_line:, start_column:, end_column:}
|
|
149
|
+
def source_position: () -> Hash[Symbol, Integer]
|
|
150
|
+
|
|
151
|
+
# Get first child node (convenience method)
|
|
152
|
+
def first_child: () -> Node?
|
|
153
|
+
|
|
137
154
|
# Get the number of child nodes
|
|
138
155
|
def child_count: () -> Integer
|
|
139
156
|
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tree_haver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -133,6 +133,26 @@ dependencies:
|
|
|
133
133
|
- - "~>"
|
|
134
134
|
- !ruby/object:Gem::Version
|
|
135
135
|
version: '3.0'
|
|
136
|
+
- !ruby/object:Gem::Dependency
|
|
137
|
+
name: kettle-soup-cover
|
|
138
|
+
requirement: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - "~>"
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '1.1'
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 1.1.1
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.1'
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: 1.1.1
|
|
136
156
|
- !ruby/object:Gem::Dependency
|
|
137
157
|
name: kettle-test
|
|
138
158
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -241,9 +261,13 @@ files:
|
|
|
241
261
|
- SECURITY.md
|
|
242
262
|
- lib/tree_haver.rb
|
|
243
263
|
- lib/tree_haver/backends/citrus.rb
|
|
264
|
+
- lib/tree_haver/backends/commonmarker.rb
|
|
244
265
|
- lib/tree_haver/backends/ffi.rb
|
|
245
266
|
- lib/tree_haver/backends/java.rb
|
|
267
|
+
- lib/tree_haver/backends/markly.rb
|
|
246
268
|
- lib/tree_haver/backends/mri.rb
|
|
269
|
+
- lib/tree_haver/backends/prism.rb
|
|
270
|
+
- lib/tree_haver/backends/psych.rb
|
|
247
271
|
- lib/tree_haver/backends/rust.rb
|
|
248
272
|
- lib/tree_haver/citrus_grammar_finder.rb
|
|
249
273
|
- lib/tree_haver/compat.rb
|
|
@@ -251,6 +275,9 @@ files:
|
|
|
251
275
|
- lib/tree_haver/language_registry.rb
|
|
252
276
|
- lib/tree_haver/node.rb
|
|
253
277
|
- lib/tree_haver/path_validator.rb
|
|
278
|
+
- lib/tree_haver/point.rb
|
|
279
|
+
- lib/tree_haver/rspec.rb
|
|
280
|
+
- lib/tree_haver/rspec/dependency_tags.rb
|
|
254
281
|
- lib/tree_haver/tree.rb
|
|
255
282
|
- lib/tree_haver/version.rb
|
|
256
283
|
- sig/tree_haver.rbs
|
|
@@ -262,10 +289,10 @@ licenses:
|
|
|
262
289
|
- MIT
|
|
263
290
|
metadata:
|
|
264
291
|
homepage_uri: https://tree-haver.galtzo.com/
|
|
265
|
-
source_code_uri: https://github.com/kettle-rb/tree_haver/tree/v3.
|
|
266
|
-
changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v3.
|
|
292
|
+
source_code_uri: https://github.com/kettle-rb/tree_haver/tree/v3.1.1
|
|
293
|
+
changelog_uri: https://github.com/kettle-rb/tree_haver/blob/v3.1.1/CHANGELOG.md
|
|
267
294
|
bug_tracker_uri: https://github.com/kettle-rb/tree_haver/issues
|
|
268
|
-
documentation_uri: https://www.rubydoc.info/gems/tree_haver/3.
|
|
295
|
+
documentation_uri: https://www.rubydoc.info/gems/tree_haver/3.1.1
|
|
269
296
|
funding_uri: https://github.com/sponsors/pboling
|
|
270
297
|
wiki_uri: https://github.com/kettle-rb/tree_haver/wiki
|
|
271
298
|
news_uri: https://www.railsbling.com/tags/tree_haver
|
|
@@ -295,7 +322,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
295
322
|
- !ruby/object:Gem::Version
|
|
296
323
|
version: '0'
|
|
297
324
|
requirements: []
|
|
298
|
-
rubygems_version: 4.0.
|
|
325
|
+
rubygems_version: 4.0.3
|
|
299
326
|
specification_version: 4
|
|
300
327
|
summary: "\U0001F334 Cross-Ruby adapter for tree-sitter parsing that works on MRI,
|
|
301
328
|
JRuby, and TruffleRuby"
|
metadata.gz.sig
CHANGED
|
Binary file
|