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()
2 Answers 2
Your implementation of the ,
command, using input('> ')
, is non-standard. Standard Brainfuck shouldn't print a prompt, nor should it wait for Return to be pressed. Rather, it should just read one byte from sys.stdin
.
The condition
while self._src_ptr <= len(self._src)-1:
... would be more idiomatically written as
while self._src_ptr < len(self._src):
Similarly simplify if self._cell_ptr > len(self._cells)-1
.
What would you guess that
self._cell_value = 1
does? I find it deceptive that it increments the cell value by 1 (with wraparound) rather than setting the value. I recommend writing it as self._cell_value += 1
. Keep the wraparound logic in the setter, if you want.
Looks like fairly reasonable code to me.
In _clean(), rather than commands = '+-<>[],.#' if debug else '+-<>[],.'
, consider setting the value and then conditionally appending '#' if we're debugging. It's just easier to read, to avoid needing to visually diff them.
In _evaluate(), the giant if
is perfectly nice, but there is an opportunity to dispatch using a dict that maps from character to code, if you like. And this expression:
while self._src_ptr <= len(self._src)-1:
is less natural than a for
loop over the "good part" of _src
, perhaps using a slice.
EDIT: The expression is odd enough that I just mentally flagged it as "odd" and moved on, without being curious enough to consider its details. Thank you, 200_success, for pointing out that <=
is just trouble there. I saw the - 1
and thought that for command in self._src[:-1]:
was what was intended, but no, it's actually saying for command in self._src:
, just not in the obvious way. There should be one - and preferably only one -obvious way to do it.
-
\$\begingroup\$ Could you be more specific about what you mean by 'a
for
loop over the "good part" of_src
'? \$\endgroup\$200_success– 200_success2018年01月17日 03:57:01 +00:00Commented Jan 17, 2018 at 3:57
Explore related questions
See similar questions with these tags.
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\$