janeway-jsonpath 1.0.0 → 1.1.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/lib/janeway/ast/array_slice_selector.rb +6 -90
- data/lib/janeway/ast/binary_operator.rb +57 -51
- data/lib/janeway/ast/child_segment.rb +2 -5
- data/lib/janeway/ast/current_node.rb +2 -15
- data/lib/janeway/ast/descendant_segment.rb +0 -3
- data/lib/janeway/ast/expression.rb +34 -5
- data/lib/janeway/ast/function.rb +15 -10
- data/lib/janeway/ast/name_selector.rb +12 -7
- data/lib/janeway/ast/root_node.rb +2 -15
- data/lib/janeway/ast/selector.rb +5 -6
- data/lib/janeway/ast/unary_operator.rb +12 -2
- data/lib/janeway/ast/wildcard_selector.rb +2 -2
- data/lib/janeway/enumerator.rb +2 -4
- data/lib/janeway/error.rb +6 -1
- data/lib/janeway/functions/count.rb +1 -8
- data/lib/janeway/functions/length.rb +1 -12
- data/lib/janeway/functions/match.rb +3 -8
- data/lib/janeway/functions/search.rb +3 -8
- data/lib/janeway/functions/value.rb +1 -7
- data/lib/janeway/functions.rb +75 -2
- data/lib/janeway/interpreter.rb +50 -31
- data/lib/janeway/interpreters/array_slice_selector_delete_if.rb +10 -13
- data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +56 -13
- data/lib/janeway/interpreters/binary_operator_interpreter.rb +18 -13
- data/lib/janeway/interpreters/descendant_segment_interpreter.rb +32 -13
- data/lib/janeway/interpreters/filter_selector_interpreter.rb +26 -34
- data/lib/janeway/interpreters/function_interpreter.rb +5 -1
- data/lib/janeway/interpreters/index_selector_delete_if.rb +0 -1
- data/lib/janeway/interpreters/iteration_helper.rb +19 -5
- data/lib/janeway/interpreters/root_node_delete_if.rb +57 -16
- data/lib/janeway/interpreters/tree_constructor.rb +0 -20
- data/lib/janeway/interpreters/wildcard_selector_delete_if.rb +16 -0
- data/lib/janeway/interpreters/yielder.rb +8 -2
- data/lib/janeway/lexer.rb +61 -78
- data/lib/janeway/location.rb +3 -1
- data/lib/janeway/normalized_path.rb +11 -2
- data/lib/janeway/parser.rb +46 -34
- data/lib/janeway/query.rb +47 -2
- data/lib/janeway/token.rb +2 -5
- data/lib/janeway/version.rb +1 -1
- metadata +2 -9
- data/lib/janeway/interpreters/array_slice_selector_deleter.rb +0 -41
- data/lib/janeway/interpreters/child_segment_deleter.rb +0 -19
- data/lib/janeway/interpreters/filter_selector_deleter.rb +0 -65
- data/lib/janeway/interpreters/index_selector_deleter.rb +0 -26
- data/lib/janeway/interpreters/name_selector_deleter.rb +0 -27
- data/lib/janeway/interpreters/root_node_deleter.rb +0 -34
- data/lib/janeway/interpreters/wildcard_selector_deleter.rb +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ff463dd64e17811dddaa23fb3fb82c9bc5cb728dd5b288574f39010174c4f705
|
|
4
|
+
data.tar.gz: 27f5f8d6c39435fb4ec97eb8a96c7e204c2f06a1e3e7dc569b606d4a52b5cf58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 113d48240302143e72de6249d1975d0be52074374e94abd0d2a340e54867031653d6f912e0ccb3ab25e0b9734a3d33119bea1fb100c4283bce95560f9c02ad69
|
|
7
|
+
data.tar.gz: cfdca8a82c1c38f7b3b646afcca7c54d6d996564f2f191cb9cd407fa5abcac8c258c4d4534ce7cd437739174298fda0c66b3d41602e4058a47b7f5d94e4b1208
|
|
@@ -22,6 +22,11 @@ module Janeway
|
|
|
22
22
|
# "explicit" arguments, since they're interpreted differently.
|
|
23
23
|
#
|
|
24
24
|
class ArraySliceSelector < Janeway::AST::Selector
|
|
25
|
+
# Raw start / end / step values from the query source. Any of them may
|
|
26
|
+
# be nil, meaning "use the default for the current step direction" —
|
|
27
|
+
# the interpreter resolves that.
|
|
28
|
+
attr_reader :start, :end, :step
|
|
29
|
+
|
|
25
30
|
# @param start [Integer, nil]
|
|
26
31
|
# @param end_ [Integer, nil]
|
|
27
32
|
# @param step [Integer, nil]
|
|
@@ -40,102 +45,13 @@ module Janeway
|
|
|
40
45
|
raise Error, "Array slice selector value too large: #{arg.inspect}" if arg > INTEGER_MAX
|
|
41
46
|
end
|
|
42
47
|
|
|
43
|
-
# Nil values are kept to indicate that the
|
|
48
|
+
# Nil values are kept to indicate that the default value should be used.
|
|
44
49
|
# The interpreter selects the actual values.
|
|
45
50
|
@start = start
|
|
46
51
|
@end = end_
|
|
47
52
|
@step = step
|
|
48
53
|
end
|
|
49
54
|
|
|
50
|
-
# Return the step size to use for stepping through the array.
|
|
51
|
-
# Defaults to 1 if it was not given to the constructor.
|
|
52
|
-
#
|
|
53
|
-
# @return [Integer]
|
|
54
|
-
def step
|
|
55
|
-
# The iteration behavior of jsonpath does not match that of ruby Integer#step.
|
|
56
|
-
# Support the behavior of Integer#step, which needs this:
|
|
57
|
-
# 1. for stepping forward between positive numbers, use a positive number
|
|
58
|
-
# 2. for stepping backward between positive numbers, use a negative number
|
|
59
|
-
# 3. for stepping backward from positive to negative, use a negative number
|
|
60
|
-
# 4. for stepping backward from negative to negative, use a positive number
|
|
61
|
-
# Case #4 has to be detected and the sign of step inverted
|
|
62
|
-
@step || 1
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Return the start index to use for stepping through the array, based on a specified array size
|
|
66
|
-
#
|
|
67
|
-
# @param input_size [Integer]
|
|
68
|
-
# @return [Integer]
|
|
69
|
-
def upper_index(input_size)
|
|
70
|
-
calculate_index_values(input_size).last
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Return the end index to use for stepping through the array, based on a specified array size
|
|
74
|
-
# End index is calculated to omit the final index value, as per the RFC.
|
|
75
|
-
#
|
|
76
|
-
# @param input_size [Integer]
|
|
77
|
-
# @return [Integer]
|
|
78
|
-
def lower_index(input_size)
|
|
79
|
-
calculate_index_values(input_size).first
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Assign lower and upper bounds to instance variables, based on the input array size.
|
|
83
|
-
# @see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.3.4.2.2-3
|
|
84
|
-
#
|
|
85
|
-
# @param input_size [Integer]
|
|
86
|
-
def calculate_index_values(input_size)
|
|
87
|
-
if step >= 0
|
|
88
|
-
start = @start || 0
|
|
89
|
-
end_ = @end || input_size
|
|
90
|
-
else
|
|
91
|
-
start = @start || (input_size - 1)
|
|
92
|
-
end_ = @end || ((-1 * input_size) - 1)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
bounds(start, end_, step, input_size)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# NOTE: Conversion of start:end:step to array indexes is defined as pseudocode
|
|
99
|
-
# in the IETF spec.
|
|
100
|
-
# The methods #normalize and #bounds are ruby implementations of that code.
|
|
101
|
-
# Don't make changes here without referring to the original code in the spec.
|
|
102
|
-
# @see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.3.4.2.2-6
|
|
103
|
-
|
|
104
|
-
# IETF: Slice expression parameters start and end are not directly usable
|
|
105
|
-
# as slice bounds and must first be normalized.
|
|
106
|
-
#
|
|
107
|
-
# @param index [Integer]
|
|
108
|
-
# @param len [Integer]
|
|
109
|
-
def normalize(index, len)
|
|
110
|
-
return index if index >= 0
|
|
111
|
-
|
|
112
|
-
len + index
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# IETF: Slice expression parameters start and end are used to derive
|
|
116
|
-
# slice bounds lower and upper. The direction of the iteration, defined
|
|
117
|
-
# by the sign of step, determines which of the parameters is the lower
|
|
118
|
-
# bound and which is the upper bound:¶
|
|
119
|
-
# @see https://www.rfc-editor.org/rfc/rfc9535.html#section-2.3.4.2.2-9
|
|
120
|
-
# @param start [Integer] start index, normalized
|
|
121
|
-
# @param end_ [Integer] end index, normalized
|
|
122
|
-
# @param step [Integer] step value
|
|
123
|
-
# @param len [Integer] length of input array
|
|
124
|
-
def bounds(start, end_, step, len)
|
|
125
|
-
n_start = normalize(start, len)
|
|
126
|
-
n_end = normalize(end_, len)
|
|
127
|
-
|
|
128
|
-
if step >= 0
|
|
129
|
-
lower = n_start.clamp(0, len)
|
|
130
|
-
upper = n_end.clamp(0, len)
|
|
131
|
-
else
|
|
132
|
-
upper = n_start.clamp(-1, len - 1)
|
|
133
|
-
lower = n_end.clamp(-1, len - 1)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
[lower, upper]
|
|
137
|
-
end
|
|
138
|
-
|
|
139
55
|
# @param brackets [Boolean] add surrounding brackets if true
|
|
140
56
|
# @return [String]
|
|
141
57
|
def to_s(brackets: true, **_ignored)
|
|
@@ -5,48 +5,31 @@ module Janeway
|
|
|
5
5
|
# AST node for binary operators:
|
|
6
6
|
# == != <= >= < > || &&
|
|
7
7
|
class BinaryOperator < Janeway::AST::Expression
|
|
8
|
-
attr_reader :name
|
|
8
|
+
attr_reader :name
|
|
9
|
+
attr_accessor :left, :right
|
|
9
10
|
|
|
10
11
|
def initialize(operator, left = nil, right = nil)
|
|
11
12
|
super(nil)
|
|
12
13
|
raise ArgumentError, "expect symbol, got #{operator.inspect}" unless operator.is_a?(Symbol)
|
|
13
14
|
|
|
14
15
|
@name = operator # eg. :equal
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
@left = left
|
|
17
|
+
@right = right
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
@left = expr
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Set the left-hand-side expression
|
|
38
|
-
# @param expr [AST::Expression]
|
|
39
|
-
def right=(expr)
|
|
40
|
-
if comparison_operator? && !(expr.literal? || expr.singular_query?)
|
|
41
|
-
raise Error, "Expression #{expr} does not produce a singular value for #{operator_to_s} comparison"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Compliance test suite requires error for this, but don't have go so far as to bar every literal
|
|
45
|
-
if expr.is_a?(Boolean) && left.is_a?(Boolean)
|
|
46
|
-
raise Error, "Literal \"#{expr}\" must be compared to an expression, not another literal (\"#{left}\")"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
@right = expr
|
|
20
|
+
# Validate the currently-set left and right operands as a well-formed
|
|
21
|
+
# binary expression. Called by the parser once both sides are assigned —
|
|
22
|
+
# the previous per-setter validation was order-dependent (left=
|
|
23
|
+
# couldn't see right and vice-versa) and missed the both-literal case
|
|
24
|
+
# when the parser assigned left first.
|
|
25
|
+
#
|
|
26
|
+
# @raise [Janeway::Error] on any semantic error
|
|
27
|
+
def validate!
|
|
28
|
+
raise Error, 'BinaryOperator requires both left and right' unless @left && @right
|
|
29
|
+
|
|
30
|
+
validate_side(@left)
|
|
31
|
+
validate_side(@right)
|
|
32
|
+
validate_pair
|
|
50
33
|
end
|
|
51
34
|
|
|
52
35
|
def to_s
|
|
@@ -76,31 +59,54 @@ module Janeway
|
|
|
76
59
|
operator_type == :logical
|
|
77
60
|
end
|
|
78
61
|
|
|
62
|
+
# Single source of truth for what binary operators exist and how they
|
|
63
|
+
# render / classify. Adding a new operator here fills both call sites.
|
|
64
|
+
OPERATOR_META = {
|
|
65
|
+
and: { str: '&&', type: :logical },
|
|
66
|
+
or: { str: '||', type: :logical },
|
|
67
|
+
equal: { str: '==', type: :comparison },
|
|
68
|
+
not_equal: { str: '!=', type: :comparison },
|
|
69
|
+
less_than: { str: '<', type: :comparison },
|
|
70
|
+
less_than_or_equal: { str: '<=', type: :comparison },
|
|
71
|
+
greater_than: { str: '>', type: :comparison },
|
|
72
|
+
greater_than_or_equal: { str: '>=', type: :comparison },
|
|
73
|
+
}.freeze
|
|
74
|
+
|
|
79
75
|
private
|
|
80
76
|
|
|
77
|
+
def operator_meta
|
|
78
|
+
OPERATOR_META.fetch(name) { raise "unknown binary operator #{name}" }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
81
|
def operator_to_s
|
|
82
|
-
|
|
83
|
-
when :and then '&&'
|
|
84
|
-
when :equal then '=='
|
|
85
|
-
when :greater_than then '>'
|
|
86
|
-
when :greater_than_or_equal then '>='
|
|
87
|
-
when :less_than then '<'
|
|
88
|
-
when :less_than_or_equal then '<='
|
|
89
|
-
when :not_equal then '!='
|
|
90
|
-
when :or then '||'
|
|
91
|
-
else
|
|
92
|
-
raise "unknown binary operator #{name}"
|
|
93
|
-
end
|
|
82
|
+
operator_meta[:str]
|
|
94
83
|
end
|
|
95
84
|
|
|
96
85
|
def operator_type
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
86
|
+
operator_meta[:type]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Per-side checks that apply to either operand of a comparison.
|
|
90
|
+
def validate_side(expr)
|
|
91
|
+
return unless comparison_operator?
|
|
92
|
+
|
|
93
|
+
unless expr.literal? || expr.singular_query?
|
|
94
|
+
raise Error, "Expression #{expr} does not produce a singular value for #{operator_to_s} comparison"
|
|
95
|
+
end
|
|
96
|
+
if expr.is_a?(AST::Function) && !expr.literal?
|
|
97
|
+
raise Error,
|
|
98
|
+
"Function #{expr} returns a non-comparable value which is not usable for #{operator_to_s} comparison"
|
|
102
99
|
end
|
|
103
100
|
end
|
|
101
|
+
|
|
102
|
+
# Checks that need both sides in scope.
|
|
103
|
+
def validate_pair
|
|
104
|
+
return unless @left.is_a?(Boolean) && @right.is_a?(Boolean)
|
|
105
|
+
|
|
106
|
+
# Compliance test suite requires error for this, but don't go so far as to bar every literal.
|
|
107
|
+
raise Error,
|
|
108
|
+
"Literal \"#{@right}\" must be compared to an expression, not another literal (\"#{@left}\")"
|
|
109
|
+
end
|
|
104
110
|
end
|
|
105
111
|
end
|
|
106
112
|
end
|
|
@@ -18,9 +18,6 @@ module Janeway
|
|
|
18
18
|
|
|
19
19
|
def_delegators :@value, :size, :first, :last, :each, :map, :empty?
|
|
20
20
|
|
|
21
|
-
# Subsequent expression that modifies the result of this selector list.
|
|
22
|
-
attr_accessor :next
|
|
23
|
-
|
|
24
21
|
def initialize
|
|
25
22
|
super([]) # @value holds the expressions in the selector
|
|
26
23
|
end
|
|
@@ -32,8 +29,8 @@ module Janeway
|
|
|
32
29
|
@value << selector
|
|
33
30
|
end
|
|
34
31
|
|
|
35
|
-
def to_s(with_child: true)
|
|
36
|
-
str = @value.map { |selector| selector.to_s(brackets: false, dot_prefix: false) }.join(', ')
|
|
32
|
+
def to_s(with_child: true, **)
|
|
33
|
+
str = @value.map { |selector| selector.to_s(brackets: false, dot_prefix: false, bracketed: true) }.join(', ')
|
|
37
34
|
with_child ? "[#{str}]#{@next}" : "[#{str}]"
|
|
38
35
|
end
|
|
39
36
|
|
|
@@ -29,9 +29,6 @@ module Janeway
|
|
|
29
29
|
#
|
|
30
30
|
# Construct accepts an optional Selector which will be applied to the "current" node
|
|
31
31
|
class CurrentNode < Janeway::AST::Expression
|
|
32
|
-
# Subsequent expression that modifies the output of this expression
|
|
33
|
-
attr_accessor :next
|
|
34
|
-
|
|
35
32
|
def to_s
|
|
36
33
|
"@#{@next}"
|
|
37
34
|
end
|
|
@@ -41,17 +38,7 @@ module Janeway
|
|
|
41
38
|
#
|
|
42
39
|
# @return [Boolean]
|
|
43
40
|
def singular_query?
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
selector_types = []
|
|
47
|
-
selector = @next
|
|
48
|
-
loop do
|
|
49
|
-
selector_types << selector.class
|
|
50
|
-
selector = selector.next
|
|
51
|
-
break unless selector
|
|
52
|
-
end
|
|
53
|
-
allowed = [AST::IndexSelector, AST::NameSelector]
|
|
54
|
-
selector_types.uniq.all? { allowed.include?(_1) }
|
|
41
|
+
chain_of?(AST::IndexSelector, AST::NameSelector)
|
|
55
42
|
end
|
|
56
43
|
|
|
57
44
|
# True if this is a bare current node operator, without a following expression
|
|
@@ -63,7 +50,7 @@ module Janeway
|
|
|
63
50
|
# @param level [Integer]
|
|
64
51
|
# @return [Array]
|
|
65
52
|
def tree(level)
|
|
66
|
-
[indented(level, '@'), @next
|
|
53
|
+
[indented(level, '@'), @next&.tree(level + 1)]
|
|
67
54
|
end
|
|
68
55
|
end
|
|
69
56
|
end
|
|
@@ -16,9 +16,6 @@ module Janeway
|
|
|
16
16
|
# $..[*, *] All values, twice non-deterministic order
|
|
17
17
|
# $..[0, 1] Multiple segments
|
|
18
18
|
class DescendantSegment < Janeway::AST::Selector
|
|
19
|
-
# Subsequent expression that modifies the result of this selector list.
|
|
20
|
-
attr_accessor :next
|
|
21
|
-
|
|
22
19
|
def to_s
|
|
23
20
|
"..#{@next&.to_s(dot_prefix: false)}"
|
|
24
21
|
end
|
|
@@ -6,6 +6,9 @@ require_relative 'error'
|
|
|
6
6
|
module Janeway
|
|
7
7
|
module AST
|
|
8
8
|
INDENT = ' '
|
|
9
|
+
# Precomputed indent prefixes so #indented does not do `INDENT * level`
|
|
10
|
+
# (a fresh String allocation) on every line printed.
|
|
11
|
+
INDENTS = Array.new(16) { |i| (INDENT * i).freeze }.freeze
|
|
9
12
|
|
|
10
13
|
# Base class for jsonpath expressions.
|
|
11
14
|
#
|
|
@@ -17,8 +20,10 @@ module Janeway
|
|
|
17
20
|
# Value provided by subclass constructor.
|
|
18
21
|
attr_accessor :value
|
|
19
22
|
|
|
20
|
-
# Next expression in the AST, if any
|
|
21
|
-
|
|
23
|
+
# Next expression in the AST, if any. Writable so the parser can link
|
|
24
|
+
# nodes as it constructs the chain, and so Query#dup / #pop can rewire
|
|
25
|
+
# the top-level chain.
|
|
26
|
+
attr_accessor :next
|
|
22
27
|
|
|
23
28
|
def initialize(val = nil)
|
|
24
29
|
# don't set the instance variable if unused, because it makes the
|
|
@@ -28,8 +33,16 @@ module Janeway
|
|
|
28
33
|
|
|
29
34
|
# @return [String]
|
|
30
35
|
def type
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
self.class.type_name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Cached camelcase→underscore transform of the class name.
|
|
40
|
+
# Same value on every instance of the class — compute once per class.
|
|
41
|
+
#
|
|
42
|
+
# @return [String]
|
|
43
|
+
def self.type_name
|
|
44
|
+
@type_name ||=
|
|
45
|
+
Helpers.camelcase_to_underscore(name.split('::').last).freeze
|
|
33
46
|
end
|
|
34
47
|
|
|
35
48
|
# Return the given message, indented
|
|
@@ -38,7 +51,7 @@ module Janeway
|
|
|
38
51
|
# @param msg [String]
|
|
39
52
|
# @return [String]
|
|
40
53
|
def indented(level, msg)
|
|
41
|
-
|
|
54
|
+
(INDENTS[level] || (INDENT * level)) + msg
|
|
42
55
|
end
|
|
43
56
|
|
|
44
57
|
# @param level [Integer]
|
|
@@ -60,6 +73,22 @@ module Janeway
|
|
|
60
73
|
def singular_query?
|
|
61
74
|
false
|
|
62
75
|
end
|
|
76
|
+
|
|
77
|
+
# True if every selector in the @next chain is one of the allowed classes.
|
|
78
|
+
# An empty chain (no @next) returns true. Extracted from the identical
|
|
79
|
+
# implementations that used to live in CurrentNode and RootNode.
|
|
80
|
+
#
|
|
81
|
+
# @param allowed_classes [Array<Class>]
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def chain_of?(*allowed_classes)
|
|
84
|
+
selector = @next
|
|
85
|
+
while selector
|
|
86
|
+
return false unless allowed_classes.include?(selector.class)
|
|
87
|
+
|
|
88
|
+
selector = selector.next
|
|
89
|
+
end
|
|
90
|
+
true
|
|
91
|
+
end
|
|
63
92
|
end
|
|
64
93
|
end
|
|
65
94
|
end
|
data/lib/janeway/ast/function.rb
CHANGED
|
@@ -5,18 +5,28 @@ require_relative 'expression'
|
|
|
5
5
|
module Janeway
|
|
6
6
|
module AST
|
|
7
7
|
# Represents a JSONPath built-in function.
|
|
8
|
+
#
|
|
9
|
+
# This is pure structure: name + parameters. The body / literal_return
|
|
10
|
+
# metadata lives in Functions::REGISTRY (looked up by name), so AST nodes
|
|
11
|
+
# don't drag closures over the parser's binding.
|
|
8
12
|
class Function < Janeway::AST::Expression
|
|
9
13
|
alias name value
|
|
10
14
|
|
|
11
|
-
attr_reader :parameters
|
|
15
|
+
attr_reader :parameters
|
|
12
16
|
|
|
13
|
-
def initialize(name, parameters
|
|
17
|
+
def initialize(name, parameters)
|
|
14
18
|
raise ArgumentError, "expect string, got #{name.inspect}" unless name.is_a?(String)
|
|
15
19
|
|
|
20
|
+
spec = Functions::REGISTRY[name]
|
|
21
|
+
raise ArgumentError, "unknown function #{name.inspect}" unless spec
|
|
22
|
+
unless spec[:arity] == parameters.size
|
|
23
|
+
raise ArgumentError,
|
|
24
|
+
"function #{name.inspect}: declared arity #{spec[:arity]} does not " \
|
|
25
|
+
"match parameter count #{parameters.size}"
|
|
26
|
+
end
|
|
27
|
+
|
|
16
28
|
super(name)
|
|
17
29
|
@parameters = parameters
|
|
18
|
-
@body = body
|
|
19
|
-
raise "expect body to be a Proc, got #{body.class}" unless body.is_a?(Proc)
|
|
20
30
|
end
|
|
21
31
|
|
|
22
32
|
def to_s
|
|
@@ -39,12 +49,7 @@ module Janeway
|
|
|
39
49
|
|
|
40
50
|
# True if the function's return value is a literal
|
|
41
51
|
def literal?
|
|
42
|
-
|
|
43
|
-
when 'length', 'count', 'value' then true
|
|
44
|
-
when 'search', 'match' then false
|
|
45
|
-
else
|
|
46
|
-
raise "Unknown jsonpath function #{name}"
|
|
47
|
-
end
|
|
52
|
+
Functions::REGISTRY.fetch(name)[:literal_return]
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
end
|
|
@@ -17,16 +17,21 @@ module Janeway
|
|
|
17
17
|
raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# Chars that force bracket notation (they are not permitted in the
|
|
21
|
+
# dotted shorthand).
|
|
22
|
+
SPECIAL_CHARS = /[ .]/
|
|
23
|
+
|
|
20
24
|
# @param brackets [Boolean] request for bracket syntax
|
|
21
25
|
# @param dot_prefix [Boolean] include . prefix, if shorthand notation is used
|
|
22
|
-
|
|
26
|
+
# @param bracketed [Boolean] caller already provided brackets (e.g. within a union);
|
|
27
|
+
# emit the quoted name only, without adding brackets
|
|
28
|
+
def to_s(brackets: false, dot_prefix: true, bracketed: false)
|
|
23
29
|
# Add quotes and surrounding brackets if the name includes chars that require quoting.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"[#{name_str}]#{@next}"
|
|
30
|
+
brackets ||= @value.match?(SPECIAL_CHARS)
|
|
31
|
+
if bracketed
|
|
32
|
+
"#{quote(@value)}#{@next}"
|
|
33
|
+
elsif brackets
|
|
34
|
+
"[#{quote(@value)}]#{@next}"
|
|
30
35
|
elsif dot_prefix
|
|
31
36
|
".#{@value}#{@next}"
|
|
32
37
|
else # omit dot prefix after a descendant segment
|
|
@@ -14,9 +14,6 @@ module Janeway
|
|
|
14
14
|
# $(? $.key1 == $.key2 )
|
|
15
15
|
#
|
|
16
16
|
class RootNode < Janeway::AST::Expression
|
|
17
|
-
# Subsequent expression that modifies the output of this expression
|
|
18
|
-
attr_accessor :next
|
|
19
|
-
|
|
20
17
|
def to_s
|
|
21
18
|
"$#{@next}"
|
|
22
19
|
end
|
|
@@ -26,23 +23,13 @@ module Janeway
|
|
|
26
23
|
#
|
|
27
24
|
# @return [Boolean]
|
|
28
25
|
def singular_query?
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
selector_types = []
|
|
32
|
-
selector = @next
|
|
33
|
-
loop do
|
|
34
|
-
selector_types << selector.class
|
|
35
|
-
selector = selector&.next
|
|
36
|
-
break unless selector
|
|
37
|
-
end
|
|
38
|
-
allowed = [AST::IndexSelector, AST::NameSelector]
|
|
39
|
-
selector_types.uniq.all? { allowed.include?(_1) }
|
|
26
|
+
chain_of?(AST::IndexSelector, AST::NameSelector)
|
|
40
27
|
end
|
|
41
28
|
|
|
42
29
|
# @param level [Integer]
|
|
43
30
|
# @return [Array]
|
|
44
31
|
def tree(level = 0)
|
|
45
|
-
[indented(level, '$'), @next
|
|
32
|
+
[indented(level, '$'), @next&.tree(level + 1)]
|
|
46
33
|
end
|
|
47
34
|
end
|
|
48
35
|
end
|
data/lib/janeway/ast/selector.rb
CHANGED
|
@@ -23,12 +23,11 @@ module Janeway
|
|
|
23
23
|
# Filter expressions ?<logical-expr> select certain children of an object or array, as in:
|
|
24
24
|
# $.store.book[?@.price < 10].title
|
|
25
25
|
class Selector < Janeway::AST::Expression
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
26
|
+
# Intentionally NO #== override. The previous implementation compared
|
|
27
|
+
# only `value`, ignoring class and @next, which made two selector chains
|
|
28
|
+
# with identical heads but different tails compare equal (and matched
|
|
29
|
+
# across selector types by coincidence of value). Query#== is the
|
|
30
|
+
# correct equality entry point for query comparisons.
|
|
32
31
|
end
|
|
33
32
|
end
|
|
34
33
|
end
|
|
@@ -13,13 +13,23 @@ module Janeway
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def to_s
|
|
16
|
-
"#{
|
|
16
|
+
"#{operator_to_s}#{operand}"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# @param level [Integer]
|
|
20
20
|
# @return [Array]
|
|
21
21
|
def tree(level)
|
|
22
|
-
indented(level, "#{
|
|
22
|
+
indented(level, "#{operator_to_s}#{operand}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def operator_to_s
|
|
28
|
+
case @name
|
|
29
|
+
when :not then '!'
|
|
30
|
+
else
|
|
31
|
+
raise "unknown unary operator #{@name}"
|
|
32
|
+
end
|
|
23
33
|
end
|
|
24
34
|
end
|
|
25
35
|
end
|
|
@@ -23,7 +23,7 @@ module Janeway
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# @return [String]
|
|
26
|
-
def to_s(brackets: false, dot_prefix: true)
|
|
26
|
+
def to_s(brackets: false, dot_prefix: true, **)
|
|
27
27
|
if brackets
|
|
28
28
|
"[*]#{@next&.to_s(brackets: true)}"
|
|
29
29
|
elsif dot_prefix
|
|
@@ -36,7 +36,7 @@ module Janeway
|
|
|
36
36
|
# @param level [Integer]
|
|
37
37
|
# @return [Array]
|
|
38
38
|
def tree(level)
|
|
39
|
-
[indented(level, '*'), @next
|
|
39
|
+
[indented(level, '*'), @next&.tree(level + 1)]
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|
data/lib/janeway/enumerator.rb
CHANGED
|
@@ -134,9 +134,7 @@ module Janeway
|
|
|
134
134
|
# @!visibility private
|
|
135
135
|
# @return [Object, Object, String] parent object (Hash / Array), key/index (String / Integer), path to parent
|
|
136
136
|
def find_parent
|
|
137
|
-
|
|
138
|
-
parent_query = @query.dup
|
|
139
|
-
selector = parent_query.pop # returns a name or index selector
|
|
137
|
+
parent_query, leaf = @query.parent_query
|
|
140
138
|
|
|
141
139
|
# Find parent element, give up if parent does not exist
|
|
142
140
|
results = Interpreter.new(parent_query).interpret(@input)
|
|
@@ -146,7 +144,7 @@ module Janeway
|
|
|
146
144
|
else raise "query #{parent_query} matched multiple elements!" # not possible for singular query
|
|
147
145
|
end
|
|
148
146
|
|
|
149
|
-
[parent,
|
|
147
|
+
[parent, leaf.value, parent_query.to_s]
|
|
150
148
|
end
|
|
151
149
|
|
|
152
150
|
# Insert value into hash at the given key
|
data/lib/janeway/error.rb
CHANGED
|
@@ -20,7 +20,12 @@ module Janeway
|
|
|
20
20
|
# @param query [String] entire query string
|
|
21
21
|
# @param location [Location] location of error
|
|
22
22
|
def initialize(msg, query = nil, location = nil)
|
|
23
|
-
|
|
23
|
+
full = +''
|
|
24
|
+
full << "Jsonpath query #{query}" if query
|
|
25
|
+
full << " (col #{location.col})" if location
|
|
26
|
+
full << ' - ' unless full.empty?
|
|
27
|
+
full << msg
|
|
28
|
+
super(full)
|
|
24
29
|
@query = query
|
|
25
30
|
@location = location
|
|
26
31
|
end
|
|
@@ -29,14 +29,7 @@ module Janeway
|
|
|
29
29
|
raise Error, "Invalid parameter - count() expects node list, got #{arg.value.inspect}" if arg.literal?
|
|
30
30
|
raise Error, 'Too many parameters for count() function call' unless current.type == :group_end
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
AST::Function.new('count', parameters) do |node_list|
|
|
34
|
-
if node_list.is_a?(Array)
|
|
35
|
-
node_list.size
|
|
36
|
-
else
|
|
37
|
-
1 # the count of a non-empty singular nodelist such as count(@) is always 1.
|
|
38
|
-
end
|
|
39
|
-
end
|
|
32
|
+
AST::Function.new('count', parameters)
|
|
40
33
|
end
|
|
41
34
|
end
|
|
42
35
|
end
|
|
@@ -21,18 +21,7 @@ module Janeway
|
|
|
21
21
|
end
|
|
22
22
|
raise err('Too many parameters for length() function call') unless current.type == :group_end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
# * string - number of Unicode scalar values in the string.
|
|
26
|
-
# * array - number of elements in the array.
|
|
27
|
-
# * object - number of members in the object.
|
|
28
|
-
# For any other argument value, the result is the special result Nothing.
|
|
29
|
-
AST::Function.new('length', parameters) do |value|
|
|
30
|
-
if [Array, Hash, String].include?(value.class)
|
|
31
|
-
value.size
|
|
32
|
-
else
|
|
33
|
-
:nothing
|
|
34
|
-
end
|
|
35
|
-
end
|
|
24
|
+
AST::Function.new('length', parameters)
|
|
36
25
|
end
|
|
37
26
|
end
|
|
38
27
|
end
|