Skip to content

Commit

Permalink
Add intial implementation of an RPN calculator.
Browse files Browse the repository at this point in the history
  • Loading branch information
damiendart committed Nov 7, 2023
1 parent 641b737 commit 32ecad0
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[{git-hometime,install,*.py}]
[{git-hometime,install,rpncalc,*.py}]
indent_size = 4
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ tasks:

lint:python:black:
cmds:
- '.venv/bin/black --check --diff install bin/git-hometime bin/markdown-tidy'
- '.venv/bin/black --check --diff install bin/git-hometime bin/markdown-tidy bin/rpncalc'
desc: 'Lint Python scripts with Black'

lint:python:flake8:
cmds:
- '.venv/bin/flake8 install bin/git-hometime bin/markdown-tidy'
- '.venv/bin/flake8 install bin/git-hometime bin/markdown-tidy bin/rpncalc'
desc: 'Lint Python scripts with Flake8'

lint:shell:
Expand Down
139 changes: 139 additions & 0 deletions bin/rpncalc
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)

0 comments on commit 32ecad0

Please sign in to comment.