#! /usr/bin/env python

"""
RPNit is a simple calculator based upon reverse polish notation.

(C)2001 Romain Guy
romain.guy@jext.org
www.jext.org
GNU/GPL Licensed

Although this program does not offer any interesting function, beginners should give it a look.
This calculator makes use of a list of classes, which all inherits from Operator, to perform
computations.

This program was my very first attempt of Python programming :-)
"""

##
## IMPORTS STATEMENTS
##
import math
import re
import sys

# OPERATOR CLASS
class Operator:
    "This class defines an operator for the calculator."

    def getName(self):
        "returns the operator itself"
        return ""

    def getOpsCount(self):
        "returns the number of requested operands"
        return 0

    def compute(self, ops):
        "performs computation and returns result"
        return None

##
## We may wonder if operands counts tests are really necessary
## since the main loop *always* do pass the requested amount
## of parameters to the compute() method
##

# COSINE
class CosOperator(Operator):
    def getName(self):
        return "cos"
    def getOpsCount(self):
        return 1
    def compute(self, ops):
        if len(ops) == 1:
            return math.cos(ops[0])
        else:
            return None

# DIVIDE
class DivideOperator(Operator):
    def getName(self):
        return "/"
    def getOpsCount(self):
        return 2
    def compute(self, ops):
        if len(ops) == 2:
            if ops[0] == 0:
                return None
            else:
                return ops[1] / ops[0]
        else:
            return None

# SUBSTRACTION
class MinusOperator(Operator):
    def getName(self):
        return "-"
    def getOpsCount(self):
        return 2
    def compute(self, ops):
        if len(ops) == 2:
            return ops[1] - ops[0]
        else:
            return None

# MULTIPLY
class MultiplyOperator(Operator):
    def getName(self):
        return "*"
    def getOpsCount(self):
        return 2
    def compute(self, ops):
        if len(ops) == 2:
            return ops[1] * ops[0]
        else:
            return None

# ADDITION
class PlusOperator(Operator):
    def getName(self):
        return "+"
    def getOpsCount(self):
        return 2
    def compute(self, ops):
        if len(ops) == 2:
            return ops[1] + ops[0]
        else:
            return None

# SINE
class SinOperator(Operator):
    def getName(self):
        return "sin"
    def getOpsCount(self):
        return 1
    def compute(self, ops):
        if len(ops) == 1:
            return math.sin(ops[0])
        else:
            return None

# grammar
_nb_ = "\d+"
_op_ = [CosOperator(), DivideOperator(), MinusOperator(), MultiplyOperator(), PlusOperator(), SinOperator()]

# performs the computation
def compute(line):
    # the stack
    stack = []
    # tokens
    tokens = line.split()
    # state
    error = 0

    # stack pushes
    for token in tokens:

        # if the token is a number
        if re.compile(_nb_).search(token):
            stack.append(float(token))
        else:

            # we check if the token is an operator
            for op in _op_:
                if op.getName() == token:
                    # it is a valid operator
                    opList = []

                    # we check if the stack contains enough elements
                    if len(stack) >= op.getOpsCount():
                        # we pops the requested amount of operands
                        for i in range(op.getOpsCount()):
                            opList.append(stack.pop())
                        # compute
                        result = op.compute(opList)
                    else:
                        result = None
                        print op.getOpsCount() - len(stack), "operand(s) is(are) missing"

                    # check result
                    if result:
                        stack.append(result)
                    else:
                        # error during calcul
                        print "Following operation caused an error:", token
                        error = 1
                    break
            else:
                # unknown operator
                print "Unknown keyword:", token
                error = 1

        if error:
            break
    else:
        print "Result:", stack.pop()

# main
def main():
    print "\n", sys.argv[0], "is an RPN computation program."
    print """
(C)2001 Romain Guy
Programmed using Python 2.1/IDLE GUI/Jext 2.12 from python.org and jext.org

Type 'exit' to quit.
"""

    # RPNit can perform calculs from an argument or
    # from the standard input pipe
    if len(sys.argv) < 2:
        line = ""
        while line != "exit\n":
            sys.stdout.write("> ")
            line = sys.stdin.readline()
            if line != "exit\n":
                compute(line)
    else:
        compute(sys.argv[1])

##
## MAIN ENTRY POINT
##
if __name__ == '__main__':
    main()

# End of rpnit.py
