I've wanted to make a calculator for a long time now, so I wrote one of my first parsers, where the input is in reverse polish notation, \$1 \ 1 +\$ becomes \$2\$.
It supports to following operators; +
, -
, *
, /
, //
floor division, ^
or **
exponent, and %
or mod
modulo.
While it's the best calculator I've written, the tokens are limited to only ones that take two operators. And I don't really like the idea of having a separate dictionary for ones that take more or less. This is a shame as now I can't add functions like sqrt
or sin
, or constants like pi
or e
, without having to have a different group for each.
Personally I dislike the error you get when you ^C
out of the program, and so I silenced it. I also made a new error CalculatorError
to not mask unknown errors, to easily exit out of the current equation if more than one function is called, make_pop
, and to have a human readable error message.
TOKENS = {
'+': lambda a, b: a + b,
'-': lambda a, b: a - b,
'*': lambda a, b: a * b,
'/': lambda a, b: a / b,
'^': lambda a, b: a ** b,
'**': lambda a, b: a ** b,
'%': lambda a, b: a % b,
'mod': lambda a, b: a % b,
'//': lambda a, b: a // b
}
class CalculatorError(Exception):
pass
def make_pop(stack):
def pop():
try:
return stack.pop()
except IndexError:
raise CalculatorError('Not enough arguments for function')
return pop
def call():
stack = []
pop = make_pop(stack)
buf = []
calculation = iter(input('> ') + ' ')
for char in calculation:
if char not in ' \t':
buf.append(char)
continue
token = ''.join(buf)
if not token:
continue
if token in TOKENS:
a = pop()
b = pop()
function = TOKENS[token]
try:
return_value = function(b, a)
except ZeroDivisionError:
raise CalculatorError("Can't devide by zero")
else:
stack.append(return_value)
else:
try:
token = float(token)
except ValueError:
raise CalculatorError('{!r} is not a number'.format(token))
stack.append(token)
buf = []
if len(stack) > 1 or buf:
raise CalculatorError('Invalid function, not enough operators.', stack, buf)
if stack[0] % 1:
print(stack[0])
else:
print(int(stack[0]))
def main():
while True:
try:
call()
except CalculatorError as e:
print(e)
if __name__ == '__main__':
print('Press ^C to exit')
try:
main()
except KeyboardInterrupt:
pass
# For some reason my Windows raises EOF on ^C...
except EOFError:
print()
print('Goodbye!')
Some example output is:
Press ^C to exit > 2 2 + 2 2 - / Can't devide by zero > 3 5 * 2 // 7 > 20 mod 11 Not enough arguments for function > 20 11 mod 9 > Goodbye!