janeway-jsonpath 0.2.0 → 0.3.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +109 -38
  3. data/lib/janeway/ast/array_slice_selector.rb +9 -7
  4. data/lib/janeway/ast/binary_operator.rb +25 -23
  5. data/lib/janeway/ast/current_node.rb +8 -5
  6. data/lib/janeway/ast/descendant_segment.rb +1 -1
  7. data/lib/janeway/ast/expression.rb +11 -1
  8. data/lib/janeway/ast/filter_selector.rb +1 -1
  9. data/lib/janeway/ast/helpers.rb +1 -0
  10. data/lib/janeway/ast/index_selector.rb +2 -3
  11. data/lib/janeway/ast/name_selector.rb +2 -2
  12. data/lib/janeway/ast/null.rb +1 -0
  13. data/lib/janeway/ast/query.rb +42 -1
  14. data/lib/janeway/ast/root_node.rb +8 -5
  15. data/lib/janeway/ast/string_type.rb +2 -0
  16. data/lib/janeway/ast/unary_operator.rb +2 -2
  17. data/lib/janeway/ast/wildcard_selector.rb +1 -0
  18. data/lib/janeway/error.rb +1 -1
  19. data/lib/janeway/functions/count.rb +2 -4
  20. data/lib/janeway/functions/length.rb +2 -3
  21. data/lib/janeway/functions/match.rb +3 -9
  22. data/lib/janeway/functions/search.rb +4 -9
  23. data/lib/janeway/functions/value.rb +2 -3
  24. data/lib/janeway/interpreter.rb +29 -587
  25. data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +44 -0
  26. data/lib/janeway/interpreters/base.rb +43 -0
  27. data/lib/janeway/interpreters/binary_operator_interpreter.rb +163 -0
  28. data/lib/janeway/interpreters/child_segment_interpreter.rb +44 -0
  29. data/lib/janeway/interpreters/current_node_interpreter.rb +33 -0
  30. data/lib/janeway/interpreters/descendant_segment_interpreter.rb +40 -0
  31. data/lib/janeway/interpreters/filter_selector_interpreter.rb +63 -0
  32. data/lib/janeway/interpreters/function_interpreter.rb +99 -0
  33. data/lib/janeway/interpreters/index_selector_interpreter.rb +39 -0
  34. data/lib/janeway/interpreters/name_selector_interpreter.rb +30 -0
  35. data/lib/janeway/interpreters/root_node_interpreter.rb +23 -0
  36. data/lib/janeway/interpreters/tree_constructor.rb +39 -0
  37. data/lib/janeway/interpreters/unary_operator_interpreter.rb +32 -0
  38. data/lib/janeway/interpreters/wildcard_selector_interpreter.rb +38 -0
  39. data/lib/janeway/interpreters/yielder.rb +34 -0
  40. data/lib/janeway/lexer.rb +12 -16
  41. data/lib/janeway/parser.rb +51 -90
  42. data/lib/janeway/token.rb +1 -0
  43. data/lib/janeway/version.rb +1 -1
  44. data/lib/janeway.rb +25 -5
  45. metadata +17 -3
  46. data/lib/janeway/ast/identifier.rb +0 -35
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Janeway
6
+ module Interpreters
7
+ # Interprets the "not" operator within a filter selector logical expression.
8
+ # The only other unary operator is "minus", which is consumed during parsing and is not part of the AST.
9
+ class UnaryOperatorInterpreter < Base
10
+ alias operator node
11
+
12
+ def initialize(operator)
13
+ super
14
+ raise "Unknown unary operator: #{name}" unless operator.name == :not
15
+
16
+ # Expression which must be evaluated, not operator will be applied to result
17
+ @operand = TreeConstructor.ast_node_to_interpreter(operator.operand)
18
+ end
19
+
20
+ # @return [Boolean]
21
+ def interpret(input, root)
22
+ result = @operand.interpret(input, root)
23
+ case result
24
+ when Array then result.empty?
25
+ when TrueClass, FalseClass then !result
26
+ else
27
+ raise "don't know how to apply not operator to #{result.inspect}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Janeway
6
+ module Interpreters
7
+ # Interprets a wildcard selector, returns results or forwards them to next selector
8
+ class WildcardSelectorInterpreter < Base
9
+ alias selector node
10
+
11
+ # Return values from the input.
12
+ # For array, return the array.
13
+ # For Hash, return hash values.
14
+ # For anything else, return empty list.
15
+ #
16
+ # @param input [Array, Hash] the results of processing so far
17
+ # @param root [Array, Hash] the entire input
18
+ def interpret(input, root)
19
+ values =
20
+ case input
21
+ when Array then input
22
+ when Hash then input.values
23
+ else []
24
+ end
25
+
26
+ return values if values.empty? # early exit, no need for further processing on empty list
27
+ return values unless @next
28
+
29
+ # Apply child selector to each node in the output node list
30
+ results = []
31
+ values.each do |value|
32
+ results.concat @next.interpret(value, root)
33
+ end
34
+ results
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Janeway
6
+ module Interpreters
7
+ # Yields each input value.
8
+ # It is inserted at the end of the "real" selectors in the AST, to receive and yield the output.
9
+ # This is a supporting class for the supports the Janeway.each method.
10
+ #
11
+ # This will get pushed onto the end of the query AST.
12
+ # Currently it must act like both an AST node and an interpreter.
13
+ # This will be simpler if TODO some day may the interpreter subclass methods can be merged into the AST classes
14
+ class Yielder < Base
15
+ # The implicit constructor forwards a closure to the base class constructor.
16
+ # Base class constructor stores it in @node.
17
+ def initialize(&block)
18
+ super(Struct.new(:next).new)
19
+ @block = block
20
+ end
21
+
22
+ # Yield each input value
23
+ #
24
+ # @param input [Array, Hash] the results of processing so far
25
+ # @param _root [Array, Hash] the entire input
26
+ # @yieldparam [Object] matched value
27
+ # @return [Object] input
28
+ def interpret(input, _root)
29
+ @block.call(input)
30
+ input.is_a?(Array) ? input : [input]
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/janeway/lexer.rb CHANGED
@@ -101,12 +101,9 @@ module Janeway
101
101
  elsif name_first_char?(c)
102
102
  lex_member_name_shorthand(ignore_keywords: tokens.last&.type == :dot)
103
103
  end
104
+ raise err("Unknown character: #{c.inspect}") unless token
104
105
 
105
- if token
106
- tokens << token
107
- else
108
- raise err("Unknown character: #{c.inspect}")
109
- end
106
+ tokens << token
110
107
  end
111
108
 
112
109
  def digit?(lexeme)
@@ -141,6 +138,7 @@ module Janeway
141
138
  if next_two_chars == '..' && WHITESPACE.include?(lookahead)
142
139
  raise err("Operator #{next_two_chars.inspect} must not be followed by whitespace")
143
140
  end
141
+
144
142
  Token.new(OPERATORS.key(next_two_chars), next_two_chars, nil, current_location)
145
143
  else
146
144
  token_from_one_char_lex(lexeme)
@@ -151,9 +149,7 @@ module Janeway
151
149
  # @return [Token]
152
150
  def token_from_two_char_lex(lexeme)
153
151
  next_two_chars = [lexeme, lookahead].join
154
- unless TWO_CHAR_LEX.include?(next_two_chars)
155
- raise err("Unknown operator \"#{lexeme}\"")
156
- end
152
+ raise err("Unknown operator \"#{lexeme}\"") unless TWO_CHAR_LEX.include?(next_two_chars)
157
153
 
158
154
  consume
159
155
  Token.new(OPERATORS.key(next_two_chars), next_two_chars, nil, current_location)
@@ -172,7 +168,9 @@ module Janeway
172
168
  # @param delimiter [String] eg. ' or "
173
169
  # @return [Token] string token
174
170
  def lex_delimited_string(delimiter)
175
- non_delimiter = %w[' "].reject { _1 == delimiter }.first
171
+ allowed_delimiters = %w[' "]
172
+ # the "other" delimiter char, which is not currently being treated as a delimiter
173
+ non_delimiter = allowed_delimiters.reject { _1 == delimiter }.first
176
174
 
177
175
  literal_chars = []
178
176
  while lookahead != delimiter && source_uncompleted?
@@ -191,7 +189,7 @@ module Janeway
191
189
  end
192
190
  elsif unescaped?(next_char)
193
191
  consume
194
- elsif %w[' "].include?(next_char) && next_char != delimiter
192
+ elsif allowed_delimiters.include?(next_char) && next_char != delimiter
195
193
  consume
196
194
  else
197
195
  raise err("invalid character #{next_char.inspect}")
@@ -225,12 +223,10 @@ module Janeway
225
223
  when '/', '\\', '"', "'" then char
226
224
  when 'u' then consume_unicode_escape_sequence
227
225
  else
228
- if unescaped?(char)
229
- raise err("Character #{char} must not be escaped")
230
- else
231
- # whatever this is, it is not allowed even when escaped
232
- raise err("Invalid character #{char.inspect}")
233
- end
226
+ raise err("Character #{char} must not be escaped") if unescaped?(char)
227
+
228
+ # whatever this is, it is not allowed even when escaped
229
+ raise err("Invalid character #{char.inspect}")
234
230
  end
235
231
  end
236
232
 
@@ -1,23 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'error'
3
4
  require_relative 'functions'
4
5
  require_relative 'lexer'
5
6
 
6
7
  module Janeway
7
8
  # Transform a list of tokens into an Abstract Syntax Tree
8
9
  class Parser
9
- class Error < Janeway::Error; end
10
-
11
- attr_accessor :tokens
10
+ attr_reader :tokens
12
11
 
13
12
  include Functions
14
13
 
15
14
  UNARY_OPERATORS = %w[! -].freeze
16
15
  BINARY_OPERATORS = %w[== != > < >= <= ,].freeze
17
16
  LOGICAL_OPERATORS = %w[&& ||].freeze
18
-
19
17
  LOWEST_PRECEDENCE = 0
20
- PREFIX_PRECEDENCE = 7
21
18
  OPERATOR_PRECEDENCE = {
22
19
  ',' => 0,
23
20
  '||' => 1,
@@ -31,9 +28,8 @@ module Janeway
31
28
  '(' => 8,
32
29
  }.freeze
33
30
 
34
- # @param query [String] jsonpath query to be lexed and parsed
35
- #
36
- # @return [AST]
31
+ # @param jsonpath [String] jsonpath query to be lexed and parsed
32
+ # @return [AST::Query]
37
33
  def self.parse(jsonpath)
38
34
  raise ArgumentError, "expect jsonpath string, got #{jsonpath.inspect}" unless jsonpath.is_a?(String)
39
35
 
@@ -41,7 +37,7 @@ module Janeway
41
37
  new(tokens, jsonpath).parse
42
38
  end
43
39
 
44
- # @param token [Array<Token>]
40
+ # @param tokens [Array<Token>]
45
41
  # @param jsonpath [String] original jsonpath query string
46
42
  def initialize(tokens, jsonpath)
47
43
  @tokens = tokens
@@ -55,23 +51,18 @@ module Janeway
55
51
  consume
56
52
  raise err('JsonPath queries must start with root identifier "$"') unless current.type == :root
57
53
 
58
- root_node = parse_expr_recursively
54
+ root_node = parse_root
59
55
  consume
60
56
  unless current.type == :eof
61
57
  remaining = tokens[@next_p..].map(&:lexeme).join
62
58
  raise err("Unrecognized expressions after query: #{remaining}")
63
59
  end
64
60
 
65
- # Freeze so this can be used in ractors
66
- AST::Query.new(root_node, @jsonpath).freeze
61
+ AST::Query.new(root_node, @jsonpath)
67
62
  end
68
63
 
69
64
  private
70
65
 
71
- def build_token(type, lexeme = nil)
72
- Token.new(type, lexeme, nil, nil)
73
- end
74
-
75
66
  def pending_tokens?
76
67
  @next_p < tokens.length
77
68
  end
@@ -94,12 +85,12 @@ module Janeway
94
85
  current.literal.tap { consume }
95
86
  end
96
87
 
97
- def consume_if_next_is(expected)
98
- if next_token.type == expected.type
88
+ def consume_if_next_is(token_type)
89
+ if next_token.type == token_type
99
90
  consume
100
91
  true
101
92
  else
102
- unexpected_token_error(expected)
93
+ unexpected_token_error(token_type)
103
94
  false
104
95
  end
105
96
  end
@@ -131,20 +122,14 @@ module Janeway
131
122
  OPERATOR_PRECEDENCE[next_token.lexeme] || LOWEST_PRECEDENCE
132
123
  end
133
124
 
134
- def unexpected_token_error(expected = nil)
135
- if expected
125
+ def unexpected_token_error(expected_type = nil)
126
+ if expected_type
136
127
  raise err(
137
128
  "Unexpected token #{current.lexeme.inspect} " \
138
- "(expected #{expected.inspect}, got #{next_token.lexeme.inspect} )"
129
+ "(expected #{expected_type}, got #{next_token.lexeme} )"
139
130
  )
140
131
  end
141
- raise err("Unexpected token #{current.lexeme.inspect} (next is #{next_token.inspect})")
142
- end
143
-
144
- def check_syntax_compliance(ast_node)
145
- return if ast_node.expects?(next_token)
146
-
147
- unexpected_token_error
132
+ raise err("Unexpected token #{current.lexeme.inspect} (next is #{next_token})")
148
133
  end
149
134
 
150
135
  def determine_parsing_function
@@ -179,10 +164,11 @@ module Janeway
179
164
  :parse_binary_operator
180
165
  end
181
166
 
167
+ # A non-delimited word that is not a keyword within a jsonpath query is not allowed.
168
+ # eg. $[?@==foo]
169
+ # @raise
182
170
  def parse_identifier
183
- ident = AST::Identifier.new(current.lexeme)
184
- check_syntax_compliance(ident)
185
- ident
171
+ unexpected_token_error
186
172
  end
187
173
 
188
174
  def parse_string
@@ -194,7 +180,7 @@ module Janeway
194
180
  end
195
181
 
196
182
  # Consume minus operator and apply it to the (expected) number token following it.
197
- # Don't consume the number token.
183
+ # Don't consume the number token. The minus operator does not end up in the AST.
198
184
  def parse_minus_operator
199
185
  raise err("Expect token '-', got #{current.lexeme.inspect}") unless current.type == :minus
200
186
 
@@ -282,7 +268,7 @@ module Janeway
282
268
  def parse_dot_notation
283
269
  consume # "."
284
270
  unless current.type == :dot
285
- # Parse error, determine most useful error message
271
+ # Parse error. Determine the most useful error message for this situation:
286
272
  msg =
287
273
  if current.type == :number
288
274
  "Decimal point must be preceded by number, got \".#{current.lexeme}\""
@@ -308,39 +294,29 @@ module Janeway
308
294
  consume
309
295
 
310
296
  expr = parse_expr_recursively
311
- return unless consume_if_next_is(build_token(:group_end, ')'))
297
+ return unless consume_if_next_is(:group_end)
312
298
 
313
299
  expr
314
300
  end
315
301
 
316
- # TODO: Temporary impl; reflect more deeply about the appropriate way of parsing a terminator.
317
302
  def parse_terminator
318
303
  nil
319
304
  end
320
305
 
306
+ # Parse the root identifier "$", and any subsequent selector
307
+ # @return [AST::RootNode]
321
308
  def parse_root
322
- # detect optional following selector
323
- selector =
324
- case next_token.type
325
- when :dot then parse_dot_notation
326
- when :child_start then parse_child_segment
327
- when :descendants then parse_descendant_segment
328
- end
329
-
330
- AST::RootNode.new(selector)
309
+ AST::RootNode.new.tap do |root_node|
310
+ root_node.next = parse_next_selector
311
+ end
331
312
  end
332
313
 
333
- # Parse the current node operator "@", and optionally a selector which is applied to it
314
+ # Parse the current node operator "@", and any subsequent selector
315
+ # @return [AST::CurrentNode]
334
316
  def parse_current_node
335
- # detect optional following selector
336
- selector =
337
- case next_token.type
338
- when :dot then parse_dot_notation
339
- when :child_start then parse_child_segment
340
- when :descendants then parse_descendant_segment
341
- end
342
-
343
- AST::CurrentNode.new(selector)
317
+ AST::CurrentNode.new.tap do |current_node|
318
+ current_node.next = parse_next_selector
319
+ end
344
320
  end
345
321
 
346
322
  # Parse one or more selectors surrounded by parentheses.
@@ -411,21 +387,10 @@ module Janeway
411
387
  when :child_start then parse_child_segment
412
388
  when :dot then parse_dot_notation
413
389
  when :descendants then parse_descendant_segment
414
- when :wildcard then parse_wildcard_selector
415
390
  when :eof, :child_end then nil
416
391
  end
417
392
  end
418
393
 
419
- # Return true if the given token represents the start of any type of selector,
420
- # or a collection of selectors.
421
- #
422
- # @param token [Token]
423
- # @return [Boolean]
424
- def selector?(token)
425
- type = token.type.to_s
426
- type.include?('selector') || %w[dot child_start].include?(type)
427
- end
428
-
429
394
  # Parse a selector which is inside brackets
430
395
  def parse_current_selector
431
396
  case current.type
@@ -451,13 +416,12 @@ module Janeway
451
416
  end
452
417
 
453
418
  # Parse wildcard selector and any following selector
419
+ # @return [AST::WildcardSelector]
454
420
  def parse_wildcard_selector
455
- selector = AST::WildcardSelector.new
456
- consume
457
- return selector if %i[child_end union].include?(current.type)
458
-
459
- selector.next = parse_next_selector
460
- selector
421
+ AST::WildcardSelector.new.tap do |selector|
422
+ consume # *
423
+ selector.next = parse_next_selector unless %i[child_end union].include?(current.type)
424
+ end
461
425
  end
462
426
 
463
427
  # An array slice start:end:step selects a series of elements from
@@ -474,7 +438,10 @@ module Janeway
474
438
  # @return [AST::ArraySliceSelector]
475
439
  def parse_array_slice_selector
476
440
  start, end_, step = Array.new(3) { parse_array_slice_component }.map { _1&.literal }
477
- raise err("After array slice selector, expect ], got #{current.lexeme}") unless current.type == :child_end # ]
441
+
442
+ unless %i[child_end union].include?(current.type)
443
+ raise err("Array slice selector must be followed by \",\" or \"]\", got #{current.lexeme}")
444
+ end
478
445
 
479
446
  AST::ArraySliceSelector.new(start, end_, step)
480
447
  end
@@ -500,19 +467,13 @@ module Janeway
500
467
 
501
468
  # Parse a name selector.
502
469
  # The name selector may have been in dot notation or parentheses, that part is already parsed.
503
- # Next token is just the name.
470
+ # Next token is the name.
504
471
  #
505
472
  # @return [AST::NameSelector]
506
473
  def parse_name_selector
507
- consume
508
- selector = AST::NameSelector.new(current.lexeme)
509
- # If there is a following expression, parse that too
510
- case next_token.type
511
- when :dot then selector.next = parse_dot_notation
512
- when :child_start then selector.next = parse_child_segment
513
- when :descendants then selector.next = parse_descendant_segment
474
+ AST::NameSelector.new(consume.lexeme).tap do |selector|
475
+ selector.next = parse_next_selector
514
476
  end
515
- selector
516
477
  end
517
478
 
518
479
  # Feed tokens to the FilterSelector until hitting a terminator
@@ -528,11 +489,11 @@ module Janeway
528
489
  parse_expr_recursively
529
490
  end
530
491
 
531
- # may replace existing node with a binary operator that incorporates the original node
492
+ # may replace existing node with a binary operator that contains the existing node
532
493
  selector.value = node
533
494
  end
534
495
 
535
- # Check for literal, they are not allowed to be a complete condition in a filter selector
496
+ # Check for literals, they are not allowed to be a complete condition in a filter selector.
536
497
  # This includes jsonpath functions that return a numeric value.
537
498
  raise err("Literal value #{selector.value} must be used within a comparison") if selector.value.literal?
538
499
 
@@ -556,7 +517,7 @@ module Janeway
556
517
  def parse_not_operator
557
518
  AST::UnaryOperator.new(current.type).tap do |op|
558
519
  consume
559
- op.operand = parse_expr_recursively(PREFIX_PRECEDENCE)
520
+ op.operand = parse_expr_recursively
560
521
  end
561
522
  end
562
523
 
@@ -584,18 +545,18 @@ module Janeway
584
545
  send(parsing_function)
585
546
  end
586
547
 
548
+ # Parse an expression which may contain binary operators and varying expression precedence.
549
+ # This is only needed within a filter expression.
587
550
  def parse_expr_recursively(precedence = LOWEST_PRECEDENCE)
588
551
  parsing_function = determine_parsing_function
589
552
  raise err("Unrecognized token: #{current.lexeme.inspect}") unless parsing_function
590
553
 
591
- current
592
554
  expr = send(parsing_function)
593
- return unless expr # When expr is nil, it means we have reached a \n or a eof.
555
+ return unless expr
594
556
 
595
- # Note that here we are checking the NEXT token.
557
+ # Keep parsing until next token is higher precedence, or a terminator
596
558
  while next_not_terminator? && precedence < next_precedence
597
559
  infix_parsing_function = determine_infix_function(next_token)
598
-
599
560
  return expr if infix_parsing_function.nil?
600
561
 
601
562
  consume
@@ -610,7 +571,7 @@ module Janeway
610
571
  # @param msg [String] error message
611
572
  # @return [Parser::Error]
612
573
  def err(msg)
613
- Error.new(msg, @jsonpath)
574
+ Janeway::Error.new(msg, @jsonpath)
614
575
  end
615
576
 
616
577
  alias parse_true parse_boolean
data/lib/janeway/token.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'forwardable'
4
4
 
5
5
  module Janeway
6
+ # Tokens are produced by the lexer, they represent jsonpath query elements in a low-level, non-hierarchical way.
6
7
  class Token
7
8
  extend Forwardable
8
9
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Janeway
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/janeway.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'English'
4
4
 
5
- # Janeway jsonpath parsing library
5
+ # Janeway JSONPath parsing library
6
6
  module Janeway
7
7
  # Abstract Syntax Tree
8
8
  module AST
@@ -11,17 +11,17 @@ module Janeway
11
11
  INTEGER_MAX = 9_007_199_254_740_991
12
12
  end
13
13
 
14
- # Apply a JsonPath query to the input, and return the result.
14
+ # Apply a JSONPath query to the input, and return all matched values.
15
15
  #
16
- # @param query [String] jsonpath query
17
- # @param input [Object] ruby object to be searched
16
+ # @param query [String] JSONPath query
17
+ # @param input [Hash, Array] ruby object to be searched
18
18
  # @return [Array] all matched objects
19
19
  def self.find_all(query, input)
20
20
  ast = compile(query)
21
21
  Janeway::Interpreter.new(ast).interpret(input)
22
22
  end
23
23
 
24
- # Compile a JsonPath query into an Abstract Syntax Tree.
24
+ # Compile a JSONPath query into an Abstract Syntax Tree.
25
25
  #
26
26
  # This can be used and re-used later on multiple inputs.
27
27
  #
@@ -30,6 +30,26 @@ module Janeway
30
30
  def self.compile(query)
31
31
  Janeway::Parser.parse(query)
32
32
  end
33
+
34
+ # Iterate through each value matched by the JSONPath query.
35
+ #
36
+ # @param query [String] jsonpath query
37
+ # @param input [Hash, Array] ruby object to be searched
38
+ # @yieldparam [Object] matched value
39
+ # @return [void]
40
+ def self.each(query, input, &block)
41
+ raise ArgumentError, "Invalid jsonpath query: #{query.inspect}" unless query.is_a?(String)
42
+ unless [Hash, Array, String].include?(input.class)
43
+ raise ArgumentError, "Invalid input, expecting array or hash: #{input.inspect}"
44
+ end
45
+ return enum_for(:each, query, input) unless block_given?
46
+
47
+ ast = Janeway::Parser.parse(query)
48
+ interpreter = Janeway::Interpreter.new(ast)
49
+ yielder = Janeway::Interpreters::Yielder.new(&block)
50
+ interpreter.push(yielder)
51
+ interpreter.interpret(input)
52
+ end
33
53
  end
34
54
 
35
55
  # Require ruby source files in the given dir. Do not recurse to subdirs.
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: janeway-jsonpath
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fraser Hanson
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-13 00:00:00.000000000 Z
10
+ date: 2025-01-22 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: |+
13
13
  JSONPath is a query language for selecting and extracting values from a JSON text.
@@ -42,7 +42,6 @@ files:
42
42
  - lib/janeway/ast/filter_selector.rb
43
43
  - lib/janeway/ast/function.rb
44
44
  - lib/janeway/ast/helpers.rb
45
- - lib/janeway/ast/identifier.rb
46
45
  - lib/janeway/ast/index_selector.rb
47
46
  - lib/janeway/ast/name_selector.rb
48
47
  - lib/janeway/ast/null.rb
@@ -61,6 +60,21 @@ files:
61
60
  - lib/janeway/functions/search.rb
62
61
  - lib/janeway/functions/value.rb
63
62
  - lib/janeway/interpreter.rb
63
+ - lib/janeway/interpreters/array_slice_selector_interpreter.rb
64
+ - lib/janeway/interpreters/base.rb
65
+ - lib/janeway/interpreters/binary_operator_interpreter.rb
66
+ - lib/janeway/interpreters/child_segment_interpreter.rb
67
+ - lib/janeway/interpreters/current_node_interpreter.rb
68
+ - lib/janeway/interpreters/descendant_segment_interpreter.rb
69
+ - lib/janeway/interpreters/filter_selector_interpreter.rb
70
+ - lib/janeway/interpreters/function_interpreter.rb
71
+ - lib/janeway/interpreters/index_selector_interpreter.rb
72
+ - lib/janeway/interpreters/name_selector_interpreter.rb
73
+ - lib/janeway/interpreters/root_node_interpreter.rb
74
+ - lib/janeway/interpreters/tree_constructor.rb
75
+ - lib/janeway/interpreters/unary_operator_interpreter.rb
76
+ - lib/janeway/interpreters/wildcard_selector_interpreter.rb
77
+ - lib/janeway/interpreters/yielder.rb
64
78
  - lib/janeway/lexer.rb
65
79
  - lib/janeway/location.rb
66
80
  - lib/janeway/parser.rb
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Janeway
4
- module AST
5
- class Identifier < Janeway::AST::Expression
6
- alias name value
7
-
8
- # Tokens which may follow an identifier string
9
- EXPECTED_NEXT_TOKENS = %I[
10
- \n
11
- +
12
- -
13
- *
14
- /
15
- ==
16
- !=
17
- >
18
- <
19
- >=
20
- <=
21
- &&
22
- ||
23
- ].freeze
24
-
25
- # @return [String]
26
- def to_s
27
- @value
28
- end
29
-
30
- def expects?(next_token)
31
- EXPECTED_NEXT_TOKENS.include?(next_token)
32
- end
33
- end
34
- end
35
- end