#! /usr/bin/python3m

__author__ = 'Michael Aquilina'

"""
Simple script that is able to parse and solve input formulas from the command line.
This is performed by transforming the formula to postfix (Reverse-Polish) notation.
Contains support for all standard operators including:
* Multiplication
* Division
* Addition
* Subtraction
* Modulus
Ambiguous formulas are solved using standard BODMAS rules. Parentheses are also
supported to specify exact orderings.
"""

from string import whitespace

# List of operators along with their associated precedence
operators = {'+': 3, '-': 3, '%': 2, '*': 2, '/': 2, '(': 1, ')': 1}


def operation(v1, v2, operator):
    if operator == '+':
        return v1 + v2
    elif operator == '-':
        return v1 - v2
    elif operator == '*':
        return v1 * v2
    elif operator == '/':
        return float(v1 / v2)
    elif operator == '%':
        return v1 % v2
    else:
        raise ValueError('Unknown operator specified: {}'.format(operator))


def parse_formula(text):

    tokens = []
    buffer = ''

    for c in text:
        if '0' <= c <= '9' or c == '.':
            buffer += c
        elif c in operators:
            if buffer:
                tokens.append(float(buffer))
            tokens.append(c)
            buffer = ''
        elif c in whitespace and buffer:
            tokens.append(float(buffer))
            buffer = ''

    if buffer:
        tokens.append(float(buffer))

    return tokens


def calculate(formula):
    tokens = parse_formula(formula)

    operator_stack = []
    operand_stack = []

    for item in tokens:
        if type(item) is float:
            operand_stack.append(item)
        elif type(item) is str:

            if operator_stack:
                peek = operator_stack[-1]
            else:
                peek = None

            if item == ')':
                item = operator_stack.pop()

                while item != '(':
                    value2 = operand_stack.pop()
                    value1 = operand_stack.pop()

                    operand_stack.append(operation(value1, value2, item))

                    item = operator_stack.pop()

            elif peek is None or peek == '(' or operators[item] <= operators[peek]:
                operator_stack.append(item)
            else:
                value2 = operand_stack.pop()
                value1 = operand_stack.pop()

                operand_stack.append(operation(value1, value2, item))
        else:
            raise ValueError('Unknown item found in tokens')

    while operator_stack:
        item = operator_stack.pop()
        value2 = operand_stack.pop()
        value1 = operand_stack.pop()

        operand_stack.append(operation(value1, value2, item))

    return operand_stack.pop()


if __name__ == '__main__':

    import sys

    if len(sys.argv) < 2:
        print('Input formula required')
    else:
        text = ' '.join(sys.argv[1:])

        print(text)
        print(calculate(text))

@@ -48,20 +48,41 @@
 ilina'%0A%0A
-%22%22%22%0A
+__desc__ = %5C%0A    %22%22%22%0A    
 Simple s
@@ -157,16 +157,20 @@
 d line.%0A
+    
 This is 
@@ -245,16 +245,20 @@
 tation.%0A
+    
 Contains
@@ -304,16 +304,20 @@
 luding:%0A
+    
 * Multip
@@ -325,16 +325,20 @@
 ication%0A
+    
 * Divisi
@@ -340,16 +340,20 @@
 ivision%0A
+    
 * Additi
@@ -355,16 +355,20 @@
 ddition%0A
+    
 * Subtra
@@ -373,16 +373,20 @@
 raction%0A
+    
 * Modulu
@@ -387,16 +387,20 @@
 Modulus%0A
+    
 Ambiguou
@@ -471,16 +471,20 @@
 re also%0A
+    
 supporte
@@ -513,16 +513,20 @@
 erings.%0A
+    
 %22%22%22%0A%0Afro
@@ -2919,31 +2919,91 @@
 nt('
-Input formula required'
+Usage: ')%0A        print('    python calculate.py %3Cformula%3E')%0A        print(__desc__
 )%0A  

Print usage and tool description when no arguments passed