I've been writing a brainfuck interpreter in python 3. It's nothing serious, but I'm trying to make it as good as possible (considering program structure, user experience and etc) for my own learning purpose. Any suggestion is welcomed.
"""A brainfuck interpreter written in Python 3.5."""
import argparse
__all__ = ['Brainfuck']
class Brainfuck:
"""Brainfuck interpreter main class."""
def __init__(self, src, debug=False):
"""
Parameters
----------
src: string
Brainfuck code to be evaluated.
debug: bool (optional, default: False)
Pause at '#' and print some status information.
Public attributes
-----------------
output: string
Output of the brainfuck script.
"""
self._cells = [0]
self._cell_ptr = 0
self._src = self._clean(src, debug)
self._src_ptr = 0
self._open_bracket_indexes = []
self._close_bracket_indexes = []
self._pair_brackets()
self.output = ''
self._evaluate()
@property
def _cell_value(self):
return self._cells[self._cell_ptr]
@_cell_value.setter
def _cell_value(self, value):
self._cells[self._cell_ptr] += value
if self._cells[self._cell_ptr] > 255:
self._cells[self._cell_ptr] = 0
elif self._cells[self._cell_ptr] < 0:
self._cells[self._cell_ptr] = 255
@property
def _command(self):
return self._src[self._src_ptr]
@staticmethod
def _clean(src, debug):
commands = '+-<>[],.#' if debug else '+-<>[],.'
return ''.join(c for c in src if c in commands)
def _pair_brackets(self):
"""
_open_bracket_indexes[i] is paired with _close_bracket_indexes[i].
"""
stack = []
for index, command in enumerate(self._src):
if command == '[':
stack.append(index)
elif command == ']':
self._open_bracket_indexes.append(stack.pop())
self._close_bracket_indexes.append(index)
def _evaluate(self):
while self._src_ptr <= len(self._src)-1:
command = self._command
if command == '+':
self._cell_value = 1
elif command == '-':
self._cell_value = -1
elif command == '<':
self._cell_ptr -= 1
elif command == '>':
self._cell_ptr += 1
if self._cell_ptr > len(self._cells)-1:
self._cells.append(0)
elif command == '[' and self._cell_value == 0:
self._to_paired_close_bracket()
elif command == ']' and self._cell_value != 0:
self._to_paired_open_bracket()
elif command == ',':
self._cell_value = ord(input('> ')[0])
elif command == '.':
new_char = chr(self._cell_value)
self.output += new_char
print(new_char, end='')
elif command == '#':
self._print_status()
self._src_ptr += 1
def _to_paired_open_bracket(self):
index = self._close_bracket_indexes.index(self._src_ptr)
self._src_ptr = self._open_bracket_indexes[index]
def _to_paired_close_bracket(self):
index = self._open_bracket_indexes.index(self._src_ptr)
self._src_ptr = self._close_bracket_indexes[index]
def _print_status(self):
cells_string = self._cells_string()
cell_ptr_string = self._cell_ptr_string(cells_string)
print('\n---------')
print('CELLS: {}'.format(cells_string))
print('POINTER: {}'.format(cell_ptr_string))
print('POSITION: {}'.format(self._cell_ptr))
print('---------')
input('[DEBUGGING] \'enter\' to continue')
def _cells_string(self):
"""
Transform cells array into a string.
Example:
array: [0, 87, 100, 33, 10]
string: |0| 87| 100| 33| 10|
"""
return (str(self._cells).replace('[', '|')
.replace(',', '|')
.replace(']', '|'))
def _cell_ptr_string(self, cells_string):
"""
Example:
_cell_ptr: 3
cells_string: |0| 87| 100| 33| 10|
cell_ptr_string: ^
"""
index = self._cell_ptr_index_on_cells_string(cells_string)
return ' ' * (index-1) + '^'
def _cell_ptr_index_on_cells_string(self, cells_string):
"""Calculate the index of '^' in cell_ptr_string."""
n = self._cell_ptr + 2
index = cells_string.find('|')
while n > 1 and index >= 0:
index = cells_string.find('|', index+1)
n -= 1
return index
def _parser():
p = argparse.ArgumentParser()
p.add_argument('file', help='brainfuck file to be evaluated')
p.add_argument('-d', '--debug',
action='store_true',
help='pause at \'#\' and print some status information')
return p.parse_args()
def _main():
args = _parser()
with open(args.file) as file:
src = file.read()
Brainfuck(src, args.debug)
if __name__ == '__main__':
_main()
command = self._command
is missing something. Is it supposed to becommand = self._command[self._src_ptr]
? \$\endgroup\$self._command
is a property, which returnsself._src[self._src_ptr]
. \$\endgroup\$