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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +239 -0
- data/README.md +101 -0
- data/lib/cel/ast/elements.rb +322 -0
- data/lib/cel/ast/types.rb +78 -0
- data/lib/cel/checker.rb +323 -0
- data/lib/cel/context.rb +36 -0
- data/lib/cel/environment.rb +48 -0
- data/lib/cel/errors.rb +31 -0
- data/lib/cel/macro.rb +75 -0
- data/lib/cel/parser.rb +996 -0
- data/lib/cel/program.rb +147 -0
- data/lib/cel/protobuf.rb +175 -0
- data/lib/cel/version.rb +7 -0
- data/lib/cel.rb +29 -0
- metadata +80 -0
data/lib/cel/program.rb
ADDED
@@ -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
|
data/lib/cel/protobuf.rb
ADDED
@@ -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
|
data/lib/cel/version.rb
ADDED
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: []
|