janeway-jsonpath 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07de626741809ed0f0c9d62f626a45a1eb91513cfda3b536160060428e23cda0
4
- data.tar.gz: 70953f9cee1de853145ff4eb56a80ed535a93d6126211982b7adaada9438c9a0
3
+ metadata.gz: 10f6ca7766f713b6272f971fd9f6b119895a59385dcebf0cfdb6e0b7d7b27de8
4
+ data.tar.gz: b33f60624ea2fc41740c888ce00c818c8a873ca56c3d8f85f49b00abee7ea466
5
5
  SHA512:
6
- metadata.gz: 003fbc4fe8e79e174f8e06147f4790554ca2577ed0b29503f1ef61619a33ef082a7e2f2ee3a39a2eb412e0b28778ed90b7ad3c9b6205d67888b2121f9f32e488
7
- data.tar.gz: ee42a2d1601aa765d748a5a806f79653622a7cfc83c1fdbe87a04cd0c3f7effb67b5a8c3a88fa6733bfb0c50fb138fa0b152aed58c5a28b646032a6951309b84
6
+ metadata.gz: 2cf900d4b424ba23d436a6a8d3714e274ad5b6198c3062ec73760d489634105effed9350c665c29673032f899a855ea7d946b85b583b83e3b0d0730ec1e2244f
7
+ data.tar.gz: 5b28155e3e56346e95f512688cde87310329626d8deb50af76f7ff2d59529f3bb8816b1c62d2bc402266feda338b29bd7197835543c96ea2930d572199cdb293
data/README.md CHANGED
@@ -24,7 +24,7 @@ This project includes:
24
24
 
25
25
  ### Non-goals
26
26
 
27
- * Changing behavior to follow [other implementations] (https://cburgmer.github.io/json-path-comparison/)
27
+ * Changing behavior to follow [other implementations](https://cburgmer.github.io/json-path-comparison/)
28
28
 
29
29
  The JSONPath RFC was in draft status for a long time and has seen many changes.
30
30
  There are many implementations based on older drafts, or which add features that were never in the RFC at all.
@@ -35,9 +35,30 @@ The RFC was finalized in 2024, and it has a rigorous [suite of compliance tests.
35
35
 
36
36
  With these tools it is possible to have JSONPath implementations in many languages with identical behavior.
37
37
 
38
+ ### Differences from joshbuddy/jsonpath
39
+
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.
44
+
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
49
+
50
+ * joshbuddy/jsonpath allows root selector to be omitted
51
+ grid.first('gid').to_i
52
+
53
+ $ jsonpath 'store' example.json
54
+ $ janeway '$.store' example.json
55
+
56
+ *
57
+ $ jsonpath '$.nodes..services..id' spec/resources/grid-full.json
58
+
38
59
  ### Implementation
39
60
 
40
61
  Functionality is based on [IETF RFC 9535, "JSONPath: Query Expressions for JSON"](https://www.rfc-editor.org/rfc/rfc9535.html#filter-selector)
41
62
  The examples in the RFC have been implemented as unit tests.
42
63
 
43
- 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 recommended behaviors there contradict the RFC, so it is one or the other.
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.
data/bin/janeway CHANGED
@@ -123,8 +123,7 @@ begin
123
123
  options = parse_args(ARGV.dup)
124
124
  main(options)
125
125
  rescue Janeway::Error => e
126
- puts
127
- abort("Error: #{e}")
126
+ abort e.detailed_message
128
127
  rescue Interrupt, Errno::EPIPE
129
128
  abort("\n")
130
129
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'janeway'
3
4
  require_relative 'selector'
4
5
 
5
6
  module Janeway
@@ -25,12 +26,19 @@ module Janeway
25
26
  # @param end_ [Integer, nil]
26
27
  # @param step [Integer, nil]
27
28
  def initialize(start = nil, end_ = nil, step = nil)
29
+ super(nil)
30
+
31
+ # Check arguments
28
32
  [start, end_, step].each do |arg|
29
- next if arg.nil? || arg.is_a?(Integer)
33
+ unless [NilClass, Integer].include?(arg.class)
34
+ raise Error, "Array slice selector index must be integer or nothing, got #{arg.inspect}"
35
+ end
36
+ next unless arg
30
37
 
31
- raise ArgumentError, "Expect Integer or nil, got #{arg.inspect}"
38
+ # Check integer size limits
39
+ raise Error, "Array slice selector value too small: #{arg.inspect}" if arg < INTEGER_MIN
40
+ raise Error, "Array slice selector value too large: #{arg.inspect}" if arg > INTEGER_MAX
32
41
  end
33
- super(nil)
34
42
 
35
43
  # Nil values are kept to indicate that the "default" values is to be used.
36
44
  # The interpreter selects the actual values.
@@ -58,14 +66,8 @@ module Janeway
58
66
  #
59
67
  # @param input_size [Integer]
60
68
  # @return [Integer]
61
- def start_index(input_size)
62
- if @start
63
- @start.clamp(0, input_size)
64
- elsif step.positive?
65
- 0
66
- else # negative step
67
- input_size - 1 # last index of input
68
- end
69
+ def upper_index(input_size)
70
+ calculate_index_values(input_size).last
69
71
  end
70
72
 
71
73
  # Return the end index to use for stepping through the array, based on a specified array size
@@ -73,15 +75,65 @@ module Janeway
73
75
  #
74
76
  # @param input_size [Integer]
75
77
  # @return [Integer]
76
- def end_index(input_size)
77
- if @end
78
- value = @end.clamp(0, input_size)
79
- step.positive? ? value - 1 : value + 1 # +/- to exclude the final element
80
- elsif step.positive?
81
- input_size - 1 # last index of input
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)
82
131
  else
83
- 0
132
+ upper = n_start.clamp(-1, len - 1)
133
+ lower = n_end.clamp(-1, len - 1)
84
134
  end
135
+
136
+ [lower, upper]
85
137
  end
86
138
 
87
139
  # ignores the brackets: argument, this always needs surrounding brackets
@@ -19,6 +19,9 @@ module Janeway
19
19
  def left=(expr)
20
20
  if comparison_operator? && !(expr.literal? || expr.singular_query?)
21
21
  raise Error, "Expression #{expr} does not produce a singular value for #{operator_to_s} comparison"
22
+ elsif comparison_operator? && expr.is_a?(AST::Function) && !expr.literal?
23
+ msg = "Function #{expr} returns a non-comparable value which is not usable for #{operator_to_s} comparison"
24
+ raise Error, msg
22
25
  end
23
26
 
24
27
  # Compliance test suite requires error for this, but don't have go so far as to bar every literal
@@ -17,12 +17,10 @@ module Janeway
17
17
  def_delegators :@value, :size, :first, :last, :each, :map, :empty?
18
18
 
19
19
  # Subsequent expression that modifies the result of this selector list.
20
- # This one is not in the selector list, it comes afterward.
21
- attr_accessor :child
20
+ attr_accessor :next
22
21
 
23
22
  def initialize
24
23
  super([]) # @value holds the expressions in the selector
25
- @child = nil # @child is the next expression, which modifies the output of this one
26
24
  end
27
25
 
28
26
  # Add a selector to the list
@@ -33,15 +31,15 @@ module Janeway
33
31
  end
34
32
 
35
33
  def to_s(with_child: true)
36
- str = @value.map { |selector| selector.to_s(brackets: false) }.join(', ')
37
- with_child ? "[#{str}]#{@child}" : "[#{str}]"
34
+ str = @value.map { |selector| selector.to_s(brackets: false, dot_prefix: false) }.join(', ')
35
+ with_child ? "[#{str}]#{@next}" : "[#{str}]"
38
36
  end
39
37
 
40
38
  # @param level [Integer]
41
39
  # @return [Array]
42
40
  def tree(level)
43
41
  msg = format('[%s]', @value.map(&:to_s).join(', '))
44
- [indented(level, msg), @child&.tree(level + 1)]
42
+ [indented(level, msg), @next&.tree(level + 1)]
45
43
  end
46
44
  end
47
45
  end
@@ -30,11 +30,7 @@ module Janeway
30
30
  # Construct accepts an optional Selector which will be applied to the "current" node
31
31
  class CurrentNode < Janeway::AST::Expression
32
32
  def to_s
33
- if @value.is_a?(NameSelector) || @value.is_a?(WildcardSelector)
34
- "@.#{@value.to_s(brackets: false)}"
35
- else
36
- "@#{@value}"
37
- end
33
+ "@#{@value}"
38
34
  end
39
35
 
40
36
  # True if this is the root of a singular-query.
@@ -48,7 +44,7 @@ module Janeway
48
44
  selector = @value
49
45
  loop do
50
46
  selector_types << selector.class
51
- selector = selector.child
47
+ selector = selector.next
52
48
  break unless selector
53
49
  end
54
50
  allowed = [AST::IndexSelector, AST::NameSelector]
@@ -16,16 +16,11 @@ module Janeway
16
16
  # $..[*, *] All values, twice non-deterministic order
17
17
  # $..[0, 1] Multiple segments
18
18
  class DescendantSegment < Janeway::AST::Selector
19
- attr_accessor :child
20
-
21
- def initialize(selector)
22
- super
23
-
24
- @child = nil
25
- end
19
+ # Subsequent expression that modifies the result of this selector list.
20
+ attr_accessor :next
26
21
 
27
22
  def to_s
28
- "..#{@value}#{@child}"
23
+ "..#{@next&.to_s(dot_prefix: false)}"
29
24
  end
30
25
 
31
26
  # @return [AST::Selector]
@@ -47,14 +47,20 @@ module Janeway
47
47
  #
48
48
  # @example: $.store[@.price < 10]
49
49
  class FilterSelector < Janeway::AST::Selector
50
- def to_s(brackets: true)
51
- brackets ? "[?#{value}]" : "?#{value}"
50
+ # @param brackets [Boolean] include brackets around selector
51
+ # ** ignores keyword arguments that don't apply to this selector
52
+ def to_s(brackets: true, **)
53
+ brackets ? "[?#{value}]#{@next}" : "?#{value}#{@next}"
52
54
  end
53
55
 
54
56
  # @param level [Integer]
55
57
  # @return [Array]
56
58
  def tree(level)
57
- [indented(level, to_s)]
59
+ if @next
60
+ [indented(level, to_s), indented(level + 1, @next&.tree)]
61
+ else
62
+ [indented(level, to_s)]
63
+ end
58
64
  end
59
65
  end
60
66
  end
@@ -8,7 +8,6 @@ module Janeway
8
8
 
9
9
  attr_reader :parameters, :body
10
10
 
11
- # FIXME: provide function body too as a proc?
12
11
  def initialize(name, parameters, &body)
13
12
  raise ArgumentError, "expect string, got #{name.inspect}" unless name.is_a?(String)
14
13
 
@@ -35,6 +34,27 @@ module Janeway
35
34
  def singular_query?
36
35
  true
37
36
  end
37
+
38
+ # True if the function's return value is a literal
39
+ def literal?
40
+ case name
41
+ when 'length', 'count', 'value' then true
42
+ when 'search', 'match' then false
43
+ else
44
+ raise "Unknown jsonpath function #{name}"
45
+ end
46
+ end
47
+
48
+ # True if the function's return value is a boolean
49
+ # @return [Boolean]
50
+ def logical?
51
+ case name
52
+ when 'length', 'count', 'value' then false
53
+ when 'search', 'match' then true
54
+ else
55
+ raise "Unknown jsonpath function #{name}"
56
+ end
57
+ end
38
58
  end
39
59
  end
40
60
  end
@@ -5,7 +5,7 @@ module Janeway
5
5
  class Identifier < Janeway::AST::Expression
6
6
  alias name value
7
7
 
8
- # TODO: This list is incomplete. Complete after some aspects of the parser become clearer.
8
+ # Tokens which may follow an identifier string
9
9
  EXPECTED_NEXT_TOKENS = %I[
10
10
  \n
11
11
  +
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'janeway'
3
4
  require_relative 'selector'
4
5
 
5
6
  module Janeway
@@ -7,19 +8,17 @@ module Janeway
7
8
  # An index selector, e.g. 3, selects an indexed child of an array
8
9
  # @example: $.store.book[2].title
9
10
  class IndexSelector < Janeway::AST::Selector
10
- # These are the limits of what javascript's Number type can represent
11
- MIN = -9_007_199_254_740_991
12
- MAX = 9_007_199_254_740_991
13
-
14
11
  def initialize(index)
15
12
  raise Error, "Invalid value for index selector: #{index.inspect}" unless index.is_a?(Integer)
16
- raise Error, "Index selector value too small: #{index.inspect}" if index < MIN
17
- raise Error, "Index selector value too large: #{index.inspect}" if index > MAX
13
+ raise Error, "Index selector value too small: #{index.inspect}" if index < INTEGER_MIN
14
+ raise Error, "Index selector value too large: #{index.inspect}" if index > INTEGER_MAX
18
15
 
19
16
  super
20
17
  end
21
18
 
22
- def to_s(brackets: true)
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, **)
23
22
  brackets ? "[#{@value}]" : @value.to_s
24
23
  end
25
24
  end
@@ -14,22 +14,24 @@ module Janeway
14
14
 
15
15
  def initialize(value)
16
16
  super
17
- # FIXME: implement name matching requirements here
18
17
  raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
19
18
  end
20
19
 
21
- def to_s(brackets: false)
20
+ # @param bracket [Boolean] request for bracket syntax
21
+ # @param bracket [Boolean] include . prefix, if shorthand notation is used
22
+ def to_s(brackets: false, dot_prefix: true)
22
23
  # Add quotes and surrounding brackets if the name includes chars that require quoting.
23
24
  # These chars are not allowed in dotted notation, only bracket notation.
24
25
  special_chars = [' ', '.']
25
26
  brackets ||= special_chars.any? { |char| @value.include?(char) }
26
- name_str =
27
- if brackets
28
- quote(@value)
29
- else
30
- @value
31
- end
32
- brackets ? "[#{name_str}]#{@child}" : "#{name_str}#{@child}"
27
+ if brackets
28
+ name_str = quote(@value)
29
+ "[#{name_str}]#{@next}"
30
+ elsif dot_prefix
31
+ ".#{@value}#{@next}"
32
+ else # omit dot prefix after a descendant segment
33
+ "#{@value}#{@next}"
34
+ end
33
35
  end
34
36
 
35
37
  # put surrounding quotes on a string
@@ -45,7 +47,7 @@ module Janeway
45
47
  # @param level [Integer]
46
48
  # @return [Array]
47
49
  def tree(level)
48
- [indented(level, "NameSelector:\"#{@value}\""), @child.tree(level + 1)]
50
+ [indented(level, "NameSelector:\"#{@value}\""), @next&.tree(level + 1)]
49
51
  end
50
52
  end
51
53
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Janeway
4
4
  module AST
5
- # Represent a number. From the BNF grammer:
5
+ # Represent a number. From the ABNF grammar:
6
6
  # number = (int / "-0") [ frac ] [ exp ] ; decimal number
7
7
  # frac = "." 1*DIGIT ; decimal fraction
8
8
  # exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent
@@ -2,18 +2,34 @@
2
2
 
3
3
  module Janeway
4
4
  module AST
5
- # Syntactically, a JSONPath query consists of a root identifier ($),
6
- # which stands for a nodelist that contains the root node of the
7
- # query argument, followed by a possibly empty sequence of segments.
5
+ # AST::Query holds the complete abstract syntax tree created by parsing the query.
6
+ #
7
+ # This can be frozen and passed to multiple threads or ractors for simultaneous use.
8
+ # No instance members are modified during the interpretation stage.
8
9
  class Query
9
- attr_accessor :root
10
+ # @return [AST::RootNode]
11
+ attr_reader :root
12
+
13
+ # The original jsonpath query, for use in error messages
14
+ # @return [String]
15
+ attr_reader :jsonpath
16
+
17
+ # @param root_node [AST::Root]
18
+ # @param jsonpath [String]
19
+ def initialize(root_node, jsonpath)
20
+ raise ArgumentError, "expect root identifier, got #{root_node.inspect}" unless root_node.is_a?(RootNode)
21
+ raise ArgumentError, "expect query string, got #{jsonpath.inspect}" unless jsonpath.is_a?(String)
22
+
23
+ @root = root_node
24
+ @jsonpath = jsonpath
25
+ end
10
26
 
11
27
  # Use this Query to search the input, and return the results.
12
28
  #
13
29
  # @param input [Object] ruby object to be searched
14
30
  # @return [Array] all matched objects
15
- def find_all(object)
16
- Janeway::Interpreter.new(input).interpret(self)
31
+ def find_all(input)
32
+ Janeway::Interpreter.new(self).interpret(input)
17
33
  end
18
34
 
19
35
  def to_s
@@ -15,11 +15,7 @@ module Janeway
15
15
  #
16
16
  class RootNode < Janeway::AST::Expression
17
17
  def to_s
18
- if @value.is_a?(NameSelector) || @value.is_a?(WildcardSelector)
19
- "$.#{@value}"
20
- else
21
- "$#{@value}"
22
- end
18
+ "$#{@value}"
23
19
  end
24
20
 
25
21
  # True if this is the root of a singular-query.
@@ -33,7 +29,7 @@ module Janeway
33
29
  selector = @value
34
30
  loop do
35
31
  selector_types << selector.class
36
- selector = selector.child
32
+ selector = selector.next
37
33
  break unless selector
38
34
  end
39
35
  allowed = [AST::IndexSelector, AST::NameSelector]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'expression'
4
+
3
5
  # https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/blob/main/draft-ietf-jsonpath-base.md#selectors
4
6
  # Selectors:
5
7
  #
@@ -22,7 +24,8 @@ module Janeway
22
24
  module AST
23
25
  # Represent a selector, which is an expression that filters nodes from a list based on a predicate.
24
26
  class Selector < Janeway::AST::Expression
25
- attr_accessor :child
27
+ # Subsequent expression that modifies the result of this selector list.
28
+ attr_accessor :next
26
29
 
27
30
  def ==(other)
28
31
  value == other&.value
@@ -17,29 +17,25 @@ module Janeway
17
17
  # It has only one possible value: '*'
18
18
  # @example: $.store.book[*]
19
19
  class WildcardSelector < Janeway::AST::Selector
20
- attr_accessor :child
21
-
22
20
  def initialize
23
21
  super
24
- @child = nil
22
+ @next = nil
25
23
  end
26
24
 
27
- def to_s(brackets: true)
28
- if @child.is_a?(NameSelector) || @child.is_a?(WildcardSelector)
29
- if brackets
30
- "[*]#{@child.to_s(brackets: true)}"
31
- else
32
- "*.#{@child}"
33
- end
25
+ def to_s(brackets: false, dot_prefix: true)
26
+ if brackets
27
+ "[*]#{@next&.to_s(brackets: true)}"
28
+ elsif dot_prefix
29
+ ".*#{@next}"
34
30
  else
35
- "*#{@child}"
31
+ "*#{@next}"
36
32
  end
37
33
  end
38
34
 
39
35
  # @param level [Integer]
40
36
  # @return [Array]
41
37
  def tree(level)
42
- [indented(level, '*'), @child.tree(level + 1)]
38
+ [indented(level, '*'), @next.tree(level + 1)]
43
39
  end
44
40
  end
45
41
  end
data/lib/janeway/error.rb CHANGED
@@ -15,9 +15,15 @@ module Janeway
15
15
  # @param query [String] entire query string
16
16
  # @param location [Location] location of error
17
17
  def initialize(message, query = nil, location = nil)
18
- super(message)
18
+ super("Jsonpath query #{query} - #{message}")
19
19
  @query = query
20
20
  @location = location
21
21
  end
22
+
23
+ def detailed_message
24
+ msg = "Error: #{message}\nQuery: #{query}\n"
25
+ msg += (' ' * location.col) + '^' if @location
26
+ msg
27
+ end
22
28
  end
23
29
  end
@@ -24,14 +24,19 @@ module Janeway
24
24
  consume # (
25
25
 
26
26
  # Read parameter
27
- parameters = [parse_function_parameter]
28
- raise "expect group_end token, found #{current}" unless current.type == :group_end
27
+ arg = parse_function_parameter
28
+ parameters = [arg]
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
29
33
 
34
+ # Define function body
30
35
  AST::Function.new('count', parameters) do |node_list|
31
36
  if node_list.is_a?(Array)
32
37
  node_list.size
33
38
  else
34
- 1
39
+ 1 # The count of a non-empty singulare noselist such as count(@) is always 1
35
40
  end
36
41
  end
37
42
  end
@@ -13,8 +13,14 @@ module Janeway
13
13
  consume # (
14
14
 
15
15
  # Read parameter
16
- parameters = [parse_function_parameter]
17
- raise "expect group_end token, found #{current}" unless current.type == :group_end
16
+ arg = parse_function_parameter
17
+ parameters = [arg]
18
+ unless arg.singular_query? || arg.literal?
19
+ raise Error, "Invalid parameter - length() expects literal value or singular query, got #{arg.value.inspect}"
20
+ end
21
+ unless current.type == :group_end
22
+ raise Error, 'Too many parameters for length() function call'
23
+ end
18
24
 
19
25
  # Meaning of return value depends on the JSON type:
20
26
  # * string - number of Unicode scalar values in the string.
@@ -46,7 +46,7 @@ module Janeway
46
46
  if current.type == :union
47
47
  consume # ,
48
48
  else
49
- raise "expect comma token, found #{current}"
49
+ raise Error, 'Not enough parameters for match() function call'
50
50
  end
51
51
 
52
52
  # Read second parameter (the regexp)
@@ -54,7 +54,9 @@ module Janeway
54
54
  # Otherwise it is an expression that takes the regexp from the input document,
55
55
  # and the iregexp will not be available until interpretation.
56
56
  parameters << parse_function_parameter
57
- raise "expect group_end token, found #{current}" unless current.type == :group_end
57
+ unless current.type == :group_end
58
+ raise Error, 'Too many parameters for match() function call'
59
+ end
58
60
 
59
61
  AST::Function.new('match', parameters) do |str, str_iregexp|
60
62
  if str.is_a?(String) && str_iregexp.is_a?(String)
@@ -40,7 +40,7 @@ module Janeway
40
40
  if current.type == :union
41
41
  consume # ,
42
42
  else
43
- raise "expect comma token, found #{current}"
43
+ raise Error, 'Insufficient parameters for search() function call'
44
44
  end
45
45
 
46
46
  # Read second parameter (the regexp)
@@ -48,7 +48,9 @@ module Janeway
48
48
  # Otherwise it is an expression that takes the regexp from the input document,
49
49
  # and the iregexp will not be available until interpretation.
50
50
  parameters << parse_function_parameter
51
- raise "expect group_end token, found #{current}" unless current.type == :group_end
51
+ unless current.type == :group_end
52
+ raise Error, 'Too many parameters for match() function call'
53
+ end
52
54
 
53
55
  AST::Function.new('search', parameters) do |str, str_iregexp|
54
56
  if str.is_a?(String) && str_iregexp.is_a?(String)