#!/usr/bin/python
# Author: mbomb007
# Date: Sept. 26, 2015
# Description: Interpret a Self-modifying Brainfuck program
# - Language created by Simon Howard <fraggle@removethisbit.gmail.com>
# References:
# - [https://]esolangs.org/wiki/Self-modifying_Brainfuck
# - [https://]soulsphere.org/hacks/smbf/
# Edits:
# 10/08/2015 - Try/Except in Tape.__getitem__
# - Implemented Tape.__len__
# 10/09/2015 - Fixed bug with pc where dynamically created code wouldn't execute.
# - Added debug print statments and dynamic code test
# 10/13/2015 - Fixed input not working at all
# 10/19/2015 - Changed error(objs) to error(*objs)
# - Added errors checks for bracket mismatches
# 03/03/2016 - Added MySTDIN for custom input. Works with non-printable ascii. by ZachGates.
# 12/23/2016 - Added debug option to record instruction execution
# 04/17/2017 - Updated Hello, World! example program to the golfed version.
# 07/31/2018 - Added EOF option
# 08/08/2018 - Fixed debug history not printing
from __future__ import print_function
import os, sys
import io
class Tape(bytearray):
def __init__(self):
self.data = bytearray(b'\0' * 1000)
self.center = len(self.data) // 2
self.end = 0
def __len__(self):
return len(self.data)
def __getitem__(self, index):
try:
return self.data[index + self.center]
except:
return 0
def __setitem__(self, index, val):
i = index + self.center
if i < 0 or i >= len(self.data):
# resize the data array to be large enough
new_size = len(self.data)
while True:
new_size *= 2
test_index = index + (new_size // 2)
if test_index >= 0 and test_index < new_size:
# array is big enough now
break
# generate the new array
new_data = bytearray(b'\0' * new_size)
new_center = new_size // 2
# copy old data into new array
for j in range(0, len(self.data)):
new_data[j - self.center + new_center] = self.data[j]
self.data = new_data
self.center = new_center
self.data[index + self.center] = val & 0xff
self.end = max(self.end, index+1)
class Interpreter():
def __init__(self, data, eof=None, debug=False):
self.tape = Tape()
# the value to write to the tape for end of file (EOF), or no change if None
self.eof = eof
# whether to keep execution history
self.debug = "" if debug else None
# copy the data into the tape
for i in range(0, len(data)):
self.tape[i - len(data)] = data[i]
# program start point
self.entrypoint = -len(data)
def call(self):
pc = self.entrypoint
ptr = 0
while pc < self.tape.end:
c = chr(self.tape[pc])
if self.debug != None:
self.debug += c
if c == '>':
ptr += 1
elif c == '<':
ptr -= 1
elif c == '+':
self.tape[ptr] += 1
elif c == '-':
self.tape[ptr] -= 1
elif c == '.':
print(chr(self.tape[ptr]), end="")
elif c == ',':
#self.tape[ptr] = ord(sys.stdin.read(1) or '\0')
inChar = sys.stdin.read(1)
if inChar:
self.tape[ptr] = ord(inChar)
else:
if self.eof != None:
self.tape[ptr] = self.eof
elif c == '[':
if self.tape[ptr] == 0:
# advance to end of loop
loop_level = 1
while loop_level > 0:
pc += 1
if pc > self.tape.end:
error("Bracket mismatch")
if chr(self.tape[pc]) == '[': loop_level += 1
elif chr(self.tape[pc]) == ']': loop_level -= 1
elif c == ']':
# rewind to the start of the loop
loop_level = 1
while loop_level > 0:
pc -= 1
if pc < -len(self.tape):
error("Bracket mismatch")
if chr(self.tape[pc]) == '[': loop_level -= 1
elif chr(self.tape[pc]) == ']': loop_level += 1
pc -= 1
pc += 1
def load_script(filename):
buf = bytearray(os.path.getsize(filename))
with open(filename, "rb") as f:
try:
f.readinto(buf)
return buf
except Exception as e:
error("Failed to load script file '%s'."%filename, e)
def error(*objs):
print(*objs, file=sys.stderr)
sys.exit(1)
# For testing with non-printable input
class MySTDIN(list):
def __init__(self, obj=''):
if not isinstance(obj, str):
raise TypeError("a string is required")
self._init(obj)
def __repr__(self):
return str(self)
def __str__(self):
return "".join(self)
def _init(self, obj=''):
for k in obj:
list.append(self, k)
def read(self, n=1):
retval = ""
while n and self:
retval += self.pop(0)
n -= 1
return retval
def main():
#L = len(sys.argv)
#if L < 2: error("Please provide a file to interpret.")
#if L >=2: data = load_script(sys.argv[1])
#if L > 3: sys.stdin = open(sys.argv[2])
# Override stdin. Helpful for non-printable input. Comment to use normal input.
#sys.stdin = MySTDIN("<[.<]")
#data = bytearray(b',[>,]')
data = bytearray(b'<[.<]\x00!dlroW ,olleH')
# I/O without buffer
#sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
#sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
intr = Interpreter(data, eof=0, debug=False)
#intr = Interpreter(data, eof=None, debug=False)
#intr = Interpreter(data, eof=-1, debug=False)
try:
intr.call()
finally:
sys.stdin = sys.__stdin__
if intr.debug:
print("\n\nHISTORY: " + intr.debug)
#print(intr.tape.data, end="")
#print(intr.tape.data.decode('ascii'), end="")
#print(intr.tape.data.decode('ascii').replace('\0','_').strip('_'), end="")
#print(bytearray(intr.tape.data.decode('ascii').strip('\0'),'ascii'), end="")
if __name__ == "__main__":
main()