├── README.md ├── helloworld.bf └── pyfuck.py /README.md: -------------------------------------------------------------------------------- 1 | # PyFuck 2 | Command-line brainfuck interpreter written in Python 3 3 | If you have any suggestions or improvements, please go ahead! 4 | 5 | Any program executed may access up to 30,000 cells. 6 | 7 | ## Usage 8 | ``` 9 | python3 brainfuck.py [-d] file_name 10 | 11 | -d Display the data array once execution is complete. 12 | ``` 13 | 14 | ## Example 15 | ``` 16 | $ python3 brainfuck.py -d helloworld.bf 17 | Hello World! 18 | Array: [0, 0, 72, 100, 87, 33, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 19 | ``` 20 | ## Brainfuck Instructions 21 | | Character | Meaning | 22 | |-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 23 | | **>** | increment the data pointer (to point to the next cell to the right). | 24 | | **<** | decrement the data pointer (to point to the next cell to the left). | 25 | | **+** | increment (increase by one) the byte at the data pointer. | 26 | | **-** | decrement (decrease by one) the byte at the data pointer. | 27 | | **.** | output the byte at the data pointer. | 28 | | **,** | accept one byte of input, storing its value in the byte at the data pointer. | 29 | | **\[** | if the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next command, jump it forward to the command after the matching ] command. | 30 | | **]** | if the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the next command, jump it back to the command after the matching \[ command. | 31 | -------------------------------------------------------------------------------- /helloworld.bf: -------------------------------------------------------------------------------- 1 | ++++++++ Set Cell #0 to 8 2 | [ 3 | >++++ Add 4 to Cell #1; this will always set Cell #1 to 4 4 | [ as the cell will be cleared by the loop 5 | >++ Add 2 to Cell #2 6 | >+++ Add 3 to Cell #3 7 | >+++ Add 3 to Cell #4 8 | >+ Add 1 to Cell #5 9 | <<<<- Decrement the loop counter in Cell #1 10 | ] Loop till Cell #1 is zero; number of iterations is 4 11 | >+ Add 1 to Cell #2 12 | >+ Add 1 to Cell #3 13 | >- Subtract 1 from Cell #4 14 | >>+ Add 1 to Cell #6 15 | [<] Move back to the first zero cell you find; this will 16 | be Cell #1 which was cleared by the previous loop 17 | <- Decrement the loop Counter in Cell #0 18 | ] Loop till Cell #0 is zero; number of iterations is 8 19 | 20 | The result of this is: 21 | Cell No : 0 1 2 3 4 5 6 22 | Contents: 0 0 72 104 88 32 8 23 | Pointer : ^ 24 | 25 | >>. Cell #2 has value 72 which is 'H' 26 | >---. Subtract 3 from Cell #3 to get 101 which is 'e' 27 | +++++++..+++. Likewise for 'llo' from Cell #3 28 | >>. Cell #5 is 32 for the space 29 | <-. Subtract 1 from Cell #4 for 87 to give a 'W' 30 | <. Cell #3 was set to 'o' from the end of 'Hello' 31 | +++.------.--------. Cell #3 for 'rl' and 'd' 32 | >>+. Add 1 to Cell #5 gives us an exclamation point 33 | >++. And finally a newline from Cell #6 34 | -------------------------------------------------------------------------------- /pyfuck.py: -------------------------------------------------------------------------------- 1 | import re 2 | import argparse 3 | 4 | MAPPINGS = { 5 | '+': 'add', 6 | '-': 'subtract', 7 | '>': 'increment_pointer', 8 | '<': 'decrement_pointer', 9 | '.': 'output', 10 | ',': 'input', 11 | '[': 'skip_open', 12 | ']': 'skip_close' 13 | } 14 | 15 | 16 | class Brainfuck: 17 | """Provides methods to interface with the data array. 18 | Each method corresponds to a brainfuck instruction. 19 | """ 20 | 21 | def __init__(self, file_name, show_array=False): 22 | """Creates 30,000 cells, initialised to 0.""" 23 | 24 | self.data = [0 for i in range(30000)] 25 | self.data_pointer = 0 26 | self.instruction_pointer = 0 27 | self.program = '' 28 | self.show_array = show_array 29 | 30 | with open(file_name, 'r') as f: 31 | program = f.read() 32 | self.program = re.sub('[^\+\-<>.,\[\]]', '', program) 33 | 34 | self.parens = self.find_pairs(self.program) 35 | self.invparens = {v: k for k, v in self.parens.items()} 36 | 37 | def run(self): 38 | """Main loop translating each instruction character""" 39 | 40 | while self.instruction_pointer < len(self.program): 41 | func = getattr(self, 42 | MAPPINGS.get(self.program[self.instruction_pointer])) 43 | if func: 44 | func() 45 | self.instruction_pointer += 1 46 | 47 | if self.show_array: 48 | print("Array:", self.data[:self.program.count('>')]) 49 | 50 | def find_pairs(self, program): 51 | """Returns a dict object containing the start and ends 52 | of each set of square brackets. 53 | """ 54 | pairs = {} 55 | stack = [] 56 | 57 | for i, c in enumerate(program): 58 | if c == '[': 59 | stack.append(i) 60 | elif c == ']': 61 | if len(stack) == 0: 62 | raise IndexError("No matching closing bracket for %i" % i) 63 | pairs[stack.pop()] = i 64 | 65 | if len(stack) > 0: 66 | raise IndexError("No matching opening bracket for %i" % i) 67 | 68 | return pairs 69 | 70 | def add(self): 71 | """Increments the current cell by 1.""" 72 | self.data[self.data_pointer] += 1 73 | 74 | def subtract(self): 75 | """Decrements the current cell by 1.""" 76 | self.data[self.data_pointer] -= 1 77 | 78 | def increment_pointer(self): 79 | """Moves pointer to the next cell.""" 80 | if self.data_pointer < 30000: 81 | self.data_pointer += 1 82 | 83 | def decrement_pointer(self): 84 | """Moves pointer to the previous cell.""" 85 | if self.data_pointer > 0: 86 | self.data_pointer -= 1 87 | 88 | def output(self): 89 | """Outputs the ASCII representation of the value in the current cell. 90 | """ 91 | print(chr(self.data[self.data_pointer]), end='') 92 | 93 | def input(self): 94 | """Allows for integer input.""" 95 | valid = False 96 | 97 | while True: 98 | data = int(input("> ")) 99 | if 0 <= data <= 127: 100 | break 101 | print("Input must be an integer less than 127.") 102 | 103 | self.data[self.data_pointer] = data 104 | 105 | def skip_open(self): 106 | """Skips to the corresponding closing square bracket if the current 107 | cell is 0.""" 108 | if self.data[self.data_pointer] == 0: 109 | self.instruction_pointer = self.parens[self.instruction_pointer] 110 | 111 | def skip_close(self): 112 | """Skips to the corresponding open square bracket if the current cell 113 | is not 0.""" 114 | if self.data[self.data_pointer] != 0: 115 | self.instruction_pointer = self.invparens[self.instruction_pointer] 116 | 117 | 118 | parser = argparse.ArgumentParser('Execute a brainfuck program.') 119 | parser.add_argument('file_name', metavar='file', type=str, 120 | help='Name of brainfuck program.') 121 | parser.add_argument('-d', dest='show_array', action='store_true', 122 | help='Show data array at the end of execution.') 123 | 124 | if __name__ == "__main__": 125 | args = parser.parse_args() 126 | 127 | bf = Brainfuck(args.file_name, show_array=args.show_array) 128 | bf.run() 129 | --------------------------------------------------------------------------------