cel 0.3.1 → 0.4.1

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/ast/types.rb CHANGED
@@ -2,12 +2,18 @@
2
2
 
3
3
  module Cel
4
4
  class Type
5
+ include CelMethods
6
+
5
7
  def initialize(type)
6
8
  @type = type
7
9
  end
8
10
 
9
- def ==(other)
10
- (other.is_a?(Symbol) && other == @type) || super
11
+ define_cel_method(:==) do |other|
12
+ (other.is_a?(Symbol) && other == @type) || super(other)
13
+ end
14
+
15
+ define_cel_method(:"!=") do |other|
16
+ !cel_send(:==, other)
11
17
  end
12
18
 
13
19
  def to_str
@@ -154,6 +160,29 @@ module Cel
154
160
  raise Error, "unsupported cast operation to #{@type}"
155
161
  end
156
162
  end
163
+
164
+ def convert(primitive_value)
165
+ case @type
166
+ when :int, :uint, :double
167
+ Number.new(@type, primitive_value)
168
+ when :string
169
+ String.new(primitive_value)
170
+ when :bytes
171
+ Bytes.new(primitive_value)
172
+ when :bool
173
+ Bool.new(primitive_value)
174
+ when :null_type
175
+ Null::INSTANCE
176
+ when :timestamp
177
+ Timestamp.new(primitive_value)
178
+ when :duration
179
+ Duration.new(primitive_value)
180
+ when :any
181
+ Literal.to_cel_type(primitive_value)
182
+ else
183
+ raise EvaluateError, "Type conversion error"
184
+ end
185
+ end
157
186
  end
158
187
 
159
188
  class ListType < Type
@@ -176,7 +205,11 @@ module Cel
176
205
  end
177
206
 
178
207
  def cast(value)
179
- value.is_a?(List) ? value : List.new(value)
208
+ value.is_a?(List) ? value : convert(value)
209
+ end
210
+
211
+ def convert(primitive_value)
212
+ List.new(primitive_value)
180
213
  end
181
214
  end
182
215
 
@@ -203,15 +236,27 @@ module Cel
203
236
  end
204
237
 
205
238
  def cast(value)
206
- value.is_a?(Map) ? value : Map.new(value)
239
+ value.is_a?(Map) ? value : convert(value)
240
+ end
241
+
242
+ def convert(primitive_value)
243
+ Map.new(primitive_value)
207
244
  end
208
245
  end
209
246
 
210
247
  class AbstractType < Type
248
+ attr_reader :params
249
+ protected :params
211
250
  def initialize(name, *params)
212
251
  super(name)
213
252
  @params = params
214
253
  end
254
+
255
+ define_cel_method(:==) do |other|
256
+ return super(other) unless other.is_a?(AbstractType)
257
+
258
+ other.type && @type && other.params == @params
259
+ end
215
260
  end
216
261
  # Primitive Cel Types
217
262
 
data/lib/cel/checker.rb CHANGED
@@ -21,7 +21,7 @@ module Cel
21
21
  timestamp: %i[int string timestamp],
22
22
  }.freeze
23
23
 
24
- attr_reader :declarations
24
+ attr_reader :environment, :declarations
25
25
 
26
26
  def initialize(environment, declarations = environment.declarations)
27
27
  @environment = environment
@@ -51,6 +51,40 @@ module Cel
51
51
 
52
52
  alias_method :call, :check
53
53
 
54
+ def find_match_all_types(expected, types)
55
+ # at least an expected type must match all values
56
+ type = expected.find do |expected_type|
57
+ case types
58
+ when Array
59
+ types.all? { |typ| typ == expected_type }
60
+ else
61
+ types == expected_type
62
+ end
63
+ end
64
+
65
+ type && types.is_a?(Type) ? types : TYPES[type]
66
+ end
67
+
68
+ def check_arity(func, args, arity, op = :===)
69
+ return if arity.__send__(op, args.size)
70
+
71
+ raise CheckError, "`#{func}` invoked with wrong number of arguments (should be #{arity})"
72
+ end
73
+
74
+ def check_arity_any(func, args)
75
+ return if args.size.positive?
76
+
77
+ raise CheckError, "`#{func}` invoked with no arguments"
78
+ end
79
+
80
+ def unsupported_type(op)
81
+ raise CheckError, "no matching overload: #{op}"
82
+ end
83
+
84
+ def unsupported_operation(op)
85
+ raise CheckError, "unsupported operation (#{op})"
86
+ end
87
+
54
88
  private
55
89
 
56
90
  def merge(declarations)
@@ -194,6 +228,10 @@ module Cel
194
228
 
195
229
  return check_standard_func(funcall) unless var
196
230
 
231
+ if var.is_a?(Identifier) && Cel::EXTENSIONS.include?(var.to_sym)
232
+ return Cel::EXTENSIONS[var.to_sym].__check(funcall, checker: self)
233
+ end
234
+
197
235
  var_type ||= infer_variable_type(var)
198
236
 
199
237
  case var_type
@@ -283,6 +321,8 @@ module Cel
283
321
  var_type.element_type = element_checker.check(predicate)
284
322
  var_type
285
323
  else
324
+ return Cel::EXTENSIONS[:strings].__check(funcall, checker: self) if Cel::EXTENSIONS.key?(:strings)
325
+
286
326
  unsupported_operation(funcall)
287
327
  end
288
328
  when TYPES[:string]
@@ -295,6 +335,8 @@ module Cel
295
335
  # TODO: verify if string can be transformed into a regex
296
336
  return TYPES[:bool] if find_match_all_types(%i[string], call(args.first))
297
337
  else
338
+ return Cel::EXTENSIONS[:strings].__check(funcall, checker: self) if Cel::EXTENSIONS.key?(:strings)
339
+
298
340
  unsupported_type(funcall)
299
341
  end
300
342
  unsupported_operation(funcall)
@@ -373,7 +415,7 @@ module Cel
373
415
 
374
416
  arg = call(args.first)
375
417
 
376
- return TYPES[func] if find_match_all_types(allowed_types, arg)
418
+ return TYPES[func] if find_match_all_types(allowed_types, arg) || arg == TYPES[:any]
377
419
  when :matches
378
420
  check_arity(func, args, 2)
379
421
  return TYPES[:bool] if find_match_all_types(%i[string], args.map(&method(:call)))
@@ -424,7 +466,9 @@ module Cel
424
466
  def check_identifier(identifier)
425
467
  return identifier.type unless identifier.type == :any
426
468
 
427
- return TYPES[:type] if Cel::PRIMITIVE_TYPES.include?(identifier.to_sym)
469
+ id_sym = identifier.to_sym
470
+
471
+ return TYPES[:type] if Cel::PRIMITIVE_TYPES.include?(id_sym)
428
472
 
429
473
  proto_type = identifier.try_convert_to_proto_type
430
474
 
@@ -472,33 +516,5 @@ module Cel
472
516
  typ
473
517
  end
474
518
  end
475
-
476
- def find_match_all_types(expected, types)
477
- # at least an expected type must match all values
478
- type = expected.find do |expected_type|
479
- case types
480
- when Array
481
- types.all? { |typ| typ == expected_type }
482
- else
483
- types == expected_type
484
- end
485
- end
486
-
487
- type && types.is_a?(Type) ? types : TYPES[type]
488
- end
489
-
490
- def check_arity(func, args, arity)
491
- return if arity === args.size # rubocop:disable Style/CaseEquality
492
-
493
- raise CheckError, "`#{func}` invoked with wrong number of arguments (should be #{arity})"
494
- end
495
-
496
- def unsupported_type(op)
497
- raise CheckError, "no matching overload: #{op}"
498
- end
499
-
500
- def unsupported_operation(op)
501
- raise CheckError, "unsupported operation (#{op})"
502
- end
503
519
  end
504
520
  end
data/lib/cel/context.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Cel
4
4
  class Context
5
- attr_reader :declarations
5
+ attr_reader :declarations, :bindings
6
6
 
7
7
  def initialize(declarations, bindings)
8
8
  @declarations = declarations
@@ -53,8 +53,8 @@ module Cel
53
53
  end
54
54
 
55
55
  def check(expr)
56
- ast = @parser.parse(expr)
57
- @checker.check(ast)
56
+ expr = @parser.parse(expr) if expr.is_a?(::String)
57
+ @checker.check(expr)
58
58
  end
59
59
 
60
60
  def program(expr)
@@ -91,6 +91,19 @@ module Cel
91
91
  [declarations, bindings]
92
92
  end
93
93
 
94
+ def with(declarations:, disable_check: @disable_check)
95
+ prev_declarations = @declarations
96
+ prev_disable_check = @disable_check
97
+
98
+ @declarations = declarations.merge(declarations)
99
+ @disable_check = disable_check
100
+
101
+ yield
102
+ ensure
103
+ @declarations = prev_declarations
104
+ @disable_check = prev_disable_check
105
+ end
106
+
94
107
  private
95
108
 
96
109
  def validate(ast, structs); end
data/lib/cel/errors.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  module Cel
4
4
  class Error < StandardError; end
5
5
 
6
+ class NoCelMethodError < Error; end
7
+
6
8
  class ParseError < Error; end
7
9
 
8
10
  class MaxRecursionDepthExceededError < ParseError; end
@@ -48,5 +50,14 @@ module Cel
48
50
  end
49
51
  end
50
52
 
53
+ class NoOverloadError < EvaluateError
54
+ attr_reader :code
55
+
56
+ def initialize
57
+ super("No such overload")
58
+ @code = :no_overload
59
+ end
60
+ end
61
+
51
62
  class BindingError < EvaluateError; end
52
63
  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