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.
data/lib/cel/parser.rb CHANGED
@@ -89,7 +89,14 @@ var void while
89
89
 
90
90
  IDENTIFIER_REGEX = /[_a-zA-Z][_a-zA-Z0-9]*/
91
91
 
92
- def initialize(package = nil)
92
+ def initialize(
93
+ package = nil,
94
+ max_recursion_depth: 32,
95
+ max_nesting_depth: 12,
96
+ expression_size_limit: 2048
97
+ )
98
+ @max_recursion_depth = max_recursion_depth
99
+ @max_nesting_depth = max_nesting_depth
93
100
  @package = package
94
101
  end
95
102
 
@@ -243,12 +250,49 @@ def literal_null
243
250
  Cel::Null::INSTANCE
244
251
  end
245
252
 
246
- def new_map(val)
247
- Cel::Map.new(val)
253
+ def new_map(elements)
254
+ depth = 1
255
+
256
+ if elements.size > @max_recursion_depth
257
+ raise MaxRecursionDepthExceededError, "max number of map elements exceeded"
258
+ end
259
+
260
+ depth = elements.filter_map do |_, elem|
261
+ next unless elem.is_a?(Cel::Map)
262
+
263
+ d = depth + elem.depth
264
+
265
+ if d > @max_nesting_depth
266
+ raise MaxNestingDepthExceededError, "max nesting of map literals exceeded"
267
+ end
268
+
269
+ d
270
+ end.max || depth
271
+
272
+ Cel::Map.new(elements, depth: depth)
248
273
  end
249
274
 
250
275
  def new_list(val)
251
- Cel::List.new(Array(val))
276
+ elements = Array(val)
277
+ depth = 1
278
+
279
+ if elements.size > @max_recursion_depth
280
+ raise MaxRecursionDepthExceededError, "max number of elements for list literals exceeded"
281
+ end
282
+
283
+ depth = elements.filter_map do |elem|
284
+ next unless elem.is_a?(Cel::List)
285
+
286
+ d = depth + elem.depth
287
+
288
+ if d > @max_nesting_depth
289
+ raise MaxNestingDepthExceededError, "max nesting of list literals exceeded"
290
+ end
291
+
292
+ d
293
+ end.max || depth
294
+
295
+ Cel::List.new(elements, depth: depth)
252
296
  end
253
297
 
254
298
  def new_identifier(id)
@@ -256,19 +300,159 @@ def new_identifier(id)
256
300
  end
257
301
 
258
302
  def new_message(type, struct)
259
- Cel::Message.new(type, struct, package: @package)
303
+ depth = 1
304
+ if struct
305
+ if struct.size > @max_recursion_depth
306
+ raise MaxRecursionDepthExceededError, "max number of message elements exceeded"
307
+ end
308
+
309
+ depth = struct.filter_map do |_, elem|
310
+ next unless elem.is_a?(Cel::Message)
311
+
312
+ d = depth + elem.depth
313
+
314
+ if d > @max_nesting_depth
315
+ raise MaxNestingDepthExceededError, "max nesting of map literals exceeded"
316
+ end
317
+
318
+ d
319
+ end.max || depth
320
+ end
321
+
322
+ Cel::Message.new(type, struct, package: @package, depth: depth)
260
323
  end
261
324
 
262
325
  def global_call(function, args = nil)
263
- Cel::Invoke.new(func: function, var: nil, args: args, package: @package)
326
+ depth = 1
327
+
328
+ if args && args.size > @max_recursion_depth
329
+ raise MaxRecursionDepthExceededError, "max number of function call arguments exceeded"
330
+ end
331
+
332
+ depth = args.filter_map do |arg|
333
+ next unless arg.is_a?(Cel::Invoke)
334
+
335
+ d = depth + arg.depth
336
+
337
+ if d > @max_nesting_depth
338
+ raise MaxNestingDepthExceededError, "max nesting of function calls exceeded"
339
+ end
340
+
341
+ d
342
+ end.max || depth
343
+
344
+ Cel::Invoke.new(func: function, var: nil, args: args, package: @package, depth: depth)
264
345
  end
265
346
 
266
- def receiver_call(function, target, args = nil)
267
- Cel::Invoke.new(func: function, var: target, args: args, package: @package)
347
+ def indexing_call(target, args)
348
+ depth = 1
349
+
350
+ if target.is_a?(Cel::Invoke)
351
+ depth += target.depth if target.indexing?
352
+
353
+ if depth > @max_nesting_depth
354
+ raise MaxNestingDepthExceededError, "max nesting of indexing operators exceeded"
355
+ end
356
+ end
357
+
358
+ receiver_call(:[], target, args, package: @package, depth: depth)
268
359
  end
269
360
 
270
- def operation(op, operands)
271
- Cel::Operation.new(op, operands)
361
+ def selector_call(function, target)
362
+ depth = 1
363
+
364
+ # if selector operator
365
+ if target.is_a?(Cel::Invoke) && !target.var.nil?
366
+ depth += target.depth
367
+
368
+ if depth > @max_nesting_depth
369
+ raise MaxNestingDepthExceededError, "max nesting of selection operators exceeded"
370
+ end
371
+ end
372
+
373
+ receiver_call(function, target, package: @package, depth: depth)
374
+ end
375
+
376
+ def receiver_call(function, target, args = nil, **kwargs)
377
+ Cel::Invoke.new(func: function, var: target, args: args, package: @package, **kwargs)
378
+ end
379
+
380
+ def logical_operation(op, operands)
381
+ lhs, rhs = operands
382
+
383
+ if lhs.is_a?(Cel::Operation) && lhs.op == op
384
+ # concat them
385
+ operands = [*lhs.operands, rhs]
386
+ end
387
+
388
+ if operands.size - 1 > @max_recursion_depth
389
+ raise Cel::MaxRecursionDepthExceededError, "max recursion depth exceeded for #{op} operation"
390
+ end
391
+
392
+ operation(op, operands)
393
+ end
394
+
395
+ def math_operation(op, operands)
396
+ # 32 binary arithmetic operators of the same precedence in a row
397
+ lhs, _ = operands
398
+ depth = 1
399
+
400
+ if lhs.is_a?(Cel::Operation)
401
+ case op
402
+ when "*", "/", "%"
403
+ case lhs.op
404
+ when "*", "/", "%"
405
+ depth = lhs.depth + 1
406
+ end
407
+ when "+"
408
+ case lhs.op
409
+ when "+"
410
+ depth = lhs.depth + 1
411
+ end
412
+ end
413
+ end
414
+
415
+ if depth > @max_recursion_depth
416
+ raise MaxRecursionDepthExceededError, "max number of arithmetic operators with the same precedence exceeded"
417
+ end
418
+
419
+ operation(op, operands, depth: depth)
420
+ end
421
+
422
+ def unary_operation(op, operand)
423
+ depth = 1
424
+
425
+ if operand.is_a?(Cel::Operation) && operand.op == op && operand.unary?
426
+ depth = depth + operand.depth
427
+
428
+ if depth > @max_recursion_depth
429
+ raise MaxRecursionDepthExceededError, "max number of an unary operator exceeded"
430
+ end
431
+ end
432
+
433
+ operation(op, [operand], depth: depth)
434
+ end
435
+
436
+ def operation(op, operands, **kwargs)
437
+ Cel::Operation.new(op, operands, **kwargs)
438
+ end
439
+
440
+ def ternary_condition(if_op, *ops)
441
+ depth = 1
442
+
443
+ depth = ops.filter_map do |op|
444
+ next unless op.is_a?(Cel::Condition)
445
+
446
+ d = depth + op.depth
447
+
448
+ if d > @max_recursion_depth
449
+ raise MaxRecursionDepthExceededError, "max number of consecutive ternary operators exceeded"
450
+ end
451
+
452
+ d
453
+ end.max || depth
454
+
455
+ Cel::Condition.new(if_op, *ops, depth: depth)
272
456
  end
273
457
 
274
458
  # Checks whether the given identifier token is a reserved word or not. Throws
@@ -276,7 +460,7 @@ end
276
460
  def validated_id!(identifier)
277
461
  return identifier unless RESERVED.include?(identifier)
278
462
 
279
- raise Cel::ParseError.new("invalid usage of the reserved word \"#{identifier}\"")
463
+ raise Cel::ParseError, "invalid usage of the reserved word \"#{identifier}\""
280
464
  end
281
465
 
282
466
  ...end parser.ry/module_eval...
@@ -646,7 +830,7 @@ Racc_debug_parser = false
646
830
 
647
831
  module_eval(<<'.,.,', 'parser.ry', 17)
648
832
  def _reduce_4(val, _values, result)
649
- result = Cel::Condition.new(val[0], val[2], val[4])
833
+ result = ternary_condition(val[0], val[2], val[4])
650
834
  result
651
835
  end
652
836
  .,.,
@@ -655,7 +839,7 @@ module_eval(<<'.,.,', 'parser.ry', 17)
655
839
 
656
840
  module_eval(<<'.,.,', 'parser.ry', 20)
657
841
  def _reduce_6(val, _values, result)
658
- result = operation(val[1], [val[0], val[2]])
842
+ result = logical_operation(val[1], [val[0], val[2]])
659
843
  result
660
844
  end
661
845
  .,.,
@@ -664,7 +848,7 @@ module_eval(<<'.,.,', 'parser.ry', 20)
664
848
 
665
849
  module_eval(<<'.,.,', 'parser.ry', 23)
666
850
  def _reduce_8(val, _values, result)
667
- result = operation(val[1], [val[0], val[2]])
851
+ result = logical_operation(val[1], [val[0], val[2]])
668
852
  result
669
853
  end
670
854
  .,.,
@@ -682,14 +866,14 @@ module_eval(<<'.,.,', 'parser.ry', 26)
682
866
 
683
867
  module_eval(<<'.,.,', 'parser.ry', 29)
684
868
  def _reduce_12(val, _values, result)
685
- result = operation(val[1], [val[0], val[2]])
869
+ result = math_operation(val[1], [val[0], val[2]])
686
870
  result
687
871
  end
688
872
  .,.,
689
873
 
690
874
  module_eval(<<'.,.,', 'parser.ry', 30)
691
875
  def _reduce_13(val, _values, result)
692
- result = operation(val[1], [val[0], val[2]])
876
+ result = math_operation(val[1], [val[0], val[2]])
693
877
  result
694
878
  end
695
879
  .,.,
@@ -698,7 +882,7 @@ module_eval(<<'.,.,', 'parser.ry', 30)
698
882
 
699
883
  module_eval(<<'.,.,', 'parser.ry', 33)
700
884
  def _reduce_15(val, _values, result)
701
- result = operation(val[1], [val[0], val[2]])
885
+ result = math_operation(val[1], [val[0], val[2]])
702
886
  result
703
887
  end
704
888
  .,.,
@@ -709,21 +893,21 @@ module_eval(<<'.,.,', 'parser.ry', 33)
709
893
 
710
894
  module_eval(<<'.,.,', 'parser.ry', 38)
711
895
  def _reduce_18(val, _values, result)
712
- result = operation("!", [val[1]])
896
+ result = unary_operation("!", val[1])
713
897
  result
714
898
  end
715
899
  .,.,
716
900
 
717
901
  module_eval(<<'.,.,', 'parser.ry', 39)
718
902
  def _reduce_19(val, _values, result)
719
- result = operation("-", [val[1]])
903
+ result = unary_operation("-", val[1])
720
904
  result
721
905
  end
722
906
  .,.,
723
907
 
724
908
  module_eval(<<'.,.,', 'parser.ry', 41)
725
909
  def _reduce_20(val, _values, result)
726
- result = operation("!", [val[1]])
910
+ result = unary_operation("!", val[1])
727
911
  result
728
912
  end
729
913
  .,.,
@@ -732,7 +916,7 @@ module_eval(<<'.,.,', 'parser.ry', 41)
732
916
 
733
917
  module_eval(<<'.,.,', 'parser.ry', 44)
734
918
  def _reduce_22(val, _values, result)
735
- result = operation("-", [val[1]])
919
+ result = unary_operation("-", val[1])
736
920
  result
737
921
  end
738
922
  .,.,
@@ -743,7 +927,7 @@ module_eval(<<'.,.,', 'parser.ry', 44)
743
927
 
744
928
  module_eval(<<'.,.,', 'parser.ry', 48)
745
929
  def _reduce_25(val, _values, result)
746
- result = receiver_call("[]", val[0], val[2])
930
+ result = indexing_call(val[0], val[2])
747
931
  result
748
932
  end
749
933
  .,.,
@@ -757,7 +941,7 @@ module_eval(<<'.,.,', 'parser.ry', 49)
757
941
 
758
942
  module_eval(<<'.,.,', 'parser.ry', 50)
759
943
  def _reduce_27(val, _values, result)
760
- result = receiver_call(val[2], val[0])
944
+ result = selector_call(val[2], val[0])
761
945
  result
762
946
  end
763
947
  .,.,
data/lib/cel/program.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Cel
4
4
  class Program
5
- attr_reader :context
5
+ attr_reader :environment, :context
6
6
 
7
7
  def initialize(context, environment)
8
8
  @context = context
@@ -35,6 +35,16 @@ module Cel
35
35
 
36
36
  alias_method :call, :evaluate
37
37
 
38
+ def disable_cel_conversion
39
+ convert_to_cel = @convert_to_cel
40
+ begin
41
+ @convert_to_cel = false
42
+ yield(convert_to_cel)
43
+ ensure
44
+ @convert_to_cel = convert_to_cel
45
+ end
46
+ end
47
+
38
48
  private
39
49
 
40
50
  def try_evaluate_lookup(key)
@@ -45,7 +55,11 @@ module Cel
45
55
  end
46
56
 
47
57
  def evaluate_identifier(identifier)
48
- if Cel::PRIMITIVE_TYPES.include?(identifier.to_sym)
58
+ id_sym = identifier.to_sym
59
+
60
+ if Cel::EXTENSIONS.key?(id_sym)
61
+ Cel::EXTENSIONS[id_sym]
62
+ elsif Cel::PRIMITIVE_TYPES.include?(id_sym)
49
63
  TYPES[identifier.to_sym]
50
64
  else
51
65
  val, func = @context.lookup(identifier)
@@ -68,21 +82,18 @@ module Cel
68
82
  end
69
83
 
70
84
  def evaluate_message(val)
71
- convert_to_cel = @convert_to_cel
72
-
73
- @convert_to_cel = false
74
- values = val.struct.transform_values do |value|
75
- v = call(value)
76
- v = v.to_ruby_type if v.respond_to?(:to_ruby_type)
77
- v
78
- end
79
- value = Protobuf.convert_to_proto(val.message_type, values)
85
+ disable_cel_conversion do |should_have_converted|
86
+ values = val.struct.transform_values do |value|
87
+ v = call(value)
88
+ v = v.to_ruby_type if v.respond_to?(:to_ruby_type)
89
+ v
90
+ end
91
+ value = Protobuf.convert_to_proto(val.message_type, values)
80
92
 
81
- return value unless convert_to_cel
93
+ return value unless should_have_converted
82
94
 
83
- Literal.to_cel_type(value)
84
- ensure
85
- @convert_to_cel = convert_to_cel
95
+ Literal.to_cel_type(value)
96
+ end
86
97
  end
87
98
 
88
99
  def evaluate_operation(operation)
@@ -276,8 +287,10 @@ module Cel
276
287
  value = Protobuf.lookup(var, func)
277
288
 
278
289
  Literal.to_cel_type(value)
279
- else
280
- if var.is_a?(Module) && var.const_defined?(func)
290
+ when Module
291
+ if var.respond_to?(func) && !Module.respond_to?(func)
292
+ return var.__send__(func, *args, program: self)
293
+ elsif var.const_defined?(func) && !Module.const_defined?(func)
281
294
  # this block assumes a message based call on a protobuf message, either to a
282
295
  # subclass/namespace (Foo.Bar), or an enum (Foo.BAR)
283
296
  # protobuf accessing enum module
@@ -306,6 +319,8 @@ module Cel
306
319
  return Literal.to_cel_type(value)
307
320
  end
308
321
 
322
+ raise EvaluateError, "#{invoke} is not supported"
323
+ else
309
324
  raise EvaluateError, "#{invoke} is not supported"
310
325
  end
311
326
  end
data/lib/cel/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cel
4
4
  module Ruby
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
data/lib/cel.rb CHANGED
@@ -5,6 +5,7 @@ require "cel/version"
5
5
  require "cel/errors"
6
6
  require "cel/ast/types"
7
7
  require "cel/ast/elements"
8
+ require "cel/extensions"
8
9
  require "cel/parser"
9
10
  require "cel/macro"
10
11
  require "cel/context"
@@ -115,7 +116,7 @@ rescue LoadError => e
115
116
  BASE_CLASS = Class.new(Object)
116
117
 
117
118
  def base_class
118
- BASE_CLASS
119
+ Struct
119
120
  end
120
121
 
121
122
  def enum_class
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -51,14 +51,28 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: tzinfo-data
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  description: Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
55
69
  email:
56
70
  - cardoso_tiago@hotmail.com
57
71
  executables: []
58
72
  extensions: []
59
73
  extra_rdoc_files:
60
- - LICENSE.txt
61
74
  - CHANGELOG.md
75
+ - LICENSE.txt
62
76
  - README.md
63
77
  files:
64
78
  - CHANGELOG.md
@@ -73,6 +87,11 @@ files:
73
87
  - lib/cel/encoder.rb
74
88
  - lib/cel/environment.rb
75
89
  - lib/cel/errors.rb
90
+ - lib/cel/extensions.rb
91
+ - lib/cel/extensions/bind.rb
92
+ - lib/cel/extensions/encoders.rb
93
+ - lib/cel/extensions/math.rb
94
+ - lib/cel/extensions/string.rb
76
95
  - lib/cel/macro.rb
77
96
  - lib/cel/parser.rb
78
97
  - lib/cel/program.rb
@@ -99,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
118
  - !ruby/object:Gem::Version
100
119
  version: '0'
101
120
  requirements: []
102
- rubygems_version: 3.6.2
121
+ rubygems_version: 3.6.9
103
122
  specification_version: 4
104
123
  summary: Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
105
124
  test_files: []