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/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 Message
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)