class Dentaku::AST::Arithmetic
def calculate(left_value, right_value)
def calculate(left_value, right_value) l = cast(left_value) r = cast(right_value) l.public_send(operator, r) rescue ::TypeError => e # Right cannot be converted to a suitable type for left. e.g. [] + 1 raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message end
def cast(val)
def cast(val) validate_value(val) numeric(val) end
def datetime?(val)
def datetime?(val) # val is a Date, Time, or DateTime return true if val.respond_to?(:strftime) val.to_s =~ Dentaku::TokenScanner::DATE_TIME_REGEXP end
def decimal(val)
def decimal(val) BigDecimal(val.to_s, Float::DIG + 1) rescue # return as is, in case value can't be coerced to big decimal val end
def initialize(*)
def initialize(*) super unless valid_left? raise NodeError.new(:numeric, left.type, :left), "#{self.class} requires numeric operands" end unless valid_right? raise NodeError.new(:numeric, right.type, :right), "#{self.class} requires numeric operands" end end
def numeric(val)
def numeric(val) case val.to_s when DECIMAL then decimal(val) when INTEGER then val.to_i else val end end
def operator
def operator raise NotImplementedError end
def type
def type :numeric end
def valid_left?
def valid_left? valid_node?(left) || left.type == :datetime end
def valid_node?(node)
def valid_node?(node) node && (node.type == :numeric || node.type == :integer || node.dependencies.any?) end
def valid_right?
def valid_right? valid_node?(right) || right.type == :duration || right.type == :datetime end
def validate_format(string)
def validate_format(string) unless string =~ /\A-?\d*(\.\d+)?\z/ && !string.empty? raise Dentaku::ArgumentError.for(:invalid_value, value: string, for: BigDecimal), "String input '#{string}' is not coercible to numeric" end end
def validate_operation(val)
def validate_operation(val) unless val.respond_to?(operator) raise Dentaku::ArgumentError.for(:invalid_operator, operation: self.class, operator: operator), "#{ self.class } requires operands that respond to #{operator}" end end
def validate_value(val)
def validate_value(val) if val.is_a?(::String) validate_format(val) else validate_operation(val) end end
def value(context = {})
def value(context = {}) calculate(left.value(context), right.value(context)) end