jmespath 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of jmespath might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/jmespath.rb +5 -4
- data/lib/jmespath/errors.rb +2 -0
- data/lib/jmespath/lexer.rb +291 -84
- data/lib/jmespath/nodes.rb +40 -0
- data/lib/jmespath/nodes/comparator.rb +77 -0
- data/lib/jmespath/nodes/condition.rb +136 -0
- data/lib/jmespath/nodes/current.rb +10 -0
- data/lib/jmespath/nodes/expression.rb +25 -0
- data/lib/jmespath/nodes/field.rb +74 -0
- data/lib/jmespath/nodes/flatten.rb +29 -0
- data/lib/jmespath/nodes/function.rb +591 -0
- data/lib/jmespath/nodes/index.rb +6 -0
- data/lib/jmespath/nodes/literal.rb +16 -0
- data/lib/jmespath/nodes/multi_select_hash.rb +37 -0
- data/lib/jmespath/nodes/multi_select_list.rb +22 -0
- data/lib/jmespath/nodes/or.rb +24 -0
- data/lib/jmespath/nodes/pipe.rb +6 -0
- data/lib/jmespath/nodes/projection.rb +82 -0
- data/lib/jmespath/nodes/slice.rb +92 -0
- data/lib/jmespath/nodes/subexpression.rb +63 -0
- data/lib/jmespath/parser.rb +78 -116
- data/lib/jmespath/runtime.rb +2 -7
- data/lib/jmespath/token.rb +22 -23
- data/lib/jmespath/version.rb +1 -1
- metadata +29 -14
- data/lib/jmespath/expr_node.rb +0 -15
- data/lib/jmespath/tree_interpreter.rb +0 -523
@@ -0,0 +1,40 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Node
|
5
|
+
def visit(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def hash_like?(value)
|
9
|
+
Hash === value || Struct === value
|
10
|
+
end
|
11
|
+
|
12
|
+
def optimize
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def chains_with?(other)
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
autoload :Comparator, 'jmespath/nodes/comparator'
|
22
|
+
autoload :Condition, 'jmespath/nodes/condition'
|
23
|
+
autoload :Current, 'jmespath/nodes/current'
|
24
|
+
autoload :Expression, 'jmespath/nodes/expression'
|
25
|
+
autoload :Field, 'jmespath/nodes/field'
|
26
|
+
autoload :Flatten, 'jmespath/nodes/flatten'
|
27
|
+
autoload :Function, 'jmespath/nodes/function'
|
28
|
+
autoload :Index, 'jmespath/nodes/index'
|
29
|
+
autoload :Literal, 'jmespath/nodes/literal'
|
30
|
+
autoload :MultiSelectHash, 'jmespath/nodes/multi_select_hash'
|
31
|
+
autoload :MultiSelectList, 'jmespath/nodes/multi_select_list'
|
32
|
+
autoload :Or, 'jmespath/nodes/or'
|
33
|
+
autoload :Pipe, 'jmespath/nodes/pipe'
|
34
|
+
autoload :Projection, 'jmespath/nodes/projection'
|
35
|
+
autoload :ArrayProjection, 'jmespath/nodes/projection'
|
36
|
+
autoload :ObjectProjection, 'jmespath/nodes/projection'
|
37
|
+
autoload :Slice, 'jmespath/nodes/slice'
|
38
|
+
autoload :Subexpression, 'jmespath/nodes/subexpression'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Comparator < Node
|
5
|
+
attr_reader :left, :right
|
6
|
+
|
7
|
+
def initialize(left, right)
|
8
|
+
@left = left
|
9
|
+
@right = right
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create(relation, left, right)
|
13
|
+
type = begin
|
14
|
+
case relation
|
15
|
+
when '==' then EqComparator
|
16
|
+
when '!=' then NeqComparator
|
17
|
+
when '>' then GtComparator
|
18
|
+
when '>=' then GteComparator
|
19
|
+
when '<' then LtComparator
|
20
|
+
when '<=' then LteComparator
|
21
|
+
end
|
22
|
+
end
|
23
|
+
type.new(left, right)
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit(value)
|
27
|
+
check(@left.visit(value), @right.visit(value))
|
28
|
+
end
|
29
|
+
|
30
|
+
def optimize
|
31
|
+
self.class.new(@left.optimize, @right.optimize)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def check(left_value, right_value)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class EqComparator < Comparator
|
42
|
+
def check(left_value, right_value)
|
43
|
+
left_value == right_value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class NeqComparator < Comparator
|
48
|
+
def check(left_value, right_value)
|
49
|
+
left_value != right_value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class GtComparator < Comparator
|
54
|
+
def check(left_value, right_value)
|
55
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value > right_value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class GteComparator < Comparator
|
60
|
+
def check(left_value, right_value)
|
61
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value >= right_value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class LtComparator < Comparator
|
66
|
+
def check(left_value, right_value)
|
67
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value < right_value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class LteComparator < Comparator
|
72
|
+
def check(left_value, right_value)
|
73
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value <= right_value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Condition < Node
|
5
|
+
def initialize(test, child)
|
6
|
+
@test = test
|
7
|
+
@child = child
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit(value)
|
11
|
+
if @test.visit(value)
|
12
|
+
@child.visit(value)
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def optimize
|
19
|
+
test = @test.optimize
|
20
|
+
if (new_type = ComparatorCondition::COMPARATOR_TO_CONDITION[@test.class])
|
21
|
+
new_type.new(test.left, test.right, @child).optimize
|
22
|
+
else
|
23
|
+
self.class.new(test, @child.optimize)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ComparatorCondition < Node
|
29
|
+
COMPARATOR_TO_CONDITION = {}
|
30
|
+
|
31
|
+
def initialize(left, right, child)
|
32
|
+
@left = left
|
33
|
+
@right = right
|
34
|
+
@child = child
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit(value)
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class EqCondition < ComparatorCondition
|
43
|
+
COMPARATOR_TO_CONDITION[EqComparator] = self
|
44
|
+
|
45
|
+
def visit(value)
|
46
|
+
@left.visit(value) == @right.visit(value) ? @child.visit(value) : nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def optimize
|
50
|
+
if @right.is_a?(Literal)
|
51
|
+
LiteralRightEqCondition.new(@left, @right, @child)
|
52
|
+
else
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class LiteralRightEqCondition < EqCondition
|
59
|
+
def initialize(left, right, child)
|
60
|
+
super
|
61
|
+
@right = @right.value
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit(value)
|
65
|
+
@left.visit(value) == @right ? @child.visit(value) : nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class NeqCondition < ComparatorCondition
|
70
|
+
COMPARATOR_TO_CONDITION[NeqComparator] = self
|
71
|
+
|
72
|
+
def visit(value)
|
73
|
+
@left.visit(value) != @right.visit(value) ? @child.visit(value) : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def optimize
|
77
|
+
if @right.is_a?(Literal)
|
78
|
+
LiteralRightNeqCondition.new(@left, @right, @child)
|
79
|
+
else
|
80
|
+
self
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class LiteralRightNeqCondition < NeqCondition
|
86
|
+
def initialize(left, right, child)
|
87
|
+
super
|
88
|
+
@right = @right.value
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit(value)
|
92
|
+
@left.visit(value) != @right ? @child.visit(value) : nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class GtCondition < ComparatorCondition
|
97
|
+
COMPARATOR_TO_CONDITION[GtComparator] = self
|
98
|
+
|
99
|
+
def visit(value)
|
100
|
+
left_value = @left.visit(value)
|
101
|
+
right_value = @right.visit(value)
|
102
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value > right_value ? @child.visit(value) : nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class GteCondition < ComparatorCondition
|
107
|
+
COMPARATOR_TO_CONDITION[GteComparator] = self
|
108
|
+
|
109
|
+
def visit(value)
|
110
|
+
left_value = @left.visit(value)
|
111
|
+
right_value = @right.visit(value)
|
112
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value >= right_value ? @child.visit(value) : nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class LtCondition < ComparatorCondition
|
117
|
+
COMPARATOR_TO_CONDITION[LtComparator] = self
|
118
|
+
|
119
|
+
def visit(value)
|
120
|
+
left_value = @left.visit(value)
|
121
|
+
right_value = @right.visit(value)
|
122
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value < right_value ? @child.visit(value) : nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class LteCondition < ComparatorCondition
|
127
|
+
COMPARATOR_TO_CONDITION[LteComparator] = self
|
128
|
+
|
129
|
+
def visit(value)
|
130
|
+
left_value = @left.visit(value)
|
131
|
+
right_value = @right.visit(value)
|
132
|
+
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value <= right_value ? @child.visit(value) : nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Expression < Node
|
5
|
+
attr_reader :expression
|
6
|
+
|
7
|
+
def initialize(expression)
|
8
|
+
@expression = expression
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit(value)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def eval(value)
|
16
|
+
@expression.visit(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def optimize
|
20
|
+
self.class.new(@expression.optimize)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Field < Node
|
5
|
+
def initialize(key)
|
6
|
+
@key = key
|
7
|
+
@key_sym = key.respond_to?(:to_sym) ? key.to_sym : nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit(value)
|
11
|
+
if value.is_a?(Array) && @key.is_a?(Integer)
|
12
|
+
value[@key]
|
13
|
+
elsif value.is_a?(Hash)
|
14
|
+
if !(v = value[@key]).nil?
|
15
|
+
v
|
16
|
+
elsif @key_sym && !(v = value[@key_sym]).nil?
|
17
|
+
v
|
18
|
+
end
|
19
|
+
elsif value.is_a?(Struct) && value.respond_to?(@key)
|
20
|
+
value[@key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def chains_with?(other)
|
25
|
+
other.is_a?(Field)
|
26
|
+
end
|
27
|
+
|
28
|
+
def chain(other)
|
29
|
+
ChainedField.new([@key, *other.keys])
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def keys
|
35
|
+
[@key]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ChainedField < Field
|
40
|
+
def initialize(keys)
|
41
|
+
@keys = keys
|
42
|
+
@key_syms = keys.each_with_object({}) do |k, syms|
|
43
|
+
if k.respond_to?(:to_sym)
|
44
|
+
syms[k] = k.to_sym
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit(value)
|
50
|
+
@keys.reduce(value) do |value, key|
|
51
|
+
if value.is_a?(Array) && key.is_a?(Integer)
|
52
|
+
value[key]
|
53
|
+
elsif value.is_a?(Hash)
|
54
|
+
if !(v = value[key]).nil?
|
55
|
+
v
|
56
|
+
elsif (sym = @key_syms[key]) && !(v = value[sym]).nil?
|
57
|
+
v
|
58
|
+
end
|
59
|
+
elsif value.is_a?(Struct) && value.respond_to?(key)
|
60
|
+
value[key]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def chain(other)
|
66
|
+
ChainedField.new([*@keys, *other.keys])
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
attr_reader :keys
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Flatten < Node
|
5
|
+
def initialize(child)
|
6
|
+
@child = child
|
7
|
+
end
|
8
|
+
|
9
|
+
def visit(value)
|
10
|
+
value = @child.visit(value)
|
11
|
+
if Array === value
|
12
|
+
value.each_with_object([]) do |v, values|
|
13
|
+
if Array === v
|
14
|
+
values.concat(v)
|
15
|
+
else
|
16
|
+
values.push(v)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def optimize
|
25
|
+
self.class.new(@child.optimize)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,591 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Function < Node
|
5
|
+
|
6
|
+
FUNCTIONS = {}
|
7
|
+
|
8
|
+
def initialize(children)
|
9
|
+
@children = children
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create(name, children)
|
13
|
+
if (type = FUNCTIONS[name])
|
14
|
+
type.new(children)
|
15
|
+
else
|
16
|
+
raise Errors::UnknownFunctionError, "unknown function #{name}()"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit(value)
|
21
|
+
call(@children.map { |child| child.visit(value) })
|
22
|
+
end
|
23
|
+
|
24
|
+
def optimize
|
25
|
+
self.class.new(@children.map(&:optimize))
|
26
|
+
end
|
27
|
+
|
28
|
+
class FunctionName
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
def initialize(name)
|
32
|
+
@name = name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def call(args)
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module TypeChecker
|
44
|
+
def get_type(value)
|
45
|
+
case value
|
46
|
+
when String then STRING_TYPE
|
47
|
+
when true, false then BOOLEAN_TYPE
|
48
|
+
when nil then NULL_TYPE
|
49
|
+
when Numeric then NUMBER_TYPE
|
50
|
+
when Hash, Struct then OBJECT_TYPE
|
51
|
+
when Array then ARRAY_TYPE
|
52
|
+
when Expression then EXPRESSION_TYPE
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ARRAY_TYPE = 0
|
57
|
+
BOOLEAN_TYPE = 1
|
58
|
+
EXPRESSION_TYPE = 2
|
59
|
+
NULL_TYPE = 3
|
60
|
+
NUMBER_TYPE = 4
|
61
|
+
OBJECT_TYPE = 5
|
62
|
+
STRING_TYPE = 6
|
63
|
+
|
64
|
+
TYPE_NAMES = {
|
65
|
+
ARRAY_TYPE => 'array',
|
66
|
+
BOOLEAN_TYPE => 'boolean',
|
67
|
+
EXPRESSION_TYPE => 'expression',
|
68
|
+
NULL_TYPE => 'null',
|
69
|
+
NUMBER_TYPE => 'number',
|
70
|
+
OBJECT_TYPE => 'object',
|
71
|
+
STRING_TYPE => 'string',
|
72
|
+
}.freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
class AbsFunction < Function
|
76
|
+
FUNCTIONS['abs'] = self
|
77
|
+
|
78
|
+
def call(args)
|
79
|
+
if args.count == 1
|
80
|
+
value = args.first
|
81
|
+
else
|
82
|
+
raise Errors::InvalidArityError, "function abs() expects one argument"
|
83
|
+
end
|
84
|
+
if Numeric === value
|
85
|
+
value.abs
|
86
|
+
else
|
87
|
+
raise Errors::InvalidTypeError, "function abs() expects a number"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class AvgFunction < Function
|
93
|
+
FUNCTIONS['avg'] = self
|
94
|
+
|
95
|
+
def call(args)
|
96
|
+
if args.count == 1
|
97
|
+
values = args.first
|
98
|
+
else
|
99
|
+
raise Errors::InvalidArityError, "function avg() expects one argument"
|
100
|
+
end
|
101
|
+
if Array === values
|
102
|
+
values.inject(0) do |total,n|
|
103
|
+
if Numeric === n
|
104
|
+
total + n
|
105
|
+
else
|
106
|
+
raise Errors::InvalidTypeError, "function avg() expects numeric values"
|
107
|
+
end
|
108
|
+
end / values.size.to_f
|
109
|
+
else
|
110
|
+
raise Errors::InvalidTypeError, "function avg() expects a number"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class CeilFunction < Function
|
116
|
+
FUNCTIONS['ceil'] = self
|
117
|
+
|
118
|
+
def call(args)
|
119
|
+
if args.count == 1
|
120
|
+
value = args.first
|
121
|
+
else
|
122
|
+
raise Errors::InvalidArityError, "function ceil() expects one argument"
|
123
|
+
end
|
124
|
+
if Numeric === value
|
125
|
+
value.ceil
|
126
|
+
else
|
127
|
+
raise Errors::InvalidTypeError, "function ceil() expects a numeric value"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class ContainsFunction < Function
|
133
|
+
FUNCTIONS['contains'] = self
|
134
|
+
|
135
|
+
def call(args)
|
136
|
+
if args.count == 2
|
137
|
+
haystack = args[0]
|
138
|
+
needle = args[1]
|
139
|
+
if String === haystack || Array === haystack
|
140
|
+
haystack.include?(needle)
|
141
|
+
else
|
142
|
+
raise Errors::InvalidTypeError, "contains expects 2nd arg to be a list"
|
143
|
+
end
|
144
|
+
else
|
145
|
+
raise Errors::InvalidArityError, "function contains() expects 2 arguments"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class FloorFunction < Function
|
151
|
+
FUNCTIONS['floor'] = self
|
152
|
+
|
153
|
+
def call(args)
|
154
|
+
if args.count == 1
|
155
|
+
value = args.first
|
156
|
+
else
|
157
|
+
raise Errors::InvalidArityError, "function floor() expects one argument"
|
158
|
+
end
|
159
|
+
if Numeric === value
|
160
|
+
value.floor
|
161
|
+
else
|
162
|
+
raise Errors::InvalidTypeError, "function floor() expects a numeric value"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class LengthFunction < Function
|
168
|
+
FUNCTIONS['length'] = self
|
169
|
+
|
170
|
+
def call(args)
|
171
|
+
if args.count == 1
|
172
|
+
value = args.first
|
173
|
+
else
|
174
|
+
raise Errors::InvalidArityError, "function length() expects one argument"
|
175
|
+
end
|
176
|
+
case value
|
177
|
+
when Hash, Array, String then value.size
|
178
|
+
else raise Errors::InvalidTypeError, "function length() expects string, array or object"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class MaxFunction < Function
|
184
|
+
include TypeChecker
|
185
|
+
|
186
|
+
FUNCTIONS['max'] = self
|
187
|
+
|
188
|
+
def call(args)
|
189
|
+
if args.count == 1
|
190
|
+
values = args.first
|
191
|
+
else
|
192
|
+
raise Errors::InvalidArityError, "function max() expects one argument"
|
193
|
+
end
|
194
|
+
if Array === values
|
195
|
+
return nil if values.empty?
|
196
|
+
first = values.first
|
197
|
+
first_type = get_type(first)
|
198
|
+
unless first_type == NUMBER_TYPE || first_type == STRING_TYPE
|
199
|
+
msg = "function max() expects numeric or string values"
|
200
|
+
raise Errors::InvalidTypeError, msg
|
201
|
+
end
|
202
|
+
values.inject([first, first_type]) do |(max, max_type), v|
|
203
|
+
v_type = get_type(v)
|
204
|
+
if max_type == v_type
|
205
|
+
v > max ? [v, v_type] : [max, max_type]
|
206
|
+
else
|
207
|
+
msg = "function max() encountered a type mismatch in sequence: "
|
208
|
+
msg << "#{max_type}, #{v_type}"
|
209
|
+
raise Errors::InvalidTypeError, msg
|
210
|
+
end
|
211
|
+
end.first
|
212
|
+
else
|
213
|
+
raise Errors::InvalidTypeError, "function max() expects an array"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class MinFunction < Function
|
219
|
+
include TypeChecker
|
220
|
+
|
221
|
+
FUNCTIONS['min'] = self
|
222
|
+
|
223
|
+
def call(args)
|
224
|
+
if args.count == 1
|
225
|
+
values = args.first
|
226
|
+
else
|
227
|
+
raise Errors::InvalidArityError, "function min() expects one argument"
|
228
|
+
end
|
229
|
+
if Array === values
|
230
|
+
return nil if values.empty?
|
231
|
+
first = values.first
|
232
|
+
first_type = get_type(first)
|
233
|
+
unless first_type == NUMBER_TYPE || first_type == STRING_TYPE
|
234
|
+
msg = "function min() expects numeric or string values"
|
235
|
+
raise Errors::InvalidTypeError, msg
|
236
|
+
end
|
237
|
+
values.inject([first, first_type]) do |(min, min_type), v|
|
238
|
+
v_type = get_type(v)
|
239
|
+
if min_type == v_type
|
240
|
+
v < min ? [v, v_type] : [min, min_type]
|
241
|
+
else
|
242
|
+
msg = "function min() encountered a type mismatch in sequence: "
|
243
|
+
msg << "#{min_type}, #{v_type}"
|
244
|
+
raise Errors::InvalidTypeError, msg
|
245
|
+
end
|
246
|
+
end.first
|
247
|
+
else
|
248
|
+
raise Errors::InvalidTypeError, "function min() expects an array"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class TypeFunction < Function
|
254
|
+
include TypeChecker
|
255
|
+
|
256
|
+
FUNCTIONS['type'] = self
|
257
|
+
|
258
|
+
def call(args)
|
259
|
+
if args.count == 1
|
260
|
+
TYPE_NAMES[get_type(args.first)]
|
261
|
+
else
|
262
|
+
raise Errors::InvalidArityError, "function type() expects one argument"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class KeysFunction < Function
|
268
|
+
FUNCTIONS['keys'] = self
|
269
|
+
|
270
|
+
def call(args)
|
271
|
+
if args.count == 1
|
272
|
+
value = args.first
|
273
|
+
if hash_like?(value)
|
274
|
+
case value
|
275
|
+
when Hash then value.keys.map(&:to_s)
|
276
|
+
when Struct then value.members.map(&:to_s)
|
277
|
+
else raise NotImplementedError
|
278
|
+
end
|
279
|
+
else
|
280
|
+
raise Errors::InvalidTypeError, "function keys() expects a hash"
|
281
|
+
end
|
282
|
+
else
|
283
|
+
raise Errors::InvalidArityError, "function keys() expects one argument"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class ValuesFunction < Function
|
289
|
+
FUNCTIONS['values'] = self
|
290
|
+
|
291
|
+
def call(args)
|
292
|
+
if args.count == 1
|
293
|
+
value = args.first
|
294
|
+
if hash_like?(value)
|
295
|
+
value.values
|
296
|
+
elsif Array === value
|
297
|
+
value
|
298
|
+
else
|
299
|
+
raise Errors::InvalidTypeError, "function values() expects an array or a hash"
|
300
|
+
end
|
301
|
+
else
|
302
|
+
raise Errors::InvalidArityError, "function values() expects one argument"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
class JoinFunction < Function
|
308
|
+
FUNCTIONS['join'] = self
|
309
|
+
|
310
|
+
def call(args)
|
311
|
+
if args.count == 2
|
312
|
+
glue = args[0]
|
313
|
+
values = args[1]
|
314
|
+
if !(String === glue)
|
315
|
+
raise Errors::InvalidTypeError, "function join() expects the first argument to be a string"
|
316
|
+
elsif Array === values && values.all? { |v| String === v }
|
317
|
+
values.join(glue)
|
318
|
+
else
|
319
|
+
raise Errors::InvalidTypeError, "function join() expects values to be an array of strings"
|
320
|
+
end
|
321
|
+
else
|
322
|
+
raise Errors::InvalidArityError, "function join() expects an array of strings"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class ToStringFunction < Function
|
328
|
+
FUNCTIONS['to_string'] = self
|
329
|
+
|
330
|
+
def call(args)
|
331
|
+
if args.count == 1
|
332
|
+
value = args.first
|
333
|
+
String === value ? value : JSON.dump(value)
|
334
|
+
else
|
335
|
+
raise Errors::InvalidArityError, "function to_string() expects one argument"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
class ToNumberFunction < Function
|
341
|
+
FUNCTIONS['to_number'] = self
|
342
|
+
|
343
|
+
def call(args)
|
344
|
+
if args.count == 1
|
345
|
+
begin
|
346
|
+
value = Float(args.first)
|
347
|
+
Integer(value) === value ? value.to_i : value
|
348
|
+
rescue
|
349
|
+
nil
|
350
|
+
end
|
351
|
+
else
|
352
|
+
raise Errors::InvalidArityError, "function to_number() expects one argument"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
class SumFunction < Function
|
358
|
+
FUNCTIONS['sum'] = self
|
359
|
+
|
360
|
+
def call(args)
|
361
|
+
if args.count == 1 && Array === args.first
|
362
|
+
args.first.inject(0) do |sum,n|
|
363
|
+
if Numeric === n
|
364
|
+
sum + n
|
365
|
+
else
|
366
|
+
raise Errors::InvalidTypeError, "function sum() expects values to be numeric"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
else
|
370
|
+
raise Errors::InvalidArityError, "function sum() expects one argument"
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
class NotNullFunction < Function
|
376
|
+
FUNCTIONS['not_null'] = self
|
377
|
+
|
378
|
+
def call(args)
|
379
|
+
if args.count > 0
|
380
|
+
args.find { |value| !value.nil? }
|
381
|
+
else
|
382
|
+
raise Errors::InvalidArityError, "function not_null() expects one or more arguments"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
class SortFunction < Function
|
388
|
+
include TypeChecker
|
389
|
+
|
390
|
+
FUNCTIONS['sort'] = self
|
391
|
+
|
392
|
+
def call(args)
|
393
|
+
if args.count == 1
|
394
|
+
value = args.first
|
395
|
+
if Array === value
|
396
|
+
value.sort do |a, b|
|
397
|
+
a_type = get_type(a)
|
398
|
+
b_type = get_type(b)
|
399
|
+
if (a_type == STRING_TYPE || a_type == NUMBER_TYPE) && a_type == b_type
|
400
|
+
a <=> b
|
401
|
+
else
|
402
|
+
raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
|
403
|
+
end
|
404
|
+
end
|
405
|
+
else
|
406
|
+
raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
|
407
|
+
end
|
408
|
+
else
|
409
|
+
raise Errors::InvalidArityError, "function sort() expects one argument"
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
class SortByFunction < Function
|
415
|
+
include TypeChecker
|
416
|
+
|
417
|
+
FUNCTIONS['sort_by'] = self
|
418
|
+
|
419
|
+
def call(args)
|
420
|
+
if args.count == 2
|
421
|
+
if get_type(args[0]) == ARRAY_TYPE && get_type(args[1]) == EXPRESSION_TYPE
|
422
|
+
values = args[0]
|
423
|
+
expression = args[1]
|
424
|
+
values.sort do |a,b|
|
425
|
+
a_value = expression.eval(a)
|
426
|
+
b_value = expression.eval(b)
|
427
|
+
a_type = get_type(a_value)
|
428
|
+
b_type = get_type(b_value)
|
429
|
+
if (a_type == STRING_TYPE || a_type == NUMBER_TYPE) && a_type == b_type
|
430
|
+
a_value <=> b_value
|
431
|
+
else
|
432
|
+
raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
|
433
|
+
end
|
434
|
+
end
|
435
|
+
else
|
436
|
+
raise Errors::InvalidTypeError, "function sort_by() expects an array and an expression"
|
437
|
+
end
|
438
|
+
else
|
439
|
+
raise Errors::InvalidArityError, "function sort_by() expects two arguments"
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
module CompareBy
|
445
|
+
include TypeChecker
|
446
|
+
|
447
|
+
def compare_by(mode, *args)
|
448
|
+
if args.count == 2
|
449
|
+
values = args[0]
|
450
|
+
expression = args[1]
|
451
|
+
if get_type(values) == ARRAY_TYPE && get_type(expression) == EXPRESSION_TYPE
|
452
|
+
type = get_type(expression.eval(values.first))
|
453
|
+
if type != NUMBER_TYPE && type != STRING_TYPE
|
454
|
+
msg = "function #{mode}() expects values to be strings or numbers"
|
455
|
+
raise Errors::InvalidTypeError, msg
|
456
|
+
end
|
457
|
+
values.send(mode) do |entry|
|
458
|
+
value = expression.eval(entry)
|
459
|
+
value_type = get_type(value)
|
460
|
+
if value_type != type
|
461
|
+
msg = "function #{mode}() encountered a type mismatch in "
|
462
|
+
msg << "sequence: #{type}, #{value_type}"
|
463
|
+
raise Errors::InvalidTypeError, msg
|
464
|
+
end
|
465
|
+
value
|
466
|
+
end
|
467
|
+
else
|
468
|
+
msg = "function #{mode}() expects an array and an expression"
|
469
|
+
raise Errors::InvalidTypeError, msg
|
470
|
+
end
|
471
|
+
else
|
472
|
+
msg = "function #{mode}() expects two arguments"
|
473
|
+
raise Errors::InvalidArityError, msg
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
class MaxByFunction < Function
|
479
|
+
include CompareBy
|
480
|
+
|
481
|
+
FUNCTIONS['max_by'] = self
|
482
|
+
|
483
|
+
def call(args)
|
484
|
+
compare_by(:max_by, *args)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
class MinByFunction < Function
|
489
|
+
include CompareBy
|
490
|
+
|
491
|
+
FUNCTIONS['min_by'] = self
|
492
|
+
|
493
|
+
def call(args)
|
494
|
+
compare_by(:min_by, *args)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
class EndsWithFunction < Function
|
499
|
+
include TypeChecker
|
500
|
+
|
501
|
+
FUNCTIONS['ends_with'] = self
|
502
|
+
|
503
|
+
def call(args)
|
504
|
+
if args.count == 2
|
505
|
+
search, suffix = args
|
506
|
+
search_type = get_type(search)
|
507
|
+
suffix_type = get_type(suffix)
|
508
|
+
if search_type != STRING_TYPE
|
509
|
+
msg = "function ends_with() expects first argument to be a string"
|
510
|
+
raise Errors::InvalidTypeError, msg
|
511
|
+
end
|
512
|
+
if suffix_type != STRING_TYPE
|
513
|
+
msg = "function ends_with() expects second argument to be a string"
|
514
|
+
raise Errors::InvalidTypeError, msg
|
515
|
+
end
|
516
|
+
search.end_with?(suffix)
|
517
|
+
else
|
518
|
+
msg = "function ends_with() expects two arguments"
|
519
|
+
raise Errors::InvalidArityError, msg
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
class StartsWithFunction < Function
|
525
|
+
include TypeChecker
|
526
|
+
|
527
|
+
FUNCTIONS['starts_with'] = self
|
528
|
+
|
529
|
+
def call(args)
|
530
|
+
if args.count == 2
|
531
|
+
search, prefix = args
|
532
|
+
search_type = get_type(search)
|
533
|
+
prefix_type = get_type(prefix)
|
534
|
+
if search_type != STRING_TYPE
|
535
|
+
msg = "function starts_with() expects first argument to be a string"
|
536
|
+
raise Errors::InvalidTypeError, msg
|
537
|
+
end
|
538
|
+
if prefix_type != STRING_TYPE
|
539
|
+
msg = "function starts_with() expects second argument to be a string"
|
540
|
+
raise Errors::InvalidTypeError, msg
|
541
|
+
end
|
542
|
+
search.start_with?(prefix)
|
543
|
+
else
|
544
|
+
msg = "function starts_with() expects two arguments"
|
545
|
+
raise Errors::InvalidArityError, msg
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
class MergeFunction < Function
|
551
|
+
FUNCTIONS['merge'] = self
|
552
|
+
|
553
|
+
def call(args)
|
554
|
+
if args.count == 0
|
555
|
+
msg = "function merge() expects 1 or more arguments"
|
556
|
+
raise Errors::InvalidArityError, msg
|
557
|
+
end
|
558
|
+
args.inject({}) do |h, v|
|
559
|
+
h.merge(v)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
class ReverseFunction < Function
|
565
|
+
FUNCTIONS['reverse'] = self
|
566
|
+
|
567
|
+
def call(args)
|
568
|
+
if args.count == 0
|
569
|
+
msg = "function reverse() expects 1 or more arguments"
|
570
|
+
raise Errors::InvalidArityError, msg
|
571
|
+
end
|
572
|
+
value = args.first
|
573
|
+
if Array === value || String === value
|
574
|
+
value.reverse
|
575
|
+
else
|
576
|
+
msg = "function reverse() expects an array or string"
|
577
|
+
raise Errors::InvalidTypeError, msg
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
class ToArrayFunction < Function
|
583
|
+
FUNCTIONS['to_array'] = self
|
584
|
+
|
585
|
+
def call(args)
|
586
|
+
value = args.first
|
587
|
+
Array === value ? value : [value]
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|