lib/calc/calculator.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require 'calc/error'

module Calc
  # Calculates the result of {Operation}s in Reverse Polish (or Postfix)
  # notation. It is composable and can support a wide range of operations.
  class Calculator
    attr_reader :operations, :stack
    private :operations, :stack

    # Raised when an operation is performed with insufficient elements
    # on the stack
    TooFewElementsOnStackError = Class.new(Error)

    # @param operations [Array<Operation>] operations to support
    def initialize(operations:)
      @operations = to_hash(Array(operations))
      @stack = []
    end

    # @api
    # @param input [BigDecimal, String] number to perform an {Operation} on or
    #   the +sign+ of the {Operation} to perform
    # @raise [TooFewElementsOnStackError] if an operation is attempted
    #   and there are not enough elements on the stack
    # @return [BigDecimal] the +input+ was a number or the result of
    #   the {Operation} if +input+ was a sign
    def process(input:)
      result = if (operation = operations[input])
                 raise invalid_stack if stack.size < operation.operands

                 terms = stack.pop(2)

                 operation.calculate(*terms)
               else
                 input
               end

      stack.push(result)

      result
    end

    private

    def invalid_stack
      TooFewElementsOnStackError.new('Too few elements on stack')
    end

    def to_hash(operations)
      operations.each_with_object({}) { |op, mapping| mapping[op.sign] = op }
    end
  end
end