-
Notifications
You must be signed in to change notification settings - Fork 0
/
brainfuck.py
158 lines (124 loc) · 4.03 KB
/
brainfuck.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# bloody dependencies
import sys
# defaults (NOTE: x & 255 == x % 256)
CELLMAX = 255
PTRMAX = 32767
ALPHABET = "><+-.,[]"
MAXFLOPS = 8192
class Monitor:
def __init__(self, maxflops: int):
self.flops = 0
self.maxflops = maxflops
def ping(self) -> bool:
self.flops += 1
ok = self.flops < self.maxflops
return ok
def match_brackets(code: str) -> dict:
"""Calculates dictionary of matching brackets."""
# build map
brackets = {}
mem = []
L = len(code)
_range = range(L)
for ptr in _range:
if code[ptr] == "[":
mem.append(ptr)
continue
if code[ptr] == "]":
assert len(mem) > 0, f"Unmatched close bracket ] at position {ptr}"
opener = mem.pop()
closer = ptr
brackets[opener] = closer
brackets[closer] = opener
# unmatched open
assert len(mem) == 0, f"Unmatched open bracket(s) [ at position(s) " + ",".join(map(str, mem))
# return dictionary
return brackets
def load(fname: str) -> str:
"""Opens a file and returns the contents."""
with open(fname, "r") as f:
code = f.read()
f.close()
return code
def run(code: str, input: str = "") -> str:
"""
Runs brainfuck code.
:param code: (string) Brainfuck code
:param input: (string, optional) Code input
:returns output: (string) Code output
"""
# clean
code = "".join([ char for char in code if char in ALPHABET ])
# initialize
cells = [0] * (PTRMAX + 1)
ptr, inptr, readr = 0, 0, 0
brackets = match_brackets(code)
output = ""
# monitor
monitor = Monitor(maxflops = MAXFLOPS)
# loop
L = len(code)
while readr < L:
# monitor
assert monitor.ping(), f"Error: Flops exceeded {monitor.maxflops}."
# command
cmd = code[readr]
readr += 1
# increment the data pointer (to point to the next cell to the right).
if cmd == ">":
ptr = (ptr+1) & PTRMAX
continue
# decrement the data pointer (to point to the next cell to the left).
if cmd == "<":
ptr = (ptr-1) & PTRMAX
continue
# increment (increase by one) the byte at the data pointer.
if cmd == "+":
cells[ptr] = (cells[ptr]+1) & CELLMAX
continue
# decrement (decrease by one) the byte at the data pointer.
if cmd == "-":
cells[ptr] = (cells[ptr]-1) & CELLMAX
continue
# output the byte at the data pointer.
if cmd == ".":
output += chr(cells[ptr])
continue
# accept one byte of input, storing its value in the byte at the data pointer.
if cmd == ",":
if inptr < len(input):
inchar = input[inptr]
inptr += 1
cells[ptr] = ord(inchar)
else:
# terminate program if run out of input
break
continue
# if the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next command, jump it forward to the command after the matching ] command.
if cmd == "[":
if cells[ptr] == 0:
readr = brackets[readr-1]
continue
# if the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the next command, jump it back to the command after the matching [ command.
if cmd == "]":
if cells[ptr] > 0:
readr = brackets[readr-1]
continue
# return
return output
def main():
L = len(sys.argv)
if L in [2, 3]:
# (required) file name
fname = sys.argv[1]
code = load(fname)
# (optional) input
input = ""
if L > 2:
input = sys.argv[2]
# zoomzoom!
output = run(code, input)
print(output)
else:
print("Usage:", sys.argv[0], "<filename> (<input>)")
if __name__ == "__main__": main()