class Rubulator

Constants

MATH_CONSTANTS
MATH_FUNCTIONS
OPERATORS
RESULT_VARIABLE
VARIABLE_PREFIX
VARIABLE_PREFIX_MATCH_RE
VERSION

Attributes

variables[RW]

Public Class Methods

calculate(expression) click to toggle source
# File lib/rubulator.rb, line 34
def self.calculate(expression)
  Rubulator.new.calculate(expression)
end
new() click to toggle source
# File lib/rubulator.rb, line 28
def initialize
  @variables = {}

  result.store(0.0)
end

Public Instance Methods

calculate(expression) click to toggle source
# File lib/rubulator.rb, line 42
def calculate(expression)
  return unless expression && expression != ''

  operands = expression.sub(/^\s+/, '').sub(/\s+$/, '').split(/\s+/)
  result.store(consume(nil, 'initial', operands).to_f)

  unless operands.empty?
    raise CalculationError, "Operands #{operands.inspect} left over in expression '#{expression}'"
  end

  result.to_f
end
constants() click to toggle source
# File lib/rubulator.rb, line 63
def constants
  MATH_CONSTANTS.keys.map(&:to_s)
end
functions() click to toggle source
# File lib/rubulator.rb, line 59
def functions
  MATH_FUNCTIONS
end
operators() click to toggle source
# File lib/rubulator.rb, line 55
def operators
  ['='] + OPERATORS
end
result() click to toggle source
# File lib/rubulator.rb, line 38
def result
  @variables[RESULT_VARIABLE] ||= Variable.new(RESULT_VARIABLE)
end
variable_names() click to toggle source
# File lib/rubulator.rb, line 67
def variable_names
  variables.keys.map { |v| VARIABLE_PREFIX + v }
end

Private Instance Methods

consume(operator, name, operands) click to toggle source
# File lib/rubulator.rb, line 87
def consume(operator, name, operands)
  raise CalculationError, "Missing #{name} operand for '#{operator || 'main expression'}'" if operands.empty?

  operand = operands.shift

  case # rubocop:disable Style/EmptyCaseCondition
  when operand == '='
    variable = consume('=', 'assignment variable', operands)
    unless variable.is_a?(Variable)
      raise CalculationError, "Variable expected for 1st operand, found '#{variable}' instead"
    end

    value = consume('=', 'value', operands).to_f

    variable.store(value)
  when operand.start_with?(VARIABLE_PREFIX)
    name = operand.sub(VARIABLE_PREFIX_MATCH_RE, '')
    raise CalculationError, "No variable name provided for '$'" if name == ''

    @variables[name] ||= Variable.new(name)
  when operand == ','
    consume(',', '1st', operands)
    consume(',', '2nd', operands)
  when OPERATORS.include?(operand)
    op1 = consume(operand, '1st', operands).to_f
    op2 = consume(operand, '2nd', operands).to_f
    op1.send(operand.to_sym, op2)
  when MATH_FUNCTIONS.include?(operand)
    op1 = consume(operand, 'input', operands).to_f
    Math.send(operand.to_sym, op1)
  when MATH_CONSTANTS.include?(operand.downcase.to_sym)
    MATH_CONSTANTS.fetch(operand.downcase.to_sym)
  when /[+-]?[0-9][0-9.]*/.match(operand)
    operand
  else
    raise CalculationError, "Unknown argument '#{operand}'"
  end
end