[Tutor] A simple RPN calculator

Scot W. Stevenson scot@possum.in-berlin.de
Wed, 28 Aug 2002 06:50:39 +0200


Hello there, 

I had started a thread about Reverse Polish Notation calculators a while 
ago, and Gregor actually wrote one, and I did that smart-ass thing of 
coming back and saying, hey, really neat, Gregor, but how about the 
following functions...So I guess I owe him some code =8). 

Included is my attempt at a simple RPN calculator, any feedback of course 
would be most appreciated.

Y, Scot

============================================================
#!/usr/bin/env python
# Simple Reverse Polish Notation Calculator
# Scot W. Stevenson  28. August 2002
# For the Python Tutor Mailinglist

import cmd, sys

class rpn_calc(cmd.Cmd):
    """Simple RPN calculator"""

    def __init__(self, stacksize=4):
        self.stack = [0]*stacksize
        self.stacksize = len(self.stack)
        self.lastregister = self.stacksize-1
        self.intro='Simple RPN Calculator\nScot W. Stevenson 28. Aug 2002' 
        self.lastx = 0

        self.operations = { '+': self.do_add, 
                            '-': self.do_subtract,
                            '*': self.do_multiply,
                            '/': self.do_divide,
                            '^': self.do_power}

    # Helper functions

    def _stacklift(self, new_x):
        """Lift stack by one entry, last register is lost"""

        del self.stack[self.lastregister]
        self.stack.insert(0, new_x)

    def _stackdrop(self, new_x):
        """Drop stack by one entry, losing Y register entry, last register 
        is doubled"""

        self.stack.append(self.stack[self.lastregister])
        del self.stack[0]
        self.stack[0]=new_x

    # Catch numbers and operators

    def default(self, entry):
        """Catch numbers and operators and process them. If entry is
        neither number nor operator, ignore and pass on to cmd 
        loop."""

        # Catch numbers
        try:
            number = float(entry)
            self.lastx = self.stack[0]
            self._stacklift(number)
        except ValueError:
            pass

        # Catch operations
        if entry in self.operations:
            operation = self.operations[entry]
            operation()


    # Show X register after each command

    def postcmd(self, *dummy):
        """Display the contents of the X register after each
        command"""
        print " %f" % self.stack[0]


    # Calculator commands

    def do_add(self, dummy=None):
        result = self.stack[1] + self.stack[0]
        self._stackdrop(result)

    def do_clrx(self, rest):
        """Clear X register"""
        self.stack[0] = 0

    def do_divide(self, dummy=None):
        try:
            result = self.stack[2] / self.stack[0]
            self._stackdrop(result)
        except ZeroDivisionError:
            print "*** Division by Zero Error ***"

    def do_enter(self, dummy):
        """Perform a stack lift; last register value is lost, 
        first (X) register value is pushed into the second (Y) 
        register"""
        self._stacklift(self.stack[0])

    def emptyline(self, dummy=None):
        """An empty line is treated like hitting the ENTER key"""
        self.do_enter(None)

    def do_lastx(self, dummy):
        """Restore X register value from before the operation in the
        X register, performing a stack lift"""

        self._stacklift(self.lastx)

    def do_multiply(self, dummy=None):
        try: 
            result = self.stack[1] * self.stack[0]
            self._stackdrop(result)
        except OverflowError:
            print '*** Overflow Error ***'

    def do_power(self, dummy=None):
        try:
            result = pow(self.stack[1], self.stack[0])
            self._stackdrop(result)
        except OverflowError:
            print '*** Overflow Error ***'

    def do_print(self, rest):
        """Print stack. Mostly used for debugging"""
        for i in range(self.stacksize-1, -1, -1):
            print 'Reg %s: %f' % (i, self.stack[i])

    def do_quit(self, dummy):
        sys.exit()

    def do_rdown(self, dummy):
        """Roll down stack"""
        self.stack.append(self.stack[0])
        del self.stack[0]

    def do_rup(self, dummy):
        """Roll up stack"""
        self.stack.insert(0, self.stack[self.lastregister])
        del self.stack[self.lastregister+1]

    def do_subtract(self, dummy=None):
        result = self.stack[1] - self.stack[0]
        self._stackdrop(result)

    def do_xy(self, dummy):
        """Swap X and Y registers"""
        self.stack[0], self.stack[1] = self.stack[1], self.stack[0]


    # Help texts

    def help_add(self):
        print 'Add X and Y register. Use "+" key or "add" command'
    def help_clrx(self):
        print 'Clear X register'
    def help_divide(self):
        print 'Divide X by Y register. Use "/" key or "divide" command'
    def help_enter(self):
        print 'Push stack up by one, last register is lost'
    def help_help(self):
        print 'Prints list of commands'
    def help_lastx(self):
        print 'Retrieves value of the X register from before the last'
        print 'operation and pushes it in the X register, lifting the'
        print 'stack.'
    def help_multiply(self):
        print 'Multiply X by Y register. Use "*" key or "subtract" command'
    def help_power(self):
        print 'Take Y to the Xth power. Use "^" key or "power" command'
    def help_print(self):
        print 'Print stack. Used mostly for debugging'
    def help_rdown(self):
        print 'Rolls stack downwards'
    def help_rup(self):
        print 'Rolls stack upwards'
    def help_quit(self):
        print 'Quit program'
    def help_subtract(self):
        print 'Subtract X from Y register. Use "-" key or "subtract" cmd'
    def help_xy(self):
        print 'Swaps X and Y registers'


# Main loop 
if __name__ == '__main__':
    testcalc = rpn_calc()
    testcalc.cmdloop()