⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

[[Category:Ruby]] Code for parsing and evaluating RPN expressions. See

  • [[Parsing/RPN calculator algorithm]]
  • [[Parsing/RPN to infix conversion]]
  • [[Parsing/Shunting-yard algorithm]]
class RPNExpression

  # Set up the table of known operators
  Operator = Struct.new(:precedence, :associativity, :english, :ruby_operator)
  class Operator
    def left_associative?; associativity == :left; end
    def <(other)
      if left_associative? 
        precedence <= other.precedence
      else 
        precedence < other.precedence
      end
    end
  end

  Operators = {
    "+" => Operator.new(2, :left, "ADD", "+"),
    "-" => Operator.new(2, :left, "SUB", "-"),
    "*" => Operator.new(3, :left, "MUL", "*"),
    "/" => Operator.new(3, :left, "DIV", "/"),
    "^" => Operator.new(4, :right, "EXP", "**"),
  }

  # create a new object
  def initialize(str)
    @expression = str
    @infix_tree = nil
    @value = nil
  end
  attr_reader :expression
  
  # convert an infix expression into RPN
  def self.from_infix(expression)
    debug "\nfor Infix expression: #{expression}\nTerm\tAction\tOutput\tStack"
    rpn_expr = []
    op_stack = []
    tokens = expression.split
    until tokens.empty?
      term = tokens.shift

      if Operators.has_key?(term)
        op2 = op_stack.last
        if Operators.has_key?(op2) and Operators[term] < Operators[op2]
          rpn_expr << op_stack.pop
          debug "#{term}\t#{Operators[op2].english}\t#{rpn_expr}\t#{op_stack}\t#{op2} has higher precedence than #{term}"
        end
        op_stack << term
        debug "#{term}\tPUSH OP\t#{rpn_expr}\t#{op_stack}"

      elsif term == "("
        op_stack << term
        debug "#{term}\tOPEN_P\t#{rpn_expr}\t#{op_stack}"

      elsif term == ")"
        until op_stack.last == "("
          rpn_expr << op_stack.pop
          debug "#{term}\t#{Operators[rpn_expr.last].english}\t#{rpn_expr}\t#{op_stack}\tunwinding parenthesis"
        end
        op_stack.pop
        debug "#{term}\tCLOSE_P\t#{rpn_expr}\t#{op_stack}"

      else
        rpn_expr << term
        debug "#{term}\tPUSH V\t#{rpn_expr}\t#{op_stack}"
      end
    end
    until op_stack.empty?
      rpn_expr << op_stack.pop
    end
    obj = self.new(rpn_expr.join(" "))
    debug "RPN = #{obj.to_s}"
    obj
  end

  # calculate the value of an RPN expression
  def eval
    return @value unless @value.nil?

    debug "\nfor RPN expression: #{expression}\nTerm\tAction\tStack"
    stack = []
    expression.split.each do |term|
      if Operators.has_key?(term)
        a, b = stack.pop(2)
        raise ArgumentError, "not enough operands on the stack" if b.nil?
        a = a.to_f if term == "/"
        op = (term == "^" ? "**" : term)
        stack.push(a.method(op).call(b))
        debug "#{term}\t#{Operators[term].english}\t#{stack}"
      else
        begin
          number = Integer(term) rescue Float(term)
        rescue ArgumentError
          raise ArgumentError, "cannot handle term: #{term}"
        end
        stack.push(number)
        debug "#{number}\tPUSH\t#{stack}"
      end
    end
    @value = stack.pop
    debug "Value = #@value"
    @value
  end

  private
  # convert an RPN expression into an AST
  def to_infix_tree
    return @infix_tree unless @infix_tree.nil?

    debug "\nfor RPN expression: #{expression}\nTerm\tAction\tStack"
    stack = []
    expression.split.each do |term|
      if Operators.has_key?(term)
        a, b = stack.pop(2)
        raise ArgumentError, "not enough operands on the stack" if b.nil?
        op = InfixNode.new(term)
        op.left = a
        op.right = b
        stack.push(op)
        debug "#{term}\t#{Operators[term].english}\t#{stack.inspect}"
      else
        begin
          Integer(term) rescue Float(term)
        rescue ArgumentError
          raise ArgumentError, "cannot handle term: #{term}"
        end
        stack.push(InfixNode.new(term))
        debug "#{term}\tPUSH\t#{stack.inspect}"
      end
    end
    @infix_tree = stack.pop
  end

  public
  # express the AST as a string
  def to_infix
    expr = to_infix_tree.to_s
    debug "Infix = #{expr}"
    expr
  end

  # express the AST as a string, but in a form that allows Ruby to evaluate it
  def to_ruby
    expr = to_infix_tree.to_ruby
    debug "Ruby = #{expr}"
    expr
  end

  def to_s
    expression
  end


  private
  class InfixNode
    def initialize(value)
      @value = value
      @left = nil
      @right = nil
    end
    attr_reader :value
    attr_accessor :left, :right

    def leaf?
      left.nil? and right.nil?
    end

    def to_s;    to_string(false); end
    def to_ruby; to_string(true);  end 

    def to_string(to_ruby)
      result = []
      result << display_child(left, to_ruby, (to_ruby and value == "/"))
      result << (to_ruby ? Operators[value].ruby_operator : value)
      result << display_child(right, to_ruby)
      result.join(" ")
    end

    def display_child(child, to_ruby, need_float = false)
      result = if child.leaf?
                 child.value
               elsif Operators[child.value].precedence < Operators[value].precedence
                 "( #{child.to_string(to_ruby)} )"
               else
                 child.to_string(to_ruby)
               end
      result += ".to_f" if need_float
      result
    end

    def inspect
      str = "node[#{value}]"
      str << "<left=#{left.inspect}, right=#{right.inspect}>" unless leaf?
      str
    end
  end
end

def debug(msg)
  puts msg if $DEBUG
end


require 'test/unit'
class TestRPNExpression < Test::Unit::TestCase
  def setup
    @rpn_expr = "3 4 2 * 1 5 - 2 3 ^ ^ / +"
    @infix_expr = "3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3"
    @ruby_expr = "3 + 4 * 2.to_f / ( 1 - 5 ) ** 2 ** 3"
    @value = 3.0001220703125 
  end

  def test_eval
    rpn = RPNExpression.new @rpn_expr
    value = rpn.eval
    assert_equal @value, value
  end

  def test_rpn_to_infix
    rpn = RPNExpression.new @rpn_expr
    infix = rpn.to_infix
    assert_equal @infix_expr, infix
    assert_equal @value, eval(rpn.to_ruby)
  end

  def test_infix_to_rpn
    rpn = RPNExpression.from_infix @infix_expr
    assert_equal @rpn_expr, rpn.to_s
  end

  def test_other_expressions
    old_debug = $DEBUG
    $DEBUG = false

    rpn =   ["56 34 213.7 + * 678 -",     "1 56 35 + 16 9 - / +"]
    infix = ["56 * ( 34 + 213.7 ) - 678", "1 + ( 56 + 35 ) / ( 16 - 9 )"]
    value = ["13193.2",                   "14.0"]

    [0, 1].each do |idx|
      obj = RPNExpression.new rpn[idx]
      assert_equal value[idx], "%.1f" % obj.eval
      assert_equal infix[idx], obj.to_infix
      assert_equal value[idx], "%.1f" % eval(obj.to_ruby)
      obj = RPNExpression.from_infix infix[idx]
      assert_equal rpn[idx], obj.to_s
    end
    $DEBUG = old_debug 
  end
end

Running with $DEBUG on (ruby -d rpn.rb) gives:

Run options: 

# Running tests:


for RPN expression: 3 4 2 * 1 5 - 2 3 ^ ^ / +
Term	Action	Stack
3	PUSH	[3]
4	PUSH	[3, 4]
2	PUSH	[3, 4, 2]
*	MUL	[3, 8]
1	PUSH	[3, 8, 1]
5	PUSH	[3, 8, 1, 5]
-	SUB	[3, 8, -4]
2	PUSH	[3, 8, -4, 2]
3	PUSH	[3, 8, -4, 2, 3]
^	EXP	[3, 8, -4, 8]
^	EXP	[3, 8, 65536]
/	DIV	[3, 0.0001220703125]
+	ADD	[3.0001220703125]
Value = 3.0001220703125
.
for Infix expression: 3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3
Term	Action	Output	Stack
3	PUSH V	["3"]	[]
+	PUSH OP	["3"]	["+"]
4	PUSH V	["3", "4"]	["+"]
*	PUSH OP	["3", "4"]	["+", "*"]
2	PUSH V	["3", "4", "2"]	["+", "*"]
/	MUL	["3", "4", "2", "*"]	["+"]	* has higher precedence than /
/	PUSH OP	["3", "4", "2", "*"]	["+", "/"]
(	OPEN_P	["3", "4", "2", "*"]	["+", "/", "("]
1	PUSH V	["3", "4", "2", "*", "1"]	["+", "/", "("]
-	PUSH OP	["3", "4", "2", "*", "1"]	["+", "/", "(", "-"]
5	PUSH V	["3", "4", "2", "*", "1", "5"]	["+", "/", "(", "-"]
)	SUB	["3", "4", "2", "*", "1", "5", "-"]	["+", "/", "("]	unwinding parenthesis
)	CLOSE_P	["3", "4", "2", "*", "1", "5", "-"]	["+", "/"]
^	PUSH OP	["3", "4", "2", "*", "1", "5", "-"]	["+", "/", "^"]
2	PUSH V	["3", "4", "2", "*", "1", "5", "-", "2"]	["+", "/", "^"]
^	PUSH OP	["3", "4", "2", "*", "1", "5", "-", "2"]	["+", "/", "^", "^"]
3	PUSH V	["3", "4", "2", "*", "1", "5", "-", "2", "3"]	["+", "/", "^", "^"]
RPN = 3 4 2 * 1 5 - 2 3 ^ ^ / +
..
for RPN expression: 3 4 2 * 1 5 - 2 3 ^ ^ / +
Term	Action	Stack
3	PUSH	[node[3]]
4	PUSH	[node[3], node[4]]
2	PUSH	[node[3], node[4], node[2]]
*	MUL	[node[3], node[*]<left=node[4], right=node[2]>]
1	PUSH	[node[3], node[*]<left=node[4], right=node[2]>, node[1]]
5	PUSH	[node[3], node[*]<left=node[4], right=node[2]>, node[1], node[5]]
-	SUB	[node[3], node[*]<left=node[4], right=node[2]>, node[-]<left=node[1], right=node[5]>]
2	PUSH	[node[3], node[*]<left=node[4], right=node[2]>, node[-]<left=node[1], right=node[5]>, node[2]]
3	PUSH	[node[3], node[*]<left=node[4], right=node[2]>, node[-]<left=node[1], right=node[5]>, node[2], node[3]]
^	EXP	[node[3], node[*]<left=node[4], right=node[2]>, node[-]<left=node[1], right=node[5]>, node[^]<left=node[2], right=node[3]>]
^	EXP	[node[3], node[*]<left=node[4], right=node[2]>, node[^]<left=node[-]<left=node[1], right=node[5]>, right=node[^]<left=node[2], right=node[3]>>]
/	DIV	[node[3], node[/]<left=node[*]<left=node[4], right=node[2]>, right=node[^]<left=node[-]<left=node[1], right=node[5]>, right=node[^]<left=node[2], right=node[3]>>>]
+	ADD	[node[+]<left=node[3], right=node[/]<left=node[*]<left=node[4], right=node[2]>, right=node[^]<left=node[-]<left=node[1], right=node[5]>, right=node[^]<left=node[2], right=node[3]>>>>]
Infix = 3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3
Ruby = 3 + 4 * 2.to_f / ( 1 - 5 ) ** 2 ** 3
.

Finished tests in 0.012002s, 333.2831 tests/s, 999.8494 assertions/s.

4 tests, 12 assertions, 0 failures, 0 errors, 0 skips