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.
- checksums.yaml +4 -4
- data/README.md +109 -38
- data/lib/janeway/ast/array_slice_selector.rb +9 -7
- data/lib/janeway/ast/binary_operator.rb +25 -23
- data/lib/janeway/ast/current_node.rb +8 -5
- data/lib/janeway/ast/descendant_segment.rb +1 -1
- data/lib/janeway/ast/expression.rb +11 -1
- data/lib/janeway/ast/filter_selector.rb +1 -1
- data/lib/janeway/ast/helpers.rb +1 -0
- data/lib/janeway/ast/index_selector.rb +2 -3
- data/lib/janeway/ast/name_selector.rb +2 -2
- data/lib/janeway/ast/null.rb +1 -0
- data/lib/janeway/ast/query.rb +42 -1
- data/lib/janeway/ast/root_node.rb +8 -5
- data/lib/janeway/ast/string_type.rb +2 -0
- data/lib/janeway/ast/unary_operator.rb +2 -2
- data/lib/janeway/ast/wildcard_selector.rb +1 -0
- data/lib/janeway/error.rb +1 -1
- data/lib/janeway/functions/count.rb +2 -4
- data/lib/janeway/functions/length.rb +2 -3
- data/lib/janeway/functions/match.rb +3 -9
- data/lib/janeway/functions/search.rb +4 -9
- data/lib/janeway/functions/value.rb +2 -3
- data/lib/janeway/interpreter.rb +29 -587
- data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +44 -0
- data/lib/janeway/interpreters/base.rb +43 -0
- data/lib/janeway/interpreters/binary_operator_interpreter.rb +163 -0
- data/lib/janeway/interpreters/child_segment_interpreter.rb +44 -0
- data/lib/janeway/interpreters/current_node_interpreter.rb +33 -0
- data/lib/janeway/interpreters/descendant_segment_interpreter.rb +40 -0
- data/lib/janeway/interpreters/filter_selector_interpreter.rb +63 -0
- data/lib/janeway/interpreters/function_interpreter.rb +99 -0
- data/lib/janeway/interpreters/index_selector_interpreter.rb +39 -0
- data/lib/janeway/interpreters/name_selector_interpreter.rb +30 -0
- data/lib/janeway/interpreters/root_node_interpreter.rb +23 -0
- data/lib/janeway/interpreters/tree_constructor.rb +39 -0
- data/lib/janeway/interpreters/unary_operator_interpreter.rb +32 -0
- data/lib/janeway/interpreters/wildcard_selector_interpreter.rb +38 -0
- data/lib/janeway/interpreters/yielder.rb +34 -0
- data/lib/janeway/lexer.rb +12 -16
- data/lib/janeway/parser.rb +51 -90
- data/lib/janeway/token.rb +1 -0
- data/lib/janeway/version.rb +1 -1
- data/lib/janeway.rb +25 -5
- metadata +17 -3
- 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
|
-
|
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
|
-
|
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
|
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
|
-
|
230
|
-
|
231
|
-
|
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
|
|
data/lib/janeway/parser.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
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 =
|
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
|
-
|
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(
|
98
|
-
if next_token.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(
|
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(
|
135
|
-
if
|
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 #{
|
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
|
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
|
-
|
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
|
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(
|
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
|
-
|
323
|
-
|
324
|
-
|
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
|
314
|
+
# Parse the current node operator "@", and any subsequent selector
|
315
|
+
# @return [AST::CurrentNode]
|
334
316
|
def parse_current_node
|
335
|
-
|
336
|
-
|
337
|
-
|
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
|
-
|
456
|
-
|
457
|
-
|
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
|
-
|
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
|
470
|
+
# Next token is the name.
|
504
471
|
#
|
505
472
|
# @return [AST::NameSelector]
|
506
473
|
def parse_name_selector
|
507
|
-
consume
|
508
|
-
|
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
|
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
|
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
|
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
|
555
|
+
return unless expr
|
594
556
|
|
595
|
-
#
|
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
data/lib/janeway/version.rb
CHANGED
data/lib/janeway.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'English'
|
4
4
|
|
5
|
-
# Janeway
|
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
|
14
|
+
# Apply a JSONPath query to the input, and return all matched values.
|
15
15
|
#
|
16
|
-
# @param query [String]
|
17
|
-
# @param input [
|
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
|
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.
|
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-
|
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
|