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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10f6ca7766f713b6272f971fd9f6b119895a59385dcebf0cfdb6e0b7d7b27de8
4
- data.tar.gz: b33f60624ea2fc41740c888ce00c818c8a873ca56c3d8f85f49b00abee7ea466
3
+ metadata.gz: 23479bd7bd49f2ac64ce5e8f27399df191efd5c7efe76aaca4fd5615cf429319
4
+ data.tar.gz: b1dea6ae594f5ab51f0105fb32f81ad03a5028223214f38e01c05cae0fad011b
5
5
  SHA512:
6
- metadata.gz: 2cf900d4b424ba23d436a6a8d3714e274ad5b6198c3062ec73760d489634105effed9350c665c29673032f899a855ea7d946b85b583b83e3b0d0730ec1e2244f
7
- data.tar.gz: 5b28155e3e56346e95f512688cde87310329626d8deb50af76f7ff2d59529f3bb8816b1c62d2bc402266feda338b29bd7197835543c96ea2930d572199cdb293
6
+ metadata.gz: 46d9ea3bfe2b012d538f53abcad7f29f604ce5c54de51f7f0354d78aa9be4871955cf4666898fdf0ba0e6262147b4d9f29570ca394046b1473597b2c8118f023
7
+ data.tar.gz: f323676b0cfeb66a2cbbad9331ff782dbd2691d42258ca607d9e6aa4bf511f6cc6e439962d40720b75a0aceee746f5ed224f4c3f4fbd68c29a2d093395807830
data/README.md CHANGED
@@ -1,64 +1,135 @@
1
1
  # Janeway JSONPath parser
2
2
 
3
- ### Purpose
4
-
5
3
  This is a [JsonPath](https://goessner.net/articles/JsonPath/) parser.
4
+ It strictly follows [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html) and passes the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite).
6
5
 
7
- It reads a JSON input file and a query.
8
- It uses the query to find and return a set of matching values from the input.
6
+ It reads a JSON input file and a query, and uses the query to find and return a set of matching values from the input.
9
7
  This does for JSON the same job that XPath does for XML.
10
8
 
11
9
  This project includes:
10
+
12
11
  * command-line tool to run jsonpath queries on a JSON input
13
12
  * ruby library to run jsonpath queries on a JSON input
14
13
 
15
- ### Goals
14
+ **Contents**
16
15
 
17
- * parse Goessner JSONPath, similar to https://github.com/joshbuddy/jsonpath
18
- * implement all of [IETF RFC 9535](https://github.com/ietf-wg-jsonpath)
19
- * don't use regular expressions for parsing, for performance
20
- * don't use `eval`, which is known to be an attack vector
21
- * be simple and fast with minimal dependencies
22
- * use helpful query parse errors which help understand and improve queries, rather than describing issues in the code
23
- * modern, linted ruby 3 code with frozen string literals
16
+ - [Install](#install)
17
+ - [Usage](#usage)
18
+ - [Related projects](#related-projects)
19
+ - [Goals](#goals)
20
+ - [Non-goals](#non-goals)
24
21
 
25
- ### Non-goals
22
+ ### Install
26
23
 
27
- * Changing behavior to follow [other implementations](https://cburgmer.github.io/json-path-comparison/)
24
+ Install the gem from the command-line:
25
+ ```
26
+ gem install janeway-jsonpath`
27
+ ```
28
28
 
29
- The JSONPath RFC was in draft status for a long time and has seen many changes.
30
- There are many implementations based on older drafts, or which add features that were never in the RFC at all.
29
+ or add it to your Gemfile:
30
+
31
+ ```
32
+ gem 'janeway-jsonpath', '~> 0.2.0'
33
+ ```
34
+
35
+ ### Usage
36
+
37
+ #### Janeway command-line tool
38
+
39
+ Give it a query and some input JSON, and it prints a JSON result.
40
+ Use single quotes around the JSON query to avoid shell interaction.
41
+ Example:
42
+
43
+ ```
44
+ $ janeway '$..book[?(@.price<10)]' example.json
45
+ [
46
+ {
47
+ "category": "reference",
48
+ "author": "Nigel Rees",
49
+ "title": "Sayings of the Century",
50
+ "price": 8.95
51
+ },
52
+ {
53
+ "category": "fiction",
54
+ "author": "Herman Melville",
55
+ "title": "Moby Dick",
56
+ "isbn": "0-553-21311-3",
57
+ "price": 8.99
58
+ }
59
+ ]
60
+ ```
61
+
62
+ You can also pipe JSON into it:
63
+ ```
64
+ $ cat example.json | janeway '$..book[?(@.price<10)]'
65
+ ```
66
+
67
+ See the help message for more capabilities: `janeway --help`
31
68
 
32
- The goal here is perfect adherence to the finalized [RFC 9535](https://github.com/ietf-wg-jsonpath) rather than adding features that are in other implementations.
69
+ #### Janeway ruby libarary
33
70
 
34
- The RFC was finalized in 2024, and it has a rigorous [suite of compliance tests.](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite)
71
+ Here's an example of using Janeway to execute a JSONPath query in ruby code:
72
+ ```ruby
73
+ require 'janeway'
74
+ require 'json'
35
75
 
36
- With these tools it is possible to have JSONPath implementations in many languages with identical behavior.
76
+ data = JSON.parse(File.read(ARGV.first))
77
+ results = Janeway.find_all('$..book[?(@.price<10)]', data)
78
+ ```
37
79
 
38
- ### Differences from joshbuddy/jsonpath
80
+ Alternatively, compile the query once, and share it between threads or ractors.
39
81
 
40
- The only other serious ruby implementation of jsonpath is joshbuddy/jsonpath.
41
- This implementation has been around for a long time.
42
- I personally have used it in a software project for several years.
43
- Here are differences I've found in porting this application from joshbuddy/jsonpath to janeway.
82
+ The Janeway::AST::Query object is not modified after parsing, so it is easy to freeze and share concurrently.
44
83
 
45
- * joshbuddy/jsonpath allows unquoted strings in filter comparisons.
46
- Examples:
47
- $ jsonpath '$.store.book[?(@.category==reference)]' example.json
48
- $ janeway '$.store.book[?(@.category=="reference")]' example.json
84
+ ```ruby
85
+ # Create ractors with their own data sources
86
+ ractors =
87
+ Array.new(4) do |index|
88
+ Ractor.new(index) do |i|
89
+ query = receive
90
+ data = JSON.parse File.read("input-file-#{i}.json")
91
+ puts query.find_all(data)
92
+ end
93
+ end
49
94
 
50
- * joshbuddy/jsonpath allows root selector to be omitted
51
- grid.first('gid').to_i
95
+ # Construct JSONPath query object and send it to each ractor
96
+ query = Janeway.compile('$..book[?(@.price<10)]')
97
+ ractors.each { |ractor| ractor.send(query).take }
98
+ ```
52
99
 
53
- $ jsonpath 'store' example.json
54
- $ janeway '$.store' example.json
100
+ ### Related Projects
55
101
 
56
- *
57
- $ jsonpath '$.nodes..services..id' spec/resources/grid-full.json
102
+ - [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath)
58
103
 
59
- ### Implementation
104
+ This is the classic 'jsonpath' ruby gem. It has been around since 2008.
105
+ It is not compliant with RFC 9535, because it was written long before the standard was finalized, but it's a capable and useful parser and has long been the best jsonpath library available for ruby.
106
+
107
+ See [Porting](PORTING.md) for tips on converting a ruby project from [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath) to janeway.
108
+
109
+ - [JPT - reference implementation based on parsing the ABNF grammar of RFC 9535](https://github.com/cabo/jpt)
110
+
111
+ Also there are many non-ruby implementations of RFC 9535, here are just a few:
112
+ - [jesse (dart)](https://github.com/f3ath/jessie)
113
+ - [python-jsonpath-rfc9535 (python)](https://github.com/jg-rp/python-jsonpath-rfc9535)
114
+ - [theory/jsonpath (go)](https://github.com/theory/jsonpath)
115
+
116
+ ### Goals
117
+
118
+ * maintain perfect compliance with [IETF RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html)
119
+ * raise helpful query parse errors designed to help users understand and improve queries, rather than describing issues in the code
120
+ * don't use regular expressions for parsing, for performance
121
+ * don't use `eval`, which is known to be an attack vector
122
+ * be simple and fast with minimal dependencies
123
+ * provide ruby-like accessors (eg. #each, #delete_if) for processing results
124
+ * modern, linted ruby 3 code with frozen string literals
125
+
126
+ ### Non-goals
127
+
128
+ * Changing behavior to follow [other implementations](https://cburgmer.github.io/json-path-comparison/)
129
+
130
+ The JSONPath RFC was in draft status for a long time and has seen many changes.
131
+ There are many implementations based on older drafts, and others which add features that were never in the RFC at all.
60
132
 
61
- Functionality is based on [IETF RFC 9535, "JSONPath: Query Expressions for JSON"](https://www.rfc-editor.org/rfc/rfc9535.html#filter-selector)
62
- The examples in the RFC have been implemented as unit tests.
133
+ The goal is adherence to the [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html) rather than adding features that are in other implementations. This implementation's results are supposed to be identical to other RFC-compliant implementations in [dart](https://github.com/f3ath/jessie), [python](https://github.com/jg-rp/python-jsonpath-rfc9535) and other languages.
63
134
 
64
- For details not covered in the RFC, it does the most reasonable thing based on [what other JSONPath parsers do](https://cburgmer.github.io/json-path-comparison/). However, this is always secondary to following the RFC. Many of the popular behaviors there contradict the RFC, so it has to be one or the other.
135
+ The RFC was finalized in 2024. With the finalized RFC and the rigorous [suite of compliance tests](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite), it is now possible to have JSONPath implementations in many languages with identical behavior.
@@ -136,14 +136,16 @@ module Janeway
136
136
  [lower, upper]
137
137
  end
138
138
 
139
- # ignores the brackets: argument, this always needs surrounding brackets
139
+ # @param brackets [Boolean] add surrounding brackets if true
140
140
  # @return [String]
141
- def to_s(*)
142
- if @step
143
- "[#{@start}:#{@end}:#{@step}]"
144
- else
145
- "[#{@start}:#{@end}]"
146
- end
141
+ def to_s(brackets: true, **_ignored)
142
+ index_str =
143
+ if @step
144
+ "#{@start}:#{@end}:#{@step}"
145
+ else
146
+ "#{@start}:#{@end}"
147
+ end
148
+ brackets ? "[#{index_str}]" : index_str
147
149
  end
148
150
 
149
151
  # @param level [Integer]
@@ -2,19 +2,21 @@
2
2
 
3
3
  module Janeway
4
4
  module AST
5
+ # AST node for binary operators:
6
+ # == != <= >= < > || &&
5
7
  class BinaryOperator < Janeway::AST::Expression
6
- attr_reader :operator, :left, :right
8
+ attr_reader :name, :left, :right
7
9
 
8
10
  def initialize(operator, left = nil, right = nil)
9
11
  super(nil)
10
12
  raise ArgumentError, "expect symbol, got #{operator.inspect}" unless operator.is_a?(Symbol)
11
13
 
12
- @operator = operator
14
+ @name = operator # eg. :equal
13
15
  self.left = left if left
14
16
  self.right = right if right
15
17
  end
16
18
 
17
- # Set the left-hand-side value
19
+ # Set the left-hand-side expression
18
20
  # @param expr [AST::Expression]
19
21
  def left=(expr)
20
22
  if comparison_operator? && !(expr.literal? || expr.singular_query?)
@@ -32,7 +34,7 @@ module Janeway
32
34
  @left = expr
33
35
  end
34
36
 
35
- # Set the left-hand-side value
37
+ # Set the left-hand-side expression
36
38
  # @param expr [AST::Expression]
37
39
  def right=(expr)
38
40
  if comparison_operator? && !(expr.literal? || expr.singular_query?)
@@ -52,23 +54,6 @@ module Janeway
52
54
  "(#{@left} #{operator_to_s} #{@right})"
53
55
  end
54
56
 
55
- private
56
-
57
- def operator_to_s
58
- case operator
59
- when :and then '&&'
60
- when :equal then '=='
61
- when :greater_than then '>'
62
- when :greater_than_or_equal then '>='
63
- when :less_than then '<'
64
- when :less_than_or_equal then '<='
65
- when :not_equal then '!='
66
- when :or then '||'
67
- else
68
- raise "unknown binary operator #{operator}"
69
- end
70
- end
71
-
72
57
  # @param level [Integer]
73
58
  # @return [Array]
74
59
  def tree(level)
@@ -91,12 +76,29 @@ module Janeway
91
76
  operator_type == :logical
92
77
  end
93
78
 
79
+ private
80
+
81
+ def operator_to_s
82
+ case name
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
94
+ end
95
+
94
96
  def operator_type
95
- case operator
97
+ case name
96
98
  when :and, :or then :logical
97
99
  when :equal, :not_equal, :greater_than, :greater_than_or_equal, :less_than, :less_than_or_equal then :comparison
98
100
  else
99
- raise "unknown binary operator #{operator}"
101
+ raise "unknown binary operator #{name}"
100
102
  end
101
103
  end
102
104
  end
@@ -29,8 +29,11 @@ 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
+
32
35
  def to_s
33
- "@#{@value}"
36
+ "@#{@next}"
34
37
  end
35
38
 
36
39
  # True if this is the root of a singular-query.
@@ -38,10 +41,10 @@ module Janeway
38
41
  #
39
42
  # @return [Boolean]
40
43
  def singular_query?
41
- return true unless @value # there are no following selectors
44
+ return true unless @next # there are no following selectors
42
45
 
43
46
  selector_types = []
44
- selector = @value
47
+ selector = @next
45
48
  loop do
46
49
  selector_types << selector.class
47
50
  selector = selector.next
@@ -54,13 +57,13 @@ module Janeway
54
57
  # True if this is a bare current node operator, without a following expression
55
58
  # @return [Boolean]
56
59
  def empty?
57
- @value.nil?
60
+ @next.nil?
58
61
  end
59
62
 
60
63
  # @param level [Integer]
61
64
  # @return [Array]
62
65
  def tree(level)
63
- [indented(level, '@'), @value.tree(indent + 1)]
66
+ [indented(level, '@'), @next.tree(indent + 1)]
64
67
  end
65
68
  end
66
69
  end
@@ -32,7 +32,7 @@ module Janeway
32
32
  # @param level [Integer]
33
33
  # @return [Array]
34
34
  def tree(level)
35
- [indented(level, "..#{@value}"), child&.tree(level + 1)]
35
+ [indented(level, "..#{@value}"), @next&.tree(level + 1)]
36
36
  end
37
37
  end
38
38
  end
@@ -7,13 +7,23 @@ module Janeway
7
7
  module AST
8
8
  INDENT = ' '
9
9
 
10
+ # Base class for jsonpath expressions.
11
+ #
12
+ # Every AST node is a subclass of this.
13
+ # This includes selectors, root and child identifiers, descendant segments,
14
+ # and the nodes that occur within a filter selector such as the current
15
+ # node identifier, operators and literals.
10
16
  class Expression
17
+ # Value provided by subclass constructor.
11
18
  attr_accessor :value
12
19
 
20
+ # Next expression in the AST, if any
21
+ attr_reader :next
22
+
13
23
  def initialize(val = nil)
14
24
  # don't set the instance variable if unused, because it makes the
15
25
  # "#inspect" output cleaner in rspec test failures
16
- @value = val unless val.nil? # false must be stored though!
26
+ @value = val unless val.nil? # false must be stored though!
17
27
  end
18
28
 
19
29
  # @return [String]
@@ -57,7 +57,7 @@ module Janeway
57
57
  # @return [Array]
58
58
  def tree(level)
59
59
  if @next
60
- [indented(level, to_s), indented(level + 1, @next&.tree)]
60
+ [indented(level, to_s), @next.tree(level + 1)]
61
61
  else
62
62
  [indented(level, to_s)]
63
63
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Janeway
4
4
  module AST
5
+ # Helper methods for AST Expressions.
5
6
  module Helpers
6
7
  # @param str [String] ascii string, CamelCase
7
8
  # @return [String] lowercase, with underscores
@@ -17,9 +17,8 @@ module Janeway
17
17
  end
18
18
 
19
19
  # @param brackets [Boolean] include brackets around selector
20
- # ** ignores keyword arguments that don't apply to this selector
21
- def to_s(brackets: true, **)
22
- brackets ? "[#{@value}]" : @value.to_s
20
+ def to_s(brackets: true, **_ignored)
21
+ brackets ? "[#{@value}]#{@next}" : "#{@value}#{@next}"
23
22
  end
24
23
  end
25
24
  end
@@ -17,8 +17,8 @@ module Janeway
17
17
  raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
18
18
  end
19
19
 
20
- # @param bracket [Boolean] request for bracket syntax
21
- # @param bracket [Boolean] include . prefix, if shorthand notation is used
20
+ # @param brackets [Boolean] request for bracket syntax
21
+ # @param dot_prefix [Boolean] include . prefix, if shorthand notation is used
22
22
  def to_s(brackets: false, dot_prefix: true)
23
23
  # Add quotes and surrounding brackets if the name includes chars that require quoting.
24
24
  # These chars are not allowed in dotted notation, only bracket notation.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Janeway
4
4
  module AST
5
+ # Represents JSON null literal within a filter expression
5
6
  class Null < Janeway::AST::Expression
6
7
  def to_s
7
8
  'null'
@@ -32,13 +32,54 @@ module Janeway
32
32
  Janeway::Interpreter.new(self).interpret(input)
33
33
  end
34
34
 
35
+ # Iterate through each value matched by the JSONPath query.
36
+ #
37
+ # @param input [Hash, Array] ruby object to be searched
38
+ # @yieldparam [Object] matched value
39
+ # @return [void]
40
+ def each(input, &block)
41
+ return enum_for(:each, input) unless block_given?
42
+
43
+ interpreter = Janeway::Interpreter.new(self)
44
+ interpreter.push Janeway::Interpreters::Yielder.new(&block)
45
+ interpreter.interpret(input)
46
+ end
47
+
35
48
  def to_s
36
49
  @root.to_s
37
50
  end
38
51
 
52
+ # Return a list of all the nodes in the AST.
53
+ # The AST of a jsonpath query is a straight line, so this is expressible as an array.
54
+ # The only part of the AST with branches is inside a filter selector, but that doesn't show up here.
55
+ # @return [Array<Expression>]
56
+ def node_list
57
+ nodes = []
58
+ node = @root
59
+ loop do
60
+ nodes << node
61
+ break unless node.next
62
+
63
+ node = node.next
64
+ end
65
+ nodes
66
+ end
67
+
68
+ # Remove the last selector in the query from the node list, and return the removed selector.
69
+ # @return [AST::Selector, nil] last selector in the query, if any
70
+ def pop
71
+ nodes = node_list
72
+ return nil if node_list.size == 1 # only 1 node, don't pop
73
+
74
+ # Remove the last selector and return it
75
+ last_node = nodes.pop
76
+ nodes.last.next = nil # delete the second-last node's link to the last node
77
+ last_node
78
+ end
79
+
39
80
  # Queries are considered equal if their ASTs evaluate to the same JSONPath string.
40
81
  #
41
- # The string output is generated by the AST and should be considered a "normalized"
82
+ # The string output is generated from the AST and should be considered a "normalized"
42
83
  # form of the query. It may have different whitespace and parentheses than the original
43
84
  # input but will be semantically equivalent.
44
85
  def ==(other)
@@ -14,8 +14,11 @@ 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
+
17
20
  def to_s
18
- "$#{@value}"
21
+ "$#{@next}"
19
22
  end
20
23
 
21
24
  # True if this is the root of a singular-query.
@@ -23,13 +26,13 @@ module Janeway
23
26
  #
24
27
  # @return [Boolean]
25
28
  def singular_query?
26
- return true unless @value # there are no following selectors
29
+ return true unless @next # there are no following selectors
27
30
 
28
31
  selector_types = []
29
- selector = @value
32
+ selector = @next
30
33
  loop do
31
34
  selector_types << selector.class
32
- selector = selector.next
35
+ selector = selector&.next
33
36
  break unless selector
34
37
  end
35
38
  allowed = [AST::IndexSelector, AST::NameSelector]
@@ -39,7 +42,7 @@ module Janeway
39
42
  # @param level [Integer]
40
43
  # @return [Array]
41
44
  def tree(level = 0)
42
- [indented(level, '$'), @value.tree(level + 1)]
45
+ [indented(level, '$'), @next.tree(level + 1)]
43
46
  end
44
47
  end
45
48
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Janeway
4
4
  module AST
5
+ # Represents a string literal within a filter expression.
6
+ # May be used within a comparison or a jsonpath function call.
5
7
  class StringType < Janeway::AST::Expression
6
8
  def to_s
7
9
  if @value.include?("'")
@@ -4,11 +4,11 @@ module Janeway
4
4
  module AST
5
5
  # Represent unary operators "!", "-"
6
6
  class UnaryOperator < Janeway::AST::Expression
7
- attr_accessor :operator, :operand
7
+ attr_accessor :name, :operand
8
8
 
9
9
  def initialize(operator, operand = nil)
10
10
  super()
11
- @operator = operator
11
+ @name = operator # eg. :not
12
12
  @operand = operand
13
13
  end
14
14
 
@@ -22,6 +22,7 @@ module Janeway
22
22
  @next = nil
23
23
  end
24
24
 
25
+ # @return [String]
25
26
  def to_s(brackets: false, dot_prefix: true)
26
27
  if brackets
27
28
  "[*]#{@next&.to_s(brackets: true)}"
data/lib/janeway/error.rb CHANGED
@@ -22,7 +22,7 @@ module Janeway
22
22
 
23
23
  def detailed_message
24
24
  msg = "Error: #{message}\nQuery: #{query}\n"
25
- msg += (' ' * location.col) + '^' if @location
25
+ msg += "#{' ' * location.col}^" if @location
26
26
  msg
27
27
  end
28
28
  end
@@ -27,16 +27,14 @@ module Janeway
27
27
  arg = parse_function_parameter
28
28
  parameters = [arg]
29
29
  raise Error, "Invalid parameter - count() expects node list, got #{arg.value.inspect}" if arg.literal?
30
- unless current.type == :group_end
31
- raise Error, 'Too many parameters for count() function call'
32
- end
30
+ raise Error, 'Too many parameters for count() function call' unless current.type == :group_end
33
31
 
34
32
  # Define function body
35
33
  AST::Function.new('count', parameters) do |node_list|
36
34
  if node_list.is_a?(Array)
37
35
  node_list.size
38
36
  else
39
- 1 # The count of a non-empty singulare noselist such as count(@) is always 1
37
+ 1 # the count of a non-empty singular nodelist such as count(@) is always 1.
40
38
  end
41
39
  end
42
40
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Janeway
4
+ # Mixin to provide JSONPath function handlers for Parser
4
5
  module Functions
5
6
  # The length() function extension provides a way to compute the length of a value
6
7
  # and make that available for further processing in the filter expression:
@@ -18,9 +19,7 @@ module Janeway
18
19
  unless arg.singular_query? || arg.literal?
19
20
  raise Error, "Invalid parameter - length() expects literal value or singular query, got #{arg.value.inspect}"
20
21
  end
21
- unless current.type == :group_end
22
- raise Error, 'Too many parameters for length() function call'
23
- end
22
+ raise Error, 'Too many parameters for length() function call' unless current.type == :group_end
24
23
 
25
24
  # Meaning of return value depends on the JSON type:
26
25
  # * string - number of Unicode scalar values in the string.
@@ -41,22 +41,16 @@ module Janeway
41
41
  # Read first parameter
42
42
  parameters = []
43
43
  parameters << parse_function_parameter
44
+ raise Error, 'Not enough parameters for match() function call' unless current.type == :union
44
45
 
45
- # Consume comma
46
- if current.type == :union
47
- consume # ,
48
- else
49
- raise Error, 'Not enough parameters for match() function call'
50
- end
46
+ consume # ,
51
47
 
52
48
  # Read second parameter (the regexp)
53
49
  # This could be a string, in which case it is available now.
54
50
  # Otherwise it is an expression that takes the regexp from the input document,
55
51
  # and the iregexp will not be available until interpretation.
56
52
  parameters << parse_function_parameter
57
- unless current.type == :group_end
58
- raise Error, 'Too many parameters for match() function call'
59
- end
53
+ raise Error, 'Too many parameters for match() function call' unless current.type == :group_end
60
54
 
61
55
  AST::Function.new('match', parameters) do |str, str_iregexp|
62
56
  if str.is_a?(String) && str_iregexp.is_a?(String)