cel 0.4.0 → 0.5.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 +45 -1
- data/README.md +61 -1
- data/lib/cel/ast/cel_methods.rb +58 -0
- data/lib/cel/ast/elements/bool.rb +34 -0
- data/lib/cel/ast/elements/bytes.rb +34 -0
- data/lib/cel/ast/elements/condition.rb +22 -0
- data/lib/cel/ast/elements/duration.rb +114 -0
- data/lib/cel/ast/elements/function.rb +33 -0
- data/lib/cel/ast/elements/group.rb +19 -0
- data/lib/cel/ast/elements/identifier.rb +32 -0
- data/lib/cel/ast/elements/invoke.rb +64 -0
- data/lib/cel/ast/elements/list.rb +70 -0
- data/lib/cel/ast/elements/literal.rb +74 -0
- data/lib/cel/ast/elements/map.rb +120 -0
- data/lib/cel/ast/elements/message.rb +50 -0
- data/lib/cel/ast/elements/null.rb +11 -0
- data/lib/cel/ast/elements/number.rb +70 -0
- data/lib/cel/ast/elements/operation.rb +39 -0
- data/lib/cel/ast/elements/protobuf.rb +4 -0
- data/lib/cel/ast/elements/string.rb +54 -0
- data/lib/cel/ast/elements/timestamp.rb +109 -0
- data/lib/cel/ast/elements.rb +18 -840
- data/lib/cel/ast/types.rb +49 -4
- data/lib/cel/checker.rb +10 -6
- data/lib/cel/context.rb +9 -5
- data/lib/cel/environment.rb +13 -6
- data/lib/cel/errors.rb +2 -0
- data/lib/cel/extensions/bind.rb +2 -3
- data/lib/cel/extensions/string.rb +18 -16
- data/lib/cel/extensions.rb +4 -4
- data/lib/cel/parser.rb +12 -11
- data/lib/cel/program.rb +18 -11
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +30 -13
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e83a98ac5e076442ae06b53d13f43581a3982196f57582bbac02a08eef46ca9
|
|
4
|
+
data.tar.gz: 1fdb06029d13f3a47037688df7d4d4a77f65e237aa73d0e00fb6989a78600000
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59eb650749f3b0fcef9a8ee66b6850831db66f116d851f35894d375191e280cac465e48c1f39103d6fcb21f7ebcf6c695f2cd286c65bcfb6c12ef08ddcb07400
|
|
7
|
+
data.tar.gz: b6dada73b82d27a17a6d9f23a672f36ec7c9af6f89eebef788227aba7b22cdc88720100ffcf5f466db70e923db689ff4df903942abb7ab2f8adeef70de4604df
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2025-12-11
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
#### Custom extensions
|
|
8
|
+
|
|
9
|
+
A new `:extensions` kwarg is added to `Cel::Environment.new` which allows adding custom extensions, in a similar manner as what the standard extensions (like `math` or `string`) are done:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
module Ext
|
|
13
|
+
# defines a random function which takes no arguments and returns 42
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Cel::Environment.new.evaluate("ext.random()") #=> raises error
|
|
17
|
+
Cel::Environment.new(extensions: { ext: Ext }).evaluate("ext.random()") #=> 42
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Backwards Compatibility
|
|
21
|
+
|
|
22
|
+
The ractor safety introduced in 0.4.1 has been relaxed in order to allow extensions of core classes by custom extensions, And you'll need to explicitly call `Cel.freeze` before using `cel` inside ractors. This is a direct consequence of how extensions patch `cel` core classes.
|
|
23
|
+
|
|
24
|
+
ATTENTION: Changes may be introduced in the way core classes are patched by extensions, towards making `cel` ractor-safe by default. If you rely on custom extensions, do follow the migration instructions in subsequent releases.
|
|
25
|
+
|
|
26
|
+
### Bugfixes
|
|
27
|
+
|
|
28
|
+
Fixed checker type inference when using nexted expressions (like when using the `bind` extensions to evaluate cel sub-expressions).
|
|
29
|
+
|
|
30
|
+
## [0.4.1] - 2025-11-25
|
|
31
|
+
|
|
32
|
+
### Improvements
|
|
33
|
+
|
|
34
|
+
* Literal class can now mark which methods are CEL directives, the remainder being lib private helpers.
|
|
35
|
+
* `cel` is now ractor compatible.
|
|
36
|
+
* Documentation on how to support abstract types has been added.
|
|
37
|
+
|
|
38
|
+
### Security
|
|
39
|
+
|
|
40
|
+
A remote execution attack vector has been fixed, which allowed executing arbitrary Ruby code within a CEL expression when calling functions on a variable declared as a CEL map. Example:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
env = Cel::Environment.new(declarations: { webhook: :map })
|
|
44
|
+
env.evaluate("webhook.payload.send('eval', 'File.write(\"test.txt\", \"Hello, world!\")')", webhook: { payload: {} })
|
|
45
|
+
```
|
|
46
|
+
|
|
3
47
|
## [0.4.0] - 2025-11-18
|
|
4
48
|
|
|
5
49
|
### Features
|
|
@@ -23,7 +67,7 @@ When the `tzinfo-data` gem is loaded, expressions will support human-friendly ti
|
|
|
23
67
|
|
|
24
68
|
### Bugfixes
|
|
25
69
|
|
|
26
|
-
* `has()` macro correctly works for
|
|
70
|
+
* `has()` macro correctly works for maps
|
|
27
71
|
|
|
28
72
|
## [0.3.0] - 2025-06-12
|
|
29
73
|
|
data/README.md
CHANGED
|
@@ -139,6 +139,58 @@ env2 = Cel::Environment.new(declarations: {foo: ->(a, b) { a + b }})
|
|
|
139
139
|
env2.evaluate("foo(2, 2)") #=> 4
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
### Abstract types
|
|
143
|
+
|
|
144
|
+
`cel-ruby` supports defining abstract types, via the `Cel::AbstractType` class.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
class Tuple
|
|
148
|
+
attr_reader :value
|
|
149
|
+
def initialize(ary)
|
|
150
|
+
@value = ary
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
tuple_type = Class.new(Cel::AbstractType) do
|
|
155
|
+
def convert(value)
|
|
156
|
+
Tuple.new(value)
|
|
157
|
+
end
|
|
158
|
+
end.new(:tuple, Cel::TYPES[:int], Cel::TYPES[:int])
|
|
159
|
+
env = Cel::Environment.new(declarations: { tuple: Cel::Function(:int, :int, return_type: tuple_type) {|a, b| [a, b] } })
|
|
160
|
+
env.check('tuple(1, 2)') #=> tuple type
|
|
161
|
+
res = env.evaluate('tuple(1, 2)') #=> Tuple instance
|
|
162
|
+
res.value #=> [1, 2]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Custom Extensions
|
|
166
|
+
|
|
167
|
+
`cel` already supports the [conformance spec extensions packages](https://pkg.go.dev/github.com/google/cel-go/ext#section-readme). However, if you need to add your own, you can do so:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
module Ext
|
|
171
|
+
def __check(funcall, checker:)
|
|
172
|
+
func = funcall.func
|
|
173
|
+
args = funcall.args
|
|
174
|
+
|
|
175
|
+
case func
|
|
176
|
+
when :random
|
|
177
|
+
checker.check_arity(func, args, 0)
|
|
178
|
+
return TYPES[:int]
|
|
179
|
+
else
|
|
180
|
+
checker.unsupported_operation(funcall)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# extensions will always receive the program instance as a kwarg
|
|
185
|
+
def random(program:)
|
|
186
|
+
42
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
env = Cel::Environment.new(extensions: { ext: Ext})
|
|
191
|
+
env.evaluate("ext.random()") #=> 42
|
|
192
|
+
```
|
|
193
|
+
|
|
142
194
|
## Spec Coverage
|
|
143
195
|
|
|
144
196
|
`cel` is tested against the conformance suite from the [cel-spec repository](https://github.com/google/cel-spec/tree/master/conformance), and supports all features from the language except:
|
|
@@ -157,6 +209,14 @@ If this is something you're interested in (helping out), add a mention in the co
|
|
|
157
209
|
|
|
158
210
|
All Rubies greater or equal to 2.7, and always latest JRuby and Truffleruby.
|
|
159
211
|
|
|
212
|
+
`cel` can be used inside ractors, but you need to freeze it first:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# can't be used in ractors
|
|
216
|
+
Cel.freeze
|
|
217
|
+
# can be used in ractors
|
|
218
|
+
```
|
|
219
|
+
|
|
160
220
|
## Development
|
|
161
221
|
|
|
162
222
|
Clone the repo in your local machine, where you have `ruby` installed. Then you can:
|
|
@@ -185,7 +245,7 @@ The parser is based on the grammar defined in [cel-spec](https://github.com/goog
|
|
|
185
245
|
Changes in the parser are therefore accomplished by modifying the `parser.ry` file and running:
|
|
186
246
|
|
|
187
247
|
```bash
|
|
188
|
-
> bundle exec racc -o lib/cel/parser.rb lib/cel/parser.ry
|
|
248
|
+
> bundle exec racc -F -o lib/cel/parser.rb lib/cel/parser.ry
|
|
189
249
|
```
|
|
190
250
|
|
|
191
251
|
## Contributing
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
module CelMethods
|
|
5
|
+
def self.included(klass)
|
|
6
|
+
super
|
|
7
|
+
klass.class_eval do
|
|
8
|
+
@cel_methods = [] # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
9
|
+
|
|
10
|
+
extend ClassMethods
|
|
11
|
+
include InstanceMethods
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
attr_reader :cel_methods
|
|
17
|
+
|
|
18
|
+
def define_cel_method(meth, &blk)
|
|
19
|
+
Ractor.make_shareable(blk) if defined?(Ractor)
|
|
20
|
+
define_method(meth, &blk)
|
|
21
|
+
|
|
22
|
+
@cel_methods << meth # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def inherited(klass)
|
|
26
|
+
super
|
|
27
|
+
|
|
28
|
+
klass.instance_variable_set(:@cel_methods, [])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def freeze(*a)
|
|
32
|
+
super
|
|
33
|
+
@cel_methods.freeze(*a) # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module InstanceMethods
|
|
38
|
+
def cel_send(meth, *args)
|
|
39
|
+
meth = meth.to_sym
|
|
40
|
+
raise NoCelMethodError unless cel_method_defined?(meth)
|
|
41
|
+
|
|
42
|
+
public_send(meth, *args)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cel_method_defined?(meth)
|
|
46
|
+
klass = self.class
|
|
47
|
+
|
|
48
|
+
while klass != Literal
|
|
49
|
+
return true if klass.cel_methods.include?(meth)
|
|
50
|
+
|
|
51
|
+
klass = klass.superclass
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
klass.cel_methods.include?(meth)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Bool < Literal
|
|
5
|
+
def initialize(value)
|
|
6
|
+
super(:bool, value)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
(LOGICAL_OPERATORS - %w[== != in]).each do |op|
|
|
10
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
11
|
+
define_cel_method(:#{op}) do |other|
|
|
12
|
+
return super(other) unless other.is_a?(Bool)
|
|
13
|
+
|
|
14
|
+
lhs = @value ? 1 : 0
|
|
15
|
+
rhs = other.value ? 1 : 0
|
|
16
|
+
lhs.__send__(__method__, rhs)
|
|
17
|
+
end
|
|
18
|
+
OUT
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def !
|
|
22
|
+
Bool.cast(super)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
TRUE_VALUE = new(true)
|
|
26
|
+
FALSE_VALUE = new(false)
|
|
27
|
+
|
|
28
|
+
def self.cast(val)
|
|
29
|
+
return val if val.is_a?(Bool)
|
|
30
|
+
|
|
31
|
+
val ? TRUE_VALUE : FALSE_VALUE
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Bytes < Literal
|
|
5
|
+
attr_reader :string
|
|
6
|
+
|
|
7
|
+
def initialize(value)
|
|
8
|
+
super(:bytes, value)
|
|
9
|
+
@string = value.pack("C*")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_ary
|
|
13
|
+
[self]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
alias_method :to_ruby_type, :string
|
|
17
|
+
|
|
18
|
+
(LOGICAL_OPERATORS - %w[==]).each do |op|
|
|
19
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
20
|
+
define_cel_method(:#{op}) do |other|
|
|
21
|
+
other.is_a?(Cel::Bytes) ? Bool.cast(@string.__send__(__method__, other.string)) : super(other)
|
|
22
|
+
end
|
|
23
|
+
OUT
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
%i[+ -].each do |op|
|
|
27
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
28
|
+
define_cel_method(:#{op}) do |other|
|
|
29
|
+
Bytes.new(@value + other.value)
|
|
30
|
+
end
|
|
31
|
+
OUT
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Condition
|
|
5
|
+
attr_reader :if, :then, :else, :depth
|
|
6
|
+
|
|
7
|
+
def initialize(if_, then_, else_, depth: 1)
|
|
8
|
+
@if = if_
|
|
9
|
+
@then = then_
|
|
10
|
+
@else = else_
|
|
11
|
+
@depth = depth
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
TYPES[:any]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ==(other)
|
|
19
|
+
other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Duration < Literal
|
|
5
|
+
def initialize(value)
|
|
6
|
+
value =
|
|
7
|
+
case value
|
|
8
|
+
when ::String
|
|
9
|
+
init_from_string(value)
|
|
10
|
+
when Hash
|
|
11
|
+
seconds, nanos = value.values_at(:seconds, :nanos)
|
|
12
|
+
seconds ||= 0
|
|
13
|
+
nanos ||= 0
|
|
14
|
+
seconds + (nanos / 1_000_000_000.0)
|
|
15
|
+
else
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
raise EvaluateError, "out of range" unless (value * 1_000_000_000).between?(-MAX_INT, MAX_INT)
|
|
20
|
+
|
|
21
|
+
super(:duration, value)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
seconds = getSeconds
|
|
26
|
+
millis = getMilliseconds
|
|
27
|
+
millis.positive? ? "#{seconds}s#{millis}m" : "#{seconds}s"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
define_cel_method(:+) do |other|
|
|
31
|
+
case other
|
|
32
|
+
when Cel::Duration
|
|
33
|
+
Cel::Duration.new(@value + other.value)
|
|
34
|
+
when Cel::Timestamp
|
|
35
|
+
Cel::Timestamp.new(other.value + @value)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
define_cel_method(:-) do |other|
|
|
40
|
+
case other
|
|
41
|
+
when Cel::Duration
|
|
42
|
+
Cel::Duration.new(@value - other.value)
|
|
43
|
+
else
|
|
44
|
+
raise Error, "invalid operand #{other} for `-`"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
LOGICAL_OPERATORS.each do |op|
|
|
49
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
50
|
+
define_cel_method(:#{op}) do |other|
|
|
51
|
+
case other
|
|
52
|
+
when Cel::Literal
|
|
53
|
+
Bool.cast(super(other))
|
|
54
|
+
when Numeric
|
|
55
|
+
@value == other
|
|
56
|
+
|
|
57
|
+
else
|
|
58
|
+
super(other)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
OUT
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Cel Functions
|
|
65
|
+
|
|
66
|
+
define_cel_method(:getHours) do
|
|
67
|
+
Cel::Number.new(:int, (getMinutes / 60).to_i)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
define_cel_method(:getMinutes) do
|
|
71
|
+
Cel::Number.new(:int, (getSeconds / 60).to_i)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
define_cel_method(:getSeconds) do
|
|
75
|
+
Cel::Number.new(:int, @value.divmod(1).first)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
define_cel_method(:getMilliseconds) do
|
|
79
|
+
Cel::Number.new(:int, (@value.divmod(1).last * 1000).round)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def to_ruby_type
|
|
83
|
+
Protobuf.duration_class.new(seconds: getSeconds.value, nanos: getMilliseconds.value * 1_000_000_000.0)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def init_from_string(value)
|
|
89
|
+
seconds = 0
|
|
90
|
+
nanos = 0
|
|
91
|
+
value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
|
|
92
|
+
case units
|
|
93
|
+
when "h"
|
|
94
|
+
seconds += Cel.to_numeric(duration) * 60 * 60
|
|
95
|
+
when "m"
|
|
96
|
+
seconds += Cel.to_numeric(duration) * 60
|
|
97
|
+
when "s"
|
|
98
|
+
seconds += Cel.to_numeric(duration)
|
|
99
|
+
when "ms"
|
|
100
|
+
nanos += Cel.to_numeric(duration) * 1000 * 1000
|
|
101
|
+
when "us"
|
|
102
|
+
nanos += Cel.to_numeric(duration) * 1000
|
|
103
|
+
when "ns"
|
|
104
|
+
nanos += Cel.to_numeric(duration)
|
|
105
|
+
else
|
|
106
|
+
raise EvaluateError, "#{units} is unsupported"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
duration = seconds + (nanos / 1_000_000_000.0)
|
|
110
|
+
duration = -duration if value.start_with?("-")
|
|
111
|
+
duration
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Function
|
|
5
|
+
attr_reader :label, :types, :type
|
|
6
|
+
|
|
7
|
+
def initialize(*types, label: nil, return_type: nil, &func)
|
|
8
|
+
unless func.nil?
|
|
9
|
+
types = Array.new(func.arity) { TYPES[:any] } if types.empty?
|
|
10
|
+
raise(Error, "number of arg types does not match number of yielded args") unless types.size == func.arity
|
|
11
|
+
end
|
|
12
|
+
@types = types.map { |typ| typ.is_a?(Type) ? typ : TYPES[typ] }
|
|
13
|
+
@type = if return_type.nil?
|
|
14
|
+
TYPES[:any]
|
|
15
|
+
else
|
|
16
|
+
return_type.is_a?(Type) ? return_type : TYPES[return_type]
|
|
17
|
+
end
|
|
18
|
+
@func = func
|
|
19
|
+
@label = label
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(*args)
|
|
23
|
+
result = @func.call(*args)
|
|
24
|
+
|
|
25
|
+
@type.convert(result)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
mod = self
|
|
30
|
+
mod.define_singleton_method(:Function) do |*args, **kwargs, &blk|
|
|
31
|
+
mod::Function.new(*args, **kwargs, &blk)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Group
|
|
5
|
+
attr_reader :value
|
|
6
|
+
|
|
7
|
+
def initialize(value)
|
|
8
|
+
@value = value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def type
|
|
12
|
+
TYPES[:any]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ==(other)
|
|
16
|
+
other.is_a?(Group) && @value == other.value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
module Cel
|
|
6
|
+
class Identifier < SimpleDelegator
|
|
7
|
+
attr_reader :id
|
|
8
|
+
|
|
9
|
+
attr_accessor :type
|
|
10
|
+
|
|
11
|
+
def initialize(identifier, package = nil)
|
|
12
|
+
@id = identifier
|
|
13
|
+
@type = TYPES[:any]
|
|
14
|
+
@package = package
|
|
15
|
+
super(@id)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ==(other)
|
|
19
|
+
super || other.to_s == @id.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
@id.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
alias_method :to_ruby_type, :to_s
|
|
27
|
+
|
|
28
|
+
def try_convert_to_proto_type
|
|
29
|
+
Protobuf.convert_to_proto_type(@id.to_s, @package)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class Invoke
|
|
5
|
+
attr_accessor :var
|
|
6
|
+
|
|
7
|
+
attr_reader :func, :args, :depth
|
|
8
|
+
|
|
9
|
+
def self.new(func:, var: nil, args: nil, **rest)
|
|
10
|
+
Protobuf.try_invoke_from(var, func, args) || super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def type
|
|
14
|
+
TYPES[:any]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(func:, var: nil, args: nil, depth: 1, package: nil)
|
|
18
|
+
@var = var
|
|
19
|
+
@func = func.to_sym
|
|
20
|
+
@args = args
|
|
21
|
+
@package = package
|
|
22
|
+
@depth = depth
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ==(other)
|
|
26
|
+
case other
|
|
27
|
+
when Invoke
|
|
28
|
+
@var == other.var && @func == other.func && @args == other.args
|
|
29
|
+
when Array
|
|
30
|
+
[@var, @func, @args].compact == other
|
|
31
|
+
else
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
if var
|
|
38
|
+
if indexing?
|
|
39
|
+
"#{var}[#{args}]"
|
|
40
|
+
else
|
|
41
|
+
"#{var}.#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
"#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def try_convert_to_proto_type
|
|
49
|
+
return unless @var
|
|
50
|
+
|
|
51
|
+
proto_type = Protobuf.convert_to_proto_type(@var.to_s, @package)
|
|
52
|
+
|
|
53
|
+
return unless proto_type
|
|
54
|
+
|
|
55
|
+
return proto_type unless proto_type.const_defined?(@func)
|
|
56
|
+
|
|
57
|
+
proto_type.const_get(@func)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def indexing?
|
|
61
|
+
@func == :[]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cel
|
|
4
|
+
class List < Literal
|
|
5
|
+
attr_reader :depth
|
|
6
|
+
|
|
7
|
+
def initialize(value, depth: 1)
|
|
8
|
+
value = value.map do |v|
|
|
9
|
+
Literal.to_cel_type(v)
|
|
10
|
+
end
|
|
11
|
+
super(TYPES[:list], value)
|
|
12
|
+
@depth = depth
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
define_cel_method(:[]) do |key|
|
|
16
|
+
case key
|
|
17
|
+
when Number
|
|
18
|
+
if key.type == :double
|
|
19
|
+
val = key.value
|
|
20
|
+
|
|
21
|
+
raise InvalidArgumentError, key unless (val % 1).zero?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
super(key)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_ary
|
|
29
|
+
[self]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_ruby_type
|
|
33
|
+
value.map(&:to_ruby_type)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
%i[+ -].each do |op|
|
|
37
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
38
|
+
define_cel_method(:#{op}) do |other|
|
|
39
|
+
List.new(@value.send(__method__, other.value))
|
|
40
|
+
end
|
|
41
|
+
OUT
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def by_max_depth
|
|
45
|
+
max = @value.max { |a1, a2| calc_depth(a1, 0) <=> calc_depth(a2, 0) }
|
|
46
|
+
|
|
47
|
+
# return the last value if all options have the same depth
|
|
48
|
+
return @value.last if (max == @value.first) && (calc_depth(max, 0) == calc_depth(@value.last, 0))
|
|
49
|
+
|
|
50
|
+
max
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
"[#{@value.map(&:to_s).join(", ")}]"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def calc_depth(element, acc)
|
|
60
|
+
case element
|
|
61
|
+
when List
|
|
62
|
+
element.value.map { |el| calc_depth(el, acc + 1) }.max || (acc + 1)
|
|
63
|
+
when Map
|
|
64
|
+
element.value.map { |(_, el)| calc_depth(el, acc + 1) }.max || (acc + 1)
|
|
65
|
+
else
|
|
66
|
+
acc
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|