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,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets array slice selector on the given input
|
8
|
+
class ArraySliceSelectorInterpreter < Base
|
9
|
+
alias selector node
|
10
|
+
|
11
|
+
# Filter the input by applying the array slice selector.
|
12
|
+
#
|
13
|
+
# @param selector [ArraySliceSelector]
|
14
|
+
# @param input [Array, Hash] the results of processing so far
|
15
|
+
# @param root [Array, Hash] the entire input
|
16
|
+
# @return [Array]
|
17
|
+
def interpret(input, root)
|
18
|
+
return [] unless input.is_a?(Array)
|
19
|
+
return [] if selector&.step&.zero? # RFC: When step is 0, no elements are selected.
|
20
|
+
|
21
|
+
# Calculate the upper and lower indices of the target range
|
22
|
+
lower = selector.lower_index(input.size)
|
23
|
+
upper = selector.upper_index(input.size)
|
24
|
+
|
25
|
+
# Collect values from target indices. Omit the value from the final index.
|
26
|
+
results =
|
27
|
+
if selector.step.positive?
|
28
|
+
lower.step(to: upper - 1, by: selector.step).map { input[_1] }
|
29
|
+
else
|
30
|
+
upper.step(to: lower + 1, by: selector.step).map { input[_1] }
|
31
|
+
end
|
32
|
+
return results unless @next
|
33
|
+
|
34
|
+
# Apply child selector to each node in the output node list
|
35
|
+
node_list = results
|
36
|
+
results = []
|
37
|
+
node_list.each do |node|
|
38
|
+
results.concat @next.interpret(node, root)
|
39
|
+
end
|
40
|
+
results
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'tree_constructor'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
# Interpreters module contains interpreter classes which correspond to each
|
7
|
+
# type of AST::Node.
|
8
|
+
module Interpreters
|
9
|
+
# Base class for interpreters.
|
10
|
+
class Base
|
11
|
+
# The special result NOTHING represents the absence of a JSON value and
|
12
|
+
# is distinct from any JSON value, including null.
|
13
|
+
# It represents:
|
14
|
+
# * object keys referred to by name that do not exist in the input
|
15
|
+
# * array values referred to by index that are out of range
|
16
|
+
# * return value of function calls with an invalid input data type
|
17
|
+
NOTHING = :nothing
|
18
|
+
|
19
|
+
# Subsequent expression interpreter that filters the output of this one
|
20
|
+
# @return [Interpreters::Base, nil]
|
21
|
+
attr_accessor :next
|
22
|
+
|
23
|
+
# @return [AST::Expression]
|
24
|
+
attr_reader :node
|
25
|
+
|
26
|
+
def initialize(node)
|
27
|
+
@node = node
|
28
|
+
|
29
|
+
return unless node.next
|
30
|
+
|
31
|
+
@next = TreeConstructor.ast_node_to_interpreter(node.next)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Interpret the input, return result or forward to next node.
|
35
|
+
#
|
36
|
+
# @param input [Array, Hash] the results of processing so far
|
37
|
+
# @param root [Array, Hash] the entire input
|
38
|
+
def interpret(_input, _root)
|
39
|
+
raise NotImplementedError, 'subclass must implement #interpret'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets a binary operator within filter selector.
|
8
|
+
class BinaryOperatorInterpreter < Base
|
9
|
+
alias operator node
|
10
|
+
|
11
|
+
# Set up the internal interpreter chain for the BinaryOperator.
|
12
|
+
def initialize(operator)
|
13
|
+
super
|
14
|
+
@left = TreeConstructor.ast_node_to_interpreter(operator.left)
|
15
|
+
@right = TreeConstructor.ast_node_to_interpreter(operator.right)
|
16
|
+
end
|
17
|
+
|
18
|
+
# The binary operators are all comparison operators that test equality.
|
19
|
+
#
|
20
|
+
# * boolean values specified in the query
|
21
|
+
# * JSONPath expressions which must be evaluated
|
22
|
+
#
|
23
|
+
# After a JSONPath expression is evaluated, it results in a node list.
|
24
|
+
# This may contain literal values or nodes, whose value must be extracted before comparison.
|
25
|
+
#
|
26
|
+
# @param input [Array, Hash] the results of processing so far
|
27
|
+
# @param root [Array, Hash] the entire input
|
28
|
+
def interpret(input, root)
|
29
|
+
case operator.name
|
30
|
+
when :and, :or
|
31
|
+
# handle node list for existence check
|
32
|
+
lhs = @left.interpret(input, root)
|
33
|
+
rhs = @right.interpret(input, root)
|
34
|
+
when :equal, :not_equal, :less_than, :greater_than, :less_than_or_equal, :greater_than_or_equal
|
35
|
+
# handle node values for comparison check
|
36
|
+
lhs = to_single_value @left.interpret(input, root)
|
37
|
+
rhs = to_single_value @right.interpret(input, root)
|
38
|
+
else
|
39
|
+
raise "Don't know how to handle binary operator #{operator.name.inspect}"
|
40
|
+
end
|
41
|
+
send(:"interpret_#{operator.name}", lhs, rhs)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Interpret a node and extract its value, in preparation for using the node
|
45
|
+
# in a comparison operator.
|
46
|
+
# Basic literals such as AST::Number and AST::StringType evaluate to a number or string,
|
47
|
+
# but selectors and segments evaluate to a node list. Extract the value (if any)
|
48
|
+
# from the node list, or return basic type.
|
49
|
+
|
50
|
+
# Convert an expression result into a single value suitable for use by a comparison operator.
|
51
|
+
# Expression result may already ba single value (ie. from a literal like AST::String)
|
52
|
+
# or it may be a node list from a singular query.
|
53
|
+
#
|
54
|
+
# Any node list is guaranteed not to contain multiple values because the expression that
|
55
|
+
# produce it was already verified to be a singular query.
|
56
|
+
#
|
57
|
+
# @param node [AST::Expression]
|
58
|
+
# @param input [Object]
|
59
|
+
def to_single_value(result)
|
60
|
+
# Return basic types (ie. from AST::Number, AST::StringType, AST::Null)
|
61
|
+
return result unless result.is_a?(Array)
|
62
|
+
|
63
|
+
# Node lists are returned by Selectors, ChildSegment, DescendantSegment.
|
64
|
+
#
|
65
|
+
# For a comparison operator, an empty node list represents a missing element.
|
66
|
+
# This must not match any literal value (including null/nil) but must match another missing value.
|
67
|
+
return NOTHING if result.empty?
|
68
|
+
|
69
|
+
# The parsing stage has already verified that both the left and right
|
70
|
+
# expressions evaluate to a single value. Both are either a literal or a singular query.
|
71
|
+
# So, this check will never raise an error.
|
72
|
+
raise 'node list contains multiple elements but this is a comparison' unless result.size == 1
|
73
|
+
|
74
|
+
result.first # Return the only node in the node list
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param lhs [String, Numeric, Symbol, nil] string/number/null or NOTHING
|
78
|
+
# @param rhs [String, Numeric, Symbol, nil] string/number/null or NOTHING
|
79
|
+
def interpret_equal(lhs, rhs)
|
80
|
+
lhs == rhs
|
81
|
+
end
|
82
|
+
|
83
|
+
# Interpret != operator
|
84
|
+
def interpret_not_equal(lhs, rhs)
|
85
|
+
!interpret_equal(lhs, rhs)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Interpret && operator
|
89
|
+
# May receive node lists, in which case empty node list == false
|
90
|
+
def interpret_and(lhs, rhs)
|
91
|
+
# non-empty array is already truthy, so that works properly without conversion
|
92
|
+
lhs = false if lhs == []
|
93
|
+
rhs = false if rhs == []
|
94
|
+
lhs && rhs
|
95
|
+
end
|
96
|
+
|
97
|
+
# Interpret || operator
|
98
|
+
# May receive node lists, in which case empty node list == false
|
99
|
+
def interpret_or(lhs, rhs)
|
100
|
+
# non-empty array is already truthy, so that works properly without conversion
|
101
|
+
lhs = false if lhs.is_a?(Array) && lhs.empty?
|
102
|
+
rhs = false if rhs.is_a?(Array) && rhs.empty?
|
103
|
+
lhs || rhs
|
104
|
+
end
|
105
|
+
|
106
|
+
def interpret_less_than(lhs, rhs)
|
107
|
+
lhs < rhs
|
108
|
+
rescue StandardError
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def interpret_less_than_or_equal(lhs, rhs)
|
113
|
+
# Must be done in 2 comparisons, because the equality comparison is
|
114
|
+
# valid for many types that do not support the < operator.
|
115
|
+
return true if interpret_equal(lhs, rhs)
|
116
|
+
|
117
|
+
lhs < rhs
|
118
|
+
rescue StandardError
|
119
|
+
# This catches type mismatches like {} <= 1
|
120
|
+
# RFC says that both < and > return false for such comparisons
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
def interpret_greater_than(lhs, rhs)
|
125
|
+
lhs > rhs
|
126
|
+
rescue StandardError
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
def interpret_greater_than_or_equal(lhs, rhs)
|
131
|
+
return true if interpret_equal(lhs, rhs)
|
132
|
+
|
133
|
+
lhs > rhs
|
134
|
+
rescue StandardError
|
135
|
+
false
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param boolean [AST::Boolean]
|
139
|
+
# @return [Boolean]
|
140
|
+
def interpret_boolean(boolean, _input)
|
141
|
+
boolean.value
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param number [AST::Number]
|
145
|
+
# @return [Integer, Float]
|
146
|
+
def interpret_number(number, _input)
|
147
|
+
number.value
|
148
|
+
end
|
149
|
+
|
150
|
+
# @param string [AST::StringType]
|
151
|
+
# @return [String]
|
152
|
+
def interpret_string_type(string, _input)
|
153
|
+
string.value
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param _null [AST::Null] ignored
|
157
|
+
# @param _input [Object] ignored
|
158
|
+
def interpret_null(_null, _input)
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# For each node in the input nodelist, the resulting nodelist of a child
|
8
|
+
# segment is the concatenation of the nodelists from each of its
|
9
|
+
# selectors in the order that the selectors appear in the list. Note: Any
|
10
|
+
# node matched by more than one selector is kept as many times in the nodelist.
|
11
|
+
#
|
12
|
+
# A child segment can only contain selectors according to the RFC's ABNF grammar, so it
|
13
|
+
# cannot contain another child segment, or a new expression starting with the root identifier.
|
14
|
+
class ChildSegmentInterpreter < Base
|
15
|
+
# @param child_segment [AST::ChildSegment]
|
16
|
+
def initialize(child_segment)
|
17
|
+
super
|
18
|
+
@nodes =
|
19
|
+
child_segment.map do |expr|
|
20
|
+
TreeConstructor.ast_node_to_interpreter(expr)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Interpret a set of 2 or more selectors, seperated by the union operator.
|
25
|
+
# All selectors are sent the identical input, the results are combined.
|
26
|
+
#
|
27
|
+
# @param input [Array, Hash] the results of processing so far
|
28
|
+
# @param root [Array, Hash] the entire input
|
29
|
+
# @return [Array]
|
30
|
+
def interpret(input, root)
|
31
|
+
# Apply each expression to the input, collect results
|
32
|
+
results = []
|
33
|
+
@nodes.each do |node|
|
34
|
+
results.concat node.interpret(input, root)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return results, or forward them to the next selector
|
38
|
+
return results unless @next
|
39
|
+
|
40
|
+
@next.interpret(results, root)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interpets current node identifier.
|
8
|
+
# This applies the following selector to the input.
|
9
|
+
class CurrentNodeInterpreter < Base
|
10
|
+
alias current_node node
|
11
|
+
|
12
|
+
# Apply selector to each value in the current node and return result.
|
13
|
+
#
|
14
|
+
# The result is an Array containing all results of evaluating the CurrentNode's selector (if any.)
|
15
|
+
#
|
16
|
+
# If the selector extracted values from nodes such as strings, numbers or nil/null,
|
17
|
+
# these will be included in the array.
|
18
|
+
# If the selector did not match any node, the array may be empty.
|
19
|
+
# If there was no selector, then the current input node is returned in the array.
|
20
|
+
#
|
21
|
+
# @param _input [Array, Hash] the results of processing so far
|
22
|
+
# @param root [Array, Hash] the entire input
|
23
|
+
# @return [Array] Node List containing all results from evaluating this node's selectors.
|
24
|
+
def interpret(input, root)
|
25
|
+
if @next
|
26
|
+
@next.interpret(input, root)
|
27
|
+
else
|
28
|
+
input
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Find all descendants of the current input that match the selector in the DescendantSegment
|
8
|
+
class DescendantSegmentInterpreter < Base
|
9
|
+
alias descendant_segment node
|
10
|
+
|
11
|
+
# Find all descendants of the current input that match the selector in the DescendantSegment
|
12
|
+
#
|
13
|
+
# @param input [Array, Hash] the results of processing so far
|
14
|
+
# @param root [Array, Hash] the entire input
|
15
|
+
# @return [Array<AST::Expression>] node list
|
16
|
+
def interpret(input, root)
|
17
|
+
visit(input) do |node|
|
18
|
+
@next.interpret(node, root)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Visit all descendants of `input`.
|
23
|
+
# Return results of applying `action` on each.
|
24
|
+
def visit(input, &action)
|
25
|
+
results = [yield(input)]
|
26
|
+
|
27
|
+
case input
|
28
|
+
when Array
|
29
|
+
results.concat(input.map { |elt| visit(elt, &action) })
|
30
|
+
when Hash
|
31
|
+
results.concat(input.values.map { |value| visit(value, &action) })
|
32
|
+
else
|
33
|
+
input
|
34
|
+
end
|
35
|
+
|
36
|
+
results.flatten(1).compact
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets a filter selector, returns results or forwards them to next selector
|
8
|
+
class FilterSelectorInterpreter < Base
|
9
|
+
alias selector node
|
10
|
+
|
11
|
+
# Set up the internal interpreter chain for the FilterSelector.
|
12
|
+
# @param selector [AST::FilterSelector]
|
13
|
+
def initialize(selector)
|
14
|
+
super
|
15
|
+
@expr = self.class.setup_interpreter_tree(selector)
|
16
|
+
end
|
17
|
+
|
18
|
+
# FIXME: should this be combined with similar func in Interpreter?
|
19
|
+
# FIXME: move this to a separate module?
|
20
|
+
#
|
21
|
+
# Set up a tree of interpreters which can process input for the filter expression.
|
22
|
+
# For a jsonpath query like '$.a[?@.b == $.x]', this sets up interpreters for '@.b == $.x'.
|
23
|
+
# @return [Interpreters::Base] root of the filter expression
|
24
|
+
def self.setup_interpreter_tree(selector)
|
25
|
+
TreeConstructor.ast_node_to_interpreter(selector.value)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Interpret selector on the input.
|
29
|
+
# @param input [Array, Hash] the results of processing so far
|
30
|
+
# @param root [Array, Hash] the entire input
|
31
|
+
def interpret(input, root)
|
32
|
+
values =
|
33
|
+
case input
|
34
|
+
when Array then input
|
35
|
+
when Hash then input.values
|
36
|
+
else return [] # early exit
|
37
|
+
end
|
38
|
+
|
39
|
+
# Apply filter expressions to the input data
|
40
|
+
node_list = []
|
41
|
+
values.each do |value|
|
42
|
+
# Run filter and interpret result
|
43
|
+
result = @expr.interpret(value, root)
|
44
|
+
case result
|
45
|
+
when TrueClass then node_list << value # comparison test - pass
|
46
|
+
when FalseClass then nil # comparison test - fail
|
47
|
+
when Array then node_list << value unless result.empty? # existence test - node list
|
48
|
+
else
|
49
|
+
node_list << value # existence test. Null values here == success.
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return node_list unless @next
|
53
|
+
|
54
|
+
# Apply child selector to each node in the output node list
|
55
|
+
results = []
|
56
|
+
node_list.each do |node|
|
57
|
+
results.concat @next.interpret(node, root)
|
58
|
+
end
|
59
|
+
results
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets a jsonpath function call within a filter selector.
|
8
|
+
class FunctionInterpreter < Base
|
9
|
+
alias function node
|
10
|
+
|
11
|
+
# Specify the parameter types that built-in JsonPath functions require
|
12
|
+
# @return [Hash<Symbol, Array<Symbol>] function name => list of parameter types
|
13
|
+
FUNCTION_PARAMETER_TYPES = {
|
14
|
+
length: [:value_type],
|
15
|
+
count: [:nodes_type],
|
16
|
+
match: %i[value_type value_type],
|
17
|
+
search: %i[value_type value_type],
|
18
|
+
value: [:nodes_type],
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# @param [AST::Function]
|
22
|
+
def initialize(function)
|
23
|
+
super
|
24
|
+
@params = function.parameters.map { |param| TreeConstructor.ast_node_to_interpreter(param) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param input [Array, Hash] the results of processing so far
|
28
|
+
# @param root [Array, Hash] the entire input
|
29
|
+
def interpret(input, root)
|
30
|
+
params = interpret_function_parameters(@params, input, root)
|
31
|
+
function.body.call(*params)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Evaluate the expressions in the parameter list to make the parameter values
|
35
|
+
# to pass in to a JsonPath function.
|
36
|
+
#
|
37
|
+
# The node lists returned by a singular query must be deconstructed into a single value for
|
38
|
+
# parameters of ValueType, this is done here.
|
39
|
+
# For explanation:
|
40
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#name-well-typedness-of-function-
|
41
|
+
#
|
42
|
+
# @param parameters [Array] parameters before evaluation
|
43
|
+
# @param func [String] function name (eg. "length", "count")
|
44
|
+
# @param input [Object]
|
45
|
+
# @return [Array] parameters after evaluation
|
46
|
+
def interpret_function_parameters(parameters, input, root)
|
47
|
+
param_types = FUNCTION_PARAMETER_TYPES[function.name.to_sym]
|
48
|
+
|
49
|
+
parameters.map.with_index do |parameter, i|
|
50
|
+
type = param_types[i]
|
51
|
+
case parameter.node
|
52
|
+
when AST::CurrentNode, AST::RootNode
|
53
|
+
result = parameter.interpret(input, root)
|
54
|
+
if type == :value_type && parameter.node.singular_query?
|
55
|
+
deconstruct(result)
|
56
|
+
else
|
57
|
+
result
|
58
|
+
end
|
59
|
+
when AST::Function, AST::StringType then parameter.interpret(input, root)
|
60
|
+
when String then parameter.value # from a TreeConstructor::Literal
|
61
|
+
else
|
62
|
+
# invalid parameter type. Function must accept it and return Nothing result
|
63
|
+
parameter
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Prepare a value to be passed to as a parameter with type ValueType.
|
69
|
+
# Singular queries (see RFC) produce a node list containing one value.
|
70
|
+
# Return the value.
|
71
|
+
#
|
72
|
+
# Implements this part of the RFC:
|
73
|
+
# > When the declared type of the parameter is ValueType and
|
74
|
+
# the argument is one of the following:
|
75
|
+
# > ...
|
76
|
+
# >
|
77
|
+
# > A singular query. In this case:
|
78
|
+
# > * If the query results in a nodelist consisting of a single node,
|
79
|
+
# the argument is the value of the node.
|
80
|
+
# > * If the query results in an empty nodelist, the argument is
|
81
|
+
# the special result Nothing.
|
82
|
+
#
|
83
|
+
# @param input [Object] usually an array - sometimes a basic type like String, Numeric
|
84
|
+
# @return [Object] basic type -- string or number
|
85
|
+
def deconstruct(input)
|
86
|
+
return input unless input.is_a?(Array)
|
87
|
+
|
88
|
+
if input.size == 1
|
89
|
+
# FIXME: what if it was a size 1 array that was intended to be a node not a node list? How to detect this?
|
90
|
+
input.first
|
91
|
+
elsif input.empty?
|
92
|
+
NOTHING
|
93
|
+
else
|
94
|
+
input # input is a single node, which happens to be an Array
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets an index selector, returns results or forwards them to next selector
|
8
|
+
#
|
9
|
+
# Filter the input by returning the array element with the given index.
|
10
|
+
# Return empty list if input is not an array, or if array does not contain index.
|
11
|
+
#
|
12
|
+
# Output is an array because all selectors must return node lists, even if
|
13
|
+
# they only select a single element.
|
14
|
+
#
|
15
|
+
# @param selector [IndexSelector]
|
16
|
+
# @param input [Array]
|
17
|
+
# @return [Array]
|
18
|
+
class IndexSelectorInterpreter < Base
|
19
|
+
alias selector node
|
20
|
+
|
21
|
+
# Interpret an index selector on the given input.
|
22
|
+
# NOTHING is selected, and it is not an error, if the index lies outside the range of the array.
|
23
|
+
# NOTHING is selected from a value that is not an array.
|
24
|
+
#
|
25
|
+
# @param input [Array, Hash] the results of processing so far
|
26
|
+
# @param root [Array, Hash] the entire input
|
27
|
+
def interpret(input, root)
|
28
|
+
return [] unless input.is_a?(Array)
|
29
|
+
|
30
|
+
result = input.fetch(selector.value) # raises IndexError if no such index
|
31
|
+
return [result] unless @next
|
32
|
+
|
33
|
+
@next.interpret(result, root)
|
34
|
+
rescue IndexError
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets a name selector, returns results or forwards them to next selector
|
8
|
+
#
|
9
|
+
# Filters the input by returning the key that has the given name.
|
10
|
+
#
|
11
|
+
# Must differentiate between a null value of a key that exists (nil)
|
12
|
+
# and a key that does not exist ([])
|
13
|
+
class NameSelectorInterpreter < Base
|
14
|
+
alias selector node
|
15
|
+
|
16
|
+
# Interpret selector on the given input.
|
17
|
+
# @param input [Array, Hash] the results of processing so far
|
18
|
+
# @param root [Array, Hash] the entire input
|
19
|
+
def interpret(input, root)
|
20
|
+
return [] unless input.is_a?(Hash) && input.key?(selector.name)
|
21
|
+
|
22
|
+
result = input[selector.name]
|
23
|
+
return [result] unless @next
|
24
|
+
|
25
|
+
# Forward result to next selector
|
26
|
+
@next.interpret(result, root)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Interprets the root identifer, returns results or forwards them to next selector.
|
8
|
+
#
|
9
|
+
# This is required at the beginning of every jsonpath query.
|
10
|
+
# It may also be used within filter selector expressions.
|
11
|
+
class RootNodeInterpreter < Base
|
12
|
+
# Start an expression chain using the entire, unfiltered input.
|
13
|
+
#
|
14
|
+
# @param _input [Array, Hash] the results of processing so far
|
15
|
+
# @param root [Array, Hash] the entire input
|
16
|
+
def interpret(_input, root)
|
17
|
+
return [root] unless @next
|
18
|
+
|
19
|
+
@next.interpret(root, root)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Janeway
|
6
|
+
module Interpreters
|
7
|
+
# Constructs a tree of interpreter objects
|
8
|
+
module TreeConstructor
|
9
|
+
# Fake interpreter which just returns the given value
|
10
|
+
Literal = Struct.new(:value) do
|
11
|
+
def interpret(*)
|
12
|
+
value
|
13
|
+
end
|
14
|
+
alias_method :node, :interpret
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.ast_node_to_interpreter(expr)
|
18
|
+
case expr
|
19
|
+
when AST::RootNode then Interpreters::RootNodeInterpreter.new(expr)
|
20
|
+
when AST::ArraySliceSelector then Interpreters::ArraySliceSelectorInterpreter.new(expr)
|
21
|
+
when AST::IndexSelector then Interpreters::IndexSelectorInterpreter.new(expr)
|
22
|
+
when AST::NameSelector then Interpreters::NameSelectorInterpreter.new(expr)
|
23
|
+
when AST::WildcardSelector then Interpreters::WildcardSelectorInterpreter.new(expr)
|
24
|
+
when AST::FilterSelector then Interpreters::FilterSelectorInterpreter.new(expr)
|
25
|
+
when AST::ChildSegment then Interpreters::ChildSegmentInterpreter.new(expr)
|
26
|
+
when AST::DescendantSegment then Interpreters::DescendantSegmentInterpreter.new(expr)
|
27
|
+
when AST::BinaryOperator then Interpreters::BinaryOperatorInterpreter.new(expr)
|
28
|
+
when AST::UnaryOperator then Interpreters::UnaryOperatorInterpreter.new(expr)
|
29
|
+
when AST::CurrentNode then Interpreters::CurrentNodeInterpreter.new(expr)
|
30
|
+
when AST::Function then Interpreters::FunctionInterpreter.new(expr)
|
31
|
+
when AST::StringType, AST::Number, AST::Null, AST::Boolean then Literal.new expr.value
|
32
|
+
when nil then nil # caller has no @next node
|
33
|
+
else
|
34
|
+
raise "Unknown AST expression: #{expr.inspect}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|