tree_haver 1.0.0 → 2.0.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/CHANGELOG.md +47 -3
- data/README.md +130 -76
- data/lib/tree_haver/backends/citrus.rb +302 -0
- data/lib/tree_haver/backends/ffi.rb +75 -17
- data/lib/tree_haver/backends/java.rb +11 -7
- data/lib/tree_haver/backends/mri.rb +10 -20
- data/lib/tree_haver/backends/rust.rb +8 -20
- data/lib/tree_haver/grammar_finder.rb +1 -1
- data/lib/tree_haver/node.rb +376 -0
- data/lib/tree_haver/path_validator.rb +18 -3
- data/lib/tree_haver/tree.rb +205 -0
- data/lib/tree_haver/version.rb +2 -2
- data/lib/tree_haver.rb +44 -229
- data/sig/tree_haver/backends.rbs +68 -1
- data/sig/tree_haver/path_validator.rbs +1 -0
- data/sig/tree_haver.rbs +95 -9
- data.tar.gz.sig +0 -0
- metadata +11 -8
- metadata.gz.sig +0 -0
data/lib/tree_haver.rb
CHANGED
|
@@ -7,9 +7,9 @@ require "version_gem"
|
|
|
7
7
|
require_relative "tree_haver/version"
|
|
8
8
|
require_relative "tree_haver/language_registry"
|
|
9
9
|
|
|
10
|
-
# TreeHaver is a cross-Ruby adapter for the
|
|
10
|
+
# TreeHaver is a cross-Ruby adapter for the tree-sitter parsing library.
|
|
11
11
|
#
|
|
12
|
-
# It provides a unified API for parsing source code using
|
|
12
|
+
# It provides a unified API for parsing source code using tree-sitter grammars,
|
|
13
13
|
# working seamlessly across MRI Ruby, JRuby, and TruffleRuby.
|
|
14
14
|
#
|
|
15
15
|
# @example Basic usage with TOML
|
|
@@ -54,7 +54,7 @@ require_relative "tree_haver/language_registry"
|
|
|
54
54
|
# TreeHaver.backend = :mri # Force MRI backend
|
|
55
55
|
# TreeHaver.backend = :auto # Auto-select (default)
|
|
56
56
|
#
|
|
57
|
-
# @see https://tree-sitter.github.io/tree-sitter/
|
|
57
|
+
# @see https://tree-sitter.github.io/tree-sitter/ tree-sitter documentation
|
|
58
58
|
# @see GrammarFinder For automatic grammar library discovery
|
|
59
59
|
module TreeHaver
|
|
60
60
|
# Base error class for TreeHaver exceptions
|
|
@@ -83,12 +83,14 @@ module TreeHaver
|
|
|
83
83
|
# - {Backends::MRI} - Uses ruby_tree_sitter (MRI C extension)
|
|
84
84
|
# - {Backends::Rust} - Uses tree_stump (Rust extension with precompiled binaries)
|
|
85
85
|
# - {Backends::FFI} - Uses Ruby FFI to call libtree-sitter directly
|
|
86
|
-
# - {Backends::Java} - Uses JRuby's Java integration
|
|
86
|
+
# - {Backends::Java} - Uses JRuby's Java integration
|
|
87
|
+
# - {Backends::Citrus} - Uses Citrus PEG parser (pure Ruby, portable)
|
|
87
88
|
module Backends
|
|
88
89
|
autoload :MRI, File.join(__dir__, "tree_haver", "backends", "mri")
|
|
89
90
|
autoload :Rust, File.join(__dir__, "tree_haver", "backends", "rust")
|
|
90
91
|
autoload :FFI, File.join(__dir__, "tree_haver", "backends", "ffi")
|
|
91
92
|
autoload :Java, File.join(__dir__, "tree_haver", "backends", "java")
|
|
93
|
+
autoload :Citrus, File.join(__dir__, "tree_haver", "backends", "citrus")
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
# Security utilities for validating paths before loading shared libraries
|
|
@@ -119,9 +121,15 @@ module TreeHaver
|
|
|
119
121
|
# @see PathValidator
|
|
120
122
|
autoload :GrammarFinder, File.join(__dir__, "tree_haver", "grammar_finder")
|
|
121
123
|
|
|
124
|
+
# Unified Node wrapper providing consistent API across backends
|
|
125
|
+
autoload :Node, File.join(__dir__, "tree_haver", "node")
|
|
126
|
+
|
|
127
|
+
# Unified Tree wrapper providing consistent API across backends
|
|
128
|
+
autoload :Tree, File.join(__dir__, "tree_haver", "tree")
|
|
129
|
+
|
|
122
130
|
# Get the current backend selection
|
|
123
131
|
#
|
|
124
|
-
# @return [Symbol] one of :auto, :mri, :ffi, or :
|
|
132
|
+
# @return [Symbol] one of :auto, :mri, :rust, :ffi, :java, or :citrus
|
|
125
133
|
# @note Can be set via ENV["TREE_HAVER_BACKEND"]
|
|
126
134
|
class << self
|
|
127
135
|
# @example
|
|
@@ -132,13 +140,14 @@ module TreeHaver
|
|
|
132
140
|
when "rust" then :rust
|
|
133
141
|
when "ffi" then :ffi
|
|
134
142
|
when "java" then :java
|
|
143
|
+
when "citrus" then :citrus
|
|
135
144
|
else :auto
|
|
136
145
|
end
|
|
137
146
|
end
|
|
138
147
|
|
|
139
148
|
# Set the backend to use
|
|
140
149
|
#
|
|
141
|
-
# @param name [Symbol, String, nil] backend name (:auto, :mri, :rust, :ffi, :java)
|
|
150
|
+
# @param name [Symbol, String, nil] backend name (:auto, :mri, :rust, :ffi, :java, :citrus)
|
|
142
151
|
# @return [Symbol, nil] the backend that was set
|
|
143
152
|
# @example Force FFI backend
|
|
144
153
|
# TreeHaver.backend = :ffi
|
|
@@ -165,10 +174,11 @@ module TreeHaver
|
|
|
165
174
|
# Determine the concrete backend module to use
|
|
166
175
|
#
|
|
167
176
|
# This method performs backend auto-selection when backend is :auto.
|
|
168
|
-
# On JRuby, prefers Java backend if available, then FFI.
|
|
169
|
-
# On MRI, prefers MRI backend if available, then Rust, then FFI.
|
|
177
|
+
# On JRuby, prefers Java backend if available, then FFI, then Citrus.
|
|
178
|
+
# On MRI, prefers MRI backend if available, then Rust, then FFI, then Citrus.
|
|
179
|
+
# Citrus is the final fallback as it's pure Ruby and works everywhere.
|
|
170
180
|
#
|
|
171
|
-
# @return [Module, nil] the backend module (Backends::MRI, Backends::Rust, Backends::FFI, or Backends::
|
|
181
|
+
# @return [Module, nil] the backend module (Backends::MRI, Backends::Rust, Backends::FFI, Backends::Java, or Backends::Citrus), or nil if none available
|
|
172
182
|
# @example
|
|
173
183
|
# mod = TreeHaver.backend_module
|
|
174
184
|
# if mod
|
|
@@ -184,8 +194,10 @@ module TreeHaver
|
|
|
184
194
|
Backends::FFI
|
|
185
195
|
when :java
|
|
186
196
|
Backends::Java
|
|
197
|
+
when :citrus
|
|
198
|
+
Backends::Citrus
|
|
187
199
|
else
|
|
188
|
-
# auto-select:
|
|
200
|
+
# auto-select: prefer native/fast backends, fall back to pure Ruby (Citrus)
|
|
189
201
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" && Backends::Java.available?
|
|
190
202
|
Backends::Java
|
|
191
203
|
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && Backends::MRI.available?
|
|
@@ -194,8 +206,10 @@ module TreeHaver
|
|
|
194
206
|
Backends::Rust
|
|
195
207
|
elsif Backends::FFI.available?
|
|
196
208
|
Backends::FFI
|
|
209
|
+
elsif Backends::Citrus.available?
|
|
210
|
+
Backends::Citrus # Pure Ruby fallback
|
|
197
211
|
else
|
|
198
|
-
# No backend available
|
|
212
|
+
# No backend available
|
|
199
213
|
nil
|
|
200
214
|
end
|
|
201
215
|
end
|
|
@@ -276,7 +290,7 @@ module TreeHaver
|
|
|
276
290
|
end
|
|
277
291
|
end
|
|
278
292
|
|
|
279
|
-
# Represents a
|
|
293
|
+
# Represents a tree-sitter language grammar
|
|
280
294
|
#
|
|
281
295
|
# A Language object is an opaque handle to a TSLanguage* that defines
|
|
282
296
|
# the grammar rules for parsing a specific programming language.
|
|
@@ -400,7 +414,7 @@ module TreeHaver
|
|
|
400
414
|
end
|
|
401
415
|
end
|
|
402
416
|
|
|
403
|
-
# Represents a
|
|
417
|
+
# Represents a tree-sitter parser instance
|
|
404
418
|
#
|
|
405
419
|
# A Parser is used to parse source code into a syntax tree. You must
|
|
406
420
|
# set a language before parsing.
|
|
@@ -448,7 +462,7 @@ module TreeHaver
|
|
|
448
462
|
#
|
|
449
463
|
# == Incremental Parsing
|
|
450
464
|
#
|
|
451
|
-
#
|
|
465
|
+
# tree-sitter supports **incremental parsing** where you can pass a previously
|
|
452
466
|
# parsed tree along with edit information to efficiently re-parse only the
|
|
453
467
|
# changed portions of source code. This is a major performance optimization
|
|
454
468
|
# for editors and IDEs that need to re-parse on every keystroke.
|
|
@@ -458,7 +472,7 @@ module TreeHaver
|
|
|
458
472
|
# 2. User edits the source (e.g., inserts a character)
|
|
459
473
|
# 3. Call `tree.edit(...)` to update the tree's position data
|
|
460
474
|
# 4. Re-parse with the old tree: `new_tree = parser.parse_string(tree, new_source)`
|
|
461
|
-
# 5.
|
|
475
|
+
# 5. tree-sitter reuses unchanged nodes, only re-parsing affected regions
|
|
462
476
|
#
|
|
463
477
|
# TreeHaver passes through to the underlying backend if it supports incremental
|
|
464
478
|
# parsing (MRI and Rust backends do). Check `TreeHaver.capabilities[:incremental]`
|
|
@@ -467,7 +481,7 @@ module TreeHaver
|
|
|
467
481
|
# @param old_tree [Tree, nil] previously parsed tree for incremental parsing, or nil for fresh parse
|
|
468
482
|
# @param source [String] the source code to parse (should be UTF-8)
|
|
469
483
|
# @return [Tree] the parsed syntax tree
|
|
470
|
-
# @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing
|
|
484
|
+
# @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing tree-sitter incremental parsing docs
|
|
471
485
|
# @see Tree#edit For marking edits before incremental re-parsing
|
|
472
486
|
# @example First parse (no old tree)
|
|
473
487
|
# tree = parser.parse_string(nil, "x = 1")
|
|
@@ -478,7 +492,14 @@ module TreeHaver
|
|
|
478
492
|
# Pass through to backend if it supports incremental parsing
|
|
479
493
|
if old_tree && @impl.respond_to?(:parse_string)
|
|
480
494
|
# Extract the underlying implementation from our Tree wrapper
|
|
481
|
-
old_impl = old_tree.
|
|
495
|
+
old_impl = if old_tree.respond_to?(:inner_tree)
|
|
496
|
+
old_tree.inner_tree
|
|
497
|
+
elsif old_tree.respond_to?(:instance_variable_get)
|
|
498
|
+
# Fallback for compatibility
|
|
499
|
+
old_tree.instance_variable_get(:@inner_tree) || old_tree.instance_variable_get(:@impl) || old_tree
|
|
500
|
+
else
|
|
501
|
+
old_tree
|
|
502
|
+
end
|
|
482
503
|
tree_impl = @impl.parse_string(old_impl, source)
|
|
483
504
|
Tree.new(tree_impl)
|
|
484
505
|
elsif @impl.respond_to?(:parse_string)
|
|
@@ -491,219 +512,13 @@ module TreeHaver
|
|
|
491
512
|
end
|
|
492
513
|
end
|
|
493
514
|
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
#
|
|
497
|
-
# the root node of the AST and supports incremental parsing via the
|
|
498
|
-
# {#edit} method.
|
|
499
|
-
#
|
|
500
|
-
# @example Basic usage
|
|
501
|
-
# tree = parser.parse(source)
|
|
502
|
-
# root = tree.root_node
|
|
503
|
-
#
|
|
504
|
-
# @example Incremental parsing
|
|
505
|
-
# tree = parser.parse_string(nil, original_source)
|
|
506
|
-
# tree.edit(
|
|
507
|
-
# start_byte: 10,
|
|
508
|
-
# old_end_byte: 15,
|
|
509
|
-
# new_end_byte: 20,
|
|
510
|
-
# start_point: { row: 0, column: 10 },
|
|
511
|
-
# old_end_point: { row: 0, column: 15 },
|
|
512
|
-
# new_end_point: { row: 0, column: 20 }
|
|
513
|
-
# )
|
|
514
|
-
# new_tree = parser.parse_string(tree, edited_source)
|
|
515
|
-
class Tree
|
|
516
|
-
# @api private
|
|
517
|
-
def initialize(impl)
|
|
518
|
-
@impl = impl
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
# Get the root node of the syntax tree
|
|
522
|
-
#
|
|
523
|
-
# @return [Node] the root node
|
|
524
|
-
# @example
|
|
525
|
-
# root = tree.root_node
|
|
526
|
-
# puts root.type # => "document" or similar
|
|
527
|
-
def root_node
|
|
528
|
-
Node.new(@impl.root_node)
|
|
529
|
-
end
|
|
530
|
-
|
|
531
|
-
# Mark the tree as edited for incremental re-parsing
|
|
532
|
-
#
|
|
533
|
-
# Call this method after the source code has been modified but before
|
|
534
|
-
# re-parsing. This tells Tree-sitter which parts of the tree are
|
|
535
|
-
# invalidated so it can efficiently re-parse only the affected regions.
|
|
536
|
-
#
|
|
537
|
-
# @param start_byte [Integer] byte offset where the edit starts
|
|
538
|
-
# @param old_end_byte [Integer] byte offset where the old text ended
|
|
539
|
-
# @param new_end_byte [Integer] byte offset where the new text ends
|
|
540
|
-
# @param start_point [Hash] starting position as `{ row:, column: }`
|
|
541
|
-
# @param old_end_point [Hash] old ending position as `{ row:, column: }`
|
|
542
|
-
# @param new_end_point [Hash] new ending position as `{ row:, column: }`
|
|
543
|
-
# @return [void]
|
|
544
|
-
# @raise [NotAvailable] if the backend doesn't support incremental parsing
|
|
545
|
-
# @see https://tree-sitter.github.io/tree-sitter/using-parsers#editing
|
|
546
|
-
#
|
|
547
|
-
# @example
|
|
548
|
-
# # Original: "x = 1"
|
|
549
|
-
# # Edited: "x = 42" (replaced "1" with "42" at byte 4)
|
|
550
|
-
# tree.edit(
|
|
551
|
-
# start_byte: 4,
|
|
552
|
-
# old_end_byte: 5,
|
|
553
|
-
# new_end_byte: 6,
|
|
554
|
-
# start_point: { row: 0, column: 4 },
|
|
555
|
-
# old_end_point: { row: 0, column: 5 },
|
|
556
|
-
# new_end_point: { row: 0, column: 6 }
|
|
557
|
-
# )
|
|
558
|
-
def edit(start_byte:, old_end_byte:, new_end_byte:, start_point:, old_end_point:, new_end_point:)
|
|
559
|
-
unless @impl.respond_to?(:edit)
|
|
560
|
-
raise NotAvailable, "Incremental parsing not supported by current backend. " \
|
|
561
|
-
"Use MRI (ruby_tree_sitter) or Rust (tree_stump) backend."
|
|
562
|
-
end
|
|
563
|
-
|
|
564
|
-
@impl.edit(
|
|
565
|
-
start_byte: start_byte,
|
|
566
|
-
old_end_byte: old_end_byte,
|
|
567
|
-
new_end_byte: new_end_byte,
|
|
568
|
-
start_point: start_point,
|
|
569
|
-
old_end_point: old_end_point,
|
|
570
|
-
new_end_point: new_end_point,
|
|
571
|
-
)
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
# Check if the underlying implementation supports incremental parsing
|
|
575
|
-
#
|
|
576
|
-
# @return [Boolean] true if {#edit} can be called on this tree
|
|
577
|
-
def supports_editing?
|
|
578
|
-
@impl.respond_to?(:edit)
|
|
579
|
-
end
|
|
580
|
-
end
|
|
581
|
-
|
|
582
|
-
# Represents a node in the syntax tree
|
|
515
|
+
# Tree and Node classes have been moved to separate files:
|
|
516
|
+
# - tree_haver/tree.rb: TreeHaver::Tree - unified wrapper providing consistent API
|
|
517
|
+
# - tree_haver/node.rb: TreeHaver::Node - unified wrapper providing consistent API
|
|
583
518
|
#
|
|
584
|
-
#
|
|
585
|
-
#
|
|
586
|
-
|
|
587
|
-
# @example Traversing nodes
|
|
588
|
-
# root = tree.root_node
|
|
589
|
-
# root.each do |child|
|
|
590
|
-
# puts "Child type: #{child.type}"
|
|
591
|
-
# child.each { |grandchild| puts " Grandchild: #{grandchild.type}" }
|
|
592
|
-
# end
|
|
593
|
-
class Node
|
|
594
|
-
# @api private
|
|
595
|
-
def initialize(impl)
|
|
596
|
-
@impl = impl
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
# Get the type name of this node
|
|
600
|
-
#
|
|
601
|
-
# The type corresponds to the grammar rule that produced this node
|
|
602
|
-
# (e.g., "document", "table", "string_literal", "pair", etc.).
|
|
603
|
-
#
|
|
604
|
-
# @return [String] the node type
|
|
605
|
-
# @example
|
|
606
|
-
# node.type # => "table"
|
|
607
|
-
def type
|
|
608
|
-
@impl.type
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
# Iterate over child nodes
|
|
612
|
-
#
|
|
613
|
-
# @yieldparam child [Node] each child node
|
|
614
|
-
# @return [Enumerator, nil] an enumerator if no block given, nil otherwise
|
|
615
|
-
# @example With a block
|
|
616
|
-
# node.each { |child| puts child.type }
|
|
617
|
-
#
|
|
618
|
-
# @example Without a block
|
|
619
|
-
# children = node.each.to_a
|
|
620
|
-
def each(&blk)
|
|
621
|
-
return enum_for(:each) unless block_given?
|
|
622
|
-
@impl.each { |child_impl| blk.call(Node.new(child_impl)) }
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# Get the start position of this node in the source
|
|
626
|
-
#
|
|
627
|
-
# @return [Object] point object with row and column
|
|
628
|
-
# @example
|
|
629
|
-
# node.start_point.row # => 0
|
|
630
|
-
# node.start_point.column # => 4
|
|
631
|
-
def start_point
|
|
632
|
-
@impl.start_point
|
|
633
|
-
end
|
|
634
|
-
|
|
635
|
-
# Get the end position of this node in the source
|
|
636
|
-
#
|
|
637
|
-
# @return [Object] point object with row and column
|
|
638
|
-
# @example
|
|
639
|
-
# node.end_point.row # => 0
|
|
640
|
-
# node.end_point.column # => 10
|
|
641
|
-
def end_point
|
|
642
|
-
@impl.end_point
|
|
643
|
-
end
|
|
644
|
-
|
|
645
|
-
# Get the start byte offset of this node in the source
|
|
646
|
-
#
|
|
647
|
-
# @return [Integer] byte offset from beginning of source
|
|
648
|
-
def start_byte
|
|
649
|
-
@impl.start_byte
|
|
650
|
-
end
|
|
651
|
-
|
|
652
|
-
# Get the end byte offset of this node in the source
|
|
653
|
-
#
|
|
654
|
-
# @return [Integer] byte offset from beginning of source
|
|
655
|
-
def end_byte
|
|
656
|
-
@impl.end_byte
|
|
657
|
-
end
|
|
658
|
-
|
|
659
|
-
# Check if this node or any descendant has a parse error
|
|
660
|
-
#
|
|
661
|
-
# @return [Boolean] true if there is an error in the subtree
|
|
662
|
-
def has_error?
|
|
663
|
-
@impl.respond_to?(:has_error?) && @impl.has_error?
|
|
664
|
-
end
|
|
665
|
-
|
|
666
|
-
# Check if this node is a MISSING node (inserted by error recovery)
|
|
667
|
-
#
|
|
668
|
-
# @return [Boolean] true if this is a missing node
|
|
669
|
-
def missing?
|
|
670
|
-
@impl.respond_to?(:missing?) && @impl.missing?
|
|
671
|
-
end
|
|
672
|
-
|
|
673
|
-
# Get string representation of this node
|
|
674
|
-
#
|
|
675
|
-
# @return [String] string representation
|
|
676
|
-
def to_s
|
|
677
|
-
@impl.to_s
|
|
678
|
-
end
|
|
679
|
-
|
|
680
|
-
# Check if node responds to a method (includes delegation to @impl)
|
|
681
|
-
#
|
|
682
|
-
# @param method_name [Symbol] method to check
|
|
683
|
-
# @param include_private [Boolean] include private methods
|
|
684
|
-
# @return [Boolean]
|
|
685
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
686
|
-
@impl.respond_to?(method_name, include_private) || super
|
|
687
|
-
end
|
|
688
|
-
|
|
689
|
-
# Delegate unknown methods to the underlying implementation
|
|
690
|
-
#
|
|
691
|
-
# This provides full compatibility with ruby_tree_sitter nodes
|
|
692
|
-
# for methods not explicitly wrapped.
|
|
693
|
-
#
|
|
694
|
-
# @param method_name [Symbol] method to call
|
|
695
|
-
# @param args [Array] arguments to pass
|
|
696
|
-
# @param block [Proc] block to pass
|
|
697
|
-
# @return [Object] result from the underlying implementation
|
|
698
|
-
def method_missing(method_name, *args, **kwargs, &block)
|
|
699
|
-
if @impl.respond_to?(method_name)
|
|
700
|
-
@impl.public_send(method_name, *args, **kwargs, &block)
|
|
701
|
-
else
|
|
702
|
-
super
|
|
703
|
-
end
|
|
704
|
-
end
|
|
705
|
-
end
|
|
706
|
-
end
|
|
519
|
+
# These provide a unified interface across all backends (MRI, Rust, FFI, Java, Citrus).
|
|
520
|
+
# All backends now return properly wrapped TreeHaver::Tree and TreeHaver::Node objects.
|
|
521
|
+
end # end module TreeHaver
|
|
707
522
|
|
|
708
523
|
TreeHaver::Version.class_eval do
|
|
709
524
|
extend VersionGem::Basic
|
data/sig/tree_haver/backends.rbs
CHANGED
|
@@ -47,7 +47,7 @@ module TreeHaver
|
|
|
47
47
|
# Get list of candidate library names
|
|
48
48
|
def self.lib_candidates: () -> Array[String]
|
|
49
49
|
|
|
50
|
-
# Load the
|
|
50
|
+
# Load the tree-sitter runtime library
|
|
51
51
|
def self.try_load!: () -> void
|
|
52
52
|
|
|
53
53
|
# Check if the library is loaded
|
|
@@ -281,5 +281,72 @@ module TreeHaver
|
|
|
281
281
|
class Node
|
|
282
282
|
end
|
|
283
283
|
end
|
|
284
|
+
|
|
285
|
+
# Citrus backend using pure Ruby Citrus parser
|
|
286
|
+
module Citrus
|
|
287
|
+
# Check if the Citrus backend is available
|
|
288
|
+
def self.available?: () -> bool
|
|
289
|
+
|
|
290
|
+
# Reset the load state (for testing)
|
|
291
|
+
def self.reset!: () -> void
|
|
292
|
+
|
|
293
|
+
# Get capabilities supported by this backend
|
|
294
|
+
def self.capabilities: () -> Hash[Symbol, untyped]
|
|
295
|
+
|
|
296
|
+
class Language
|
|
297
|
+
attr_reader grammar_module: untyped
|
|
298
|
+
|
|
299
|
+
# Create a new language from a Citrus grammar module
|
|
300
|
+
def initialize: (untyped grammar_module) -> void
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
class Parser
|
|
304
|
+
# Create a new parser instance
|
|
305
|
+
def initialize: () -> void
|
|
306
|
+
|
|
307
|
+
# Set the grammar for this parser
|
|
308
|
+
def language=: (Language | untyped grammar) -> (Language | untyped)
|
|
309
|
+
|
|
310
|
+
# Parse source code
|
|
311
|
+
def parse: (String source) -> untyped
|
|
312
|
+
|
|
313
|
+
private
|
|
314
|
+
|
|
315
|
+
@grammar: untyped
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
class Tree
|
|
319
|
+
attr_reader root_match: untyped
|
|
320
|
+
attr_reader source: String
|
|
321
|
+
|
|
322
|
+
def initialize: (untyped root_match, String source) -> void
|
|
323
|
+
|
|
324
|
+
def root_node: () -> Node
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
class Node
|
|
328
|
+
attr_reader match: untyped
|
|
329
|
+
attr_reader source: String
|
|
330
|
+
|
|
331
|
+
def initialize: (untyped match, String source) -> void
|
|
332
|
+
|
|
333
|
+
def type: () -> String
|
|
334
|
+
|
|
335
|
+
def start_byte: () -> Integer
|
|
336
|
+
|
|
337
|
+
def end_byte: () -> Integer
|
|
338
|
+
|
|
339
|
+
def text: () -> String
|
|
340
|
+
|
|
341
|
+
def child_count: () -> Integer
|
|
342
|
+
|
|
343
|
+
def child: (Integer index) -> Node?
|
|
344
|
+
|
|
345
|
+
def children: () -> Array[Node]
|
|
346
|
+
|
|
347
|
+
def each: () { (Node) -> void } -> void
|
|
348
|
+
| () -> Enumerator[Node, nil]
|
|
349
|
+
end
|
|
350
|
+
end
|
|
284
351
|
end
|
|
285
352
|
end
|
|
@@ -26,6 +26,7 @@ module TreeHaver
|
|
|
26
26
|
def self.sanitize_language_name: (String | Symbol | nil name) -> Symbol?
|
|
27
27
|
def self.validation_errors: (String? path) -> Array[String]
|
|
28
28
|
def self.windows_absolute_path?: (String path) -> bool
|
|
29
|
+
def self.has_valid_extension?: (String path) -> bool
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
data/sig/tree_haver.rbs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Type definitions for TreeHaver
|
|
2
2
|
#
|
|
3
|
-
# TreeHaver is a cross-Ruby adapter for the
|
|
3
|
+
# TreeHaver is a cross-Ruby adapter for the tree-sitter parsing library
|
|
4
4
|
|
|
5
5
|
module TreeHaver
|
|
6
6
|
VERSION: String
|
|
@@ -44,7 +44,7 @@ module TreeHaver
|
|
|
44
44
|
# Fetch a registered language entry
|
|
45
45
|
def self.registered_language: (Symbol | String name) -> Hash[Symbol, String?]?
|
|
46
46
|
|
|
47
|
-
# Represents a
|
|
47
|
+
# Represents a tree-sitter language grammar
|
|
48
48
|
class Language
|
|
49
49
|
# Load a language grammar from a shared library
|
|
50
50
|
def self.from_library: (String path, ?symbol: String?, ?name: String?) -> Language
|
|
@@ -57,7 +57,7 @@ module TreeHaver
|
|
|
57
57
|
def self.respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Represents a
|
|
60
|
+
# Represents a tree-sitter parser instance
|
|
61
61
|
class Parser
|
|
62
62
|
# Create a new parser instance
|
|
63
63
|
def initialize: () -> void
|
|
@@ -68,6 +68,9 @@ module TreeHaver
|
|
|
68
68
|
# Parse source code into a syntax tree
|
|
69
69
|
def parse: (String source) -> Tree
|
|
70
70
|
|
|
71
|
+
# Parse with optional incremental parsing support
|
|
72
|
+
def parse_string: (Tree? old_tree, String source) -> Tree
|
|
73
|
+
|
|
71
74
|
private
|
|
72
75
|
|
|
73
76
|
@impl: untyped
|
|
@@ -75,30 +78,113 @@ module TreeHaver
|
|
|
75
78
|
|
|
76
79
|
# Represents a parsed syntax tree
|
|
77
80
|
class Tree
|
|
81
|
+
attr_reader inner_tree: untyped
|
|
82
|
+
attr_reader source: String?
|
|
83
|
+
|
|
78
84
|
# Get the root node of the syntax tree
|
|
79
|
-
def root_node: () -> Node
|
|
85
|
+
def root_node: () -> Node?
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
# Edit the tree for incremental parsing
|
|
88
|
+
def edit: (
|
|
89
|
+
start_byte: Integer,
|
|
90
|
+
old_end_byte: Integer,
|
|
91
|
+
new_end_byte: Integer,
|
|
92
|
+
start_point: Hash[Symbol, Integer],
|
|
93
|
+
old_end_point: Hash[Symbol, Integer],
|
|
94
|
+
new_end_point: Hash[Symbol, Integer]
|
|
95
|
+
) -> void
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
# Check if the current backend supports incremental parsing
|
|
98
|
+
def supports_editing?: () -> bool
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
# String representation
|
|
101
|
+
def inspect: () -> String
|
|
102
|
+
|
|
103
|
+
# Check if tree responds to a method (includes delegation to inner_tree)
|
|
104
|
+
def respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
|
|
105
|
+
|
|
106
|
+
# Delegate unknown methods to the underlying backend-specific tree
|
|
107
|
+
def method_missing: (Symbol method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def initialize: (untyped inner_tree, ?source: String?) -> void
|
|
86
112
|
end
|
|
87
113
|
|
|
88
114
|
# Represents a node in the syntax tree
|
|
89
115
|
class Node
|
|
116
|
+
attr_reader inner_node: untyped
|
|
117
|
+
attr_reader source: String?
|
|
118
|
+
|
|
90
119
|
# Get the type name of this node
|
|
91
120
|
def type: () -> String
|
|
92
121
|
|
|
122
|
+
# Get the text content of this node
|
|
123
|
+
def text: () -> String?
|
|
124
|
+
|
|
125
|
+
# Get the start byte offset
|
|
126
|
+
def start_byte: () -> Integer
|
|
127
|
+
|
|
128
|
+
# Get the end byte offset
|
|
129
|
+
def end_byte: () -> Integer
|
|
130
|
+
|
|
131
|
+
# Get the start point (row, column)
|
|
132
|
+
def start_point: () -> Point
|
|
133
|
+
|
|
134
|
+
# Get the end point (row, column)
|
|
135
|
+
def end_point: () -> Point
|
|
136
|
+
|
|
137
|
+
# Get the number of child nodes
|
|
138
|
+
def child_count: () -> Integer
|
|
139
|
+
|
|
140
|
+
# Get a child node by index
|
|
141
|
+
def child: (Integer index) -> Node?
|
|
142
|
+
|
|
143
|
+
# Get all children as an array
|
|
144
|
+
def children: () -> Array[Node]
|
|
145
|
+
|
|
93
146
|
# Iterate over child nodes
|
|
94
147
|
def each: () { (Node child) -> void } -> nil
|
|
95
148
|
| () -> Enumerator[Node, nil]
|
|
96
149
|
|
|
150
|
+
# Get a named child by field name
|
|
151
|
+
def child_by_field_name: (String | Symbol field_name) -> Node?
|
|
152
|
+
|
|
153
|
+
# Get the parent node
|
|
154
|
+
def parent: () -> Node?
|
|
155
|
+
|
|
156
|
+
# Get the next sibling node
|
|
157
|
+
def next_sibling: () -> Node?
|
|
158
|
+
|
|
159
|
+
# Get the previous sibling node
|
|
160
|
+
def prev_sibling: () -> Node?
|
|
161
|
+
|
|
162
|
+
# Check if node responds to a method (includes delegation to inner_node)
|
|
163
|
+
def respond_to_missing?: (Symbol method_name, ?bool include_private) -> bool
|
|
164
|
+
|
|
165
|
+
# Delegate unknown methods to the underlying backend-specific node
|
|
166
|
+
def method_missing: (Symbol method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
|
|
167
|
+
|
|
97
168
|
private
|
|
98
169
|
|
|
99
|
-
def initialize: (untyped
|
|
170
|
+
def initialize: (untyped inner_node, ?source: String?) -> void
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Position in source code that works as both object and hash
|
|
174
|
+
class Point
|
|
175
|
+
attr_reader row: Integer
|
|
176
|
+
attr_reader column: Integer
|
|
100
177
|
|
|
101
|
-
|
|
178
|
+
def initialize: (row: Integer, column: Integer) -> void
|
|
179
|
+
|
|
180
|
+
# Hash-style access
|
|
181
|
+
def []: (Symbol key) -> Integer?
|
|
182
|
+
|
|
183
|
+
# Convert to hash
|
|
184
|
+
def to_h: () -> Hash[Symbol, Integer]
|
|
185
|
+
|
|
186
|
+
# String representation
|
|
187
|
+
def inspect: () -> String
|
|
102
188
|
end
|
|
103
189
|
|
|
104
190
|
# Thread-safe language registrations and cache
|
data.tar.gz.sig
CHANGED
|
Binary file
|