lib/cel/ast/elements.rb in cel-0.1.2 vs lib/cel/ast/elements.rb in cel-0.2.0
- old
+ new
@@ -1,8 +1,10 @@
# frozen_string_literal: true
+require "time"
require "delegate"
+require_relative "elements/protobuf"
module Cel
LOGICAL_OPERATORS = %w[< <= >= > == != in].freeze
MULTI_OPERATORS = %w[* / %].freeze
@@ -18,15 +20,26 @@
end
def ==(other)
super || other.to_s == @id.to_s
end
+
+ def to_s
+ @id.to_s
+ end
end
class Message < SimpleDelegator
attr_reader :type, :struct
+ def self.new(type, struct)
+ value = convert_from_type(type, struct)
+ return value if value.is_a?(Null) || value != struct
+
+ super
+ end
+
def initialize(type, struct)
@struct = Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values)
@type = type.is_a?(Type) ? type : MapType.new(struct.to_h do |k, v|
[Literal.to_cel_type(k), Literal.to_cel_type(v)]
end)
@@ -34,26 +47,51 @@
end
def field?(key)
!@type.get(key).nil?
end
+
+ def self.convert_from_type(type, value)
+ case type
+ when Invoke, Identifier
+ spread_type = type.to_s
+ Protobuf.convert_from_type(spread_type, value)
+ when Type
+ [type, value]
+ else
+ [
+ MapType.new(struct.to_h do |k, v|
+ [Literal.to_cel_type(k), Literal.to_cel_type(v)]
+ end),
+ Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values),
+ ]
+ end
+ end
end
class Invoke
attr_reader :var, :func, :args
+ def self.new(func:, var: nil, args: nil)
+ Protobuf.try_invoke_from(var, func, args) || super
+ end
+
def initialize(func:, var: nil, args: nil)
@var = var
@func = func.to_sym
@args = args
end
def ==(other)
- super || (
- other.respond_to?(:to_ary) &&
+ case other
+ when Invoke
+ @var == other.var && @func == other.func && @args == other.args
+ when Array
[@var, @func, @args].compact == other
- )
+ else
+ super
+ end
end
def to_s
if var
if func == :[]
@@ -65,10 +103,37 @@
"#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
end
end
end
+ class Function
+ attr_reader :types, :type
+
+ def initialize(*types, return_type: nil, &func)
+ unless func.nil?
+ types = Array.new(func.arity) { TYPES[:any] } if types.empty?
+ raise(Error, "number of arg types does not match number of yielded args") unless types.size == func.arity
+ end
+ @types = types.map { |typ| typ.is_a?(Type) ? typ : TYPES[typ] }
+ @type = if return_type.nil?
+ TYPES[:any]
+ else
+ return_type.is_a?(Type) ? return_type : TYPES[return_type]
+ end
+ @func = func
+ end
+
+ def call(*args)
+ Literal.to_cel_type(@func.call(*args))
+ end
+ end
+
+ mod = self
+ mod.define_singleton_method(:Function) do |*args, **kwargs, &blk|
+ mod::Function.new(*args, **kwargs, &blk)
+ end
+
class Literal < SimpleDelegator
attr_reader :type, :value
def initialize(type, value)
@type = type.is_a?(Type) ? type : TYPES[type]
@@ -80,10 +145,12 @@
def ==(other)
@value == other || super
end
def self.to_cel_type(val)
+ val = Protobuf.convert_from_protobuf(val) if val.is_a?(Google::Protobuf::MessageExts)
+
case val
when Literal, Identifier
val
# TODO: should support byte streams?
when ::String
@@ -100,10 +167,12 @@
List.new(val)
when true, false
Bool.new(val)
when nil
Null.new
+ when Time
+ Timestamp.new(val)
else
raise BindingError, "can't convert #{val} to CEL type"
end
end
@@ -272,16 +341,182 @@
raise CheckError, "#{self} is invalid (keys must be of an allowed type (int, uint, bool, or string)"
end
end
+ class Timestamp < Literal
+ def initialize(value)
+ value = case value
+ when String then Time.parse(value)
+ when Numeric then Time.at(value)
+ else value
+ end
+ super(:timestamp, value)
+ end
+
+ def +(other)
+ Timestamp.new(@value + other.to_f)
+ end
+
+ def -(other)
+ case other
+ when Timestamp
+ Duration.new(@value - other.value)
+ when Duration
+ Timestamp.new(@value - other.to_f)
+ end
+ end
+
+ LOGICAL_OPERATORS.each do |op|
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
+ def #{op}(other)
+ other.is_a?(Cel::Literal) ? Bool.new(super) : super
+ end
+ OUT
+ end
+
+ # Cel Functions
+
+ def getDate(tz = nil)
+ to_local_time(tz).day
+ end
+
+ def getDayOfMonth(tz = nil)
+ getDate(tz) - 1
+ end
+
+ def getDayOfWeek(tz = nil)
+ to_local_time(tz).wday
+ end
+
+ def getDayOfYear(tz = nil)
+ to_local_time(tz).yday - 1
+ end
+
+ def getMonth(tz = nil)
+ to_local_time(tz).month - 1
+ end
+
+ def getFullYear(tz = nil)
+ to_local_time(tz).year
+ end
+
+ def getHours(tz = nil)
+ to_local_time(tz).hour
+ end
+
+ def getMinutes(tz = nil)
+ to_local_time(tz).min
+ end
+
+ def getSeconds(tz = nil)
+ to_local_time(tz).sec
+ end
+
+ def getMilliseconds(tz = nil)
+ to_local_time(tz).nsec / 1_000_000
+ end
+
+ private
+
+ def to_local_time(tz = nil)
+ time = @value
+ if tz
+ tz = TZInfo::Timezone.get(tz) unless tz.match?(/\A[+-]\d{2,}:\d{2,}\z/)
+ time = time.getlocal(tz)
+ end
+ time
+ end
+ end
+
+ class Duration < Literal
+ def initialize(value)
+ value = case value
+ when String
+ init_from_string(value)
+ when Hash
+ seconds, nanos = value.values_at(:seconds, :nanos)
+ seconds ||= 0
+ nanos ||= 0
+ seconds + (nanos / 1_000_000_000.0)
+ else
+ value
+ end
+ super(:duration, value)
+ end
+
+ LOGICAL_OPERATORS.each do |op|
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
+ def #{op}(other)
+ case other
+ when Cel::Literal
+ Bool.new(super)
+ when Numeric
+ @value == other
+
+ else
+ super
+ end
+ end
+ OUT
+ end
+
+ # Cel Functions
+
+ def getHours
+ (getMinutes / 60).to_i
+ end
+
+ def getMinutes
+ (getSeconds / 60).to_i
+ end
+
+ def getSeconds
+ @value.divmod(1).first
+ end
+
+ def getMilliseconds
+ (@value.divmod(1).last * 1000).round
+ end
+
+ private
+
+ def init_from_string(value)
+ seconds = 0
+ nanos = 0
+ value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
+ case units
+ when "h"
+ seconds += Cel.to_numeric(duration) * 60 * 60
+ when "m"
+ seconds += Cel.to_numeric(duration) * 60
+ when "s"
+ seconds += Cel.to_numeric(duration)
+ when "ms"
+ nanos += Cel.to_numeric(duration) * 1000 * 1000
+ when "us"
+ nanos += Cel.to_numeric(duration) * 1000
+ when "ns"
+ nanos += Cel.to_numeric(duration)
+ else
+ raise EvaluateError, "#{units} is unsupported"
+ end
+ end
+ seconds + (nanos / 1_000_000_000.0)
+ end
+ end
+
class Group
attr_reader :value
def initialize(value)
@value = value
end
+
+ def ==(other)
+ other.is_a?(Group) && @value == other.value
+ end
end
class Operation
attr_reader :op, :operands
@@ -292,14 +527,17 @@
@operands = operands
@type = TYPES[:any]
end
def ==(other)
- if other.is_a?(Array)
+ case other
+ when Array
other.size == @operands.size + 1 &&
other.first == @op &&
other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
+ when Operation
+ @op == other.op && @type == other.type && @operands == other.operands
else
super
end
end
@@ -315,8 +553,12 @@
def initialize(if_, then_, else_)
@if = if_
@then = then_
@else = else_
+ end
+
+ def ==(other)
+ other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
end
end
end