add tests, update readme, requirements, move into subdir
This commit is contained in:
57
fifth/README.md
Normal file
57
fifth/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Fifth
|
||||
|
||||
A simple stack-based language called Fifth
|
||||
|
||||
## Usage
|
||||
|
||||
```shell
|
||||
python fifth.py
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
- `PUSH <n>` - 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 3
|
||||
stack is [3]
|
||||
PUSH 11
|
||||
stack is [3, 11]
|
||||
+
|
||||
stack is [14]
|
||||
DUP
|
||||
stack is [14, 14]
|
||||
PUSH 2
|
||||
stack is [14, 14, 2]
|
||||
*
|
||||
stack is [14, 28]
|
||||
SWAP
|
||||
stack is [28, 14]
|
||||
/
|
||||
stack is [2]
|
||||
+
|
||||
ERROR: two numbers required
|
||||
POP
|
||||
stack is []
|
||||
EXIT
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
pip install pytest
|
||||
pytest -v test_fifth.py
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- pytest
|
||||
96
fifth/fifth.py
Normal file
96
fifth/fifth.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import operator
|
||||
import readline
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class FifthStack:
|
||||
def __init__(self):
|
||||
self.stack: list[int] = []
|
||||
self.commands: dict[str, Callable] = {
|
||||
"push": self.push,
|
||||
"pop": self.pop,
|
||||
"swap": self.swap,
|
||||
"dup": self.dup,
|
||||
}
|
||||
self.binary_ops: dict[str, Callable[[int, int], int]] = {
|
||||
"+": operator.add,
|
||||
"-": operator.sub,
|
||||
"*": operator.mul,
|
||||
"/": operator.floordiv,
|
||||
}
|
||||
|
||||
def _require(self, number, message):
|
||||
if len(self.stack) < number:
|
||||
print(f"ERROR: {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
|
||||
b, a = self.stack.pop(), self.stack.pop()
|
||||
try:
|
||||
self.stack.append(operator(a, b))
|
||||
except ZeroDivisionError as e:
|
||||
print(f"ERROR: division by zero")
|
||||
self.stack.extend([a, b])
|
||||
|
||||
def push(self, value):
|
||||
try:
|
||||
self.stack.append(int(value))
|
||||
except ValueError:
|
||||
print("ERROR: integer required")
|
||||
|
||||
def pop(self):
|
||||
if not self.stack:
|
||||
print("ERROR: stack empty")
|
||||
return
|
||||
self.stack.pop()
|
||||
|
||||
def swap(self):
|
||||
if not self._require(2, "two numbers required"):
|
||||
return
|
||||
self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
|
||||
|
||||
def dup(self):
|
||||
if self._require(1, "stack empty"):
|
||||
self.stack.append(self.stack[-1])
|
||||
|
||||
def execute(self, command: str):
|
||||
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)
|
||||
|
||||
if function:
|
||||
if command == "push":
|
||||
function(argument)
|
||||
else:
|
||||
function()
|
||||
elif command in self.binary_ops:
|
||||
self._binary_op(self._get_binary_op(command))
|
||||
else:
|
||||
print("ERROR: unknown command")
|
||||
|
||||
|
||||
def main():
|
||||
fifth = FifthStack()
|
||||
|
||||
while True:
|
||||
print(f"stack is {fifth.stack}")
|
||||
try:
|
||||
if (command := input().strip().lower()) == "exit":
|
||||
break
|
||||
if command:
|
||||
fifth.execute(command)
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
fifth/requirements.txt
Normal file
1
fifth/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
pytest==8.4.1
|
||||
166
fifth/test_fifth.py
Normal file
166
fifth/test_fifth.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import pytest
|
||||
from fifth import FifthStack
|
||||
|
||||
|
||||
# Test the functions
|
||||
def test_push_and_pop():
|
||||
stack = FifthStack()
|
||||
stack.push(10)
|
||||
stack.push(20)
|
||||
assert stack.stack == [10, 20]
|
||||
stack.pop()
|
||||
assert stack.stack == [10]
|
||||
stack.pop()
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_push_invalid_value(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push("abc")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: integer required" in captured.out
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_pop_empty_stack(capsys):
|
||||
stack = FifthStack()
|
||||
stack.pop()
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: stack empty" in captured.out
|
||||
|
||||
|
||||
def test_swap():
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.push(2)
|
||||
stack.swap()
|
||||
assert stack.stack == [2, 1]
|
||||
|
||||
|
||||
def test_swap_insufficient_elements(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.swap()
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: two numbers required" in captured.out
|
||||
assert stack.stack == [1]
|
||||
|
||||
|
||||
def test_dup():
|
||||
stack = FifthStack()
|
||||
stack.push(5)
|
||||
stack.dup()
|
||||
assert stack.stack == [5, 5]
|
||||
|
||||
|
||||
def test_dup_empty_stack(capsys):
|
||||
stack = FifthStack()
|
||||
stack.dup()
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: stack empty" in captured.out
|
||||
|
||||
|
||||
def test_addition():
|
||||
stack = FifthStack()
|
||||
stack.push(2)
|
||||
stack.push(3)
|
||||
stack.execute("+")
|
||||
assert stack.stack == [5]
|
||||
|
||||
|
||||
def test_subtraction():
|
||||
stack = FifthStack()
|
||||
stack.push(5)
|
||||
stack.push(2)
|
||||
stack.execute("-")
|
||||
assert stack.stack == [3]
|
||||
|
||||
|
||||
def test_multiplication():
|
||||
stack = FifthStack()
|
||||
stack.push(4)
|
||||
stack.push(3)
|
||||
stack.execute("*")
|
||||
assert stack.stack == [12]
|
||||
|
||||
|
||||
def test_division():
|
||||
stack = FifthStack()
|
||||
stack.push(8)
|
||||
stack.push(2)
|
||||
stack.execute("/")
|
||||
assert stack.stack == [4]
|
||||
|
||||
|
||||
def test_division_by_zero(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push(8)
|
||||
stack.push(0)
|
||||
stack.execute("/")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: division by zero" in captured.out
|
||||
assert stack.stack == [8, 0]
|
||||
|
||||
|
||||
def test_binary_op_insufficient_elements(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.execute("+")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: two numbers required" in captured.out
|
||||
assert stack.stack == [1]
|
||||
|
||||
|
||||
# Test executions
|
||||
def test_execute_unknown_command(capsys):
|
||||
stack = FifthStack()
|
||||
stack.execute("foobar")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: unknown command" in captured.out
|
||||
|
||||
|
||||
def test_execute_push_command():
|
||||
stack = FifthStack()
|
||||
stack.execute("push 42")
|
||||
assert stack.stack == [42]
|
||||
|
||||
|
||||
def test_execute_pop_command():
|
||||
stack = FifthStack()
|
||||
stack.push(99)
|
||||
stack.execute("pop")
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_execute_swap_command():
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.push(2)
|
||||
stack.execute("swap")
|
||||
assert stack.stack == [2, 1]
|
||||
|
||||
|
||||
def test_execute_dup_command():
|
||||
stack = FifthStack()
|
||||
stack.push(7)
|
||||
stack.execute("dup")
|
||||
assert stack.stack == [7, 7]
|
||||
|
||||
|
||||
# Test edge cases
|
||||
def test_execute_empty_command():
|
||||
stack = FifthStack()
|
||||
stack.execute("") # Should do nothing
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_execute_whitespace_command():
|
||||
stack = FifthStack()
|
||||
stack.execute(" ") # Should do nothing
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_case_insensitivity():
|
||||
stack = FifthStack()
|
||||
stack.execute("PUSH 42")
|
||||
assert stack.stack == [42]
|
||||
Reference in New Issue
Block a user