cel 0.1.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.
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ class Program
5
+ def initialize(context)
6
+ @context = context
7
+ end
8
+
9
+ def evaluate(ast)
10
+ case ast
11
+ when Group
12
+ evaluate(ast.value)
13
+ when Invoke
14
+ evaluate_invoke(ast)
15
+ when Operation
16
+ evaluate_operation(ast)
17
+ when Message
18
+ ast.struct
19
+ when Literal
20
+ evaluate_literal(ast)
21
+ when Identifier
22
+ evaluate_identifier(ast)
23
+ when Condition
24
+ evaluate_condition(ast)
25
+ end
26
+ end
27
+
28
+ alias_method :call, :evaluate
29
+
30
+ private
31
+
32
+ def evaluate_identifier(identifier)
33
+ if Cel::PRIMITIVE_TYPES.include?(identifier.to_sym)
34
+ TYPES[identifier.to_sym]
35
+ else
36
+ @context.lookup(identifier)
37
+ end
38
+ end
39
+
40
+ def evaluate_literal(val)
41
+ case val
42
+ when List
43
+ List.new(val.value.map { |y| call(y) })
44
+ else
45
+ val
46
+ end
47
+ end
48
+
49
+ def evaluate_operation(operation)
50
+ op = operation.op
51
+
52
+ values = operation.operands.map do |operand|
53
+ ev_operand = call(operand)
54
+
55
+ # return ev_operand if op == "||" && ev_operand == true
56
+
57
+ ev_operand
58
+ end
59
+
60
+ if values.size == 1 &&
61
+ op != "!" # https://bugs.ruby-lang.org/issues/18246
62
+ # unary operations
63
+ values.first.__send__(:"#{op}@")
64
+ elsif op == "&&"
65
+ Bool.new(values.all? { |x| x == true })
66
+ elsif op == "||"
67
+ Bool.new(values.any? { |x| x == true })
68
+ elsif op == "in"
69
+ element, collection = values
70
+ Bool.new(collection.include?(element))
71
+ else
72
+ op_value, *values = values
73
+ op_value.public_send(op, *values)
74
+ end
75
+ end
76
+
77
+ def evaluate_invoke(invoke, var = invoke.var)
78
+ func = invoke.func
79
+ args = invoke.args
80
+
81
+ return evaluate_standard_func(invoke) unless var
82
+
83
+ var = case var
84
+ when Identifier
85
+ evaluate_identifier(var)
86
+ when Invoke
87
+ evaluate_invoke(var)
88
+ else
89
+ var
90
+ end
91
+
92
+ case var
93
+ when String
94
+ raise EvaluateError, "#{invoke} is not supported" unless String.method_defined?(func, false)
95
+
96
+ var.public_send(func, *args)
97
+ when Message
98
+ # If e evaluates to a message and f is not declared in this message, the
99
+ # runtime error no_such_field is raised.
100
+ raise NoSuchFieldError.new(var, func) unless var.field?(func)
101
+
102
+ var.public_send(func)
103
+ when Map, List
104
+ return Macro.__send__(func, var, *args, context: @context) if Macro.respond_to?(func)
105
+
106
+ # If e evaluates to a map, then e.f is equivalent to e['f'] (where f is
107
+ # still being used as a meta-variable, e.g. the expression x.foo is equivalent
108
+ # to the expression x['foo'] when x evaluates to a map).
109
+
110
+ args ?
111
+ var.public_send(func, *args) :
112
+ var.public_send(func)
113
+ else
114
+ raise EvaluateError, "#{invoke} is not supported"
115
+ end
116
+ end
117
+
118
+ def evaluate_condition(condition)
119
+ call(condition.if) ? call(condition.then) : call(condition.else)
120
+ end
121
+
122
+ def evaluate_standard_func(funcall)
123
+ func = funcall.func
124
+ args = funcall.args
125
+
126
+ case func
127
+ when :type
128
+ val = call(args.first)
129
+ return val.type if val.respond_to?(:type)
130
+
131
+ val.class
132
+ # MACROS
133
+ when :has, :size
134
+ Macro.__send__(func, *args)
135
+ when :matches
136
+ Macro.__send__(func, *args.map { |arg| call(arg) })
137
+ when :int, :uint, :string, :double, :bytes # :duration, :timestamp
138
+ type = TYPES[func]
139
+ type.cast(call(args.first))
140
+ when :dyn
141
+ call(args.first)
142
+ else
143
+ raise EvaluateError, "#{funcall} is not supported"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Cel
6
+ module Protobuf
7
+ module CheckExtensions
8
+ def self.included(klass)
9
+ super
10
+ klass.alias_method :check_standard_func_without_protobuf, :check_standard_func
11
+ klass.alias_method :check_standard_func, :check_standard_func_with_protobuf
12
+
13
+ klass.alias_method :check_invoke_without_protobuf, :check_invoke
14
+ klass.alias_method :check_invoke, :check_invoke_with_protobuf
15
+ end
16
+
17
+ private
18
+
19
+ def check_invoke_with_protobuf(funcall)
20
+ var = funcall.var
21
+
22
+ return check_standard_func(funcall) unless var
23
+
24
+ var_type = case var
25
+ when Identifier
26
+ check_identifier(var)
27
+ when Invoke
28
+ check_invoke(var)
29
+ else
30
+ var.type
31
+ end
32
+
33
+ return check_invoke_without_protobuf(funcall, var_type) if var_type.is_a?(Cel::Type)
34
+ return check_invoke_without_protobuf(funcall, var_type) unless var_type < Google::Protobuf::MessageExts
35
+
36
+ func = funcall.func
37
+
38
+ attribute = var_type.descriptor.lookup(func.to_s)
39
+
40
+ raise NoSuchFieldError.new(var, func) unless attribute
41
+
42
+ # TODO: return super to any, or raise error?
43
+
44
+ type = attribute.type
45
+
46
+ # https://github.com/google/cel-spec/blob/master/doc/langdef.md#protocol-buffer-data-conversion
47
+ case type
48
+ when :int32, :int64, :sint32, :sint64, :sfixed32, :sfixed64
49
+ TYPES[:int]
50
+ when :uint32, :uint64, :fixed32, :fixed64
51
+ TYPES[:uint]
52
+ when :float, :double
53
+ TYPES[:double]
54
+ when :bool, :string, :bytes
55
+ TYPES[type]
56
+ when :repeated
57
+ TYPES[:list]
58
+ when :map
59
+ TYPES[:map]
60
+ when :oneof
61
+ TYPES[:any]
62
+ else
63
+ type
64
+ end
65
+ end
66
+
67
+ def check_standard_func_with_protobuf(funcall)
68
+ func = funcall.func
69
+ args = funcall.args
70
+
71
+ case func
72
+ when :duration
73
+ check_arity(func, args, 1)
74
+ unsupported_type(funcall) unless args.first.is_a?(String)
75
+
76
+ return Google::Protobuf::Duration
77
+ when :timestamp
78
+ check_arity(func, args, 1)
79
+ unsupported_type(funcall) unless args.first.is_a?(String)
80
+
81
+ return Google::Protobuf::Timestamp
82
+ end
83
+
84
+ check_standard_func_without_protobuf(funcall)
85
+ end
86
+ end
87
+
88
+ module EvaluateExtensions
89
+ def self.included(klass)
90
+ super
91
+ klass.alias_method :evaluate_standard_func_without_protobuf, :evaluate_standard_func
92
+ klass.alias_method :evaluate_standard_func, :evaluate_standard_func_with_protobuf
93
+
94
+ klass.alias_method :evaluate_invoke_without_protobuf, :evaluate_invoke
95
+ klass.alias_method :evaluate_invoke, :evaluate_invoke_with_protobuf
96
+ end
97
+
98
+ private
99
+
100
+ def evaluate_invoke_with_protobuf(invoke)
101
+ var = invoke.var
102
+
103
+ return evaluate_standard_func(invoke) unless var
104
+
105
+ var = case var
106
+ when Identifier
107
+ evaluate_identifier(var)
108
+ when Invoke
109
+ evaluate_invoke(var)
110
+ else
111
+ var
112
+ end
113
+
114
+ return evaluate_invoke_without_protobuf(invoke, var) unless var.is_a?(Google::Protobuf::MessageExts)
115
+
116
+ func = invoke.func
117
+
118
+ var.public_send(func)
119
+ end
120
+
121
+ def evaluate_standard_func_with_protobuf(funcall)
122
+ func = funcall.func
123
+ args = funcall.args
124
+
125
+ case func
126
+ when :duration
127
+ seconds = 0
128
+ nanos = 0
129
+ args.first.value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
130
+ case units
131
+ when "h"
132
+ seconds += Cel.to_numeric(duration) * 60 * 60
133
+ when "m"
134
+ seconds += Cel.to_numeric(duration) * 60
135
+ when "s"
136
+ seconds += Cel.to_numeric(duration)
137
+ when "ms"
138
+ nanos += Cel.to_numeric(duration) * 1000 * 1000
139
+ when "us"
140
+ nanos += Cel.to_numeric(duration) * 1000
141
+ when "ns"
142
+ nanos += Cel.to_numeric(duration)
143
+ else
144
+ raise EvaluateError, "#{units} is unsupported"
145
+ end
146
+ end
147
+ return Google::Protobuf::Duration.new(seconds: seconds, nanos: nanos)
148
+ when :timestamp
149
+ time = Time.parse(args.first)
150
+ return Google::Protobuf::Timestamp.from_time(time)
151
+ end
152
+
153
+ evaluate_standard_func_without_protobuf(funcall)
154
+ end
155
+ end
156
+
157
+ module ContextExtensions
158
+ def self.included(klass)
159
+ super
160
+ klass.alias_method :to_cel_type_without_protobuf, :to_cel_type
161
+ klass.alias_method :to_cel_type, :to_cel_type_with_protobuf
162
+ end
163
+
164
+ def to_cel_type_with_protobuf(v)
165
+ return v if v.is_a?(Google::Protobuf::MessageExts)
166
+
167
+ to_cel_type_without_protobuf(v)
168
+ end
169
+ end
170
+ end
171
+
172
+ Checker.include(Protobuf::CheckExtensions)
173
+ Program.include(Protobuf::EvaluateExtensions)
174
+ Context.include(Protobuf::ContextExtensions)
175
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cel
4
+ module Ruby
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
data/lib/cel.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "cel/version"
5
+ require "cel/errors"
6
+ require "cel/ast/types"
7
+ require "cel/parser"
8
+ require "cel/macro"
9
+ require "cel/context"
10
+ require "cel/checker"
11
+ require "cel/program"
12
+ require "cel/environment"
13
+
14
+ begin
15
+ require "google/protobuf/well_known_types"
16
+ require "cel/protobuf"
17
+ rescue LoadError # rubocop:disable Lint/SuppressedException
18
+ end
19
+
20
+ module Cel
21
+ def self.to_numeric(anything)
22
+ num = BigDecimal(anything.to_s)
23
+ if num.frac.zero?
24
+ num.to_i
25
+ else
26
+ num.to_f
27
+ end
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tiago Cardoso
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ name: minitest
20
+ prerelease: false
21
+ type: :development
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
28
+ email:
29
+ - cardoso_tiago@hotmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files:
33
+ - LICENSE.txt
34
+ - CHANGELOG.md
35
+ - README.md
36
+ files:
37
+ - CHANGELOG.md
38
+ - LICENSE.txt
39
+ - README.md
40
+ - lib/cel.rb
41
+ - lib/cel/ast/elements.rb
42
+ - lib/cel/ast/types.rb
43
+ - lib/cel/checker.rb
44
+ - lib/cel/context.rb
45
+ - lib/cel/environment.rb
46
+ - lib/cel/errors.rb
47
+ - lib/cel/macro.rb
48
+ - lib/cel/parser.rb
49
+ - lib/cel/program.rb
50
+ - lib/cel/protobuf.rb
51
+ - lib/cel/version.rb
52
+ homepage:
53
+ licenses:
54
+ - Apache 2.0
55
+ metadata:
56
+ bug_tracker_uri: https://gitlab.com/honeyryderchuck/cel-ruby/issues
57
+ changelog_uri: https://gitlab.com/honeyryderchuck/cel-ruby/-/blob/master/CHANGELOG.md
58
+ source_code_uri: https://gitlab.com/honeyryderchuck/cel-ruby
59
+ homepage_uri: https://gitlab.com/honeyryderchuck/cel-ruby
60
+ rubygems_mfa_required: 'true'
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '2.6'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.2.14
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
80
+ test_files: []