File: bf.py

package info (click to toggle)
pyparsing 3.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,928 kB
  • sloc: python: 25,633; ansic: 422; makefile: 22
file content (159 lines) | stat: -rw-r--r-- 4,248 bytes parent folder | download | duplicates (2)
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)