Skip to content

Instantly share code, notes, and snippets.

@fishi0x01
Last active March 21, 2020 15:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fishi0x01/c47cded22b3271f7e16e to your computer and use it in GitHub Desktop.
Save fishi0x01/c47cded22b3271f7e16e to your computer and use it in GitHub Desktop.
Python Brainfuck Interpreter. Code for blog post https://fishi.devtail.io/weblog/2015/06/15/another-python-bf-interpreter/
#!/usr/bin/env python3
"""
A simple python(3) bf interpreter.
First argument is .bf file with code to execute.
Optionally a file with input arguments can be specified as second argument.
"""
import sys
import re
# Exceptions
class SegFault(Exception):
def __init__(self,value):
self.value=value
def __str__(self):
return repr(self.value)
class InstructionFault(Exception):
def __init__(self,value):
self.value=value
def __str__(self):
return repr(self.value)
class SyntaxFault(Exception):
def __init__(self,value):
self.value = value
def __str__(self):
return repr(self.value)
# Brainfuck process
class BFProcess:
def __init__(self,code,args=None,mem_size=30000):
self.mem = [0]
self.args = args # input arguments
self.args_idx = 0
self.ptr = 0 # memory pointer
self.code = code # source code
self.idx = 0 # instruction pointer
self.mem_size = mem_size # maximum memory size
self.loop_forth_idx = {} # map with loop indices for forward jumps
self.loop_back_idx = {} # map with loop indices for backwards jumps
self.ops = {
'<': self.left,
'>': self.right,
'+': self.inc,
'-': self.dec,
'.': self.output,
',': self.getin,
'[': self.loop,
']': self.pool,
}
def execute(self):
self.parse()
while self.idx < len(self.code):
if self.idx < 0:
raise InstructionFault("Instruction pointer out of bounds: -1")
self.ops[self.code[self.idx]]()
self.idx += 1
# find matching brackets
def parse(self):
starts = []
for i,op in enumerate(code):
if op == "[":
starts.append(i)
elif op == "]":
try:
a = starts.pop()
self.loop_forth_idx[a] = i
self.loop_back_idx[i] = a
except IndexError:
raise SyntaxFault("Parse error at operation %d: No matching start loop '['"%(i))
if starts != []:
raise SyntaxFault("Parse error at operation %d: No matching end loop ']'"%(len(self.code)))
# core operations
#################
# '<'
def left(self):
if self.ptr == 0:
raise SegFault("At operation %d: Memory pointer out of bounds: %d"%(self.idx,self.ptr-1))
self.ptr -= 1
# '>'
def right(self):
self.ptr += 1
if len(self.mem) <= self.ptr:
if len(self.mem) >= self.mem_size:
raise SegFault("At operation %d: Memory pointer out of bounds: %d"%(self.idx,self.ptr+1))
self.mem.append(0)
# '+'
def inc(self):
# only one byte per cell, hence modulo 256
self.mem[self.ptr] = (self.mem[self.ptr] + 1) % 256
# '-'
def dec(self):
# only one byte per cell, hence modulo 256
self.mem[self.ptr] = (self.mem[self.ptr] - 1) % 256
# '.'
def output(self):
print(chr(self.mem[self.ptr]), end="")
# ','
def getin(self):
try:
self.mem[self.ptr] = ord(self.args[self.args_idx])
self.args_idx += 1
except Exception:
self.mem[self.ptr] = ord(input().strip())
# '['
def loop(self):
if self.mem[self.ptr] == 0:
self.idx = self.loop_forth_idx[self.idx]
# ']'
def pool(self):
self.idx = self.loop_back_idx[self.idx]
self.idx -= 1
# interpreter - main
if __name__ == '__main__':
if len(sys.argv) < 2:
raise InstructionFault("No source code provided")
# source code
with open(sys.argv[1]) as src:
# remove comments etc...
code = re.sub(r'[^<>.,\+\-\[\]]',"",src.read())
# input argument file (optional)
inp_args = None
if len(sys.argv) >= 3:
with open(sys.argv[2]) as inp:
inp_args = inp.read()
# build and execute bf context
p = BFProcess(code,args=inp_args)
p.execute()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment