commit 26aaabe6a7c4381dc06aa809a78d80297d8c2664 Author: James Greenwood Date: Sat Aug 30 11:06:09 2025 +0100 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..4bf3365 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Fifth + +A simple stack-based language called Fifth + +## Usage + +```shell +python fifth.py +``` + +### Commands + +- `PUSH ` - push integer onto stack +- `POP` - remove top element +- `SWAP` - swap top two elements +- `DUP` - duplicate top element +- `+`, `-`, `*`, `/` - arithmetic operations +- `EXIT` - quit + +### Example + +``` +stack is: [] +PUSH 10 +stack is: [10] +PUSH 5 +stack is: [10, 5] ++ +stack is: [15] +PUSH 45 +stack is: [15, 45] +/ +stack is: [3] +EXIT +``` + +## Tests + +```bash +python -m pytest test_stack_calculator.py +``` + +## Requirements + +- Python 3.10+ diff --git a/fifth.py b/fifth.py new file mode 100644 index 0000000..161415f --- /dev/null +++ b/fifth.py @@ -0,0 +1,101 @@ +import operator +import readline + +class FifthStack: + def __init__(self): + self.stack = [] + self.commands = { + 'push': self.push, + 'pop': self.pop, + 'swap': self.swap, + 'dup': self.dup, + '+': self.add, + '-': self.sub, + '*': self.mul, + '/': self.div, + } + self.binary_ops = { + '+': operator.add, + '-': operator.sub, + '*': operator.mul, + '/': operator.floordiv, + } + + def _require(self, number, message): + if len(self.stack) < number: + print(message) + return False + return True + + def _get_binary_op(self, command): + return self.binary_ops.get(command) + + def _binary_op(self, operator): + if not self._require(2, "two numbers required"): + return + a, b = self.stack.pop(), self.stack.pop() + try: + self.stack.append(operator(a, b)) + except Exception as e: + print(f"ERROR: {e}") + self.stack.extend([a, b]) + + def push(self, value): + try: + self.stack.append(int(value)) + except ValueError: + print("ERROR: integer required") + + def pop(self, _value): + if not self.stack: + print("ERROR: stack empty") + return + self.stack.pop() + + def swap(self, _value): + if not self._require(2, 'two numbers required'): + return + self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1] + + def dup(self, _value): + if self._require(1, 'stack empty'): + self.stack.append(self.stack[-1]) + + def add(self, _value): + self._binary_op(self._get_binary_op('+')) + + def sub(self, _value): + self._binary_op(self._get_binary_op('-')) + + def mul(self, _value): + self._binary_op(self._get_binary_op('*')) + + def div(self, _value): + self._binary_op(self._get_binary_op('/')) + + def execute(self, command): + tokens = command.lower().strip().split() + if not tokens: + return + + command, argument = tokens[0], tokens[1] if len(tokens) > 1 else None + function = self.commands.get(command) + + try: + if function: function(argument) + else: print("ERROR: unknown command") + except: + print("ERROR: invalid command") + +def main(): + fifth = FifthStack() + + while True: + print(f'stack is: {fifth.stack}') + command = input() + if command.lower() == 'exit': + break + fifth.execute(command) + +if __name__ == '__main__': + main()