Module: Ruby::Rego::Evaluator::OperatorEvaluator

Defined in:
lib/ruby/rego/evaluator/operator_evaluator.rb

Overview

Shared operator application helpers. rubocop:disable Metrics/ModuleLength

Constant Summary collapse

EQUALITY_OPERATORS =
{
  eq: lambda do |lhs, rhs|
    return UndefinedValue.new if lhs.is_a?(UndefinedValue) || rhs.is_a?(UndefinedValue)

    BooleanValue.new(lhs == rhs)
  end,
  neq: lambda do |lhs, rhs|
    return UndefinedValue.new if lhs.is_a?(UndefinedValue) || rhs.is_a?(UndefinedValue)

    BooleanValue.new(lhs != rhs)
  end
}.freeze
LOGICAL_OPERATORS =
{
  and: ->(lhs, rhs) { BooleanValue.new(lhs.truthy? && rhs.truthy?) },
  or: ->(lhs, rhs) { BooleanValue.new(lhs.truthy? || rhs.truthy?) }
}.freeze
COMPARISON_OPERATORS =
{
  lt: ->(lhs, rhs) { lhs < rhs },
  lte: ->(lhs, rhs) { lhs <= rhs },
  gt: ->(lhs, rhs) { lhs > rhs },
  gte: ->(lhs, rhs) { lhs >= rhs }
}.freeze
ARITHMETIC_OPERATORS =
{
  plus: ->(lhs, rhs) { lhs + rhs },
  minus: ->(lhs, rhs) { lhs - rhs },
  mult: ->(lhs, rhs) { lhs * rhs },
  div: ->(lhs, rhs) { lhs / rhs },
  mod: ->(lhs, rhs) { lhs % rhs }
}.freeze
MEMBERSHIP_OPERATORS =
{
  in: ->(lhs, rhs) { membership_value(lhs, rhs) }
}.freeze
UNARY_OPERATORS =
{
  not: ->(operand) { BooleanValue.new(!operand.truthy?) },
  minus: lambda do |operand|
    number = numeric_value(operand)
    number ? Value.from_ruby(-number) : UndefinedValue.new
  end
}.freeze

Class Method Summary collapse

Class Method Details

.apply(operator, left, right) ⇒ Value

rubocop:disable Metrics/MethodLength

Parameters:

Returns:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 54

def self.apply(operator, left, right)
  handlers = [
    -> { apply_logical(operator, left, right) },
    -> { EQUALITY_OPERATORS[operator]&.call(left, right) },
    -> { MEMBERSHIP_OPERATORS[operator]&.call(left, right) },
    -> { apply_comparison(operator, left, right) },
    -> { apply_arithmetic(operator, left, right) }
  ]

  handlers.each do |handler|
    value = handler.call
    return value if value
  end

  UndefinedValue.new
end

.apply_arithmetic(operator, left, right) ⇒ Object



94
95
96
97
98
99
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 94

def self.apply_arithmetic(operator, left, right)
  arithmetic = ARITHMETIC_OPERATORS[operator]
  return unless arithmetic

  arithmetic_values(operator, left, right, &arithmetic)
end

.apply_comparison(operator, left, right) ⇒ Object



87
88
89
90
91
92
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 87

def self.apply_comparison(operator, left, right)
  comparison = COMPARISON_OPERATORS[operator]
  return unless comparison

  compare_values(left, right, &comparison)
end

.apply_logical(operator, left, right) ⇒ Object



82
83
84
85
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 82

def self.apply_logical(operator, left, right)
  handler = LOGICAL_OPERATORS[operator]
  handler&.call(left, right)
end

.apply_unary(operator, operand) ⇒ Value

Parameters:

  • operator (Symbol)
  • operand (Value)

Returns:



75
76
77
78
79
80
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 75

def self.apply_unary(operator, operand)
  handler = UNARY_OPERATORS[operator]
  return UndefinedValue.new unless handler

  handler.call(operand)
end

.arithmetic_values(operator, left, right) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 116

def self.arithmetic_values(operator, left, right)
  left_value = numeric_value(left)
  right_value = numeric_value(right)
  return UndefinedValue.new unless left_value && right_value
  return UndefinedValue.new if division_by_zero?(operator, right_value)

  Value.from_ruby(yield(left_value, right_value))
end

.collection_values(value) ⇒ Object

:reek:DuplicateMethodCall



150
151
152
153
154
155
156
157
158
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 150

def self.collection_values(value)
  return UndefinedValue.new unless value.is_a?(ArrayValue) || value.is_a?(SetValue) || value.is_a?(ObjectValue)

  collection = value.value
  return collection if value.is_a?(ArrayValue)
  return collection.to_a if value.is_a?(SetValue)

  collection.keys.map { |key| Value.from_ruby(key) }
end

.comparable?(left_value, right_value) ⇒ Boolean

Returns:

  • (Boolean)


111
112
113
114
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 111

def self.comparable?(left_value, right_value)
  (left_value.is_a?(Numeric) && right_value.is_a?(Numeric)) ||
    (left_value.is_a?(String) && right_value.is_a?(String))
end

.compare_values(left, right) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 101

def self.compare_values(left, right)
  left_value = left.to_ruby
  right_value = right.to_ruby
  return UndefinedValue.new unless comparable?(left_value, right_value)

  BooleanValue.new(yield(left_value, right_value))
rescue ArgumentError
  UndefinedValue.new
end

.division_by_zero?(operator, right_value) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 125

def self.division_by_zero?(operator, right_value)
  %i[div mod].include?(operator) && right_value.zero?
end

.membership_value(lhs, rhs) ⇒ Object



136
137
138
139
140
141
142
143
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 136

def self.membership_value(lhs, rhs)
  return UndefinedValue.new if undefined_operand?(lhs, rhs)

  values = collection_values(rhs)
  return values if values.is_a?(UndefinedValue)

  BooleanValue.new(values.any? { |element| element == lhs })
end

.numeric_value(value) ⇒ Object



129
130
131
132
133
134
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 129

def self.numeric_value(value)
  ruby = value.to_ruby
  return ruby if ruby.is_a?(Numeric)

  nil
end

.undefined_operand?(lhs, rhs) ⇒ Boolean

Returns:

  • (Boolean)


145
146
147
# File 'lib/ruby/rego/evaluator/operator_evaluator.rb', line 145

def self.undefined_operand?(lhs, rhs)
  lhs.is_a?(UndefinedValue) || rhs.is_a?(UndefinedValue)
end