ruby_tree_sitter 1.1.0-arm64-darwin-22
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 +7 -0
- data/LICENSE +22 -0
- data/README.md +199 -0
- data/ext/tree_sitter/encoding.c +29 -0
- data/ext/tree_sitter/extconf.rb +149 -0
- data/ext/tree_sitter/input.c +127 -0
- data/ext/tree_sitter/input_edit.c +42 -0
- data/ext/tree_sitter/language.c +219 -0
- data/ext/tree_sitter/logger.c +228 -0
- data/ext/tree_sitter/macros.h +163 -0
- data/ext/tree_sitter/node.c +618 -0
- data/ext/tree_sitter/parser.c +398 -0
- data/ext/tree_sitter/point.c +26 -0
- data/ext/tree_sitter/quantifier.c +43 -0
- data/ext/tree_sitter/query.c +282 -0
- data/ext/tree_sitter/query_capture.c +28 -0
- data/ext/tree_sitter/query_cursor.c +215 -0
- data/ext/tree_sitter/query_error.c +41 -0
- data/ext/tree_sitter/query_match.c +44 -0
- data/ext/tree_sitter/query_predicate_step.c +83 -0
- data/ext/tree_sitter/range.c +35 -0
- data/ext/tree_sitter/repo.rb +121 -0
- data/ext/tree_sitter/symbol_type.c +46 -0
- data/ext/tree_sitter/tree.c +234 -0
- data/ext/tree_sitter/tree_cursor.c +269 -0
- data/ext/tree_sitter/tree_sitter.c +44 -0
- data/ext/tree_sitter/tree_sitter.h +107 -0
- data/lib/tree_sitter/node.rb +197 -0
- data/lib/tree_sitter/tree_sitter.bundle +0 -0
- data/lib/tree_sitter/version.rb +8 -0
- data/lib/tree_sitter.rb +14 -0
- data/lib/tree_stand/ast_modifier.rb +30 -0
- data/lib/tree_stand/breadth_first_visitor.rb +54 -0
- data/lib/tree_stand/config.rb +13 -0
- data/lib/tree_stand/node.rb +224 -0
- data/lib/tree_stand/parser.rb +67 -0
- data/lib/tree_stand/range.rb +55 -0
- data/lib/tree_stand/tree.rb +123 -0
- data/lib/tree_stand/utils/printer.rb +73 -0
- data/lib/tree_stand/version.rb +7 -0
- data/lib/tree_stand/visitor.rb +127 -0
- data/lib/tree_stand/visitors/tree_walker.rb +37 -0
- data/lib/tree_stand.rb +48 -0
- data/tree_sitter.gemspec +35 -0
- metadata +124 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
#include "tree_sitter.h"
|
2
|
+
|
3
|
+
VALUE mTreeSitter;
|
4
|
+
|
5
|
+
void Init_tree_sitter() {
|
6
|
+
mTreeSitter = rb_define_module("TreeSitter");
|
7
|
+
|
8
|
+
/**
|
9
|
+
* The latest ABI version that is supported by the current version of the
|
10
|
+
* library. When Languages are generated by the Tree-sitter CLI, they are
|
11
|
+
* assigned an ABI version number that corresponds to the current CLI version.
|
12
|
+
* The Tree-sitter library is generally backwards-compatible with languages
|
13
|
+
* generated using older CLI versions, but is not forwards-compatible.
|
14
|
+
*/
|
15
|
+
rb_define_const(mTreeSitter, "LANGUAGE_VERSION",
|
16
|
+
INT2NUM(TREE_SITTER_LANGUAGE_VERSION));
|
17
|
+
|
18
|
+
/**
|
19
|
+
* The earliest ABI version that is supported by the current version of the
|
20
|
+
* library.
|
21
|
+
*/
|
22
|
+
rb_define_const(mTreeSitter, "MIN_COMPATIBLE_LANGUAGE_VERSION",
|
23
|
+
TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
|
24
|
+
|
25
|
+
init_encoding();
|
26
|
+
init_input();
|
27
|
+
init_input_edit();
|
28
|
+
init_language();
|
29
|
+
init_logger();
|
30
|
+
init_node();
|
31
|
+
init_parser();
|
32
|
+
init_point();
|
33
|
+
init_quantifier();
|
34
|
+
init_query();
|
35
|
+
init_query_capture();
|
36
|
+
init_query_cursor();
|
37
|
+
init_query_error();
|
38
|
+
init_query_match();
|
39
|
+
init_query_predicate_step();
|
40
|
+
init_range();
|
41
|
+
init_symbol_type();
|
42
|
+
init_tree();
|
43
|
+
init_tree_cursor();
|
44
|
+
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#ifndef _RB_TREE_SITTER_H
|
2
|
+
#define _RB_TREE_SITTER_H
|
3
|
+
|
4
|
+
#include "macros.h"
|
5
|
+
#include <dlfcn.h>
|
6
|
+
#include <fcntl.h>
|
7
|
+
#include <ruby.h>
|
8
|
+
#include <stdio.h>
|
9
|
+
#include <string.h>
|
10
|
+
#include <tree_sitter/api.h>
|
11
|
+
|
12
|
+
static inline VALUE safe_str(const char *str) {
|
13
|
+
if (str == NULL) {
|
14
|
+
return Qnil;
|
15
|
+
} else {
|
16
|
+
return rb_utf8_str_new_cstr(str);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
static inline VALUE safe_str2(const char *str, uint32_t len) {
|
21
|
+
if (str == NULL) {
|
22
|
+
return Qnil;
|
23
|
+
} else if (len == 0) {
|
24
|
+
return rb_utf8_str_new_cstr("");
|
25
|
+
} else {
|
26
|
+
return rb_utf8_str_new(str, len);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
static inline VALUE safe_symbol(const char *str) {
|
31
|
+
if (str == NULL) {
|
32
|
+
return Qnil;
|
33
|
+
} else {
|
34
|
+
return ID2SYM(rb_intern(str));
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
// VALUE to TS* converters
|
39
|
+
|
40
|
+
TSInput value_to_input(VALUE);
|
41
|
+
TSInputEdit value_to_input_edit(VALUE);
|
42
|
+
TSInputEncoding value_to_encoding(VALUE);
|
43
|
+
TSLanguage *value_to_language(VALUE);
|
44
|
+
TSLogger value_to_logger(VALUE);
|
45
|
+
TSNode value_to_node(VALUE);
|
46
|
+
TSPoint value_to_point(VALUE);
|
47
|
+
TSQuantifier value_to_quantifier(VALUE);
|
48
|
+
TSQuery *value_to_query(VALUE);
|
49
|
+
TSQueryCursor *value_to_query_cursor(VALUE);
|
50
|
+
TSQueryError value_to_query_error(VALUE);
|
51
|
+
TSQueryMatch value_to_query_match(VALUE);
|
52
|
+
TSQueryPredicateStep value_to_query_predicate_step(VALUE);
|
53
|
+
TSQueryPredicateStepType value_to_query_predicate_step_type(VALUE);
|
54
|
+
TSRange value_to_range(VALUE);
|
55
|
+
TSSymbolType value_to_symbol_type(VALUE);
|
56
|
+
TSTree *value_to_tree(VALUE);
|
57
|
+
TSTreeCursor value_to_tree_cursor(VALUE);
|
58
|
+
|
59
|
+
// TS* to VALUE converters
|
60
|
+
VALUE new_input(const TSInput *);
|
61
|
+
VALUE new_language(const TSLanguage *);
|
62
|
+
VALUE new_logger(const TSLogger *);
|
63
|
+
VALUE new_logger_by_val(TSLogger);
|
64
|
+
VALUE new_node(const TSNode *);
|
65
|
+
VALUE new_node_by_val(TSNode);
|
66
|
+
VALUE new_point(const TSPoint *);
|
67
|
+
VALUE new_point_by_val(TSPoint);
|
68
|
+
VALUE new_query_capture(const TSQueryCapture *);
|
69
|
+
VALUE new_query_match(const TSQueryMatch *);
|
70
|
+
VALUE new_query_predicate_step(const TSQueryPredicateStep *);
|
71
|
+
VALUE new_range(const TSRange *);
|
72
|
+
VALUE new_symbol_type(TSSymbolType);
|
73
|
+
VALUE new_tree(TSTree *);
|
74
|
+
|
75
|
+
// All init_* functions are called from Init_tree_sitter
|
76
|
+
void init_encoding(void);
|
77
|
+
void init_input(void);
|
78
|
+
void init_input_edit(void);
|
79
|
+
void init_language(void);
|
80
|
+
void init_logger(void);
|
81
|
+
void init_node(void);
|
82
|
+
void init_parser(void);
|
83
|
+
void init_point(void);
|
84
|
+
void init_quantifier(void);
|
85
|
+
void init_query(void);
|
86
|
+
void init_query_capture(void);
|
87
|
+
void init_query_cursor(void);
|
88
|
+
void init_query_error(void);
|
89
|
+
void init_query_match(void);
|
90
|
+
void init_query_predicate_step(void);
|
91
|
+
void init_range(void);
|
92
|
+
void init_symbol_type(void);
|
93
|
+
void init_tree(void);
|
94
|
+
void init_tree_cursor(void);
|
95
|
+
|
96
|
+
// Other helpers
|
97
|
+
const char *quantifier_str(TSQuantifier);
|
98
|
+
const char *query_error_str(TSQueryError);
|
99
|
+
|
100
|
+
// TSTree reference counting
|
101
|
+
int tree_rc_free(const TSTree *);
|
102
|
+
void tree_rc_new(const TSTree *);
|
103
|
+
|
104
|
+
// This is a special entry-point for the extension
|
105
|
+
void Init_tree_sitter(void);
|
106
|
+
|
107
|
+
#endif
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TreeSitter
|
4
|
+
# Node is a wrapper around a tree-sitter node.
|
5
|
+
class Node
|
6
|
+
# @return [Array<Symbol>] the node's named fields
|
7
|
+
def fields
|
8
|
+
return @fields if @fields
|
9
|
+
|
10
|
+
@fields = Set.new
|
11
|
+
child_count.times do |i|
|
12
|
+
name = field_name_for_child(i)
|
13
|
+
@fields << name.to_sym if name
|
14
|
+
end
|
15
|
+
|
16
|
+
@fields
|
17
|
+
end
|
18
|
+
|
19
|
+
def field?(field)
|
20
|
+
fields.include?(field)
|
21
|
+
end
|
22
|
+
|
23
|
+
# FIXME: These APIs (`[]` and `fetch`) need absolute fixing.
|
24
|
+
# 1. The documentation with the table doesn't work.
|
25
|
+
# 1. The APIs are very confusing! Make them act similarly to Hash's
|
26
|
+
# `fetch` and `[]`.
|
27
|
+
# 1. `[]` should take a single input and return nil if nothing found
|
28
|
+
# (no exceptions).
|
29
|
+
# 1. `fetch` should should accept a single argument, potentially a
|
30
|
+
# default, and raise exception if no default was provided.
|
31
|
+
# Also allow for the `all:` kwarg.
|
32
|
+
# 1. `values_at` takes many arguments.
|
33
|
+
# And I don't think we can move to 1.0 without adressing them.
|
34
|
+
#
|
35
|
+
# Access node's named children.
|
36
|
+
#
|
37
|
+
# It's similar to {#fetch}, but differes in input type, return values, and
|
38
|
+
# the internal implementation.
|
39
|
+
#
|
40
|
+
# Both of these methods exist for separate use cases, but also because
|
41
|
+
# sometime tree-sitter does some monkey business and having both separate
|
42
|
+
# implementations can help.
|
43
|
+
#
|
44
|
+
# Comparison with {#fetch}:
|
45
|
+
#
|
46
|
+
# [] | fetch
|
47
|
+
# ------------------------------+----------------------
|
48
|
+
# input types Integer, String, Symbol | Array<String, Symbol>
|
49
|
+
# Array<Integer, String, Symbol>|
|
50
|
+
# ------------------------------+----------------------
|
51
|
+
# returns 1-to-1 correspondance with | unique nodes
|
52
|
+
# input |
|
53
|
+
# ------------------------------+----------------------
|
54
|
+
# uses named_child | field_name_for_child
|
55
|
+
# child_by_field_name | via each_node
|
56
|
+
# ------------------------------+----------------------
|
57
|
+
#
|
58
|
+
# @param keys [Integer | String | Symbol | Array<Integer, String, Symbol>, #read]
|
59
|
+
#
|
60
|
+
# @return [Node | Array<Node>]
|
61
|
+
def [](*keys)
|
62
|
+
case keys.length
|
63
|
+
when 0 then raise "#{self.class.name}##{__method__} requires a key."
|
64
|
+
when 1
|
65
|
+
case k = keys.first
|
66
|
+
when Numeric then named_child(k)
|
67
|
+
when String, Symbol
|
68
|
+
raise "Cannot find field #{k}" unless fields.include?(k.to_sym)
|
69
|
+
|
70
|
+
child_by_field_name(k.to_s)
|
71
|
+
else raise <<~ERR
|
72
|
+
#{self.class.name}##{__method__} accepts Integer and returns named child at given index,
|
73
|
+
or a (String | Symbol) and returns the child by given field name.
|
74
|
+
ERR
|
75
|
+
end
|
76
|
+
else
|
77
|
+
keys.map { |key| self[key] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!visibility private
|
82
|
+
#
|
83
|
+
# Allows access to child_by_field_name without using [].
|
84
|
+
def method_missing(method_name, *_args, &_block)
|
85
|
+
if fields.include?(method_name)
|
86
|
+
child_by_field_name(method_name.to_s)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @!visibility private
|
93
|
+
#
|
94
|
+
def respond_to_missing?(*args)
|
95
|
+
args.length == 1 && fields.include?(args[0])
|
96
|
+
end
|
97
|
+
|
98
|
+
# Iterate over a node's children.
|
99
|
+
#
|
100
|
+
# @yieldparam child [Node] the child
|
101
|
+
def each
|
102
|
+
return enum_for __method__ if !block_given?
|
103
|
+
|
104
|
+
(0...(child_count)).each do |i|
|
105
|
+
yield child(i)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Iterate over a node's children assigned to a field.
|
110
|
+
#
|
111
|
+
# @yieldparam name [NilClass | String] field name.
|
112
|
+
# @yieldparam child [Node] the child.
|
113
|
+
def each_field
|
114
|
+
return enum_for __method__ if !block_given?
|
115
|
+
|
116
|
+
each.with_index do |c, i|
|
117
|
+
f = field_name_for_child(i)
|
118
|
+
next if f.nil? || f.empty?
|
119
|
+
|
120
|
+
yield f, c
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Iterate over a node's named children
|
125
|
+
#
|
126
|
+
# @yieldparam child [Node] the child
|
127
|
+
def each_named
|
128
|
+
return enum_for __method__ if !block_given?
|
129
|
+
|
130
|
+
(0...(named_child_count)).each do |i|
|
131
|
+
yield named_child(i)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Array<TreeSitter::Node>] all the node's children
|
136
|
+
def to_a
|
137
|
+
each.to_a
|
138
|
+
end
|
139
|
+
|
140
|
+
# Access node's named children.
|
141
|
+
#
|
142
|
+
# It's similar to {#fetch}, but differes in input type, return values, and
|
143
|
+
# the internal implementation.
|
144
|
+
#
|
145
|
+
# Both of these methods exist for separate use cases, but also because
|
146
|
+
# sometime tree-sitter does some monkey business and having both separate
|
147
|
+
# implementations can help.
|
148
|
+
#
|
149
|
+
# Comparison with {#fetch}:
|
150
|
+
#
|
151
|
+
# [] | fetch
|
152
|
+
# ------------------------------+----------------------
|
153
|
+
# input types Integer, String, Symbol | String, Symbol
|
154
|
+
# Array<Integer, String, Symbol>| Array<String, Symbol>
|
155
|
+
# ------------------------------+----------------------
|
156
|
+
# returns 1-to-1 correspondance with | unique nodes
|
157
|
+
# input |
|
158
|
+
# ------------------------------+----------------------
|
159
|
+
# uses named_child | field_name_for_child
|
160
|
+
# child_by_field_name | via each_node
|
161
|
+
# ------------------------------+----------------------
|
162
|
+
# @param all [Boolean] If `true`, return an array of nodes for all the
|
163
|
+
# demanded keys, putting `nil` for missing ones. If `false`, return the
|
164
|
+
# same array after calling `compact`. Defaults to `false`.
|
165
|
+
#
|
166
|
+
# See {#fetch_all}.
|
167
|
+
def fetch(*keys, all: false, **_kwargs)
|
168
|
+
dict = {}
|
169
|
+
keys.each.with_index do |k, i|
|
170
|
+
dict[k.to_s] = i
|
171
|
+
end
|
172
|
+
|
173
|
+
res = {}
|
174
|
+
each_field do |f, c|
|
175
|
+
if dict.key?(f)
|
176
|
+
res[f] = c
|
177
|
+
dict.delete(f)
|
178
|
+
end
|
179
|
+
break if dict.empty?
|
180
|
+
end
|
181
|
+
|
182
|
+
res = keys.uniq.map { |k| res[k.to_s] }
|
183
|
+
res = res.compact if !all
|
184
|
+
res
|
185
|
+
end
|
186
|
+
|
187
|
+
# Access all named children of a node, returning `nil` for missing ones.
|
188
|
+
#
|
189
|
+
# Equivalent to `fetch(…, all: true)`.
|
190
|
+
#
|
191
|
+
# See {#fetch}.
|
192
|
+
def fetch_all(*keys, **kwargs)
|
193
|
+
kwargs[:all] = true
|
194
|
+
fetch(*keys, **kwargs)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
Binary file
|
data/lib/tree_sitter.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TreeSitter is a Ruby interface to the tree-sitter parsing library.
|
4
|
+
module TreeSitter
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'set'
|
8
|
+
|
9
|
+
require 'tree_sitter/version'
|
10
|
+
|
11
|
+
require 'tree_sitter/tree_sitter'
|
12
|
+
require 'tree_sitter/node'
|
13
|
+
|
14
|
+
ObjectSpace.define_finalizer(TreeSitter::Tree.class, proc { TreeSitter::Tree.finalizer })
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module TreeStand
|
5
|
+
# An experimental class to modify the AST. It re-runs the query on the
|
6
|
+
# modified document every loop to ensure that the match is still valid.
|
7
|
+
# @see TreeStand::Tree
|
8
|
+
# @api experimental
|
9
|
+
class AstModifier
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(tree: TreeStand::Tree).void }
|
13
|
+
def initialize(tree)
|
14
|
+
@tree = tree
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param query [String]
|
18
|
+
# @yieldparam self [self]
|
19
|
+
# @yieldparam match [TreeStand::Match]
|
20
|
+
# @return [void]
|
21
|
+
def on_match(query)
|
22
|
+
matches = @tree.query(query)
|
23
|
+
|
24
|
+
while !matches.empty?
|
25
|
+
yield self, matches.first
|
26
|
+
matches = @tree.query(query)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module TreeStand
|
5
|
+
# Breadth-first traversal through the tree, calling hooks at each stop.
|
6
|
+
class BreadthFirstVisitor
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(node: TreeStand::Node).void }
|
10
|
+
def initialize(node)
|
11
|
+
@node = node
|
12
|
+
end
|
13
|
+
|
14
|
+
# Run the visitor on the document and return self. Allows chaining create and visit.
|
15
|
+
# @example
|
16
|
+
# visitor = CountingVisitor.new(node, :predicate).visit
|
17
|
+
sig { returns(T.self_type) }
|
18
|
+
def visit
|
19
|
+
queue = [@node]
|
20
|
+
visit_node(queue) while queue.any?
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# @abstract The default implementation does nothing.
|
25
|
+
sig { overridable.params(node: TreeStand::Node).void }
|
26
|
+
def on(node) = nil
|
27
|
+
|
28
|
+
# @abstract The default implementation yields to visit all children.
|
29
|
+
sig { overridable.params(node: TreeStand::Node, block: T.proc.void).void }
|
30
|
+
def around(node, &block) = yield
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def visit_node(queue)
|
35
|
+
node = queue.shift
|
36
|
+
|
37
|
+
if respond_to?("on_#{node.type}")
|
38
|
+
public_send("on_#{node.type}", node)
|
39
|
+
else
|
40
|
+
on(node)
|
41
|
+
end
|
42
|
+
|
43
|
+
if respond_to?("around_#{node.type}")
|
44
|
+
public_send("around_#{node.type}", node) do
|
45
|
+
node.each { |child| queue << child }
|
46
|
+
end
|
47
|
+
else
|
48
|
+
around(node) do
|
49
|
+
node.each { |child| queue << child }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
module TreeStand
|
5
|
+
# Wrapper around a TreeSitter node and provides convient
|
6
|
+
# methods that are missing on the original node. This class
|
7
|
+
# overrides the `method_missing` method to delegate to a nodes
|
8
|
+
# named children.
|
9
|
+
class Node
|
10
|
+
extend T::Sig
|
11
|
+
extend Forwardable
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
# @!method type
|
15
|
+
# @return [Symbol] the type of the node in the tree-sitter grammar.
|
16
|
+
# @!method error?
|
17
|
+
# @return [bool] true if the node is an error node.
|
18
|
+
def_delegators(
|
19
|
+
:@ts_node,
|
20
|
+
:type,
|
21
|
+
:error?,
|
22
|
+
)
|
23
|
+
|
24
|
+
sig { returns(TreeStand::Tree) }
|
25
|
+
attr_reader :tree
|
26
|
+
|
27
|
+
sig { returns(TreeSitter::Node) }
|
28
|
+
attr_reader :ts_node
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
sig { params(tree: TreeStand::Tree, ts_node: TreeSitter::Node).void }
|
32
|
+
def initialize(tree, ts_node)
|
33
|
+
@tree = tree
|
34
|
+
@ts_node = ts_node
|
35
|
+
@fields = @ts_node.each_field.to_a.map(&:first)
|
36
|
+
end
|
37
|
+
|
38
|
+
# TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
|
39
|
+
# `curser#next_match` repeatedly until it returns `nil`.
|
40
|
+
#
|
41
|
+
# This method does all of that for you and collects all of the matches into
|
42
|
+
# an array and each corresponding capture into a hash.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# # This will return a match for each identifier nodes in the tree.
|
46
|
+
# tree_matches = tree.query(<<~QUERY)
|
47
|
+
# (identifier) @identifier
|
48
|
+
# QUERY
|
49
|
+
#
|
50
|
+
# # It is equivalent to:
|
51
|
+
# tree.root_node.query(<<~QUERY)
|
52
|
+
# (identifier) @identifier
|
53
|
+
# QUERY
|
54
|
+
sig { params(query_string: String).returns(T::Array[T::Hash[String, TreeStand::Node]]) }
|
55
|
+
def query(query_string)
|
56
|
+
ts_query = TreeSitter::Query.new(@tree.parser.ts_language, query_string)
|
57
|
+
ts_cursor = TreeSitter::QueryCursor.exec(ts_query, ts_node)
|
58
|
+
matches = []
|
59
|
+
while ts_match = ts_cursor.next_match
|
60
|
+
captures = {}
|
61
|
+
|
62
|
+
ts_match.captures.each do |ts_capture|
|
63
|
+
capture_name = ts_query.capture_name_for_id(ts_capture.index)
|
64
|
+
captures[capture_name] = TreeStand::Node.new(@tree, ts_capture.node)
|
65
|
+
end
|
66
|
+
|
67
|
+
matches << captures
|
68
|
+
end
|
69
|
+
matches
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the first captured node that matches the query string or nil if
|
73
|
+
# there was no captured node.
|
74
|
+
#
|
75
|
+
# @example Find the first identifier node.
|
76
|
+
# identifier_node = tree.root_node.find_node("(identifier) @identifier")
|
77
|
+
#
|
78
|
+
# @see #find_node!
|
79
|
+
# @see #query
|
80
|
+
sig { params(query_string: String).returns(T.nilable(TreeStand::Node)) }
|
81
|
+
def find_node(query_string)
|
82
|
+
query(query_string).first&.values&.first
|
83
|
+
end
|
84
|
+
|
85
|
+
# Like {#find_node}, except that if no node is found, raises an
|
86
|
+
# {TreeStand::NodeNotFound} error.
|
87
|
+
#
|
88
|
+
# @see #find_node
|
89
|
+
# @raise [TreeStand::NodeNotFound]
|
90
|
+
sig { params(query_string: String).returns(TreeStand::Node) }
|
91
|
+
def find_node!(query_string)
|
92
|
+
find_node(query_string) || raise(TreeStand::NodeNotFound)
|
93
|
+
end
|
94
|
+
|
95
|
+
sig { returns(TreeStand::Range) }
|
96
|
+
def range
|
97
|
+
TreeStand::Range.new(
|
98
|
+
start_byte: @ts_node.start_byte,
|
99
|
+
end_byte: @ts_node.end_byte,
|
100
|
+
start_point: @ts_node.start_point,
|
101
|
+
end_point: @ts_node.end_point,
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Node includes enumerable so that you can iterate over the child nodes.
|
106
|
+
# @example
|
107
|
+
# node.text # => "3 * 4"
|
108
|
+
#
|
109
|
+
# @example Iterate over the child nodes
|
110
|
+
# node.each do |child|
|
111
|
+
# print child.text
|
112
|
+
# end
|
113
|
+
# # prints: 3*4
|
114
|
+
#
|
115
|
+
# @example Enumerable methods
|
116
|
+
# node.map(&:text) # => ["3", "*", "4"]
|
117
|
+
#
|
118
|
+
# @yieldparam child [TreeStand::Node]
|
119
|
+
sig do
|
120
|
+
override
|
121
|
+
.params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
|
122
|
+
.returns(T::Enumerator[TreeStand::Node])
|
123
|
+
end
|
124
|
+
def each(&block)
|
125
|
+
enumerator = Enumerator.new do |yielder|
|
126
|
+
@ts_node.each do |child|
|
127
|
+
yielder << TreeStand::Node.new(@tree, child)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
enumerator.each(&block) if block_given?
|
131
|
+
enumerator
|
132
|
+
end
|
133
|
+
|
134
|
+
# (see TreeStand::Visitors::TreeWalker)
|
135
|
+
# Backed by {TreeStand::Visitors::TreeWalker}.
|
136
|
+
#
|
137
|
+
# @example Check the subtree for error nodes
|
138
|
+
# node.walk.any? { |node| node.type == :error }
|
139
|
+
#
|
140
|
+
# @see TreeStand::Visitors::TreeWalker
|
141
|
+
#
|
142
|
+
# @yieldparam node [TreeStand::Node]
|
143
|
+
sig do
|
144
|
+
params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
|
145
|
+
.returns(T::Enumerator[TreeStand::Node])
|
146
|
+
end
|
147
|
+
def walk(&block)
|
148
|
+
enumerator = Enumerator.new do |yielder|
|
149
|
+
Visitors::TreeWalker.new(self) do |child|
|
150
|
+
yielder << child
|
151
|
+
end.visit
|
152
|
+
end
|
153
|
+
enumerator.each(&block) if block_given?
|
154
|
+
enumerator
|
155
|
+
end
|
156
|
+
|
157
|
+
# @example
|
158
|
+
# node.text # => "4"
|
159
|
+
# node.parent.text # => "3 * 4"
|
160
|
+
# node.parent.parent.text # => "1 + 3 * 4"
|
161
|
+
sig { returns(TreeStand::Node) }
|
162
|
+
def parent
|
163
|
+
TreeStand::Node.new(@tree, @ts_node.parent)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @example
|
167
|
+
# node.text # => "3 * 4"
|
168
|
+
# node.to_a.map(&:text) # => ["3", "*", "4"]
|
169
|
+
# node.children.map(&:text) # => ["3", "*", "4"]
|
170
|
+
sig { returns(T::Array[TreeStand::Node]) }
|
171
|
+
def children = to_a
|
172
|
+
|
173
|
+
# A convenience method for getting the text of the node. Each {TreeStand::Node}
|
174
|
+
# wraps the parent {TreeStand::Tree #tree} and has access to the source document.
|
175
|
+
sig { returns(String) }
|
176
|
+
def text
|
177
|
+
T.must(@tree.document[@ts_node.start_byte...@ts_node.end_byte])
|
178
|
+
end
|
179
|
+
|
180
|
+
# This class overrides the `method_missing` method to delegate to the
|
181
|
+
# node's named children.
|
182
|
+
# @example
|
183
|
+
# node.text # => "3 * 4"
|
184
|
+
#
|
185
|
+
# node.left.text # => "3"
|
186
|
+
# node.operator.text # => "*"
|
187
|
+
# node.right.text # => "4"
|
188
|
+
# node.operand # => NoMethodError
|
189
|
+
# @overload method_missing(field_name)
|
190
|
+
# @param name [Symbol, String]
|
191
|
+
# @return [TreeStand::Node] child node for the given field name
|
192
|
+
# @raise [NoMethodError] Raised if the node does not have child with name `field_name`
|
193
|
+
#
|
194
|
+
# @overload method_missing(method_name, *args, &block)
|
195
|
+
# @raise [NoMethodError]
|
196
|
+
def method_missing(method, *args, &block)
|
197
|
+
return super unless @fields.include?(method.to_s)
|
198
|
+
|
199
|
+
TreeStand::Node.new(@tree, T.unsafe(@ts_node).public_send(method, *args, &block))
|
200
|
+
end
|
201
|
+
|
202
|
+
sig { params(other: Object).returns(T::Boolean) }
|
203
|
+
def ==(other)
|
204
|
+
return false unless other.is_a?(TreeStand::Node)
|
205
|
+
|
206
|
+
T.must(range == other.range && type == other.type && text == other.text)
|
207
|
+
end
|
208
|
+
|
209
|
+
# (see TreeStand::Utils::Printer)
|
210
|
+
# Backed by {TreeStand::Utils::Printer}.
|
211
|
+
#
|
212
|
+
# @see TreeStand::Utils::Printer
|
213
|
+
sig { params(pp: PP).void }
|
214
|
+
def pretty_print(pp)
|
215
|
+
Utils::Printer.new(ralign: 80).print(self, io: pp.output)
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def respond_to_missing?(method, *)
|
221
|
+
@fields.include?(method.to_s) || super
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|