ruby_tree_sitter 0.20.8.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +2 -1
- data/README.md +32 -18
- data/ext/tree_sitter/extconf.rb +1 -43
- data/ext/tree_sitter/input.c +1 -0
- data/ext/tree_sitter/language.c +131 -46
- data/ext/tree_sitter/logger.c +28 -12
- data/ext/tree_sitter/node.c +438 -130
- data/ext/tree_sitter/parser.c +232 -37
- data/ext/tree_sitter/query.c +197 -72
- data/ext/tree_sitter/query_cursor.c +140 -28
- data/ext/tree_sitter/repo.rb +121 -0
- data/ext/tree_sitter/tree.c +118 -34
- data/ext/tree_sitter/tree_cursor.c +205 -33
- data/ext/tree_sitter/tree_sitter.c +12 -0
- data/lib/tree_sitter/node.rb +43 -9
- data/lib/tree_sitter/version.rb +4 -2
- data/lib/tree_sitter.rb +1 -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 +15 -12
- metadata +38 -108
- data/test/README.md +0 -15
- data/test/test_helper.rb +0 -9
- data/test/tree_sitter/js_test.rb +0 -48
- data/test/tree_sitter/language_test.rb +0 -73
- data/test/tree_sitter/logger_test.rb +0 -70
- data/test/tree_sitter/node_test.rb +0 -355
- data/test/tree_sitter/parser_test.rb +0 -140
- data/test/tree_sitter/query_test.rb +0 -153
- data/test/tree_sitter/tree_cursor_test.rb +0 -83
- data/test/tree_sitter/tree_test.rb +0 -51
@@ -17,58 +17,220 @@ DATA_UNWRAP(tree_cursor)
|
|
17
17
|
DATA_NEW(cTreeCursor, TSTreeCursor, tree_cursor)
|
18
18
|
DATA_FROM_VALUE(TSTreeCursor, tree_cursor)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
return
|
20
|
+
/**
|
21
|
+
* Safely copy a tree cursor.
|
22
|
+
*
|
23
|
+
* @return [TreeCursor]
|
24
|
+
*/
|
25
|
+
static VALUE tree_cursor_copy(VALUE self) {
|
26
|
+
VALUE res = tree_cursor_allocate(cTreeCursor);
|
27
|
+
tree_cursor_t *ptr = unwrap(res);
|
28
|
+
ptr->data = ts_tree_cursor_copy(&SELF);
|
29
|
+
return res;
|
30
30
|
}
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
/**
|
33
|
+
* Get the depth of the cursor's current node relative to the original
|
34
|
+
* node that the cursor was constructed with.
|
35
|
+
*
|
36
|
+
* @return [Integer]
|
37
|
+
*/
|
38
|
+
static VALUE tree_cursor_current_depth(VALUE self) {
|
39
|
+
return UINT2NUM(ts_tree_cursor_current_depth(&SELF));
|
35
40
|
}
|
36
41
|
|
37
|
-
|
38
|
-
|
42
|
+
/**
|
43
|
+
* Get the index of the cursor's current node out of all of the
|
44
|
+
* descendants of the original node that the cursor was constructed with.
|
45
|
+
*
|
46
|
+
* @return [Integer]
|
47
|
+
*/
|
48
|
+
static VALUE tree_cursor_current_descendant_index(VALUE self) {
|
49
|
+
return UINT2NUM(ts_tree_cursor_current_descendant_index(&SELF));
|
39
50
|
}
|
40
51
|
|
52
|
+
/**
|
53
|
+
* Get the field id of the tree cursor's current node.
|
54
|
+
*
|
55
|
+
* This returns zero if the current node doesn't have a field.
|
56
|
+
*
|
57
|
+
* @see Node#child_by_field_id
|
58
|
+
* @see Node#field_id_for_name
|
59
|
+
*
|
60
|
+
* @return [Integer]
|
61
|
+
*/
|
41
62
|
static VALUE tree_cursor_current_field_id(VALUE self) {
|
42
63
|
return UINT2NUM(ts_tree_cursor_current_field_id(&SELF));
|
43
64
|
}
|
44
65
|
|
45
|
-
|
46
|
-
|
66
|
+
/**
|
67
|
+
* Get the field name of the tree cursor's current node.
|
68
|
+
*
|
69
|
+
* This returns +nil+ if the current node doesn't have a field.
|
70
|
+
*
|
71
|
+
* @see Node#child_by_field_name
|
72
|
+
*
|
73
|
+
* @return [String]
|
74
|
+
*/
|
75
|
+
static VALUE tree_cursor_current_field_name(VALUE self) {
|
76
|
+
return safe_str(ts_tree_cursor_current_field_name(&SELF));
|
47
77
|
}
|
48
78
|
|
49
|
-
|
50
|
-
|
79
|
+
/**
|
80
|
+
* Get the tree cursor's current node.
|
81
|
+
*
|
82
|
+
* @return [Node]
|
83
|
+
*/
|
84
|
+
static VALUE tree_cursor_current_node(VALUE self) {
|
85
|
+
TSNode node = ts_tree_cursor_current_node(&SELF);
|
86
|
+
return new_node(&node);
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Move the cursor to the node that is the nth descendant of
|
91
|
+
* the original node that the cursor was constructed with, where
|
92
|
+
* zero represents the original node itself.
|
93
|
+
*
|
94
|
+
* @return [nil]
|
95
|
+
*/
|
96
|
+
static VALUE tree_cursor_goto_descendant(VALUE self, VALUE descendant_idx) {
|
97
|
+
uint32_t idx = NUM2UINT(descendant_idx);
|
98
|
+
ts_tree_cursor_goto_descendant(&SELF, idx);
|
99
|
+
return Qnil;
|
51
100
|
}
|
52
101
|
|
102
|
+
/**
|
103
|
+
* Move the cursor to the first child of its current node.
|
104
|
+
*
|
105
|
+
* This returns +true+ if the cursor successfully moved, and returns +false+
|
106
|
+
* if there were no children.
|
107
|
+
*
|
108
|
+
* @return [Boolean]
|
109
|
+
*/
|
53
110
|
static VALUE tree_cursor_goto_first_child(VALUE self) {
|
54
111
|
return ts_tree_cursor_goto_first_child(&SELF) ? Qtrue : Qfalse;
|
55
112
|
}
|
56
113
|
|
114
|
+
/**
|
115
|
+
* Move the cursor to the first child of its current node that extends beyond
|
116
|
+
* the given byte offset.
|
117
|
+
*
|
118
|
+
* This returns the index of the child node if one was found, and returns -1
|
119
|
+
* if no such child was found.
|
120
|
+
*
|
121
|
+
* @return [Integer]
|
122
|
+
*/
|
57
123
|
static VALUE tree_cursor_goto_first_child_for_byte(VALUE self, VALUE byte) {
|
58
124
|
return LL2NUM(
|
59
125
|
ts_tree_cursor_goto_first_child_for_byte(&SELF, NUM2UINT(byte)));
|
60
126
|
}
|
61
127
|
|
128
|
+
/**
|
129
|
+
* Move the cursor to the first child of its current node that extends beyond
|
130
|
+
* the given or point.
|
131
|
+
*
|
132
|
+
* This returns the index of the child node if one was found, and returns -1
|
133
|
+
* if no such child was found.
|
134
|
+
*
|
135
|
+
* @return [Integer]
|
136
|
+
*/
|
62
137
|
static VALUE tree_cursor_goto_first_child_for_point(VALUE self, VALUE point) {
|
63
138
|
return LL2NUM(
|
64
139
|
ts_tree_cursor_goto_first_child_for_point(&SELF, value_to_point(point)));
|
65
140
|
}
|
66
141
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
142
|
+
/**
|
143
|
+
* Move the cursor to the last child of its current node.
|
144
|
+
*
|
145
|
+
* This returns +true+ if the cursor successfully moved, and returns +false+ if
|
146
|
+
* there were no children.
|
147
|
+
*
|
148
|
+
* Note that this function may be slower than {#goto_first_child}
|
149
|
+
* because it needs to iterate through all the children to compute the child's
|
150
|
+
* position.
|
151
|
+
*/
|
152
|
+
static VALUE tree_cursor_goto_last_child(VALUE self) {
|
153
|
+
return ts_tree_cursor_goto_last_child(&SELF) ? Qtrue : Qfalse;
|
154
|
+
}
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Move the cursor to the next sibling of its current node.
|
158
|
+
*
|
159
|
+
* This returns +true+ if the cursor successfully moved, and returns +false+
|
160
|
+
* if there was no next sibling node.
|
161
|
+
*
|
162
|
+
* @return Boolean
|
163
|
+
*/
|
164
|
+
static VALUE tree_cursor_goto_next_sibling(VALUE self) {
|
165
|
+
return ts_tree_cursor_goto_next_sibling(&SELF) ? Qtrue : Qfalse;
|
166
|
+
}
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Move the cursor to the parent of its current node.
|
170
|
+
*
|
171
|
+
* This returns +true+ if the cursor successfully moved, and returns +false+
|
172
|
+
* if there was no parent node (the cursor was already on the root node).
|
173
|
+
*
|
174
|
+
* @return [Boolean]
|
175
|
+
*/
|
176
|
+
static VALUE tree_cursor_goto_parent(VALUE self) {
|
177
|
+
return ts_tree_cursor_goto_parent(&SELF) ? Qtrue : Qfalse;
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Move the cursor to the previous sibling of its current node.
|
182
|
+
*
|
183
|
+
* This returns +true+ if the cursor successfully moved, and returns +false+ if
|
184
|
+
* there was no previous sibling node.
|
185
|
+
*
|
186
|
+
* Note, that this function may be slower than
|
187
|
+
* {#goto_next_sibling} due to how node positions are stored. In
|
188
|
+
* the worst case, this will need to iterate through all the children upto the
|
189
|
+
* previous sibling node to recalculate its position.
|
190
|
+
*
|
191
|
+
* @return [Boolean]
|
192
|
+
*/
|
193
|
+
static VALUE tree_cursor_goto_previous_sibling(VALUE self) {
|
194
|
+
return ts_tree_cursor_goto_previous_sibling(&SELF) ? Qtrue : Qfalse;
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Create a new tree cursor starting from the given node.
|
199
|
+
*
|
200
|
+
* A tree cursor allows you to walk a syntax tree more efficiently than is
|
201
|
+
* possible using the {Node} functions. It is a mutable object that is always
|
202
|
+
* on a certain syntax node, and can be moved imperatively to different nodes.
|
203
|
+
*
|
204
|
+
* @return [TreeCursor]
|
205
|
+
*/
|
206
|
+
static VALUE tree_cursor_initialize(VALUE self, VALUE node) {
|
207
|
+
TSNode n = value_to_node(node);
|
208
|
+
tree_cursor_t *ptr = unwrap(self);
|
209
|
+
ptr->data = ts_tree_cursor_new(n);
|
210
|
+
return self;
|
211
|
+
}
|
212
|
+
|
213
|
+
/**
|
214
|
+
* Re-initialize a tree cursor to start at a different node.
|
215
|
+
*
|
216
|
+
* @return [nil]
|
217
|
+
*/
|
218
|
+
static VALUE tree_cursor_reset(VALUE self, VALUE node) {
|
219
|
+
ts_tree_cursor_reset(&SELF, value_to_node(node));
|
220
|
+
return Qnil;
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Re-initialize a tree cursor to the same position as another cursor.
|
225
|
+
*
|
226
|
+
* Unlike {#reset}, this will not lose parent information and allows reusing
|
227
|
+
* already created cursors.
|
228
|
+
*
|
229
|
+
* @return [nil]
|
230
|
+
*/
|
231
|
+
VALUE tree_cursor_reset_to(VALUE self, VALUE src) {
|
232
|
+
ts_tree_cursor_reset_to(&SELF, &unwrap(src)->data);
|
233
|
+
return Qnil;
|
72
234
|
}
|
73
235
|
|
74
236
|
void init_tree_cursor(void) {
|
@@ -77,21 +239,31 @@ void init_tree_cursor(void) {
|
|
77
239
|
rb_define_alloc_func(cTreeCursor, tree_cursor_allocate);
|
78
240
|
|
79
241
|
/* Class methods */
|
80
|
-
rb_define_method(cTreeCursor, "
|
81
|
-
rb_define_method(cTreeCursor, "
|
82
|
-
rb_define_method(cTreeCursor, "
|
83
|
-
|
84
|
-
tree_cursor_current_field_name, 0);
|
242
|
+
rb_define_method(cTreeCursor, "copy", tree_cursor_copy, 0);
|
243
|
+
rb_define_method(cTreeCursor, "current_depth", tree_cursor_current_depth, 0);
|
244
|
+
rb_define_method(cTreeCursor, "current_descendant_index",
|
245
|
+
tree_cursor_current_descendant_index, 0);
|
85
246
|
rb_define_method(cTreeCursor, "current_field_id",
|
86
247
|
tree_cursor_current_field_id, 0);
|
87
|
-
rb_define_method(cTreeCursor, "
|
88
|
-
|
89
|
-
|
248
|
+
rb_define_method(cTreeCursor, "current_field_name",
|
249
|
+
tree_cursor_current_field_name, 0);
|
250
|
+
rb_define_method(cTreeCursor, "current_node", tree_cursor_current_node, 0);
|
251
|
+
rb_define_method(cTreeCursor, "goto_descendant", tree_cursor_goto_descendant,
|
252
|
+
1);
|
90
253
|
rb_define_method(cTreeCursor, "goto_first_child",
|
91
254
|
tree_cursor_goto_first_child, 0);
|
92
255
|
rb_define_method(cTreeCursor, "goto_first_child_for_byte",
|
93
256
|
tree_cursor_goto_first_child_for_byte, 1);
|
94
257
|
rb_define_method(cTreeCursor, "goto_first_child_for_point",
|
95
258
|
tree_cursor_goto_first_child_for_point, 1);
|
96
|
-
rb_define_method(cTreeCursor, "
|
259
|
+
rb_define_method(cTreeCursor, "goto_last_child", tree_cursor_goto_last_child,
|
260
|
+
0);
|
261
|
+
rb_define_method(cTreeCursor, "goto_next_sibling",
|
262
|
+
tree_cursor_goto_next_sibling, 0);
|
263
|
+
rb_define_method(cTreeCursor, "goto_parent", tree_cursor_goto_parent, 0);
|
264
|
+
rb_define_method(cTreeCursor, "goto_previous_sibling",
|
265
|
+
tree_cursor_goto_previous_sibling, 0);
|
266
|
+
rb_define_method(cTreeCursor, "initialize", tree_cursor_initialize, 1);
|
267
|
+
rb_define_method(cTreeCursor, "reset", tree_cursor_reset, 1);
|
268
|
+
rb_define_method(cTreeCursor, "reset_to", tree_cursor_reset_to, 1);
|
97
269
|
}
|
@@ -5,8 +5,20 @@ VALUE mTreeSitter;
|
|
5
5
|
void Init_tree_sitter() {
|
6
6
|
mTreeSitter = rb_define_module("TreeSitter");
|
7
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
|
+
*/
|
8
15
|
rb_define_const(mTreeSitter, "LANGUAGE_VERSION",
|
9
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
|
+
*/
|
10
22
|
rb_define_const(mTreeSitter, "MIN_COMPATIBLE_LANGUAGE_VERSION",
|
11
23
|
TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION);
|
12
24
|
|
data/lib/tree_sitter/node.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TreeSitter
|
4
|
+
# Node is a wrapper around a tree-sitter node.
|
4
5
|
class Node
|
6
|
+
# @return [Array<Symbol>] the node's named fields
|
5
7
|
def fields
|
6
8
|
return @fields if @fields
|
7
9
|
|
@@ -18,6 +20,18 @@ module TreeSitter
|
|
18
20
|
fields.include?(field)
|
19
21
|
end
|
20
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
|
+
#
|
21
35
|
# Access node's named children.
|
22
36
|
#
|
23
37
|
# It's similar to {#fetch}, but differes in input type, return values, and
|
@@ -49,13 +63,11 @@ module TreeSitter
|
|
49
63
|
when 0 then raise "#{self.class.name}##{__method__} requires a key."
|
50
64
|
when 1
|
51
65
|
case k = keys.first
|
52
|
-
when Numeric
|
66
|
+
when Numeric then named_child(k)
|
53
67
|
when String, Symbol
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
raise "Cannot find field #{k}"
|
58
|
-
end
|
68
|
+
raise "Cannot find field #{k}" unless fields.include?(k.to_sym)
|
69
|
+
|
70
|
+
child_by_field_name(k.to_s)
|
59
71
|
else raise <<~ERR
|
60
72
|
#{self.class.name}##{__method__} accepts Integer and returns named child at given index,
|
61
73
|
or a (String | Symbol) and returns the child by given field name.
|
@@ -66,6 +78,8 @@ module TreeSitter
|
|
66
78
|
end
|
67
79
|
end
|
68
80
|
|
81
|
+
# @!visibility private
|
82
|
+
#
|
69
83
|
# Allows access to child_by_field_name without using [].
|
70
84
|
def method_missing(method_name, *_args, &_block)
|
71
85
|
if fields.include?(method_name)
|
@@ -75,6 +89,8 @@ module TreeSitter
|
|
75
89
|
end
|
76
90
|
end
|
77
91
|
|
92
|
+
# @!visibility private
|
93
|
+
#
|
78
94
|
def respond_to_missing?(*args)
|
79
95
|
args.length == 1 && fields.include?(args[0])
|
80
96
|
end
|
@@ -116,6 +132,7 @@ module TreeSitter
|
|
116
132
|
end
|
117
133
|
end
|
118
134
|
|
135
|
+
# @return [Array<TreeSitter::Node>] all the node's children
|
119
136
|
def to_a
|
120
137
|
each.to_a
|
121
138
|
end
|
@@ -142,7 +159,12 @@ module TreeSitter
|
|
142
159
|
# uses named_child | field_name_for_child
|
143
160
|
# child_by_field_name | via each_node
|
144
161
|
# ------------------------------+----------------------
|
145
|
-
|
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)
|
146
168
|
dict = {}
|
147
169
|
keys.each.with_index do |k, i|
|
148
170
|
dict[k.to_s] = i
|
@@ -151,13 +173,25 @@ module TreeSitter
|
|
151
173
|
res = {}
|
152
174
|
each_field do |f, c|
|
153
175
|
if dict.key?(f)
|
154
|
-
res[
|
176
|
+
res[f] = c
|
155
177
|
dict.delete(f)
|
156
178
|
end
|
157
179
|
break if dict.empty?
|
158
180
|
end
|
159
181
|
|
160
|
-
res.
|
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)
|
161
195
|
end
|
162
196
|
end
|
163
197
|
end
|
data/lib/tree_sitter/version.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TreeSitter
|
4
|
-
|
5
|
-
|
4
|
+
# The version of the tree-sitter library.
|
5
|
+
TREESITTER_VERSION = '0.20.9'
|
6
|
+
# The current version of the gem.
|
7
|
+
VERSION = '1.0.0'
|
6
8
|
end
|
data/lib/tree_sitter.rb
CHANGED
@@ -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
|