cel 0.6.1 → 0.6.2

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: d6d9641e7b6b5cd40bda6d81519f89e9b5bd8e7d8d595306291e6d470ed3aa3c
4
- data.tar.gz: 55490419080cc9eba65d907a99001deb7b3fbed08f1fc815e3dba293969162b8
3
+ metadata.gz: cba7782a9640b53263c12796cdcbf6b65d67265f6017f706cf8ae90c248e77fd
4
+ data.tar.gz: e7174cae9eab76f1d1fd9bbad8feaa9246dce94e2005b3262b1ba5df283b3d24
5
5
  SHA512:
6
- metadata.gz: 222160a59e461b0b0b6166fc839edb760b8b696a935c0471cae0f3437d1049215777a613cf9840e4fbd2c85c91fc80798e142de963ad0523eff9e586c9a53bd2
7
- data.tar.gz: 6abacc2f4aa0be7f74af0f44fec56bbfb57ccb770f60bb0072aa5334ed9381c7bb75447f948cf85278b40f42714f44139e9a3b6cfc6634e116ba6e821ea54961
6
+ metadata.gz: 3900c8e0bc26da2a9687f8b25411d308ddc9a4bdecad3aebc3ef751e2645d2fd6027a837eff52863bcdf2751e740fbe2035a4ff55ba5b71acc7c4ef6d7227cc9
7
+ data.tar.gz: faec09b8bc335bf26bb110a6ba1e36c12e4b60623d6faf3e4546e4fd91a0881d20011e003c4cc2fb41301f42a8658521d436412d66536e568f159ee23b45a850
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.2] - 2026-09-23
4
+
5
+ ### Features
6
+
7
+ * Support the lists extension functions, as listed [here](https://deepwiki.com/google/cel-go/9.2-standard-extensions):
8
+ * slice, flatten, sort, sortBy, distinct, reverse, range
9
+
10
+ ### Improvements
11
+
12
+ * Expression checker can now type-check Map expressions.
13
+ * Improve type expression for maps, where instead of the element type being the first matched, it'll infer whether all elements of the map are of the same type, and if not, fallback to any.
14
+
15
+ ### Bugfixes
16
+
17
+ * Fix issue when using map bindings via dot keys in macro expressions.
18
+ * Fix macro evaluation when variable used in expression was an accessor (instead of an identifier).
19
+ * timestamps: require RFC 3339 compliant strings on initialization (instead of all that ruby `Time.parse` accepts).
20
+
3
21
  ## [0.6.1] - 2026-07-27
4
22
 
5
23
  * parser: enabled support for declaring CEL messages inside a CEL list.
data/README.md CHANGED
@@ -197,6 +197,7 @@ env.evaluate("ext.random()") #=> 42
197
197
 
198
198
  * math extensions
199
199
  * string extensions
200
+ * list extensions
200
201
  * bindings extensions
201
202
  * block extensions
202
203
  * encoders extensions
@@ -4,15 +4,30 @@ require "time"
4
4
 
5
5
  module Cel
6
6
  class Timestamp < Literal
7
+ # Ruby 4.1 grows a core Time.rfc3339; below that DateTime.rfc3339 is the only stdlib RFC 3339
8
+ # parser. Gate on the version rather than respond_to?(:rfc3339) alone: ActiveSupport defines
9
+ # its own, laxer Time.rfc3339 on every ruby - it rolls invalid dates over, turning
10
+ # "2022-02-30T00:00:00Z" into March 2nd - so probing for the method would silently hand
11
+ # parsing to ActiveSupport, and only when it happened to be loaded first.
12
+ CORE_RFC3339 = RUBY_VERSION >= "4.1.0" && Time.respond_to?(:rfc3339)
13
+ private_constant :CORE_RFC3339
14
+
15
+ require "date" unless CORE_RFC3339
16
+
7
17
  TIMESTAMP_RANGE = (Time.parse("0001-01-01T00:00:00Z")..Time.parse("9999-12-31T23:59:59.999999999Z"))
8
18
 
9
19
  def initialize(value)
10
20
  value = case value
11
- when ::String then Time.parse(value)
21
+ when ::String then parse_rfc3339(value)
12
22
  when Numeric then Time.at(value)
13
23
  else value
14
24
  end
15
25
 
26
+ # CEL timestamps are absolute instants carrying no timezone, so normalize to UTC: that is
27
+ # what the string conversion and the offset-less accessors (getHours & co.) assume. Anything
28
+ # that is not a time falls through to the range check below, which rejects it.
29
+ value = value.getutc if value.is_a?(::Time)
30
+
16
31
  raise EvaluateError, "out of range" unless TIMESTAMP_RANGE.include?(value)
17
32
 
18
33
  super(:timestamp, value.round(9))
@@ -92,6 +107,25 @@ module Cel
92
107
 
93
108
  private
94
109
 
110
+ # The CEL spec defines timestamp() over RFC 3339 date-times, which always carry an explicit
111
+ # offset. Time.parse would instead accept anything remotely date-like and silently resolve
112
+ # offset-less input in the server-local timezone.
113
+ if CORE_RFC3339
114
+ def parse_rfc3339(value)
115
+ Time.rfc3339(value)
116
+ rescue ArgumentError
117
+ raise EvaluateError, "invalid timestamp #{value.inspect} (must be an RFC 3339 date-time)"
118
+ end
119
+ else
120
+ # Date::ITALY, the default calendar reform, reads pre-1582 date-times as Julian ones,
121
+ # shifting them off the proleptic Gregorian calendar Time uses.
122
+ def parse_rfc3339(value)
123
+ DateTime.rfc3339(value, Date::GREGORIAN).to_time
124
+ rescue ArgumentError # Date::Error
125
+ raise EvaluateError, "invalid timestamp #{value.inspect} (must be an RFC 3339 date-time)"
126
+ end
127
+ end
128
+
95
129
  def to_local_time(tz = nil)
96
130
  time = @value
97
131
  if tz
data/lib/cel/ast/types.rb CHANGED
@@ -214,25 +214,78 @@ module Cel
214
214
  end
215
215
 
216
216
  class MapType < Type
217
+ # The map's value type (`V` in `map(K, V)`). Named `element_type` for
218
+ # symmetry with ListType and because `TYPES[:map, V]` has always set the
219
+ # declared value type here.
217
220
  attr_accessor :element_type
218
221
 
222
+ # The map's key type (`K` in `map(K, V)`).
223
+ attr_accessor :key_type
224
+
225
+ # For a struct-like map literal — every key a constant — the per-key value
226
+ # types, as `{ <bare key> => Cel::Type }`. `nil` when at least one key is
227
+ # not a constant, so a caller can tell "no fixed key set" apart from "an
228
+ # empty one". Purely additional information: `==` compares `map(K, V)`, so
229
+ # two maps with the same K/V stay equal whatever their fields are.
230
+ attr_accessor :field_types
231
+
219
232
  attr_reader :type_map
220
233
  protected :type_map
221
234
 
222
235
  def initialize(type_map)
223
236
  super(:map)
224
237
  @type_map = type_map
225
- @element_type = @type_map.empty? ? TYPES[:any] : @type_map.sample.first.type
238
+ @key_type = MapType.uniform_type(@type_map.map { |k, _| MapType.node_type(k) })
239
+ @element_type = MapType.uniform_type(@type_map.map { |_, v| MapType.node_type(v) })
240
+ @field_types = nil
241
+ end
242
+
243
+ # The single type shared by `types`, or `any` when they disagree (or when
244
+ # there are none) — the same widening ListType applies to a heterogeneous
245
+ # list literal.
246
+ def self.uniform_type(types)
247
+ types = types.compact
248
+ return TYPES[:any] if types.empty?
249
+
250
+ first_type = types.first
251
+ types.all? { |type| type == first_type } ? first_type : TYPES[:any]
252
+ end
253
+
254
+ # A map-literal key is a constant or a bare identifier (see `Map#check`);
255
+ # an identifier key is a field name, i.e. a string.
256
+ #
257
+ # TODO: correct this in the parser instead. A bare identifier key is a
258
+ # field name, so the parser should emit a string node for it rather than an
259
+ # `Identifier` typed `any` that has to be patched up here.
260
+ def self.node_type(node)
261
+ return TYPES[:string] if node.is_a?(Identifier) && node.type == :any
262
+
263
+ node.respond_to?(:type) ? node.type : TYPES[:any]
226
264
  end
227
265
 
228
266
  def get(attrib)
267
+ attrib = attrib.to_s if attrib.is_a?(::Symbol)
229
268
  _, value = @type_map.find { |k, _| k == attrib }
230
269
  value
231
270
  end
232
271
 
233
272
  def ==(other)
234
- (other.is_a?(self.class) && other.element_type == @element_type && other.type_map == @type_map) || super
235
- other == :map || super
273
+ return true if other.is_a?(::Symbol) && other == :map
274
+ return super unless other.is_a?(MapType)
275
+
276
+ MapType.compatible?(@key_type, other.key_type) &&
277
+ MapType.compatible?(@element_type, other.element_type)
278
+ end
279
+
280
+ # `any` is CEL's `dyn`: it stands in for an unknown type, so it is
281
+ # compatible with every other one. A map whose keys or values could not be
282
+ # narrowed (an empty literal, a heterogeneous one, `TYPES[:map]`) therefore
283
+ # matches any map, and only two *narrowed* parameters can disagree.
284
+ def self.compatible?(left, right)
285
+ return true if left.nil? || right.nil?
286
+ return true if left.to_sym == :any || right.to_sym == :any
287
+
288
+ left == right
236
289
  end
237
290
 
238
291
  def cast(value)
data/lib/cel/checker.rb CHANGED
@@ -100,6 +100,23 @@ module Cel
100
100
  Checker.new(@environment, @declarations ? @declarations.merge(declarations) : declarations)
101
101
  end
102
102
 
103
+ def infer_variable_type(var)
104
+ case var
105
+ when Identifier
106
+ check_identifier(var)
107
+ when Invoke
108
+ check_invoke(var)
109
+ when Literal
110
+ check_literal(var)
111
+ when Group
112
+ check(var.value)
113
+ when Protobuf.base_class
114
+ var.class
115
+ else
116
+ var.type
117
+ end
118
+ end
119
+
103
120
  private
104
121
 
105
122
  def check_literal(literal)
@@ -116,12 +133,47 @@ module Cel
116
133
  end
117
134
  lit
118
135
  when Map
119
- MapType.new(literal.value)
136
+ # Check each value expression, so that a type error inside the map literal raises here
137
+ # instead of surfacing at evaluation time. Keys are not checked: map-literal keys are
138
+ # constants or identifiers fixed at parse time, not expressions resolved against
139
+ # declarations (see Map#check).
140
+ # literal.value is an array of [key, value] pairs here, not a Hash
141
+ value_types = literal.value.map { |_key, value| call(value) }
142
+
143
+ lit = MapType.new(literal.value)
144
+ # MapType's constructor can only read the types the AST nodes already
145
+ # carry, and an Invoke/Condition value node carries none until it is
146
+ # checked. Now that every value expression has been, replace the
147
+ # constructor's guess with the checked types — the same refinement
148
+ # the List branch above does with `element_type`.
149
+ lit.element_type = MapType.uniform_type(value_types)
150
+ lit.field_types = field_types_for(literal.value, value_types)
151
+ lit
120
152
  else
121
153
  literal.type
122
154
  end
123
155
  end
124
156
 
157
+ # The per-key value types of a struct-like map literal, or nil when at
158
+ # least one key is not a constant (`{someVar: 1}`), in which case the map
159
+ # has no statically known key set.
160
+ def field_types_for(pairs, value_types)
161
+ fields = {}
162
+
163
+ pairs.each_with_index do |(key, _value), index|
164
+ bare_key =
165
+ case key
166
+ when Identifier then key.to_s
167
+ when Literal then key.value
168
+ else return nil
169
+ end
170
+
171
+ fields[bare_key] = value_types[index]
172
+ end
173
+
174
+ fields
175
+ end
176
+
125
177
  def check_message(literal)
126
178
  literal.type
127
179
  end
@@ -215,23 +267,6 @@ module Cel
215
267
  unsupported_type(operation)
216
268
  end
217
269
 
218
- def infer_variable_type(var)
219
- case var
220
- when Identifier
221
- check_identifier(var)
222
- when Invoke
223
- check_invoke(var)
224
- when Literal
225
- check_literal(var)
226
- when Group
227
- check(var.value)
228
- when Protobuf.base_class
229
- var.class
230
- else
231
- var.type
232
- end
233
- end
234
-
235
270
  def check_invoke(funcall, var_type = nil)
236
271
  var = funcall.var
237
272
  func = funcall.func
@@ -240,7 +275,9 @@ module Cel
240
275
  return check_standard_func(funcall) unless var
241
276
 
242
277
  if var.is_a?(Identifier) && @environment.extensions.include?(var.to_sym)
243
- return @environment.extensions[var.to_sym].__check(funcall, checker: self)
278
+ type = @environment.extensions[var.to_sym].__check(funcall, checker: self)
279
+ unsupported_operation(funcall) unless type
280
+ return type
244
281
  end
245
282
 
246
283
  var_type ||= infer_variable_type(var)
@@ -265,7 +302,8 @@ module Cel
265
302
 
266
303
  unsupported_type(funcall) unless identifier.is_a?(Identifier)
267
304
 
268
- element_checker = merge(identifier.to_sym => var_type.element_type)
305
+ # A comprehension over a map iterates its KEYS.
306
+ element_checker = merge(identifier.to_sym => var_type.key_type)
269
307
 
270
308
  unsupported_type(funcall) if element_checker.check(predicate) != :bool
271
309
 
@@ -332,11 +370,10 @@ module Cel
332
370
  var_type.element_type = element_checker.check(predicate)
333
371
  var_type
334
372
  else
335
- if @environment.extensions.key?(:strings)
336
- return @environment.extensions[:strings].__check(funcall, checker: self)
337
- end
373
+ type = @environment.extensions[:strings]&.__check(funcall, checker: self) ||
374
+ @environment.extensions[:lists]&.__check(funcall, checker: self)
338
375
 
339
- unsupported_operation(funcall)
376
+ type || unsupported_operation(funcall)
340
377
  end
341
378
  when TYPES[:string]
342
379
  case func
@@ -349,7 +386,9 @@ module Cel
349
386
  return TYPES[:bool] if find_match_all_types(%i[string], call(args.first))
350
387
  else
351
388
  if @environment.extensions.key?(:strings)
352
- return @environment.extensions[:strings].__check(funcall, checker: self)
389
+ type = @environment.extensions[:strings].__check(funcall, checker: self)
390
+ unsupported_operation(funcall) unless type
391
+ return type
353
392
  end
354
393
 
355
394
  unsupported_type(funcall)
data/lib/cel/context.rb CHANGED
@@ -111,7 +111,10 @@ module Cel
111
111
  end
112
112
 
113
113
  def merge(bindings)
114
- bindings = bindings.to_h { |k, v| NORMALIZE_KEYS[k, v] }
114
+ # NORMALIZE_KEYS returns a flat array of keys and values ([k1, v1, k2, v2, ...]),
115
+ # which must be regrouped into pairs: a map value with more than one key expands
116
+ # into more than one binding.
117
+ bindings = bindings.flat_map { |k, v| NORMALIZE_KEYS[k, v].each_slice(2).to_a }.to_h
115
118
  if (old_bindings = @bindings)
116
119
  if @environment.container
117
120
  old_bindings = old_bindings.dup
@@ -13,13 +13,13 @@ module Cel
13
13
  func = funcall.func
14
14
  args = funcall.args
15
15
 
16
- return checker.unsupported_operation(funcall) unless func == :bind
16
+ return unless func == :bind
17
17
 
18
18
  checker.check_arity(func, args, 3)
19
19
 
20
20
  id, type, expr = args
21
21
 
22
- return checker.unsupported_operation(funcall) unless id.is_a?(Identifier)
22
+ return unless id.is_a?(Identifier)
23
23
 
24
24
  type = checker.call(type)
25
25
 
@@ -18,14 +18,12 @@ module Cel
18
18
  when :encode
19
19
  checker.check_arity(func, args, 1)
20
20
  arg = checker.call(args.first)
21
- return TYPES[:string] if checker.find_match_all_types(%i[bytes], arg)
21
+ TYPES[:string] if checker.find_match_all_types(%i[bytes], arg)
22
22
  when :decode
23
23
  checker.check_arity(func, args, 1)
24
24
  arg = checker.call(args.first)
25
- return TYPES[:bytes] if checker.find_match_all_types(%i[string], arg)
25
+ TYPES[:bytes] if checker.find_match_all_types(%i[string], arg)
26
26
  end
27
-
28
- checker.unsupported_operation(funcall)
29
27
  end
30
28
 
31
29
  def encode(str, program:)
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ module Extensions
5
+ module List
6
+ module_function
7
+
8
+ def implicit?
9
+ false
10
+ end
11
+
12
+ def __check(funcall, checker:, var_type: nil)
13
+ var = funcall.var
14
+ func = funcall.func
15
+ args = funcall.args
16
+
17
+ var_type ||= checker.infer_variable_type(var)
18
+
19
+ case func
20
+ when :distinct, :reverse
21
+ checker.check_arity(funcall, args, 0)
22
+ var_type
23
+ when :flatten
24
+ checker.check_arity(funcall, args, 1, :>=)
25
+ if (arg = args.first) && arg.type != TYPES[:int]
26
+ checker.unsupported_type(funcall)
27
+ end
28
+ TYPES[:list]
29
+ when :range
30
+ checker.check_arity(funcall, args, 1)
31
+ if (arg = args.first) && arg.type != TYPES[:int]
32
+ checker.unsupported_type(funcall)
33
+ end
34
+ var_type
35
+ when :sort
36
+ checker.check_arity(funcall, args, 0)
37
+ checker.unsupported_type(funcall) if %i[list].include?(var_type.element_type)
38
+ var_type
39
+ when :sortBy
40
+ checker.check_arity(funcall, args, 2)
41
+ identifier, predicate = args
42
+
43
+ checker.nsupported_type(funcall) unless identifier.is_a?(Identifier)
44
+
45
+ identifier.type = var_type.element_type
46
+
47
+ element_checker = checker.merge(identifier.to_sym => var_type.element_type)
48
+
49
+ checker.unsupported_type(funcall) if element_checker.check(predicate) != var_type.element_type
50
+ var_type
51
+ when :slice
52
+ checker.check_arity(funcall, args, 2)
53
+ var_type if checker.find_match_all_types(%i[int], args.map(&checker.method(:call)))
54
+ end
55
+ end
56
+
57
+ def quote(str, program:)
58
+ value = program.call(str).value
59
+
60
+ # this should use String#dump, but this also replaces printable UTF8 characters
61
+
62
+ # replace quotes
63
+ value = value.gsub(/[\\'"\t\r\a\b\v\f\n]/) { |m| m.dump[1..-2] }
64
+
65
+ # replace invalid utf-8 chars
66
+ value = value.encode(Encoding::UTF_8, invalid: :replace)
67
+
68
+ # adding leading quotes if not quoted already
69
+ value = "\"#{value}\"" unless value.start_with?("\"") && value.end_with?("\"")
70
+
71
+ Cel::String.new(value)
72
+ end
73
+
74
+ def range(num, program:)
75
+ num = program.call(num)
76
+
77
+ raise InvalidArgumentError, num unless num.between?(0, 1_000_000)
78
+
79
+ Cel::List.new(num.times)
80
+ end
81
+ end
82
+ end
83
+
84
+ class List
85
+ define_cel_method(:distinct) do
86
+ List.new(@value.uniq)
87
+ end
88
+
89
+ define_cel_method(:flatten) do |*args|
90
+ depth = args.first || 1
91
+
92
+ raise InvalidArgumentError, depth if depth.negative?
93
+
94
+ flatten_proc = proc do |dth, el|
95
+ case el
96
+ when List
97
+ case dth
98
+ when 0
99
+ el
100
+ when nil
101
+ el.value.map do |el2|
102
+ flatten_proc.call(dth, el2)
103
+ end
104
+ else
105
+ el.value.map do |el2|
106
+ flatten_proc.call(dth - 1, el2)
107
+ end
108
+ end
109
+ else
110
+ el
111
+ end
112
+ end
113
+
114
+ # Array#flatten does not know how to flatten lists; we'll need to turn
115
+ # lists into arrays so we can call Array#flatten.
116
+ flattenable_value = @value.map(&flatten_proc.curry.call(depth))
117
+
118
+ List.new(flattenable_value.flatten(depth))
119
+ end
120
+
121
+ define_cel_method(:reverse) do
122
+ List.new(@value.reverse)
123
+ end
124
+
125
+ define_cel_method(:sort) do
126
+ if @value.size == 1
127
+ # #sort call below won't enter the block otherwise, but cel requires
128
+ # validation of the unique element
129
+ el1 = @value.first
130
+ raise InvalidArgumentError, el1 if el1.is_a?(List)
131
+ end
132
+
133
+ sorted_value = @value.sort do |el1, el2|
134
+ raise InvalidArgumentError, el1 if el1.is_a?(List)
135
+ raise InvalidArgumentError, el2 if el2.is_a?(List)
136
+
137
+ el1 <=> el2
138
+ end
139
+ List.new(sorted_value)
140
+ end
141
+
142
+ define_cel_method(:slice) do |a, b|
143
+ raise InvalidArgumentError, a if a.negative?
144
+ raise InvalidArgumentError, b if b > @value.size
145
+
146
+ List.new(@value.slice(a, b - a))
147
+ end
148
+ end
149
+
150
+ class Bool
151
+ def <=>(other)
152
+ return 0 unless other.is_a?(Bool) && @value != other.value
153
+
154
+ case @value
155
+ when false
156
+ # then other is true
157
+ -1
158
+ when true
159
+ # then other is false
160
+ 1
161
+ end
162
+ end
163
+ end
164
+
165
+ class Bytes
166
+ def <=>(other)
167
+ return 0 unless other.is_a?(Bytes)
168
+
169
+ @value <=> other.value
170
+ end
171
+ end
172
+
173
+ module Macro
174
+ def sortBy(collection, *identifiers, predicate, program:) # rubocop:disable Naming/MethodName
175
+ identifiers = identifiers.map(&:to_sym)
176
+
177
+ sorted_collection = collection.zip(
178
+ with_context(collection, identifiers).map do |context|
179
+ program.with_extra_context(context).evaluate(predicate)
180
+ end
181
+ ).sort do |(_, pred1), (_, pred2)|
182
+ pred1 <=> pred2
183
+ end.map(&:first)
184
+
185
+ List.new(sorted_collection)
186
+ end
187
+ end
188
+ end
@@ -17,31 +17,29 @@ module Cel
17
17
  when :round, :trunc, :floor, :ceil
18
18
  checker.check_arity(func, args, 1)
19
19
  arg = checker.call(args.first)
20
- return TYPES[:double] if checker.find_match_all_types(%i[double], arg)
20
+ TYPES[:double] if checker.find_match_all_types(%i[double], arg)
21
21
  when :isInf, :isNaN, :isFinite
22
22
  checker.check_arity(func, args, 1)
23
23
  arg = checker.call(args.first)
24
- return TYPES[:bool] if checker.find_match_all_types(%i[double], arg)
24
+ TYPES[:bool] if checker.find_match_all_types(%i[double], arg)
25
25
  when :bitNot
26
26
  checker.check_arity(func, args, 1)
27
27
  arg = checker.call(args.first)
28
- return TYPES[:int] if checker.find_match_all_types(%i[int uint], arg)
28
+ TYPES[:int] if checker.find_match_all_types(%i[int uint], arg)
29
29
  when :bitOr, :bitAnd, :bitXor
30
30
  checker.check_arity(func, args, 2)
31
31
  args = args.map(&checker.method(:call))
32
32
 
33
- type = checker.find_match_all_types(%i[int uint], args)
34
- return type if type
33
+ checker.find_match_all_types(%i[int uint], args)
35
34
  when :sign, :abs
36
35
  checker.check_arity(func, args, 1)
37
36
  arg = checker.call(args.first)
38
- type = checker.find_match_all_types(%i[double int uint], arg)
39
- return if type
37
+ checker.find_match_all_types(%i[double int uint], arg)
40
38
  when :least, :greatest
41
39
  checker.check_arity_any(func, args)
42
40
  args = args.map(&checker.method(:call))
43
41
 
44
- return TYPES[:any] if args.all? do |arg|
42
+ TYPES[:any] if args.all? do |arg|
45
43
  case arg
46
44
  when TYPES[:any], TYPES[:int], TYPES[:uint], TYPES[:double],
47
45
  TYPES[:list, :int], TYPES[:list, :uint], TYPES[:list, :double], TYPES[:list, :any]
@@ -54,13 +52,9 @@ module Cel
54
52
  checker.check_arity(func, args, 2)
55
53
  num, amount_of_bits = args.map(&checker.method(:call))
56
54
 
57
- return num if checker.find_match_all_types(%i[int uint],
58
- num) && checker.find_match_all_types(%i[int], amount_of_bits)
59
- else
60
- checker.unsupported_operation(funcall)
55
+ num if checker.find_match_all_types(%i[int uint], num) &&
56
+ checker.find_match_all_types(%i[int], amount_of_bits)
61
57
  end
62
-
63
- checker.unsupported_operation(funcall)
64
58
  end
65
59
 
66
60
  def abs(num, program:)
@@ -143,7 +137,7 @@ module Cel
143
137
  arg = args.first
144
138
 
145
139
  case arg
146
- when List
140
+ when Cel::List
147
141
  least(*arg.value, program: program)
148
142
  else
149
143
  arg
@@ -158,7 +152,7 @@ module Cel
158
152
  arg = args.first
159
153
 
160
154
  case arg
161
- when List
155
+ when Cel::List
162
156
  greatest(*arg.value, program: program)
163
157
  else
164
158
  arg
@@ -18,7 +18,7 @@ module Cel
18
18
  when :quote
19
19
  checker.check_arity(func, args, 1)
20
20
  arg = checker.call(args.first)
21
- return TYPES[:string] if checker.find_match_all_types(%i[string], arg)
21
+ TYPES[:string] if checker.find_match_all_types(%i[string], arg)
22
22
  when :indexOf, :lastIndexOf
23
23
  if checker.call(var) == TYPES[:string]
24
24
  checker.check_arity(func, args, 2, :>=)
@@ -28,7 +28,7 @@ module Cel
28
28
  return TYPES[:int] if pos.nil?
29
29
 
30
30
  pos = checker.call(pos)
31
- return TYPES[:int] if checker.find_match_all_types(%i[int], pos)
31
+ TYPES[:int] if checker.find_match_all_types(%i[int], pos)
32
32
 
33
33
  end
34
34
  end
@@ -41,7 +41,7 @@ module Cel
41
41
  return TYPES[:list, :string] if pos.nil?
42
42
 
43
43
  pos = checker.call(pos)
44
- return TYPES[:list, :string] if checker.find_match_all_types(%i[int], pos)
44
+ TYPES[:list, :string] if checker.find_match_all_types(%i[int], pos)
45
45
 
46
46
  end
47
47
  end
@@ -54,36 +54,36 @@ module Cel
54
54
  return TYPES[:string] if pos.nil?
55
55
 
56
56
  pos = checker.call(pos)
57
- return TYPES[:string] if checker.find_match_all_types(%i[int], pos)
57
+ TYPES[:string] if checker.find_match_all_types(%i[int], pos)
58
58
 
59
59
  end
60
60
  end
61
61
  when :trim, :lowerAscii, :upperAscii, :reverse
62
62
  if checker.call(var) == TYPES[:string]
63
63
  checker.check_arity(func, args, 0)
64
- return TYPES[:string]
64
+ TYPES[:string]
65
65
  end
66
66
  when :format
67
67
  if checker.call(var) == TYPES[:string]
68
68
  checker.check_arity(func, args, 1)
69
69
  arg = checker.call(args.first)
70
- return TYPES[:string] if checker.find_match_all_types(%i[list], arg)
70
+ TYPES[:string] if checker.find_match_all_types(%i[list], arg)
71
71
  end
72
72
  when :charAt
73
73
  if checker.call(var) == TYPES[:string]
74
74
  checker.check_arity(func, args, 1)
75
75
  arg = checker.call(args.first)
76
- return TYPES[:string] if checker.find_match_all_types(%i[int], arg)
76
+ TYPES[:string] if checker.find_match_all_types(%i[int], arg)
77
77
  end
78
78
  when :replace
79
79
  if checker.call(var) == TYPES[:string]
80
80
  checker.check_arity(func, args, 2..3, :include?)
81
81
  p, r, limit = args.map(&checker.method(:call))
82
82
  if limit
83
- return TYPES[:string] if checker.find_match_all_types(%i[string], [p, r]) &&
84
- checker.find_match_all_types(%i[int], limit)
83
+ TYPES[:string] if checker.find_match_all_types(%i[string], [p, r]) &&
84
+ checker.find_match_all_types(%i[int], limit)
85
85
  elsif checker.find_match_all_types(%i[string], [p, r])
86
- return TYPES[:string]
86
+ TYPES[:string]
87
87
  end
88
88
  end
89
89
  when :join
@@ -91,12 +91,11 @@ module Cel
91
91
  checker.check_arity(func, args, 1, :>=)
92
92
  arg = checker.call(args.first)
93
93
  return TYPES[:string] unless arg
94
- return TYPES[:string] if checker.find_match_all_types(%i[string], arg)
94
+
95
+ TYPES[:string] if checker.find_match_all_types(%i[string], arg)
95
96
 
96
97
  end
97
98
  end
98
-
99
- checker.unsupported_operation(funcall)
100
99
  end
101
100
 
102
101
  def quote(str, program:)
@@ -5,6 +5,7 @@ require_relative "extensions/string"
5
5
  require_relative "extensions/bind"
6
6
  require_relative "extensions/encoders"
7
7
  require_relative "extensions/network"
8
+ require_relative "extensions/list"
8
9
 
9
10
  module Cel
10
11
  EXTENSIONS = {
@@ -14,5 +15,6 @@ module Cel
14
15
  cel: Extensions::Bind,
15
16
  net: Extensions::Network,
16
17
  ip: Extensions::Network::IP,
18
+ lists: Extensions::List,
17
19
  }.freeze
18
20
  end
data/lib/cel/macro.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Cel
4
4
  module Macro
5
- module_function
5
+ extend self
6
6
 
7
7
  # If e evaluates to a protocol buffers version 2 message and f is a defined field:
8
8
  # If f is a repeated field or map field, has(e.f) indicates whether the field is non-empty.
@@ -64,7 +64,10 @@ module Cel
64
64
  end
65
65
 
66
66
  def matches(string, pattern, program: nil)
67
- pattern = program.evaluate(pattern) if program
67
+ if program
68
+ string = program.evaluate(string)
69
+ pattern = program.evaluate(pattern)
70
+ end
68
71
  pattern = Regexp.new(pattern)
69
72
  Bool.cast(pattern.match?(string))
70
73
  end
@@ -128,6 +131,8 @@ module Cel
128
131
  List.new(return_value)
129
132
  end
130
133
 
134
+ private
135
+
131
136
  def with_context(collection, identifiers)
132
137
  case collection
133
138
  when Map
data/lib/cel/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cel
4
4
  module Ruby
5
- VERSION = "0.6.1"
5
+ VERSION = "0.6.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -108,6 +108,7 @@ files:
108
108
  - lib/cel/extensions.rb
109
109
  - lib/cel/extensions/bind.rb
110
110
  - lib/cel/extensions/encoders.rb
111
+ - lib/cel/extensions/list.rb
111
112
  - lib/cel/extensions/math.rb
112
113
  - lib/cel/extensions/network.rb
113
114
  - lib/cel/extensions/string.rb
@@ -137,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
138
  - !ruby/object:Gem::Version
138
139
  version: '0'
139
140
  requirements: []
140
- rubygems_version: 3.6.9
141
+ rubygems_version: 4.0.16
141
142
  specification_version: 4
142
143
  summary: Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
143
144
  test_files: []