ruby_tree_sitter 1.1.0-arm64-darwin-22
Sign up to get free protection for your applications and to get access to all the features.
- 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
|