janeway-jsonpath 0.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +7 -0
  3. data/README.md +43 -0
  4. data/bin/janeway +130 -0
  5. data/lib/janeway/ast/array_slice_selector.rb +104 -0
  6. data/lib/janeway/ast/binary_operator.rb +101 -0
  7. data/lib/janeway/ast/boolean.rb +24 -0
  8. data/lib/janeway/ast/child_segment.rb +48 -0
  9. data/lib/janeway/ast/current_node.rb +71 -0
  10. data/lib/janeway/ast/descendant_segment.rb +44 -0
  11. data/lib/janeway/ast/error.rb +10 -0
  12. data/lib/janeway/ast/expression.rb +55 -0
  13. data/lib/janeway/ast/filter_selector.rb +61 -0
  14. data/lib/janeway/ast/function.rb +40 -0
  15. data/lib/janeway/ast/helpers.rb +27 -0
  16. data/lib/janeway/ast/identifier.rb +35 -0
  17. data/lib/janeway/ast/index_selector.rb +27 -0
  18. data/lib/janeway/ast/name_selector.rb +52 -0
  19. data/lib/janeway/ast/null.rb +23 -0
  20. data/lib/janeway/ast/number.rb +21 -0
  21. data/lib/janeway/ast/query.rb +41 -0
  22. data/lib/janeway/ast/root_node.rb +50 -0
  23. data/lib/janeway/ast/selector.rb +32 -0
  24. data/lib/janeway/ast/string_type.rb +21 -0
  25. data/lib/janeway/ast/unary_operator.rb +26 -0
  26. data/lib/janeway/ast/wildcard_selector.rb +46 -0
  27. data/lib/janeway/error.rb +23 -0
  28. data/lib/janeway/functions/count.rb +39 -0
  29. data/lib/janeway/functions/length.rb +33 -0
  30. data/lib/janeway/functions/match.rb +69 -0
  31. data/lib/janeway/functions/search.rb +63 -0
  32. data/lib/janeway/functions/value.rb +47 -0
  33. data/lib/janeway/functions.rb +62 -0
  34. data/lib/janeway/interpreter.rb +644 -0
  35. data/lib/janeway/lexer.rb +514 -0
  36. data/lib/janeway/location.rb +3 -0
  37. data/lib/janeway/parser.rb +608 -0
  38. data/lib/janeway/token.rb +39 -0
  39. data/lib/janeway/version.rb +5 -0
  40. data/lib/janeway.rb +51 -0
  41. metadata +92 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector'
4
+
5
+ # https://datatracker.ietf.org/doc/rfc9535/
6
+
7
+ module Janeway
8
+ module AST
9
+ # Filter selectors are used to iterate over the elements or members of
10
+ # structured values, i.e., JSON arrays and objects. The structured
11
+ # values are identified in the nodelist offered by the child or
12
+ # descendant segment using the filter selector.
13
+
14
+ # For each iteration (element/member), a logical expression (the
15
+ # _filter expression_) is evaluated, which decides whether the node of
16
+ # the element/member is selected. (While a logical expression
17
+ # evaluates to what mathematically is a Boolean value, this
18
+ # specification uses the term _logical_ to maintain a distinction from
19
+ # the Boolean values that JSON can represent.)
20
+
21
+ # During the iteration process, the filter expression receives the node
22
+ # of each array element or object member value of the structured value
23
+ # being filtered; this element or member value is then known as the
24
+ # _current node_.
25
+
26
+ # The current node can be used as the start of one or more JSONPath
27
+ # queries in subexpressions of the filter expression, notated via the
28
+ # current-node-identifier @. Each JSONPath query can be used either for
29
+ # testing existence of a result of the query, for obtaining a specific
30
+ # JSON value resulting from that query that can then be used in a
31
+ # comparison, or as a _function argument_.
32
+
33
+ # Filter selectors may use function extensions, which are covered in
34
+ # Section 2.4. Within the logical expression for a filter selector,
35
+ # function expressions can be used to operate on nodelists and values.
36
+ # The set of available functions is extensible, with a number of
37
+ # functions predefined (see Section 2.4) and the ability to register
38
+ # further functions provided by the "Function Extensions" subregistry
39
+ # (Section 3.2). When a function is defined, it is given a unique
40
+ # name, and its return value and each of its parameters are given a
41
+ # _declared type_. The type system is limited in scope; its purpose is
42
+ # to express restrictions that, without functions, are implicit in the
43
+ # grammar of filter expressions. The type system also guides
44
+ # conversions (Section 2.4.2) that mimic the way different kinds of
45
+ # expressions are handled in the grammar when function expressions are
46
+ # not in use.
47
+ #
48
+ # @example: $.store[@.price < 10]
49
+ class FilterSelector < Janeway::AST::Selector
50
+ def to_s(brackets: true)
51
+ brackets ? "[?#{value}]" : "?#{value}"
52
+ end
53
+
54
+ # @param level [Integer]
55
+ # @return [Array]
56
+ def tree(level)
57
+ [indented(level, to_s)]
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ # Represents a JSONPath built-in function.
6
+ class Function < Janeway::AST::Expression
7
+ alias name value
8
+
9
+ attr_reader :parameters, :body
10
+
11
+ # FIXME: provide function body too as a proc?
12
+ def initialize(name, parameters, &body)
13
+ raise ArgumentError, "expect string, got #{name.inspect}" unless name.is_a?(String)
14
+
15
+ super(name)
16
+ @parameters = parameters
17
+ @body = body
18
+ raise "expect body to be a Proc, got #{body.class}" unless body.is_a?(Proc)
19
+ end
20
+
21
+ def to_s
22
+ "#{name}(#{@parameters.join(',')})"
23
+ end
24
+
25
+ # @param level [Integer]
26
+ # @return [Array]
27
+ def tree(level)
28
+ [indented(level, to_s)]
29
+ end
30
+
31
+ # True if this is the root of a singular-query.
32
+ # @see https://www.rfc-editor.org/rfc/rfc9535.html#name-well-typedness-of-function-
33
+ #
34
+ # @return [Boolean]
35
+ def singular_query?
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ module Helpers
6
+ # @param str [String] ascii string, CamelCase
7
+ # @return [String] lowercase, with underscores
8
+ def self.camelcase_to_underscore(str)
9
+ found_uppercase = false
10
+ chars = []
11
+ str.each_char do |char|
12
+ if char.ord.between?(65, 90) # ascii 'A'..'Z' inclusive
13
+ chars << '_'
14
+ chars << (char.ord + 32).chr
15
+ found_uppercase = true
16
+ else
17
+ chars << char
18
+ end
19
+ end
20
+ return str unless found_uppercase
21
+
22
+ chars.shift if chars.first == '_'
23
+ chars.join
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ class Identifier < Janeway::AST::Expression
6
+ alias name value
7
+
8
+ # TODO: This list is incomplete. Complete after some aspects of the parser become clearer.
9
+ EXPECTED_NEXT_TOKENS = %I[
10
+ \n
11
+ +
12
+ -
13
+ *
14
+ /
15
+ ==
16
+ !=
17
+ >
18
+ <
19
+ >=
20
+ <=
21
+ &&
22
+ ||
23
+ ].freeze
24
+
25
+ # @return [String]
26
+ def to_s
27
+ @value
28
+ end
29
+
30
+ def expects?(next_token)
31
+ EXPECTED_NEXT_TOKENS.include?(next_token)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector'
4
+
5
+ module Janeway
6
+ module AST
7
+ # An index selector, e.g. 3, selects an indexed child of an array
8
+ # @example: $.store.book[2].title
9
+ 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
+ def initialize(index)
15
+ 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
18
+
19
+ super
20
+ end
21
+
22
+ def to_s(brackets: true)
23
+ brackets ? "[#{@value}]" : @value.to_s
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector'
4
+
5
+ module Janeway
6
+ module AST
7
+ # A name selector, e.g. 'name', selects a named child of an object.
8
+ # @example
9
+ # $.store
10
+ # $[store]
11
+ # The dot or bracket part is not captured here, only the name
12
+ class NameSelector < Janeway::AST::Selector
13
+ alias name value
14
+
15
+ def initialize(value)
16
+ super
17
+ # FIXME: implement name matching requirements here
18
+ raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
19
+ end
20
+
21
+ def to_s(brackets: false)
22
+ # Add quotes and surrounding brackets if the name includes chars that require quoting.
23
+ # These chars are not allowed in dotted notation, only bracket notation.
24
+ special_chars = [' ', '.']
25
+ 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}"
33
+ end
34
+
35
+ # put surrounding quotes on a string
36
+ # @return [String]
37
+ def quote(str)
38
+ if str.include?("'")
39
+ format('"%s"', str)
40
+ else
41
+ "'#{str}'"
42
+ end
43
+ end
44
+
45
+ # @param level [Integer]
46
+ # @return [Array]
47
+ def tree(level)
48
+ [indented(level, "NameSelector:\"#{@value}\""), @child.tree(level + 1)]
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ class Null < Janeway::AST::Expression
6
+ def to_s
7
+ 'null'
8
+ end
9
+
10
+ # @param level [Integer]
11
+ # @return [Array]
12
+ def tree(level = 0)
13
+ indented(level, 'null')
14
+ end
15
+
16
+ # Return true if this is a literal expression
17
+ # @return [Boolean]
18
+ def literal?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ # Represent a number. From the BNF grammer:
6
+ # number = (int / "-0") [ frac ] [ exp ] ; decimal number
7
+ # frac = "." 1*DIGIT ; decimal fraction
8
+ # exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent
9
+ class Number < Janeway::AST::Expression
10
+ def to_s
11
+ @value.to_s
12
+ end
13
+
14
+ # Return true if this is a literal expression
15
+ # @return [Boolean]
16
+ def literal?
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
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.
8
+ class Query
9
+ attr_accessor :root
10
+
11
+ # Use this Query to search the input, and return the results.
12
+ #
13
+ # @param input [Object] ruby object to be searched
14
+ # @return [Array] all matched objects
15
+ def find_all(object)
16
+ Janeway::Interpreter.new(input).interpret(self)
17
+ end
18
+
19
+ def to_s
20
+ @root.to_s
21
+ end
22
+
23
+ # Queries are considered equal if their ASTs evaluate to the same JSONPath string.
24
+ #
25
+ # The string output is generated by the AST and should be considered a "normalized"
26
+ # form of the query. It may have different whitespace and parentheses than the original
27
+ # input but will be semantically equivalent.
28
+ def ==(other)
29
+ to_s == other.to_s
30
+ end
31
+
32
+ # Print AST in tree format
33
+ # Every AST class prints a 1-line representation of self, with children on separate lines
34
+ def tree
35
+ result = @root.tree(0)
36
+
37
+ result.flatten.join("\n")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ # Every JSONPath query (except those inside filter expressions; see Section
6
+ # 2.3.5) MUST begin with the root identifier $.
7
+ #
8
+ # The root identifier $ represents the root node of the query argument and produces
9
+ # a nodelist consisting of that root node.
10
+ #
11
+ # The root identifier may also be used in the filter selectors.
12
+ #
13
+ # @example:
14
+ # $(? $.key1 == $.key2 )
15
+ #
16
+ class RootNode < Janeway::AST::Expression
17
+ def to_s
18
+ if @value.is_a?(NameSelector) || @value.is_a?(WildcardSelector)
19
+ "$.#{@value}"
20
+ else
21
+ "$#{@value}"
22
+ end
23
+ end
24
+
25
+ # True if this is the root of a singular-query.
26
+ # @see https://www.rfc-editor.org/rfc/rfc9535.html#name-well-typedness-of-function-
27
+ #
28
+ # @return [Boolean]
29
+ def singular_query?
30
+ return true unless @value # there are no following selectors
31
+
32
+ selector_types = []
33
+ selector = @value
34
+ loop do
35
+ selector_types << selector.class
36
+ selector = selector.child
37
+ break unless selector
38
+ end
39
+ allowed = [AST::IndexSelector, AST::NameSelector]
40
+ selector_types.uniq.all? { allowed.include?(_1) }
41
+ end
42
+
43
+ # @param level [Integer]
44
+ # @return [Array]
45
+ def tree(level = 0)
46
+ [indented(level, '$'), @value.tree(level + 1)]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/blob/main/draft-ietf-jsonpath-base.md#selectors
4
+ # Selectors:
5
+ #
6
+ # A name selector, e.g. 'name', selects a named child of an object.
7
+ #
8
+ # An index selector, e.g. 3, selects an indexed child of an array.
9
+ #
10
+ # A wildcard * ({{wildcard-selector}}) in the expression [*] selects all
11
+ # children of a node and in the expression ..[*] selects all descendants of a
12
+ # node.
13
+ #
14
+ # An array slice start:end:step ({{slice}}) selects a series of elements from
15
+ # an array, giving a start position, an end position, and an optional step
16
+ # value that moves the position from the start to the end.
17
+ #
18
+ # Filter expressions ?<logical-expr> select certain children of an object or array, as in:
19
+ #
20
+ # $.store.book[?@.price < 10].title
21
+ module Janeway
22
+ module AST
23
+ # Represent a selector, which is an expression that filters nodes from a list based on a predicate.
24
+ class Selector < Janeway::AST::Expression
25
+ attr_accessor :child
26
+
27
+ def ==(other)
28
+ value == other&.value
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ class StringType < Janeway::AST::Expression
6
+ def to_s
7
+ if @value.include?("'")
8
+ %("#{@value}"')
9
+ else
10
+ "'#{@value}'"
11
+ end
12
+ end
13
+
14
+ # Return true if this is a literal expression
15
+ # @return [Boolean]
16
+ def literal?
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module AST
5
+ # Represent unary operators "!", "-"
6
+ class UnaryOperator < Janeway::AST::Expression
7
+ attr_accessor :operator, :operand
8
+
9
+ def initialize(operator, operand = nil)
10
+ super()
11
+ @operator = operator
12
+ @operand = operand
13
+ end
14
+
15
+ def to_s
16
+ "#{@operator} #{operand}"
17
+ end
18
+
19
+ # @param level [Integer]
20
+ # @return [Array]
21
+ def tree(level)
22
+ indented(level, "#{operator}#{operand}")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'selector'
4
+
5
+ module Janeway
6
+ module AST
7
+ # A wildcard selector selects the nodes of all children of an object
8
+ # or array. The order in which the children of an object appear in
9
+ # the resultant nodelist is not stipulated, since JSON objects are
10
+ # unordered. Children of an array appear in array order in the
11
+ # resultant nodelist.
12
+ #
13
+ # Note that the children of an object are its member values, not its member names/keys.
14
+ #
15
+ # The wildcard selector selects nothing from a primitive JSON value (ie. a number, a string, true, false, or null).
16
+ #
17
+ # It has only one possible value: '*'
18
+ # @example: $.store.book[*]
19
+ class WildcardSelector < Janeway::AST::Selector
20
+ attr_accessor :child
21
+
22
+ def initialize
23
+ super
24
+ @child = nil
25
+ end
26
+
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
34
+ else
35
+ "*#{@child}"
36
+ end
37
+ end
38
+
39
+ # @param level [Integer]
40
+ # @return [Array]
41
+ def tree(level)
42
+ [indented(level, '*'), @child.tree(level + 1)]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'location'
4
+
5
+ module Janeway
6
+ # Base class for JSONPath query errors
7
+ class Error < StandardError
8
+ # @return [String]
9
+ attr_reader :query
10
+
11
+ # @return [Location, nil]
12
+ attr_reader :location
13
+
14
+ # @param message [String] error message
15
+ # @param query [String] entire query string
16
+ # @param location [Location] location of error
17
+ def initialize(message, query = nil, location = nil)
18
+ super(message)
19
+ @query = query
20
+ @location = location
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ # Mixin to provide JSONPath function handlers for Parser
5
+ module Functions
6
+ # The count() function extension provides a way to obtain the number of
7
+ # nodes in a nodelist and make that available for further processing in
8
+ # the filter expression:
9
+ #
10
+ # Its only argument is a nodelist. The result is a value (an unsigned
11
+ # integer) that gives the number of nodes in the nodelist.
12
+ #
13
+ # Notes:
14
+ # * There is no deduplication of the nodelist.
15
+ # * The number of nodes in the nodelist is counted independent of
16
+ # their values or any children they may have, e.g., the count of a
17
+ # non-empty singular nodelist such as count(@) is always 1.
18
+ #
19
+ # @example $[?count(@.*.author) >= 5]
20
+ def parse_function_count
21
+ consume # function
22
+ raise "expect group_start token, found #{current}" unless current.type == :group_start
23
+
24
+ consume # (
25
+
26
+ # Read parameter
27
+ parameters = [parse_function_parameter]
28
+ raise "expect group_end token, found #{current}" unless current.type == :group_end
29
+
30
+ AST::Function.new('count', parameters) do |node_list|
31
+ if node_list.is_a?(Array)
32
+ node_list.size
33
+ else
34
+ 1
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ module Functions
5
+ # The length() function extension provides a way to compute the length of a value
6
+ # and make that available for further processing in the filter expression:
7
+ #
8
+ # JSONPath return type: ValueType
9
+ def parse_function_length
10
+ consume # function
11
+ raise "expect group_start token, found #{current}" unless current.type == :group_start
12
+
13
+ consume # (
14
+
15
+ # Read parameter
16
+ parameters = [parse_function_parameter]
17
+ raise "expect group_end token, found #{current}" unless current.type == :group_end
18
+
19
+ # Meaning of return value depends on the JSON type:
20
+ # * string - number of Unicode scalar values in the string.
21
+ # * array - number of elements in the array.
22
+ # * object - number of members in the object.
23
+ # For any other argument value, the result is the special result Nothing.
24
+ AST::Function.new('length', parameters) do |value|
25
+ if [Array, Hash, String].include?(value.class)
26
+ value.size
27
+ else
28
+ :nothing
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ # Mixin to provide JSONPath function handlers for Parser
5
+ module Functions
6
+ # The match() function extension provides a way to check whether (the
7
+ # entirety of; see Section 2.4.7) a given string matches a given regular
8
+ # expression, which is in the form described in [RFC9485].
9
+ #
10
+ # @example $[?match(@.date, "1974-05-..")]
11
+ #
12
+ # Its arguments are instances of ValueType (possibly taken from a
13
+ # singular query, as for the first argument in the example above). If the
14
+ # first argument is not a string or the second argument is not a string
15
+ # conforming to [RFC9485], the result is LogicalFalse. Otherwise, the
16
+ # string that is the first argument is matched against the I-Regexp
17
+ # contained in the string that is the second argument; the result is
18
+ # LogicalTrue if the string matches the I-Regexp and is LogicalFalse
19
+ # otherwise.
20
+ #
21
+ #
22
+ # The regexp dialect is called "I-Regexp" and is defined in RFC9485.
23
+ #
24
+ # Fortunately a shortcut is availalble, that RFC contains instructions
25
+ # for converting an I-Regexp to ruby's regexp format.
26
+ # @see https://www.rfc-editor.org/rfc/rfc9485.html#name-pcre-re2-and-ruby-regexps
27
+ #
28
+ # The instructions are:
29
+ # * For any unescaped dots (.) outside character classes (first
30
+ # alternative of charClass production), replace the dot with [^\n\r].
31
+ # * Enclose the regexp in \A(?: and )\z.
32
+ #
33
+ # tl;dr: How is this different from the search function?
34
+ # "match" must match the entire string, "search" matches a substring.
35
+ def parse_function_match
36
+ consume # function
37
+ raise "expect group_start token, found #{current}" unless current.type == :group_start
38
+
39
+ consume # (
40
+
41
+ # Read first parameter
42
+ parameters = []
43
+ parameters << parse_function_parameter
44
+
45
+ # Consume comma
46
+ if current.type == :union
47
+ consume # ,
48
+ else
49
+ raise "expect comma token, found #{current}"
50
+ end
51
+
52
+ # Read second parameter (the regexp)
53
+ # This could be a string, in which case it is available now.
54
+ # Otherwise it is an expression that takes the regexp from the input document,
55
+ # and the iregexp will not be available until interpretation.
56
+ parameters << parse_function_parameter
57
+ raise "expect group_end token, found #{current}" unless current.type == :group_end
58
+
59
+ AST::Function.new('match', parameters) do |str, str_iregexp|
60
+ if str.is_a?(String) && str_iregexp.is_a?(String)
61
+ regexp = translate_iregex_to_ruby_regex(str_iregexp)
62
+ regexp.match?(str)
63
+ else
64
+ false # result defined by RFC9535
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end