mamweb/brainfuck/interpreter.py

218 lines
7.4 KiB
Python

import sys
import re
import time
class Interpreter:
def __init__(self, commands_per_second=1_000_000, debug=False, comma_standart_input=True):
self.debug = debug
self.commands = ['+', '-', '>', '<', '.', ',', '[', ']']
self.mp = 0 # memory pointer
self.pc = 0 # program counter
self.data = [0] * 2
'''
chceme mít k dispozici i konec datového pole, (abychom mohli zavolat '<' na 0)
-> konec pole budeme mít jako self.data_end a kde jsme, poznáme podle self.is_at_end...
pak můžeme podle potřeby zvětšovat pole ze začátku nebo z konce
!poznámka autora: dalo by se také udělat s jedním polem, tak že bychom 'posouvali' index 0...
to mě ale nenapadlo, když jsem to psal, tak jsem to udělal takhle...
'''
self.data_end = [0] * 2 # tail is reversed
self.is_at_end = False
self.code = ""
self.commands_per_second = commands_per_second
self.MAX = 2**32 - 1
self.user_input = ""
self.user_input_index = 0
self.saved_output = ""
self.comma_standart_input = comma_standart_input
self.last_step = 0
def our_i32(self, value):
if value == -1:
return self.MAX
if value == self.MAX + 1:
return 0
return value
def load_code_from_input(self):
def expand_numbers(match):
count = int(match.group(1))
char = match.group(2)
return char * count
self.code = ""
code_filename = sys.argv[1]
with open(code_filename) as code_file:
for line in code_file:
line = line.strip()
line = line.split('//')[0].strip()
line = re.sub(r'(\d+)(.)', expand_numbers, line)
line = ''.join(filter(lambda c: c in self.commands, line))
self.code += line
def load_code_from_string(self, code):
def expand_numbers(match):
count = int(match.group(1))
char = match.group(2)
return char * count
self.code = ""
code = code.strip()
code = re.sub(r'(\d+)(.)', expand_numbers, code)
code = ''.join(filter(lambda c: c in self.commands, code))
self.code += code
def print_overwrite(self, message, prev_len=[0]):
sys.stdout.write('\r' + message + ' ' * max(prev_len[0] - len(message), 0))
sys.stdout.flush()
prev_len[0] = len(message)
def print_data(self, steps):
to_print = "step: " + str(steps) + " "
to_print += "mp: " + str(self.mp) + " | "
for i in range(len(self.data)):
to_print += str(self.data[i]) + " "
to_print += "|"
to_print += " end: | "
for i in range(len(self.data_end)).__reversed__():
to_print += str(self.data_end[i]) + " "
to_print += "|"
self.print_overwrite(to_print)
return to_print
def get_value_at_mp(self):
if not self.is_at_end:
return self.data[self.mp]
else:
return self.data_end[self.MAX - self.mp]
def set_value_at_mp(self, value):
if not self.is_at_end:
self.data[self.mp] = value
else:
self.data_end[self.MAX - self.mp] = value
def handle_plus(self):
self.set_value_at_mp(self.our_i32(self.get_value_at_mp() + 1))
def handle_minus(self):
self.set_value_at_mp(self.our_i32(self.get_value_at_mp() - 1))
def handle_greater(self):
temp = self.mp
self.mp = self.our_i32(self.mp + 1)
# kontrola, jesstli jsem ne přeskočil konec
if self.mp > self.MAX - len(self.data_end):
self.is_at_end = True
return
if temp > self.mp:
self.is_at_end = False
# rozšíření datového pole
if self.mp >= len(self.data):
self.data += [0] * (len(self.data))
def handle_less(self):
temp = self.mp
self.mp = self.our_i32(self.mp - 1)
# kontrola, jestli jsem ne přeskočil začátek
if self.mp < len(self.data):
self.is_at_end = False
return
if temp < self.mp:
self.is_at_end = True
if self.MAX - self.mp >= len(self.data_end):
self.data_end += [0] * (len(self.data_end))
def handle_dot(self):
sys.stdout.write(chr(self.get_value_at_mp()))
sys.stdout.flush()
self.saved_output += chr(self.get_value_at_mp())
def handle_comma(self):
if self.comma_standart_input:
input_char = sys.stdin.read(1)
else:
if self.user_input_index < len(self.user_input):
input_char = self.user_input[self.user_input_index]
self.user_input_index += 1
else:
input_char = None
if not input_char:
self.set_value_at_mp(0)
else:
self.set_value_at_mp(ord(input_char))
def handle_left_bracket(self):
if self.data[self.mp] == 0: # jump to the matching ]
open_brackets = 1
while open_brackets > 0:
self.pc += 1
if self.pc >= len(self.code):
raise SyntaxError("Unmatched \"[\"")
if self.code[self.pc] == "[":
open_brackets += 1
elif self.code[self.pc] == "]":
open_brackets -= 1
def handle_right_bracket(self):
if self.data[self.mp] != 0:
close_brackets = 1
while close_brackets > 0:
self.pc -= 1
if self.pc < 0:
raise SyntaxError("Unmatched \"]\"")
if self.code[self.pc] == "]":
close_brackets += 1
elif self.code[self.pc] == "[":
close_brackets -= 1
def execute_loaded_code(self):
steps = 0
print("|Executing code|")
while True:
if self.pc >= len(self.code):
break
com = self.code[self.pc]
if com == "+":
self.handle_plus()
elif com == "-":
self.handle_minus()
elif com == ">":
self.handle_greater()
elif com == "<":
self.handle_less()
elif com == ".":
self.handle_dot()
elif com == ",":
self.handle_comma()
elif com == "[":
self.handle_left_bracket()
elif com == "]":
self.handle_right_bracket()
# debug print
if self.debug:
self.print_data(steps)
time.sleep(1 / self.commands_per_second)
self.pc += 1
steps += 1
self.last_step = steps
print("\n|Execution done|")
'''
Usage:
python interpreter.py <code_file> < <user_input>
nastavte si DEBUG na True, pokud chcete vidět debugovací výstup, pak ale nebude správně fungovat "."...
nastavte si commands_per_second na 2, pokud chcete dva kroky za sekundu
'''
DEBUG = True
COMMANDS_PER_SECOND = 1000
if __name__ == "__main__":
interpreter = Interpreter(commands_per_second=COMMANDS_PER_SECOND, debug=DEBUG)
interpreter.load_code_from_input()
print("Loaded code:", interpreter.code, "length:", len(interpreter.code))
interpreter.execute_loaded_code()