Skip to content

Instantly share code, notes, and snippets.

@milnak
Last active October 11, 2024 16:39
Show Gist options
  • Save milnak/dd827ae683379dcd3185a6c82065e48c to your computer and use it in GitHub Desktop.
Save milnak/dd827ae683379dcd3185a6c82065e48c to your computer and use it in GitHub Desktop.
A surprisingly capable RPN calculator in about 100 lines of Python code
# Based on:
# A surprisingly capable RPN calculator in about 100 lines of Zig code
# https://cryptocode.github.io/blog/docs/prefix-calculator
# Example session:
#
# Python RPN Calculator
# Type mc to clear memory slots, exit to quit.
# # see if 2 + 2 is still 4
# # mc
# 2 2 +
# M0 = 4.0
# # quadratic formula, we M0, M1 and M2 instead of a, b and c.
# mc
# 5
# M0 = 5.0
# 6
# M1 = 6.0
# 1
# M2 = 1.0
# # First solution
# M1 1 ~ * M1 2 ^ 4 M0 * M2 * - √ + 2 M0 * /
# M3 = -0.2
# # Second solution
# M1 1 ~ * M1 2 ^ 4 M0 * M2 * - √ - 2 M0 * /
# M4 = -1.0
# # Volume of a sphere
# mc
# 4.8
# M0 = 4.8
# 4 3 / 𝜋 * M0 3 ^ *
# M1 = 463.2466863277364
# # Average of numbers
# 1 2 3 4 5 6 7 8 9 10 avg
# M0 = 5.5
# # done!
# exit
import math
memslots = []
def main():
print("Python RPN Calculator")
print("Type mc to clear memory slots, exit to quit.")
while True:
line = input().strip()
# # comment
if len(line) == 0 or line[0] == "#":
continue
match line:
# exit to terminate the calculator
case "exit":
break
# mc to clear the memory slots
case "mc":
memslots.clear()
continue
try:
memslots.append(parse_line(line))
print(f"M{len(memslots)-1} = {memslots[-1]}")
except Exception as e:
print(e)
def parse_line(line: str) -> float:
"""Parses input line using a stack
Args:
line (str): RPN input line, e.g. "2 2 3 * +"
Raises:
Exception: If there was an error parsing line.
Returns:
float: top of stack after parsing.
"""
stack = []
for tok in line.split(" "):
match tok.lower():
# * / + - % Basic arithmetic/modulus
case "*":
stack.append(stack.pop() * stack.pop())
case "/":
val = stack.pop()
stack.append(stack.pop() / val)
case "+":
stack.append(stack.pop() + stack.pop())
case "-":
val = stack.pop()
stack.append(stack.pop() - val)
case "^":
val = stack.pop()
stack.append(pow(stack.pop(), val))
case "%":
val = stack.pop()
stack.append(stack.pop() % val)
# ~ Negation
case "~":
stack.append(-stack.pop())
# float
case c if "0" <= c <= "9" or c == ".":
try:
stack.append(float(tok))
except Exception:
raise Exception(f"Invalid character in floating point number {tok}")
# M... Memory reference, such as M0
case c if c[0] == "m":
index = int(tok[1:])
try:
stack.append(memslots[index])
except Exception:
raise Exception(f"Invalid memory memory slot index in {tok}")
# sin, cos, tan Trigonometric functions
case "sin":
stack.append(math.sin(stack.pop()))
case "cos":
stack.append(math.cos(stack.pop()))
case "tan":
stack.append(math.tan(stack.pop()))
# sqrt or √ Square root
case "sqrt" | "√":
stack.append(math.sqrt(stack.pop()))
# pi or 𝜋 Push 𝜋 to the stack
case "pi" | "𝜋":
stack.append(math.pi)
# e Push Euler’s number to the stack
case "e":
stack.append(math.e)
# avg Pop all numbers from the stack, push the average
case "avg":
avg = sum(stack) / len(stack)
stack.clear()
stack.append(avg)
case _:
raise Exception(f"Invalid operation {tok}")
# print(f"{tok=} {stack=}")
return stack[-1]
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment