1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
# bf.py
#
# Brainf*ck interpreter demo
#
# BF instructions (symbols):
# + - increment value at the current pointer
# - - decrement value at the current pointer
# > - increment pointer
# < - decrement pointer
# , - input new byte value, store at the current pointer
# . - output the byte at the current pointer
# [] - evaluate value at current pointer, if nonzero, execute all statements in []'s and repeat
#
import pyparsing as pp
# define the basic parser
# define Literals for each symbol in the BF langauge
PLUS, MINUS, GT, LT, INP, OUT, LBRACK, RBRACK = pp.Literal.using_each("+-<>,.[]")
# use a pyparsing Forward for the recursive definition of an instruction that can
# itself contain instructions
instruction_expr = pp.Forward().set_name("instruction")
# define a LOOP expression for the instructions enclosed in brackets; use a
# pyparsing Group to wrap the instructions in a sub-list
LOOP = pp.Group(LBRACK + instruction_expr[...] + RBRACK)
# use '<<=' operator to insert expression definition into existing Forward
instruction_expr <<= PLUS | MINUS | GT | LT | INP | OUT | LOOP
program_expr = instruction_expr[...].set_name("program")
# ignore everything that is not a BF symbol
ignore_chars = pp.Word(pp.printables, exclude_chars="+-<>,.[]")
program_expr.ignore(ignore_chars)
class BFEngine:
"""
Brainf*ck execution environment, with a memory array and pointer.
"""
def __init__(self, memory_size: int = 1024):
self._ptr = 0
self._memory_size = memory_size
self._memory = [0] * self._memory_size
@property
def ptr(self):
return self._ptr
@ptr.setter
def ptr(self, value):
self._ptr = value % self._memory_size
@property
def at_ptr(self):
return self._memory[self._ptr]
@at_ptr.setter
def at_ptr(self, value):
self._memory[self._ptr] = value % 256
def output_value_at_ptr(self):
print(chr(self.at_ptr), end="")
def input_value(self):
input_char = input() or "\0"
self.at_ptr = ord(input_char[0])
def reset(self):
self._ptr = 0
self._memory[:] = [0] * self._memory_size
def dump_state(self):
for i in range(30):
print(f"{self._memory[i]:3d} ", end="")
print()
if self.ptr < 30:
print(f" {' ' * self.ptr}^")
# define executable classes for each instruction
class Instruction:
"""Abstract class for all instruction classes to implement."""
def __init__(self, tokens):
self.tokens = tokens
def execute(self, bf_engine: BFEngine):
raise NotImplementedError()
class IncrPtr(Instruction):
def execute(self, bf_engine: BFEngine):
bf_engine.ptr += 1
class DecrPtr(Instruction):
def execute(self, bf_engine: BFEngine):
bf_engine.ptr -= 1
class IncrPtrValue(Instruction):
def execute(self, bf_engine: BFEngine):
bf_engine.at_ptr += 1
class DecrPtrValue(Instruction):
def execute(self, bf_engine: BFEngine):
bf_engine.at_ptr -= 1
class OutputPtrValue(Instruction):
def execute(self, bf_engine: BFEngine):
bf_engine.output_value_at_ptr()
class InputPtrValue(Instruction):
def execute(self, bf_engine: BFEngine):
bf_engine.input_value()
class RunInstructionLoop(Instruction):
def __init__(self, tokens):
super().__init__(tokens)
self.instructions = self.tokens[0][1:-1]
def execute(self, bf_engine: BFEngine):
while bf_engine.at_ptr:
for i in self.instructions:
i.execute(bf_engine)
# add parse actions to all BF instruction expressions
PLUS.add_parse_action(IncrPtrValue)
MINUS.add_parse_action(DecrPtrValue)
GT.add_parse_action(IncrPtr)
LT.add_parse_action(DecrPtr)
OUT.add_parse_action(OutputPtrValue)
INP.add_parse_action(InputPtrValue)
LOOP.add_parse_action(RunInstructionLoop)
@program_expr.add_parse_action
def run_program(tokens):
bf = BFEngine()
for t in tokens:
t.execute(bf)
print()
# generate railroad diagram
program_expr.create_diagram("bf.html")
# execute an example BF program
hw = "+[-->-[>>+>-----<<]<--<---]>-.>>>+.>>..+++[.>]<<<<.+++.------.<<-.>>>>+."
program_expr.parse_string(hw)
|