#! /usr/bin/env python3 # Simple Brainfuck REPL interpreter. Written by davidhcefx, 2024.5.25 from typing import List import io import readline from select import select import sys DEBUG_STEP = False MAX_INSTRUCTIONS = 2 ** 16 last_stdin_char = '\n' def interpret(code: str, stdin: io.TextIOWrapper, stdout: io.TextIOWrapper) -> List[int]: """ @brief Interpret code and I/O to stdin and stdout. @return The resulting data array (tape) after execution @throw SyntaxError when encountered invalid instructions or unmatching brackets @throw RuntimeError when number of instructions exceeded MAX_INSTRUCTIONS """ global last_stdin_char ins_cnt = 0 # number of executed instructions cod_idx = 0 # curent code index dat_idx = 2 # current read/write index dat_len = 5 # length of data array data = [0] * dat_len left_brackets = [] # stack of cod_idx of unmatched left brackets while cod_idx < len(code) and ins_cnt < MAX_INSTRUCTIONS: c = code[cod_idx] ins_cnt += 1 if DEBUG_STEP: print('#{}'.format(ins_cnt)) datastr = list(map(str, data)) datastr[dat_idx] = '-> ' + datastr[dat_idx] print('Tape: [{}]'.format(', '.join(datastr))) print('Code: {}'.format(code)) print(' {}^'.format(' ' * cod_idx)) if c in '><': dat_idx += 1 if c == '>' else -1 # grows data array twice as large if dat_idx < 0: data = ([0] * dat_len) + data dat_idx += dat_len dat_len += dat_len elif dat_idx >= dat_len: data = data + ([0] * dat_len) dat_len += dat_len elif c in '+-': data[dat_idx] += 1 if c == '+' else -1 elif c == '.': stdout.write(chr(data[dat_idx])) elif c == ',': last_stdin_char = stdin.read(1) data[dat_idx] = ord(last_stdin_char) elif c == '[': if data[dat_idx]: left_brackets.append(cod_idx) else: # goto end of matching right bracket (NOTE: this is suboptimal) old_cod_idx = cod_idx bk = 1 cod_idx += 1 while cod_idx < len(code) and bk > 0: if code[cod_idx] == '[': bk += 1 elif code[cod_idx] == ']': bk -= 1 cod_idx += 1 if bk > 0: raise SyntaxError('Unmatched opening bracket at index {}'.format(old_cod_idx)) continue elif c == ']': if len(left_brackets) == 0: raise SyntaxError('Unmatched closing bracket at index {}'.format(cod_idx)) if data[dat_idx]: # goto matching left bracket cod_idx = left_brackets.pop() continue else: left_brackets.pop() else: raise SyntaxError('Bad instruction "{}". Brainf**k only accepts "><+-.,[]"'.format(c)) cod_idx += 1 if ins_cnt >= MAX_INSTRUCTIONS: raise RuntimeError('Exceeding maximum number of instructions! ({})'.format(MAX_INSTRUCTIONS)) return data if __name__ == '__main__': for arg in sys.argv[1:]: if arg == '--debug-step': DEBUG_STEP = True print('Brainfuck REPL intepreter in Python!') while True: try: line = input('> ').strip() if line == '': # no input continue except EOFError: break try: tape = interpret(line, sys.stdin, sys.stdout) print('\nTape dump: {}'.format(tape)) # consume trailing newlines from input if any if last_stdin_char != '\n': sys.stdin.readline() last_stdin_char = '\n' except (SyntaxError, RuntimeError) as e: print('{}: {}'.format(type(e).__name__, e)) print('Bye!') # Sample program: # ,+. input a output b # ,[>+<--]>. input b output 1