Skip to content

Commit 1b3b9ca

Browse files
authored
Merge pull request #1 from dluman/stack-and-pointer
Stack
2 parents 4ec0bb9 + b853eb6 commit 1b3b9ca

File tree

10 files changed

+134
-18
lines changed

10 files changed

+134
-18
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,6 @@ cython_debug/
160160
# and can be added to the global gitignore or merged into this file. For a more nuclear
161161
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162162
#.idea/
163+
164+
# SWP files, BEGONE!
165+
*.swp

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,21 @@ This project is available via `PyPI`: `python -m pip install paperpc`.
2525
|`8xx` |`BRP` |`BRANCH IF POSITIVE`| Verifies `Accumulator` value is greater than `0`; if so, set `Program Counter` to value `xx`, prepare to execute value in `xx` |`No` |
2626
|`901` |`INP` |`INPUT` |Read a single value fromw waiting input, replace `Accumulator` value|`Yes` |
2727
|`902` |`OUT` |`OUTPUT` |Output the current value of the `Accumulator`|`No` |
28-
|`000` |`HLT` |`HALT` |Terminates program |
28+
|`903` |`PSH` |`PUSH` |Push a value to the machine's dedicated stack (080) |`No` |
29+
|`904` |`POP` |`POP` |Pop a value from the machine's dedicated stack |`Yes` |
30+
|`905` |`PTR` |`STACK POINTER`|Retrieves and loads the current stack pointer to the `Accumulator`|`Yes` |
31+
|`906` |`SHI` |`STACK HEIGHT` |Calculates and places the current stack height in the `Accumulator`|`Yes` |
32+
|`000` |`HLT` |`HALT` |Terminates program |`No` |
33+
34+
### The stack
35+
36+
The PaperPC stack starts at storage `080`. It occupies 18 spaces (limit `098`). This memory range may be used by
37+
general heap operations (i.e. values can be assigned to this range). However, any stack operation will overwrite
38+
values in the range starting at the stack pointer base, `080`. The stack overflows if the value of the stack pointer
39+
increments above `098`.
40+
41+
In future releases, this will be configurable (e.g. a configuration file can remap, increase, or decrease the stack
42+
size). This is planned, but not yet implemented as a `.pcconfig` file, existing on a per-project basis.
2943

3044
## Using the program
3145

examples/.pcconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
storage:
2+
stack_size: 18
3+
stack_base: 80

examples/example.ppc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
1 901 @ Read one value from input to Accumulator
2+
2 360 @ Store in memory location 060
3+
3 901 @ Read one value from input to Accumulator
4+
4 648 @ Unconditional branch to data in 048
5+
48 160 @ Add data in 060 to Accumulator
6+
49 902 @ Output the sum stored in the Accumulator
7+
50 000 @ Halt

examples/stack.ppc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
1 901 @ Take an input
2+
2 903 @ Push it to the stack
3+
3 901 @ Take another input
4+
4 903 @ Push another to stack
5+
5 901 @ Take an input
6+
6 903 @ Push new value to stack
7+
7 901 @ Take in another value to overflow
8+
8 903 @ Push new value to stack
9+
9 905 @ Get the stack pointer value
10+
10 902 @ Output the value
11+
11 906 @ Calculate the stack height
12+
12 902 @ Output the value
13+
13 000 @ Halt

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ build-backend = "setuptools.build_meta:__legacy__"
66
name = "PaperPC"
77
version = "0.3.0"
88
dependencies = [
9-
"arglite"
9+
"arglite",
10+
"pyyaml"
1011
]
1112
authors = [
1213
{name = "Douglas Luman", email = "[email protected]"}

src/paperpc/cmd.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ def __init__(self, show_speed: bool = False):
7070
"8": {"cmd": self.__brp, "cycles": 2},
7171
"901": {"cmd": self.__inp, "cycles": 1},
7272
"902": {"cmd": self.__out, "cycles": 1},
73-
"0": {"cmd": self.__hlt, "cycles": 0}
73+
"903": {"cmd": self.__push, "cycles": 1},
74+
"904": {"cmd": self.__pop, "cycles": 2},
75+
"905": {"cmd": self.__ptr, "cycles": 2},
76+
"906": {"cmd": self.__shi, "cycles": 3},
77+
"000": {"cmd": self.__hlt, "cycles": 0},
78+
"0": {"cmd": self.__ptr, "cycles": 2}
7479
}
7580
self._show_speed = show_speed
7681
self._total_clock = 0
@@ -87,6 +92,11 @@ def parse(self, **kwargs):
8792
if self._arg == "9":
8893
self._arg = kwargs['arg']
8994
try:
95+
# Carve out specific escape for HALT instruction; the following
96+
# is always true
97+
if self._arg == "0" and self._val == 0:
98+
# The command must be a HALT instruction
99+
self._arg = "000"
90100
self._total_clock += self._syntax[self._arg]["cycles"]
91101
return self._syntax[self._arg]["cmd"]
92102
except:
@@ -131,6 +141,32 @@ def __brp(self, acc, storage):
131141
else:
132142
storage._counter += 1
133143

144+
@storage
145+
def __push(self, acc, storage):
146+
stack_len = len(storage.stack)
147+
storage.stack_ptr = storage.stack_base + stack_len
148+
storage.stack.append(acc.value)
149+
if storage.stack_ptr - storage.stack_base > storage.stack_size:
150+
print("[ERROR] Stack overflow!")
151+
sys.exit(1)
152+
storage._spaces[storage.stack_ptr] = acc.value
153+
154+
@storage
155+
def __pop(self, acc, storage):
156+
stack_len = len(storage.stack)
157+
stack_pos = storage.stack_base + stack_len
158+
storage._spaces[storage.stack_ptr] = "---"
159+
acc.value = int(storage.stack.pop())
160+
storage.stack_ptr -= 1
161+
162+
@storage
163+
def __ptr(self, acc, storage):
164+
acc.value = int(storage.stack_ptr)
165+
166+
@storage
167+
def __shi(self, acc, storage):
168+
acc.value = len(storage.stack)
169+
134170
@manipulate
135171
def __sft(self, acc, storage):
136172
self._val = str(self._val).zfill(2)
@@ -154,7 +190,6 @@ def __sft(self, acc, storage):
154190

155191
@inputs
156192
def __inp(self, acc, storage, input: int = 0):
157-
storage._expected_inputs += 1
158193
try:
159194
int(input)
160195
if input > 999:

src/paperpc/config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
import yaml
3+
import fnmatch
4+
5+
class Config:
6+
7+
def __init__(self):
8+
config_file = None
9+
for root, dirname, filenames in os.walk(os.getcwd()):
10+
for filename in fnmatch.filter(filenames, ".pcconfig"):
11+
config_file = f"{root}/{filename}"
12+
break
13+
try:
14+
with open(config_file, "r") as fh:
15+
config = yaml.safe_load(fh)
16+
self.__set_settings(config)
17+
except TypeError:
18+
# No config, so just move on
19+
pass
20+
21+
def __set_settings(self, config):
22+
for setting in config:
23+
setattr(self, setting, config[setting])

src/paperpc/main.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .parts import *
44
from .cmd import *
5+
from .config import *
56
from arglite import parser as cliarg
67
from itertools import islice
78
from rich.console import Console
@@ -20,7 +21,6 @@ def debug_log(acc, storage) -> None:
2021
console.print(f"ACC VALUE: {acc.value}")
2122

2223
def main() -> None:
23-
2424
# Load instruction set, crash out
2525
# if set does not exist as file
2626
try:
@@ -29,13 +29,17 @@ def main() -> None:
2929
print("Invalid source file.")
3030
sys.exit(1)
3131
with open(src, "r") as fh:
32+
# Allow for inconsistent lineation in program input
3233
data = [val.strip() for val in fh.readlines() if val.strip()]
3334

35+
# Load settings
36+
config = Config()
37+
3438
# Initialize accumulator
3539
acc = Accumulator()
3640

3741
# Set up storage for individual instructions
38-
storage = Storage(data)
42+
storage = Storage(data, config)
3943

4044
# Trigger debug output if debug flag set
4145
if cliarg.optional.debug:
@@ -49,8 +53,10 @@ def main() -> None:
4953
# comma-separated list
5054
inputs = Inputs(cliarg.optional.inputs)
5155
len_inputs = len(inputs._values)
56+
5257
# Step through instruction list, translate to
5358
# functions
59+
5460
while True:
5561

5662
cmd = commands.parse(
@@ -63,7 +69,7 @@ def main() -> None:
6369
if 'inputs' in arg_types:
6470
try:
6571
cmd(acc, storage, inputs._values.pop(0))
66-
except IndexError:
72+
except IndexError as e:
6773
# This is the last case to consider
6874
print(f"[ERROR] Reached end of inputs.")
6975
print(f" Expected:\t{storage._expected_inputs}")

src/paperpc/parts.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,36 @@
44

55
class Storage:
66

7-
def __init__(self, instructions):
7+
def __init__(self, instructions, config):
88
# Split into line numbers and instructions using the 2+ spaces
99
# rule to define separation between lines, instructions, comments
1010
self._counter = 1
11-
self._program = (
11+
self.stack = []
12+
self.stack_base = config.storage["stack_base"] or 80
13+
self.stack_size = config.storage["stack_size"] or 18
14+
self.stack_ptr = self.stack_base
15+
self._program = list(
1216
re.split(
1317
r"\s{2,}|\t{1,}",
1418
instruction
1519
) for instruction in instructions)
16-
self._expected_inputs = 0
20+
# Apparently, must initialize storage here; if we wait until the
21+
# end of the constructor, the program is...blank?
22+
self._expected_inputs = len([
23+
instruction for instruction
24+
in self._program if instruction[1] == "901"
25+
])
1726
self.__initialize_storage()
1827

1928
def __initialize_storage(self):
20-
# This implementation follows the accepted solution from SO:
21-
# https://stackoverflow.com/questions/5944708/how-can-i-automatically-limit-the-length-of-a-list-as-new-elements-are-added
29+
# This implementation follows the accepted solution from
30+
# SO question no. 5944708
2231
line = 1
23-
self._spaces = deque(maxlen=100)
32+
self._spaces = deque(maxlen = 100)
2433
for _ in range(100):
2534
self._spaces.append(None)
2635
for instruction in self._program:
2736
self._spaces[int(instruction[0])] = instruction[1]
28-
#if instruction[1] == "901":
29-
# self._expected_inputs += 1
3037
try:
3138
comment = str(instruction[2])
3239
if not comment.startswith("@"):
@@ -76,6 +83,10 @@ def value(self):
7683
class Inputs:
7784

7885
def __init__(self, inputs):
79-
if type(inputs) == int:
80-
inputs = [inputs]
81-
self._values = list(inputs)
86+
try:
87+
if type(inputs) == int:
88+
inputs = [inputs]
89+
self._values = list(inputs)
90+
except TypeError:
91+
print("[ERROR] Program expects inputs, but none given.")
92+
sys.exit(1)

0 commit comments

Comments
 (0)