cel 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +41 -9
- data/lib/cel/ast/elements/protobuf.rb +29 -26
- data/lib/cel/ast/elements.rb +52 -20
- data/lib/cel/ast/types.rb +2 -2
- data/lib/cel/checker.rb +47 -31
- data/lib/cel/context.rb +18 -19
- data/lib/cel/environment.rb +22 -4
- data/lib/cel/errors.rb +12 -0
- data/lib/cel/extensions/bind.rb +35 -0
- data/lib/cel/extensions/encoders.rb +41 -0
- data/lib/cel/extensions/math.rb +211 -0
- data/lib/cel/extensions/string.rb +228 -0
- data/lib/cel/extensions.rb +15 -0
- data/lib/cel/macro.rb +4 -3
- data/lib/cel/parser.rb +207 -23
- data/lib/cel/program.rb +32 -17
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +2 -1
- metadata +23 -4
data/lib/cel/errors.rb
CHANGED
|
@@ -5,6 +5,9 @@ module Cel
|
|
|
5
5
|
|
|
6
6
|
class ParseError < Error; end
|
|
7
7
|
|
|
8
|
+
class MaxRecursionDepthExceededError < ParseError; end
|
|
9
|
+
class MaxNestingDepthExceededError < ParseError; end
|
|
10
|
+
|
|
8
11
|
class CheckError < Error; end
|
|
9
12
|
|
|
10
13
|
class EvaluateError < Error; end
|
|
@@ -45,5 +48,14 @@ module Cel
|
|
|
45
48
|
end
|
|
46
49
|
end
|
|
47
50
|
|
|
51
|
+
class NoOverloadError < EvaluateError
|
|
52
|
+
attr_reader :code
|
|
53
|
+
|
|
54
|
+
def initialize
|
|
55
|
+
super("No such overload")
|
|
56
|
+
@code = :no_overload
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
48
60
|
class BindingError < EvaluateError; end
|
|
49
61
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
module Extensions
|
|
5
|
+
module Bind
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def __check(funcall, checker:)
|
|
9
|
+
func = funcall.func
|
|
10
|
+
args = funcall.args
|
|
11
|
+
|
|
12
|
+
return checker.unsupported_operation(funcall) unless func == :bind
|
|
13
|
+
|
|
14
|
+
checker.check_arity(func, args, 3)
|
|
15
|
+
|
|
16
|
+
id, type, expr = args
|
|
17
|
+
|
|
18
|
+
return checker.unsupported_operation(funcall) unless id.is_a?(Identifier)
|
|
19
|
+
|
|
20
|
+
type = checker.call(type)
|
|
21
|
+
|
|
22
|
+
checker.environment.with(declarations: { id => type }) do
|
|
23
|
+
checker.environment.check(expr)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def bind(var, val, expr, program:)
|
|
28
|
+
program.environment.with(declarations: { var.to_sym => val.type }, disable_check: false) do
|
|
29
|
+
bindings = program.context.bindings.merge({ var.to_sym => val })
|
|
30
|
+
program.environment.evaluate(expr, bindings)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
module Extensions
|
|
5
|
+
module Encoders
|
|
6
|
+
module Base64
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def __check(funcall, checker:)
|
|
10
|
+
func = funcall.func
|
|
11
|
+
args = funcall.args
|
|
12
|
+
|
|
13
|
+
case func
|
|
14
|
+
when :encode
|
|
15
|
+
checker.check_arity(func, args, 1)
|
|
16
|
+
arg = checker.call(args.first)
|
|
17
|
+
return TYPES[:string] if checker.find_match_all_types(%i[bytes], arg)
|
|
18
|
+
when :decode
|
|
19
|
+
checker.check_arity(func, args, 1)
|
|
20
|
+
arg = checker.call(args.first)
|
|
21
|
+
return TYPES[:bytes] if checker.find_match_all_types(%i[string], arg)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
checker.unsupported_operation(funcall)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def encode(str, program:)
|
|
28
|
+
value = program.call(str).value
|
|
29
|
+
|
|
30
|
+
Cel::String.new([value.pack("C*")].pack("m0"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def decode(str, program:)
|
|
34
|
+
value = program.call(str).value
|
|
35
|
+
|
|
36
|
+
Cel::Bytes.new(value.unpack1("m").unpack("C*"))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
module Extensions
|
|
5
|
+
module Math
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def __check(funcall, checker:)
|
|
9
|
+
func = funcall.func
|
|
10
|
+
args = funcall.args
|
|
11
|
+
|
|
12
|
+
case func
|
|
13
|
+
when :round, :trunc, :floor, :ceil
|
|
14
|
+
checker.check_arity(func, args, 1)
|
|
15
|
+
arg = checker.call(args.first)
|
|
16
|
+
return TYPES[:double] if checker.find_match_all_types(%i[double], arg)
|
|
17
|
+
when :isInf, :isNaN, :isFinite
|
|
18
|
+
checker.check_arity(func, args, 1)
|
|
19
|
+
arg = checker.call(args.first)
|
|
20
|
+
return TYPES[:bool] if checker.find_match_all_types(%i[double], arg)
|
|
21
|
+
when :bitNot
|
|
22
|
+
checker.check_arity(func, args, 1)
|
|
23
|
+
arg = checker.call(args.first)
|
|
24
|
+
return TYPES[:int] if checker.find_match_all_types(%i[int uint], arg)
|
|
25
|
+
when :bitOr, :bitAnd, :bitXor
|
|
26
|
+
checker.check_arity(func, args, 2)
|
|
27
|
+
args = args.map(&checker.method(:call))
|
|
28
|
+
|
|
29
|
+
type = checker.find_match_all_types(%i[int uint], args)
|
|
30
|
+
return type if type
|
|
31
|
+
when :sign, :abs
|
|
32
|
+
checker.check_arity(func, args, 1)
|
|
33
|
+
arg = checker.call(args.first)
|
|
34
|
+
type = checker.find_match_all_types(%i[double int uint], arg)
|
|
35
|
+
return if type
|
|
36
|
+
when :least, :greatest
|
|
37
|
+
checker.check_arity_any(func, args)
|
|
38
|
+
args = args.map(&checker.method(:call))
|
|
39
|
+
|
|
40
|
+
return TYPES[:any] if args.all? do |arg|
|
|
41
|
+
case arg
|
|
42
|
+
when TYPES[:any], TYPES[:int], TYPES[:uint], TYPES[:double],
|
|
43
|
+
TYPES[:list, :int], TYPES[:list, :uint], TYPES[:list, :double], TYPES[:list, :any]
|
|
44
|
+
true
|
|
45
|
+
else
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
when :bitShiftRight, :bitShiftLeft
|
|
50
|
+
checker.check_arity(func, args, 2)
|
|
51
|
+
num, amount_of_bits = args.map(&checker.method(:call))
|
|
52
|
+
|
|
53
|
+
return num if checker.find_match_all_types(%i[int uint],
|
|
54
|
+
num) && checker.find_match_all_types(%i[int], amount_of_bits)
|
|
55
|
+
else
|
|
56
|
+
checker.unsupported_operation(funcall)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
checker.unsupported_operation(funcall)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def abs(num, program:)
|
|
63
|
+
num = program.call(num)
|
|
64
|
+
|
|
65
|
+
raise EvaluateError, "out of range" unless num.value.between?(-MAX_INT + 1, MAX_INT - 1)
|
|
66
|
+
|
|
67
|
+
Number.new(num.type, num.value.abs)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def round(num, program:)
|
|
71
|
+
num = program.call(num)
|
|
72
|
+
|
|
73
|
+
Number.new(:double, num.value.round)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def trunc(num, program:)
|
|
77
|
+
num = program.call(num)
|
|
78
|
+
|
|
79
|
+
Number.new(:double, num.value.truncate)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def floor(num, program:)
|
|
83
|
+
num = program.call(num)
|
|
84
|
+
|
|
85
|
+
Number.new(:double, num.value.floor)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ceil(num, program:)
|
|
89
|
+
num = program.call(num)
|
|
90
|
+
|
|
91
|
+
Number.new(:double, num.value.ceil)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def bitNot(num, program:)
|
|
95
|
+
val = program.call(num).value
|
|
96
|
+
|
|
97
|
+
case num.type
|
|
98
|
+
when TYPES[:int]
|
|
99
|
+
Number.new(:int, ~val)
|
|
100
|
+
when TYPES[:uint]
|
|
101
|
+
Number.new(:uint, ((2**64) - 1) - val)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def bitOr(lhs, rhs, program:)
|
|
106
|
+
lhs = program.call(lhs)
|
|
107
|
+
rhs = program.call(rhs)
|
|
108
|
+
Number.new(lhs.type, lhs | rhs)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def bitAnd(lhs, rhs, program:)
|
|
112
|
+
lhs = program.call(lhs)
|
|
113
|
+
rhs = program.call(rhs)
|
|
114
|
+
Number.new(lhs.type, lhs & rhs)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def bitXor(lhs, rhs, program:)
|
|
118
|
+
lhs = program.call(lhs)
|
|
119
|
+
rhs = program.call(rhs)
|
|
120
|
+
Number.new(lhs.type, lhs ^ rhs)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def sign(num, program:)
|
|
124
|
+
num = program.call(num)
|
|
125
|
+
value = num.value
|
|
126
|
+
|
|
127
|
+
Number.new(num.type, if value.negative?
|
|
128
|
+
-1
|
|
129
|
+
else
|
|
130
|
+
value.positive? ? 1 : 0
|
|
131
|
+
end)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def least(*args, program:)
|
|
135
|
+
args = args.map(&program.method(:call))
|
|
136
|
+
|
|
137
|
+
return args.min if args.size > 1
|
|
138
|
+
|
|
139
|
+
arg = args.first
|
|
140
|
+
|
|
141
|
+
case arg
|
|
142
|
+
when List
|
|
143
|
+
least(*arg.value, program: program)
|
|
144
|
+
else
|
|
145
|
+
arg
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def greatest(*args, program:)
|
|
150
|
+
args = args.map(&program.method(:call))
|
|
151
|
+
|
|
152
|
+
return args.max if args.size > 1
|
|
153
|
+
|
|
154
|
+
arg = args.first
|
|
155
|
+
|
|
156
|
+
case arg
|
|
157
|
+
when List
|
|
158
|
+
greatest(*arg.value, program: program)
|
|
159
|
+
else
|
|
160
|
+
arg
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def bitShiftRight(num, amount_of_bits, program:)
|
|
165
|
+
num = program.call(num)
|
|
166
|
+
amount_of_bits = program.call(amount_of_bits).value
|
|
167
|
+
|
|
168
|
+
raise EvaluateError, "math.#{__method__}() negative offset: #{amount_of_bits}" if amount_of_bits.negative?
|
|
169
|
+
|
|
170
|
+
# When the second parameter is 64 or greater, 0 will always be returned
|
|
171
|
+
return Number.new(num.type, 0) if amount_of_bits >= 64
|
|
172
|
+
|
|
173
|
+
value = num.value.negative? ? ((2**64) - 1) & num.value : num.value
|
|
174
|
+
|
|
175
|
+
Number.new(num.type, value >> amount_of_bits)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def bitShiftLeft(num, amount_of_bits, program:)
|
|
179
|
+
num = program.call(num)
|
|
180
|
+
amount_of_bits = program.call(amount_of_bits).value
|
|
181
|
+
|
|
182
|
+
raise EvaluateError, "math.#{__method__}() negative offset: #{amount_of_bits}" if amount_of_bits.negative?
|
|
183
|
+
|
|
184
|
+
# When the second parameter is 64 or greater, 0 will always be returned
|
|
185
|
+
return Number.new(num.type, 0) if amount_of_bits >= 64
|
|
186
|
+
|
|
187
|
+
Number.new(num.type, num.value << amount_of_bits)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def isInf(num, program:)
|
|
191
|
+
val = program.call(num).value
|
|
192
|
+
|
|
193
|
+
Bool.cast(val.infinite?)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def isNaN(num, program:)
|
|
197
|
+
val = program.call(num).value
|
|
198
|
+
|
|
199
|
+
Bool.cast(val.nan?)
|
|
200
|
+
rescue FloatDomainError
|
|
201
|
+
Bool.cast(true)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def isFinite(num, program:)
|
|
205
|
+
val = program.call(num).value
|
|
206
|
+
|
|
207
|
+
Bool.cast(!val.infinite? && !val.nan?)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
module Extensions
|
|
5
|
+
module String
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def __check(funcall, checker:)
|
|
9
|
+
var = funcall.var
|
|
10
|
+
func = funcall.func
|
|
11
|
+
args = funcall.args
|
|
12
|
+
|
|
13
|
+
case func
|
|
14
|
+
when :quote
|
|
15
|
+
checker.check_arity(func, args, 1)
|
|
16
|
+
arg = checker.call(args.first)
|
|
17
|
+
return TYPES[:string] if checker.find_match_all_types(%i[string], arg)
|
|
18
|
+
when :indexOf, :lastIndexOf
|
|
19
|
+
if checker.call(var) == TYPES[:string]
|
|
20
|
+
checker.check_arity(func, args, 2, :>=)
|
|
21
|
+
arg, pos = args
|
|
22
|
+
arg = checker.call(arg)
|
|
23
|
+
if checker.find_match_all_types(%i[string], arg)
|
|
24
|
+
return TYPES[:int] if pos.nil?
|
|
25
|
+
|
|
26
|
+
pos = checker.call(pos)
|
|
27
|
+
return TYPES[:int] if checker.find_match_all_types(%i[int], pos)
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
when :split
|
|
32
|
+
if checker.call(var) == TYPES[:string]
|
|
33
|
+
checker.check_arity(func, args, 2, :>=)
|
|
34
|
+
arg, pos = args
|
|
35
|
+
arg = checker.call(arg)
|
|
36
|
+
if checker.find_match_all_types(%i[string], arg)
|
|
37
|
+
return TYPES[:list, :string] if pos.nil?
|
|
38
|
+
|
|
39
|
+
pos = checker.call(pos)
|
|
40
|
+
return TYPES[:list, :string] if checker.find_match_all_types(%i[int], pos)
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
when :substring
|
|
45
|
+
if checker.call(var) == TYPES[:string]
|
|
46
|
+
checker.check_arity(func, args, 2, :>=)
|
|
47
|
+
arg, pos = args
|
|
48
|
+
arg = checker.call(arg)
|
|
49
|
+
if checker.find_match_all_types(%i[int], arg)
|
|
50
|
+
return TYPES[:string] if pos.nil?
|
|
51
|
+
|
|
52
|
+
pos = checker.call(pos)
|
|
53
|
+
return TYPES[:string] if checker.find_match_all_types(%i[int], pos)
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
when :trim, :lowerAscii, :upperAscii, :reverse
|
|
58
|
+
if checker.call(var) == TYPES[:string]
|
|
59
|
+
checker.check_arity(func, args, 0)
|
|
60
|
+
return TYPES[:string]
|
|
61
|
+
end
|
|
62
|
+
when :format
|
|
63
|
+
if checker.call(var) == TYPES[:string]
|
|
64
|
+
checker.check_arity(func, args, 1)
|
|
65
|
+
arg = checker.call(args.first)
|
|
66
|
+
return TYPES[:string] if checker.find_match_all_types(%i[list], arg)
|
|
67
|
+
end
|
|
68
|
+
when :charAt
|
|
69
|
+
if checker.call(var) == TYPES[:string]
|
|
70
|
+
checker.check_arity(func, args, 1)
|
|
71
|
+
arg = checker.call(args.first)
|
|
72
|
+
return TYPES[:string] if checker.find_match_all_types(%i[int], arg)
|
|
73
|
+
end
|
|
74
|
+
when :replace
|
|
75
|
+
if checker.call(var) == TYPES[:string]
|
|
76
|
+
checker.check_arity(func, args, 2..3, :include?)
|
|
77
|
+
p, r, limit = args.map(&checker.method(:call))
|
|
78
|
+
if limit
|
|
79
|
+
return TYPES[:string] if checker.find_match_all_types(%i[string], [p, r]) &&
|
|
80
|
+
checker.find_match_all_types(%i[int], limit)
|
|
81
|
+
elsif checker.find_match_all_types(%i[string], [p, r])
|
|
82
|
+
return TYPES[:string]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
when :join
|
|
86
|
+
if checker.call(var) == TYPES[:list, :string] || checker.call(var) == TYPES[:list, :any]
|
|
87
|
+
checker.check_arity(func, args, 1, :>=)
|
|
88
|
+
arg = checker.call(args.first)
|
|
89
|
+
return TYPES[:string] unless arg
|
|
90
|
+
return TYPES[:string] if checker.find_match_all_types(%i[string], arg)
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
checker.unsupported_operation(funcall)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def quote(str, program:)
|
|
99
|
+
value = program.call(str).value
|
|
100
|
+
|
|
101
|
+
# this should use String#dump, but this also replaces printable UTF8 characters
|
|
102
|
+
|
|
103
|
+
# replace quotes
|
|
104
|
+
value = value.gsub(/[\\'"\t\r\a\b\v\f\n]/) { |m| m.dump[1..-2] }
|
|
105
|
+
|
|
106
|
+
# replace invalid utf-8 chars
|
|
107
|
+
value = value.encode(Encoding::UTF_8, invalid: :replace)
|
|
108
|
+
|
|
109
|
+
# adding leading quotes if not quoted already
|
|
110
|
+
value = "\"#{value}\"" unless value.start_with?("\"") && value.end_with?("\"")
|
|
111
|
+
|
|
112
|
+
Cel::String.new(value)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class String
|
|
118
|
+
def charAt(idx)
|
|
119
|
+
raise EvaluateError, "index out of range: #{idx}" unless idx.between?(0, @value.size)
|
|
120
|
+
|
|
121
|
+
String.new(@value[idx] || "")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def indexOf(substr, *args)
|
|
125
|
+
idx, = args
|
|
126
|
+
|
|
127
|
+
raise EvaluateError, "index out of range: #{idx}" if idx && !idx.between?(0, @value.size)
|
|
128
|
+
|
|
129
|
+
Number.new(:int, @value.index(substr, *args) || -1)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def lastIndexOf(substr, *args)
|
|
133
|
+
idx, = args
|
|
134
|
+
|
|
135
|
+
raise EvaluateError, "index out of range: #{idx}" if idx && !idx.between?(0, @value.size)
|
|
136
|
+
|
|
137
|
+
Number.new(:int, @value.rindex(substr, *args) || -1)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def lowerAscii
|
|
141
|
+
String.new(@value.downcase(:ascii))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def upperAscii
|
|
145
|
+
String.new(@value.upcase(:ascii))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
UNICODE_WHITESPACE = /[ \t\r\n\u000C\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000]+/
|
|
149
|
+
|
|
150
|
+
def trim
|
|
151
|
+
# why this? because String#split only takes null, horizontal tab, line feed, vertical tab, form feed,
|
|
152
|
+
# carriage return, space into account. We need the unicode definition of whitespace.
|
|
153
|
+
value = @value.gsub(/\A#{UNICODE_WHITESPACE}/o, "")
|
|
154
|
+
.gsub(/#{UNICODE_WHITESPACE}\z/o, "")
|
|
155
|
+
|
|
156
|
+
String.new(value.strip)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def reverse
|
|
160
|
+
String.new(@value.reverse)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def split(pattern, *args)
|
|
164
|
+
raise NoOverloadError unless pattern.is_a?(String) && args.size.between?(0, 1)
|
|
165
|
+
|
|
166
|
+
limit, = args
|
|
167
|
+
|
|
168
|
+
raise NoOverloadError if limit && !limit.is_a?(Number)
|
|
169
|
+
|
|
170
|
+
return List.new([]) if limit && limit.zero?
|
|
171
|
+
|
|
172
|
+
List.new(@value.split(pattern, *args))
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def substring(*args)
|
|
176
|
+
raise NoOverloadError unless args.size.between?(1, 2)
|
|
177
|
+
|
|
178
|
+
start_idx, end_idx = *args
|
|
179
|
+
end_idx ||= @value.size
|
|
180
|
+
|
|
181
|
+
raise EvaluateError, "index out of range: #{start_idx}" unless start_idx.between?(0, @value.size)
|
|
182
|
+
|
|
183
|
+
raise EvaluateError, "index out of range: #{end_idx}" if end_idx >= @value.size + 1
|
|
184
|
+
|
|
185
|
+
raise EvaluateError, "end index can't be higher than start index" if end_idx < start_idx
|
|
186
|
+
|
|
187
|
+
String.new(@value[start_idx..(end_idx - 1)])
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def replace(pattern, replacement, *args)
|
|
191
|
+
raise NoOverloadError unless args.size.between?(0, 1)
|
|
192
|
+
|
|
193
|
+
limit, = args
|
|
194
|
+
|
|
195
|
+
raise NoOverloadError unless pattern.is_a?(String) && replacement.is_a?(String)
|
|
196
|
+
|
|
197
|
+
pattern = pattern.value
|
|
198
|
+
replacement = replacement.value
|
|
199
|
+
|
|
200
|
+
value = if limit
|
|
201
|
+
raise NoOverloadError unless limit.is_a?(Number)
|
|
202
|
+
|
|
203
|
+
@value.gsub(pattern) do |m|
|
|
204
|
+
if limit.zero?
|
|
205
|
+
m
|
|
206
|
+
else
|
|
207
|
+
limit -= 1
|
|
208
|
+
replacement
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
@value.gsub(pattern, replacement)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
String.new(value)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def format(mappings)
|
|
219
|
+
String.new(format(@value, *mappings))
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class List
|
|
224
|
+
def join(*)
|
|
225
|
+
String.new(super)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cel/extensions/math"
|
|
4
|
+
require "cel/extensions/string"
|
|
5
|
+
require "cel/extensions/bind"
|
|
6
|
+
require "cel/extensions/encoders"
|
|
7
|
+
|
|
8
|
+
module Cel
|
|
9
|
+
EXTENSIONS = {
|
|
10
|
+
math: Extensions::Math,
|
|
11
|
+
strings: Extensions::String,
|
|
12
|
+
base64: Extensions::Encoders::Base64,
|
|
13
|
+
cel: Extensions::Bind,
|
|
14
|
+
}.freeze
|
|
15
|
+
end
|
data/lib/cel/macro.rb
CHANGED
|
@@ -13,12 +13,13 @@ module Cel
|
|
|
13
13
|
# If f is some other singular field, has(e.f) indicates whether the field's value is its default
|
|
14
14
|
# value (zero for numeric fields, false for booleans, empty for strings and bytes).
|
|
15
15
|
def has(invoke, program:)
|
|
16
|
-
var = invoke.var
|
|
17
16
|
func = invoke.func
|
|
17
|
+
var = program.disable_cel_conversion do
|
|
18
|
+
program.evaluate(invoke.var)
|
|
19
|
+
end
|
|
18
20
|
|
|
19
21
|
case var
|
|
20
|
-
when
|
|
21
|
-
var = program.evaluate(var)
|
|
22
|
+
when Protobuf.base_class
|
|
22
23
|
# If e evaluates to a message and f is not a declared field for the message,
|
|
23
24
|
# has(e.f) raises a no_such_field error.
|
|
24
25
|
raise NoSuchFieldError.new(var, func) unless var.respond_to?(func)
|