Last active
October 11, 2024 16:39
-
-
Save milnak/dd827ae683379dcd3185a6c82065e48c to your computer and use it in GitHub Desktop.
A surprisingly capable RPN calculator in about 100 lines of Python code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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