-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add intial implementation of an RPN calculator.
- Loading branch information
1 parent
641b737
commit 32ecad0
Showing
3 changed files
with
142 additions
and
3 deletions.
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#!/usr/bin/env python3 | ||
""" | ||
A simple command-line Reverse Polish Notation calculator. | ||
Inspired by <https://johnlekberg.com/blog/2020-05-22-cli-rpn.html>. | ||
""" | ||
|
||
# This file was written by Damien Dart, <[email protected]>. This is | ||
# free and unencumbered software released into the public domain. For | ||
# more information, please refer to the accompanying "UNLICENCE" file. | ||
|
||
import decimal | ||
import inspect | ||
import readline | ||
|
||
|
||
class RPNCalculator: | ||
def __init__(self): | ||
self._stack = [] | ||
self._operations = {} | ||
|
||
def operation(self, code: str): | ||
def decorator(func): | ||
spec = inspect.getfullargspec(func) | ||
|
||
def op(*args): | ||
arity = len(spec.args) | ||
|
||
if len(self._stack) < arity: | ||
raise IndexError( | ||
f"{code!a} requires {arity} item{'s'[:arity^1]} on stack" # noqa: E501 | ||
) | ||
|
||
arguments = [self._stack.pop() for _ in range(arity)] | ||
result = func(*reversed(arguments)) | ||
|
||
self._stack.extend(result) | ||
|
||
self._operations[code] = op | ||
|
||
return func | ||
|
||
return decorator | ||
|
||
def keywords(self): | ||
return self._operations.keys() | ||
|
||
def execute(self, command: str) -> decimal.Decimal: | ||
for token in command.split(): | ||
if token in self._operations: | ||
self._operations[token]() | ||
else: | ||
try: | ||
token = decimal.Decimal(token) | ||
self._stack.append(token) | ||
except decimal.InvalidOperation: | ||
raise ValueError(f"unrecognised token: {token}") | ||
|
||
try: | ||
return self._stack[0] | ||
except IndexError: | ||
return | ||
|
||
|
||
def make_calculator() -> RPNCalculator: | ||
calculator = RPNCalculator() | ||
|
||
@calculator.operation("+") | ||
@calculator.operation("add") | ||
def add(x, y): | ||
return [x + y] | ||
|
||
@calculator.operation("-") | ||
@calculator.operation("sub") | ||
def subtract(x, y): | ||
return [x - y] | ||
|
||
@calculator.operation("*") | ||
@calculator.operation("mul") | ||
def multiply(x, y): | ||
return [x * y] | ||
|
||
@calculator.operation("/") | ||
@calculator.operation("div") | ||
def divide(x, y): | ||
return [x / y] | ||
|
||
@calculator.operation("**") | ||
@calculator.operation("pow") | ||
def pow(x, y): | ||
return [x**y] | ||
|
||
@calculator.operation("drop") | ||
def drop(x): | ||
return [] | ||
|
||
@calculator.operation("dup") | ||
def dup(x): | ||
return [x, x] | ||
|
||
@calculator.operation("swap") | ||
def swap(x, y): | ||
return [y, x] | ||
|
||
return calculator | ||
|
||
|
||
def make_completer(calculator): | ||
def complete(text, state): | ||
keywords = calculator.keywords() | ||
results = [x for x in keywords if x.startswith(text)] + [None] | ||
|
||
return results[state] + " " | ||
|
||
return complete | ||
|
||
|
||
if __name__ == "__main__": | ||
calculator = make_calculator() | ||
|
||
readline.parse_and_bind("tab: complete") | ||
readline.set_completer(make_completer(calculator)) | ||
|
||
while True: | ||
try: | ||
line = input("> ").strip() | ||
except (EOFError, KeyboardInterrupt): | ||
print() | ||
break | ||
|
||
try: | ||
result = calculator.execute(line) | ||
except decimal.DivisionByZero: | ||
print("ERROR: division by zero") | ||
except Exception as e: | ||
print("ERROR:", e) | ||
else: | ||
if result is not None: | ||
print(result) |