cel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []