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