json_p3 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,553 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "set"
5
+
6
+ require_relative "errors"
7
+ require_relative "filter"
8
+ require_relative "function"
9
+ require_relative "segment"
10
+ require_relative "selector"
11
+ require_relative "token"
12
+ require_relative "unescape"
13
+
14
+ module JSONP3
15
+ # Step through tokens
16
+ class Stream
17
+ def initialize(tokens)
18
+ @tokens = tokens
19
+ @index = 0
20
+ @eoi = tokens.last
21
+ end
22
+
23
+ def next
24
+ token = @tokens.fetch(@index)
25
+ @index += 1
26
+ token
27
+ rescue IndexError
28
+ @eor
29
+ end
30
+
31
+ def peek
32
+ @tokens.fetch(@index)
33
+ rescue IndexError
34
+ @eor
35
+ end
36
+
37
+ def expect(token_type)
38
+ return if peek.type == token_type
39
+
40
+ token = self.next
41
+ raise JSONPathSyntaxError.new("expected #{token_type}, found #{token.type}", token)
42
+ end
43
+
44
+ def expect_not(token_type, message)
45
+ return unless peek.type == token_type
46
+
47
+ token = self.next
48
+ raise JSONPathSyntaxError.new(message, token)
49
+ end
50
+
51
+ def to_s
52
+ "JSONP3::stream(head=#{peek.inspect})"
53
+ end
54
+ end
55
+
56
+ class Precedence
57
+ LOWEST = 1
58
+ LOGICAL_OR = 3
59
+ LOGICAL_AND = 4
60
+ RELATIONAL = 5
61
+ PREFIX = 7
62
+ end
63
+
64
+ # A JSONPath expression parser.
65
+ class Parser # rubocop:disable Metrics/ClassLength
66
+ def initialize(env)
67
+ @env = env
68
+ @name_selector = env.class::NAME_SELECTOR
69
+ @index_selector = env.class::INDEX_SELECTOR
70
+ end
71
+
72
+ # Parse an array of tokens into an abstract syntax tree.
73
+ # @param tokens [Array<Token>] tokens from the lexer.
74
+ # @return [Array<Segment>]
75
+ def parse(tokens)
76
+ stream = Stream.new(tokens)
77
+ stream.expect(Token::ROOT)
78
+ stream.next
79
+ parse_query(stream)
80
+ end
81
+
82
+ protected
83
+
84
+ def parse_query(stream) # rubocop:disable Metrics/MethodLength
85
+ segments = []
86
+
87
+ loop do
88
+ case stream.peek.type
89
+ when Token::DOUBLE_DOT
90
+ token = stream.next
91
+ selectors = parse_selectors(stream)
92
+ segments << RecursiveDescentSegment.new(@env, token, selectors)
93
+ when Token::LBRACKET, Token::NAME, Token::WILD
94
+ token = stream.peek
95
+ selectors = parse_selectors(stream)
96
+ segments << ChildSegment.new(@env, token, selectors)
97
+ else
98
+ break
99
+ end
100
+ end
101
+
102
+ segments
103
+ end
104
+
105
+ def parse_selectors(stream) # rubocop:disable Metrics/MethodLength
106
+ case stream.peek.type
107
+ when Token::NAME
108
+ token = stream.next
109
+ [@name_selector.new(@env, token, token.value)]
110
+ when Token::WILD
111
+ [WildcardSelector.new(@env, stream.next)]
112
+ when Token::LBRACKET
113
+ parse_bracketed_selection(stream)
114
+ else
115
+ []
116
+ end
117
+ end
118
+
119
+ def parse_bracketed_selection(stream) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
120
+ stream.expect Token::LBRACKET
121
+ segment_token = stream.next
122
+
123
+ selectors = []
124
+
125
+ loop do # rubocop:disable Metrics/BlockLength
126
+ case stream.peek.type
127
+ when Token::RBRACKET
128
+ break
129
+ when Token::INDEX
130
+ selectors << parse_index_or_slice(stream)
131
+ when Token::DOUBLE_QUOTE_STRING, Token::SINGLE_QUOTE_STRING
132
+ token = stream.next
133
+ selectors << @name_selector.new(@env, token, decode_string_literal(token))
134
+ when Token::COLON
135
+ selectors << parse_slice_selector(stream)
136
+ when Token::WILD
137
+ selectors << WildcardSelector.new(@env, stream.next)
138
+ when Token::FILTER
139
+ selectors << parse_filter_selector(stream)
140
+ when Token::EOI
141
+ raise JSONPathSyntaxError.new("unexpected end of query", stream.next)
142
+ else
143
+ raise JSONPathSyntaxError.new("unexpected token in bracketed selection", stream.next)
144
+ end
145
+
146
+ case stream.peek.type
147
+ when Token::EOI
148
+ raise JSONPathSyntaxError.new("unexpected end of selector list", stream.next)
149
+ when Token::RBRACKET
150
+ break
151
+ else
152
+ stream.expect Token::COMMA
153
+ stream.next
154
+ stream.expect_not(Token::RBRACKET, "unexpected trailing comma")
155
+ end
156
+ end
157
+
158
+ stream.expect(Token::RBRACKET)
159
+ stream.next
160
+
161
+ raise JSONPathSyntaxError.new("empty segment", segment_token) if selectors.empty?
162
+
163
+ selectors
164
+ end
165
+
166
+ def parse_index_or_slice(stream) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
167
+ token = stream.next
168
+ index = parse_i_json_int(token)
169
+
170
+ return @index_selector.new(@env, token, index) unless stream.peek.type == Token::COLON
171
+
172
+ stream.next # move past colon
173
+ stop = nil
174
+ step = nil
175
+
176
+ case stream.peek.type
177
+ when Token::INDEX
178
+ stop = parse_i_json_int(stream.next)
179
+ when Token::COLON
180
+ stream.next # move past colon
181
+ end
182
+
183
+ stream.next if stream.peek.type == Token::COLON
184
+
185
+ case stream.peek.type
186
+ when Token::INDEX
187
+ step = parse_i_json_int(stream.next)
188
+ when Token::RBRACKET
189
+ nil
190
+ else
191
+ error_token = stream.next
192
+ raise JSONPathSyntaxError.new("expected a slice, found '#{error_token.value}'", error_token)
193
+ end
194
+
195
+ SliceSelector.new(@env, token, index, stop, step)
196
+ end
197
+
198
+ def parse_slice_selector(stream) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
199
+ stream.expect(Token::COLON)
200
+ token = stream.next
201
+
202
+ start = nil
203
+ stop = nil
204
+ step = nil
205
+
206
+ case stream.peek.type
207
+ when Token::INDEX
208
+ stop = parse_i_json_int(stream.next)
209
+ when Token::COLON
210
+ stream.next # move past colon
211
+ end
212
+
213
+ stream.next if stream.peek.type == Token::COLON
214
+
215
+ case stream.peek.type
216
+ when Token::INDEX
217
+ step = parse_i_json_int(stream.next)
218
+ when Token::RBRACKET
219
+ nil
220
+ else
221
+ error_token = stream.next
222
+ raise JSONPathSyntaxError.new("expected a slice, found '#{token.value}'", error_token)
223
+ end
224
+
225
+ SliceSelector.new(@env, token, start, stop, step)
226
+ end
227
+
228
+ def parse_filter_selector(stream) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
229
+ token = stream.next
230
+ expression = parse_filter_expression(stream)
231
+
232
+ # Raise if expression must be compared.
233
+ if expression.is_a? FunctionExpression
234
+ func = @env.function_extensions[expression.name]
235
+ if func.class::RETURN_TYPE == ExpressionType::VALUE
236
+ raise JSONPathTypeError.new("result of #{expression.name}() must be compared", expression.token)
237
+ end
238
+ end
239
+
240
+ # Raise if expression is a literal.
241
+ if expression.is_a? FilterExpressionLiteral
242
+ raise JSONPathSyntaxError.new("filter expression literals must be compared", expression.token)
243
+ end
244
+
245
+ FilterSelector.new(@env, token, FilterExpression.new(token, expression))
246
+ end
247
+
248
+ def parse_filter_expression(stream, precedence = Precedence::LOWEST) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
249
+ left = case stream.peek.type
250
+ when Token::DOUBLE_QUOTE_STRING, Token::SINGLE_QUOTE_STRING
251
+ token = stream.next
252
+ StringLiteral.new(token, decode_string_literal(token))
253
+ when Token::FALSE
254
+ BooleanLiteral.new(stream.next, false)
255
+ when Token::TRUE
256
+ BooleanLiteral.new(stream.next, true)
257
+ when Token::FLOAT
258
+ parse_float_literal(stream)
259
+ when Token::FUNCTION
260
+ parse_function_expression(stream)
261
+ when Token::INT
262
+ parse_integer_literal(stream)
263
+ when Token::LPAREN
264
+ parse_grouped_expression(stream)
265
+ when Token::NOT
266
+ parse_prefix_expression(stream)
267
+ when Token::NULL
268
+ NullLiteral.new(stream.next, nil)
269
+ when Token::ROOT
270
+ parse_root_query(stream)
271
+ when Token::CURRENT
272
+ parse_relative_query(stream)
273
+ else
274
+ token = stream.next
275
+ raise JSONPathSyntaxError.new("unexpected '#{token.value}'", token)
276
+ end
277
+
278
+ loop do
279
+ peeked = stream.peek
280
+ if peeked.type == Token::EOI ||
281
+ peeked.type == Token::RBRACKET ||
282
+ PRECEDENCES.fetch(peeked.type, Precedence::LOWEST) < precedence
283
+ break
284
+ end
285
+
286
+ return left unless BINARY_OPERATORS.key?(peeked.type)
287
+
288
+ left = parse_infix_expression(stream, left)
289
+ end
290
+
291
+ left
292
+ end
293
+
294
+ def parse_integer_literal(stream)
295
+ token = stream.next
296
+ value = token.value
297
+ raise JSONPathSyntaxError.new("invalid integer literal", token) if value.start_with?("0") && value.length > 1
298
+
299
+ IntegerLiteral.new(token, Integer(Float(token.value)))
300
+ end
301
+
302
+ def parse_float_literal(stream)
303
+ token = stream.next
304
+ value = token.value
305
+ if value.start_with?("0") && value.split(".").first.length > 1
306
+ raise JSONPathSyntaxError.new("invalid float literal", token)
307
+ end
308
+
309
+ begin
310
+ FloatLiteral.new(token, Float(value))
311
+ rescue ArgumentError
312
+ raise JSONPathSyntaxError.new("invalid float literal", token)
313
+ end
314
+ end
315
+
316
+ def parse_function_expression(stream) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
317
+ token = stream.next
318
+ args = []
319
+
320
+ while stream.peek.type != Token::RPAREN
321
+ expr = case stream.peek.type
322
+ when Token::DOUBLE_QUOTE_STRING, Token::SINGLE_QUOTE_STRING
323
+ arg_token = stream.next
324
+ StringLiteral.new(arg_token, decode_string_literal(arg_token))
325
+ when Token::FALSE
326
+ BooleanLiteral.new(stream.next, false)
327
+ when Token::TRUE
328
+ BooleanLiteral.new(stream.next, true)
329
+ when Token::FLOAT
330
+ parse_float_literal(stream)
331
+ when Token::FUNCTION
332
+ parse_function_expression(stream)
333
+ when Token::INT
334
+ parse_integer_literal(stream)
335
+ when Token::NULL
336
+ NullLiteral.new(stream.next, nil)
337
+ when Token::ROOT
338
+ parse_root_query(stream)
339
+ when Token::CURRENT
340
+ parse_relative_query(stream)
341
+ else
342
+ arg_token = stream.next
343
+ raise JSONPathSyntaxError.new("unexpected '#{arg_token.value}'", arg_token)
344
+ end
345
+
346
+ expr = parse_infix_expression(stream, expr) while BINARY_OPERATORS.key? stream.peek.type
347
+
348
+ args << expr
349
+
350
+ if stream.peek.type != Token::RPAREN
351
+ stream.expect(Token::COMMA)
352
+ stream.next
353
+ end
354
+ end
355
+
356
+ stream.expect(Token::RPAREN)
357
+ stream.next
358
+
359
+ validate_function_extension_signature(token, args)
360
+ FunctionExpression.new(token, token.value, args)
361
+ end
362
+
363
+ def parse_grouped_expression(stream)
364
+ stream.next # discard "("
365
+ expr = parse_filter_expression(stream)
366
+
367
+ while stream.peek.type != Token::RPAREN
368
+ raise JSONPathSyntaxError.new("unbalanced parentheses", stream.peek) if stream.peek.type == Token::EOI
369
+
370
+ expr = parse_infix_expression(stream, expr)
371
+ end
372
+
373
+ stream.expect(Token::RPAREN)
374
+ stream.next
375
+ expr
376
+ end
377
+
378
+ def parse_prefix_expression(stream)
379
+ token = stream.next
380
+ LogicalNotExpression.new(token, parse_filter_expression(stream, Precedence::PREFIX))
381
+ end
382
+
383
+ def parse_root_query(stream)
384
+ token = stream.next
385
+ RootQueryExpression.new(token, JSONPath.new(@env, parse_query(stream)))
386
+ end
387
+
388
+ def parse_relative_query(stream)
389
+ token = stream.next
390
+ RelativeQueryExpression.new(token, JSONPath.new(@env, parse_query(stream)))
391
+ end
392
+
393
+ def parse_infix_expression(stream, left) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
394
+ token = stream.next
395
+ precedence = PRECEDENCES.fetch(token.type, Precedence::LOWEST)
396
+ right = parse_filter_expression(stream, precedence)
397
+
398
+ if COMPARISON_OPERATORS.member? token.value
399
+ raise_for_non_comparable_function(left)
400
+ raise_for_non_comparable_function(right)
401
+ case token.type
402
+ when Token::EQ
403
+ EqExpression.new(token, left, right)
404
+ when Token::GE
405
+ GeExpression.new(token, left, right)
406
+ when Token::GT
407
+ GtExpression.new(token, left, right)
408
+ when Token::LE
409
+ LeExpression.new(token, left, right)
410
+ when Token::LT
411
+ LtExpression.new(token, left, right)
412
+ when Token::NE
413
+ NeExpression.new(token, left, right)
414
+ else
415
+ raise JSONPathSyntaxError.new("unexpected token", token)
416
+ end
417
+ else
418
+ raise_for_uncompared_literal(left)
419
+ raise_for_uncompared_literal(right)
420
+ case token.type
421
+ when Token::AND
422
+ LogicalAndExpression.new(token, left, right)
423
+ when Token::OR
424
+ LogicalOrExpression.new(token, left, right)
425
+ else
426
+ raise JSONPathSyntaxError.new("unexpected token", token)
427
+ end
428
+ end
429
+ end
430
+
431
+ def parse_i_json_int(token) # rubocop:disable Metrics/MethodLength
432
+ value = token.value
433
+
434
+ if value.length > 1 && value.start_with?("0", "-0")
435
+ raise JSONPathSyntaxError.new("invalid index '#{value}'", token)
436
+ end
437
+
438
+ begin
439
+ int = Integer(value)
440
+ rescue ArgumentError
441
+ raise JSONPathSyntaxError.new("invalid I-JSON integer", token)
442
+ end
443
+
444
+ if int < @env.class::MIN_INT_INDEX || int > @env.class::MAX_INT_INDEX
445
+ raise JSONPathSyntaxError.new("index out of range",
446
+ token)
447
+ end
448
+
449
+ int
450
+ end
451
+
452
+ def decode_string_literal(token)
453
+ if token.type == Token::SINGLE_QUOTE_STRING
454
+ JSONP3.unescape_string(token.value, "'", token)
455
+ else
456
+ JSONP3.unescape_string(token.value, '"', token)
457
+ end
458
+ end
459
+
460
+ def raise_for_non_comparable_function(expression)
461
+ if expression.is_a?(QueryExpression) && !expression.query.singular?
462
+ raise JSONPathSyntaxError.new("non-singular query is not comparable", expression.token)
463
+ end
464
+
465
+ return unless expression.is_a?(FunctionExpression)
466
+
467
+ func = @env.function_extensions[expression.name]
468
+ return unless func.class::RETURN_TYPE != ExpressionType::VALUE
469
+
470
+ raise JSONPathTypeError.new("result of #{expression.name}() is not comparable", expression.token)
471
+ end
472
+
473
+ def raise_for_uncompared_literal(expression)
474
+ return unless expression.is_a? FilterExpressionLiteral
475
+
476
+ raise JSONPathSyntaxError.new("expression literals must be compared",
477
+ expression.token)
478
+ end
479
+
480
+ def validate_function_extension_signature(token, args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
481
+ func = @env.function_extensions.fetch(token.value)
482
+ count = func.class::ARG_TYPES.length
483
+
484
+ unless args.length == count
485
+ raise JSONPathTypeError.new(
486
+ "#{token.value}() takes #{count} argument#{count == 1 ? "" : "s"} (#{args.length} given)",
487
+ token
488
+ )
489
+ end
490
+
491
+ func.class::ARG_TYPES.each_with_index do |t, i|
492
+ arg = args[i]
493
+ case t
494
+ when ExpressionType::VALUE
495
+ unless arg.is_a?(FilterExpressionLiteral) ||
496
+ (arg.is_a?(QueryExpression) && arg.query.singular?) ||
497
+ (function_return_type(arg) == ExpressionType::VALUE)
498
+ raise JSONPathTypeError.new("#{token.value}() argument #{i} must be of ValueType", arg.token)
499
+ end
500
+ when ExpressionType::LOGICAL
501
+ unless arg.is_a?(QueryExpression) || arg.is_a?(InfixExpression)
502
+ raise JSONPathTypeError.new("#{token.value}() argument #{i} must be of LogicalType", arg.token)
503
+ end
504
+ when ExpressionType::NODES
505
+ unless arg.is_a?(QueryExpression) || function_return_type(arg) == ExpressionType::NODES
506
+ raise JSONPathTypeError.new("#{token.value}() argument #{i} must be of NodesType", arg.token)
507
+ end
508
+ end
509
+ end
510
+ rescue KeyError
511
+ raise JSONPathNameError.new("function '#{token.value}' is not defined", token)
512
+ end
513
+
514
+ def function_return_type(expression)
515
+ return nil unless expression.is_a? FunctionExpression
516
+
517
+ @env.function_extensions[expression.name].class::RETURN_TYPE
518
+ end
519
+
520
+ PRECEDENCES = {
521
+ Token::AND => Precedence::LOGICAL_AND,
522
+ Token::OR => Precedence::LOGICAL_OR,
523
+ Token::NOT => Precedence::PREFIX,
524
+ Token::EQ => Precedence::RELATIONAL,
525
+ Token::GE => Precedence::RELATIONAL,
526
+ Token::GT => Precedence::RELATIONAL,
527
+ Token::LE => Precedence::RELATIONAL,
528
+ Token::LT => Precedence::RELATIONAL,
529
+ Token::NE => Precedence::RELATIONAL,
530
+ Token::RPAREN => Precedence::LOWEST
531
+ }.freeze
532
+
533
+ BINARY_OPERATORS = {
534
+ Token::AND => "&&",
535
+ Token::OR => "||",
536
+ Token::EQ => "==",
537
+ Token::GE => ">=",
538
+ Token::GT => ">",
539
+ Token::LE => "<=",
540
+ Token::LT => "<",
541
+ Token::NE => "!="
542
+ }.freeze
543
+
544
+ COMPARISON_OPERATORS = Set[
545
+ "==",
546
+ ">=",
547
+ ">",
548
+ "<=",
549
+ "<",
550
+ "!=",
551
+ ]
552
+ end
553
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "node"
4
+
5
+ module JSONP3
6
+ # A compiled JSONPath expression ready to be applied to JSON-like values.
7
+ class JSONPath
8
+ def initialize(env, segments)
9
+ @env = env
10
+ @segments = segments
11
+ end
12
+
13
+ def to_s
14
+ "$#{@segments.map(&:to_s).join}"
15
+ end
16
+
17
+ # Apply this JSONPath expression to JSON-like value _root_.
18
+ # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
19
+ # @return [Array<JSONPathNode>] the sequence of nodes found while applying this query to _root_.
20
+ def find(root)
21
+ nodes = [JSONPathNode.new(root, [], root)]
22
+ @segments.each { |segment| nodes = segment.resolve(nodes) }
23
+ JSONPathNodeList.new(nodes)
24
+ end
25
+
26
+ alias apply find
27
+
28
+ # Return _true_ if this JSONPath expression is a singular query.
29
+ def singular?
30
+ @segments.each do |segment|
31
+ return false if segment.instance_of? RecursiveDescentSegment
32
+ return false unless segment.selectors.length == 1 && segment.selectors[0].singular?
33
+ end
34
+ true
35
+ end
36
+
37
+ # Return _true_ if this JSONPath expression has no segments.
38
+ def empty?
39
+ @segments.empty?
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ # Base class for all JSONPath segments.
5
+ class Segment
6
+ # @dynamic token, selectors
7
+ attr_reader :token, :selectors
8
+
9
+ def initialize(env, token, selectors)
10
+ @env = env
11
+ @token = token
12
+ @selectors = selectors
13
+ end
14
+
15
+ # Select the children of each node in _nodes_.
16
+ def resolve(_nodes)
17
+ raise "segments must implement resolve(nodes)"
18
+ end
19
+ end
20
+
21
+ # The child selection segment.
22
+ class ChildSegment < Segment
23
+ def resolve(nodes)
24
+ rv = []
25
+ nodes.each do |node|
26
+ @selectors.each do |selector|
27
+ rv.concat selector.resolve(node)
28
+ end
29
+ end
30
+ rv
31
+ end
32
+
33
+ def to_s
34
+ "[#{@selectors.map(&:to_s).join(", ")}]"
35
+ end
36
+
37
+ def ==(other)
38
+ self.class == other.class &&
39
+ @selectors == other.selectors &&
40
+ @token == other.token
41
+ end
42
+
43
+ alias eql? ==
44
+
45
+ def hash
46
+ [@selectors, @token].hash
47
+ end
48
+ end
49
+
50
+ # The recursive descent segment
51
+ class RecursiveDescentSegment < Segment
52
+ def resolve(nodes)
53
+ rv = []
54
+ nodes.each do |node|
55
+ visit(node).each do |descendant|
56
+ @selectors.each do |selector|
57
+ rv.concat selector.resolve(descendant)
58
+ end
59
+ end
60
+ end
61
+ rv
62
+ end
63
+
64
+ def to_s
65
+ "..[#{@selectors.map(&:to_s).join(", ")}]"
66
+ end
67
+
68
+ def ==(other)
69
+ self.class == other.class &&
70
+ @selectors == other.selectors &&
71
+ @token == other.token
72
+ end
73
+
74
+ alias eql? ==
75
+
76
+ def hash
77
+ ["..", @selectors, @token].hash
78
+ end
79
+
80
+ protected
81
+
82
+ def visit(node, depth = 1) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
83
+ raise JSONPathRecursionError.new("recursion limit exceeded", @token) if depth > @env.class::MAX_RECURSION_DEPTH
84
+
85
+ rv = [node]
86
+
87
+ if node.value.is_a? Array
88
+ node.value.each_with_index do |value, i|
89
+ child = JSONPathNode.new(value, [node.location, i], node.root)
90
+ rv.concat visit(child, depth + 1)
91
+ end
92
+ elsif node.value.is_a? Hash
93
+ node.value.each do |key, value|
94
+ child = JSONPathNode.new(value, [node.location, key], node.root)
95
+ rv.concat visit(child, depth + 1)
96
+ end
97
+ end
98
+
99
+ rv
100
+ end
101
+ end
102
+ end