├── CW - BF - visualizer.py └── README.md /CW - BF - visualizer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Created on Mon Oct 23 21:59:51 2017 5 | 6 | BrainFuck tape, pointer & output vizualizer 7 | 8 | @author: Blind4Basics - for CodeWars 9 | 10 | 11 | 12 | ----------------------------------------------------------------- 13 | 14 | Debugging commands usable in the BF code itself: 15 | 16 | '?' char in the code to choose the debugging points. 17 | You can name the check points with r'\w+' characters after the ?: 18 | "?printThere" 19 | '!' char to switch on/off the full debugging mode, meaning, print at the 20 | execution of each segment (see the interpreter notes below) 21 | 22 | 23 | Other global switches available: 24 | 25 | ALL: vizualisation at each step of the code (each segment) 26 | DEACTIVATE: force the deactivation of the vizualisation whatever is found in the code or the other switches are 27 | CHAR_MODE: if True, the tape will display ascii chars instead of numbers (Note: unprintable chars won't show up...) 28 | LIMITER: interrupt the executions after this number of printing 29 | 30 | 31 | ----------------------------------------------------------------- 32 | 33 | About the BF interpreter used: 34 | 35 | "Segments": 36 | 37 | The instructions are executed by "segments" of identical consecutive 38 | command characters other than control flow ones (meaning, only "+-<>"): 39 | 40 | '[->+<]' -> 6 segments 41 | '>>>>><<<' -> 2 segments 42 | '>> >> >' -> 3 segments 43 | 44 | 45 | Configuation data: 46 | 47 | ADD_EOF: Automatically adds an EOF char at the end of the input 48 | stream, if set to True (=default). 49 | RAISE_IF_NO_INPUT: Raise an exception of ',' is used while the input 50 | stream has already been consumed entirely. 51 | (default: False -> return \0 instead) 52 | LOW_TAPE_IDX: Raises an exception if the pointer goes below this value 53 | (default: 0) 54 | HIGH_TAPE_IDX: Raises an exception if the pointer goes above this value 55 | (default: infinity) 56 | 57 | 58 | 59 | 60 | """ 61 | 62 | 63 | 64 | import re 65 | 66 | def brainFuckInterpreter(code, prog): 67 | 68 | def updateVizu(cmdSegment=''): 69 | nonlocal countDisplay, lastP 70 | if DEACTIVATE: return 71 | 72 | def formatLst(lst, charMod=False): 73 | formStr = "{: >" + str(max(map(len, map(str, data)), default=1)) + "}" 74 | return "[{}]".format(', '.join(formStr.format(chr(v) if charMod else v) for v in lst)) 75 | 76 | def formatPointerLst(s): 77 | return s.translate(str.maketrans(',[]01',' *')) 78 | 79 | 80 | countDisplay += 1 # Update the number of display already done (cf. LIMITER) 81 | vizu[-1][lastP] = 0 # Erase the previous position of the pointer 82 | vizu[-1][p] = 1 # Place the pointer at the current position 83 | lastP = p # archive the current position of the pointer 84 | 85 | out = ''.join(output) 86 | tape,point = vizu 87 | print( "\n\n{}tape: {}\npointer: {}\nout = '{}'".format( 88 | cmdSegment and cmdSegment+"\n", 89 | formatLst(tape, CHAR_MODE), 90 | formatPointerLst(formatLst(point)), 91 | out 92 | )) 93 | if LIMITER >= 0 and LIMITER == countDisplay: raise Exception("Too much printing: LIMITER = {}".format(LIMITER)) 94 | 95 | 96 | def tapeLenUpdater(): # Make the tape length consistent with the actual position of the pointer (even if no value yet in the cells) 97 | if p < LOW_TAPE_IDX or p> HIGH_TAPE_IDX: 98 | raise Exception("out of tape: "+str(p)) 99 | 100 | if p >= len(data): 101 | data.extend( [0] * (p-len(data)+1) ) 102 | vizu[-1].extend( [0] * (len(data)-len(vizu[-1])) ) 103 | 104 | 105 | def getNextInput(): # Simulate getting u'0000' when trying to get an input char after their exhaustion 106 | try: 107 | return ord(next(prog)) 108 | except StopIteration: 109 | if RAISE_IF_NO_INPUT: raise Exception("Input stream empty...") 110 | return 0 111 | 112 | 113 | p, lastP, i = 0, 0, 0 # p = pointer / lastP = previous P position (mutated) / i = segment of code index 114 | data = [0] # Tape initialization 115 | 116 | SWITCH, countDisplay = False, 0 # SWITCH: control for the "!" cmd swtich / countDisplay = control for LIMITER (as list to mutate it from a subroutine) 117 | output, vizu = [], [data, [0]] # vizu: [cmd, tape, pointer list] 118 | 119 | prog = iter(prog) 120 | code = re.findall(r'\++|<+|>+|-+|[,.[\]]|\?\w*|!', code) # Make the executions more compact by using only segments of identical commands (=> '++++', '<<<', '[', '-', ']', check points with identifiers...) 121 | 122 | while 0 <= i < len(code): 123 | if p < 0: print(p) 124 | c = code[i] 125 | if False: print(c, data, p) # activate manually. Only for debugging of the vizualiser itself... 126 | 127 | if c[0] == '+': data[p] = (data[p] + len(c)) % 256 128 | elif c[0] == '-': data[p] = (data[p] - len(c)) % 256 129 | elif c[0] == '>': p += len(c) ; tapeLenUpdater() 130 | elif c[0] == '<': p -= len(c) ; tapeLenUpdater() 131 | elif c[0] == '.': output.append(chr(data[p])) 132 | elif c[0] == ',': data[p] = getNextInput() 133 | elif c[0] == '[': 134 | if not data[p]: 135 | depth = 1 136 | while depth > 0: 137 | i += 1 138 | c = code[i] 139 | if c == '[': depth += 1 140 | elif c== ']': depth -= 1 141 | elif c == ']': 142 | if data[p]: 143 | depth = 1 144 | while depth > 0: 145 | i -= 1 146 | c = code[i] 147 | if c == ']': depth += 1 148 | elif c == '[': depth -= 1 149 | 150 | 151 | # Vizualisation commands/executions 152 | #-------------------- 153 | if c[0] == '!': SWITCH = not SWITCH 154 | if c[0] == '?' or ALL or SWITCH: updateVizu(c) 155 | #-------------------- 156 | 157 | i += 1 158 | 159 | return ''.join(output) 160 | 161 | 162 | def runTests(inputs, exp, code): 163 | print('\n----------------------------------\nProgram:\n\n{}\n\n----------------------------------\n\n'.format(code)) 164 | 165 | EOF = chr(0)*ADD_EOF 166 | for p,e in zip(inputs,exp): 167 | print("Input: ", p) 168 | act = brainFuckInterpreter(code, p+EOF) 169 | 170 | print("Input: ", p) # reminder of the input 171 | print(repr(act), " should be ", repr(e)) # print actual/expected before assertion 172 | assert act == e 173 | 174 | print("SUCCESS\n---\n") 175 | 176 | 177 | 178 | #----------------------------------------------------------- 179 | 180 | 181 | 182 | code = """ 183 | [Your BF code here] 184 | 185 | >>,. ?WhatIs_Here 186 | [->>+>+<<<] 187 | ?TapeNow 188 | >>. 189 | ?WithOut 190 | """ 191 | 192 | #----------------------------------------------------------- 193 | 194 | """ GLOBAL SWITCHES """ 195 | 196 | ALL = False 197 | DEACTIVATE = False 198 | CHAR_MODE = False 199 | LIMITER = 80 200 | LOW_TAPE_IDX = 0 201 | HIGH_TAPE_IDX = float('inf') 202 | ADD_EOF = True 203 | RAISE_IF_NO_INPUT = False 204 | 205 | 206 | #------------------------------------------------------------ 207 | # Tests: 208 | # 209 | # 'inputs' and corresponding 'expected' values 210 | # EOF char automatically added at the end of each input 211 | #------------------------------------------------------------ 212 | 213 | inputs = ("aba", 'x') 214 | exps = ['aa', 'xx'] 215 | 216 | runTests(inputs, exps, code) 217 | 218 | 219 | 220 | """ 221 | Example of informations printed to stdout: 222 | 223 | ---------------------------------- 224 | Programme: 225 | 226 | 227 | [Your BF code here] 228 | 229 | >>,. ?WhatIs_Here 230 | [->>+>+<<<] 231 | ?TapeNow 232 | >>. 233 | ?WithOut 234 | 235 | 236 | ---------------------------------- 237 | 238 | 239 | Input: a 240 | 241 | 242 | ?WhatIs_Here 243 | tape: [ 0, 0, 97] 244 | pointer: * 245 | out = 'a' 246 | 247 | 248 | ?TapeNow 249 | tape: [ 0, 0, 0, 0, 97, 97] 250 | pointer: * 251 | out = 'a' 252 | 253 | 254 | ?WithOut 255 | tape: [ 0, 0, 0, 0, 97, 97] 256 | pointer: * 257 | out = 'aa' 258 | Input: a 259 | 'aa' should be 'aa' 260 | SUCCESS 261 | --- 262 | 263 | Input: x 264 | 265 | 266 | ?WhatIs_Here 267 | tape: [ 0, 0, 120] 268 | pointer: * 269 | out = 'x' 270 | 271 | 272 | ?TapeNow 273 | tape: [ 0, 0, 0, 0, 120, 120] 274 | pointer: * 275 | out = 'x' 276 | 277 | 278 | ?WithOut 279 | tape: [ 0, 0, 0, 0, 120, 120] 280 | pointer: * 281 | out = 'xx' 282 | Input: x 283 | 'xx' should be 'xx' 284 | SUCCESS 285 | --- 286 | 287 | """ 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brainfuck-Vizsualizer-for-python 2 | Used to debug BF codes; tape and pointer visualization, flags, check points, ... 3 | 4 | --------------------------------------------------------------------------------