Last active
March 21, 2020 15:18
-
-
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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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