#!/usr/bin/env python3 import enum from collections import namedtuple class writes(enum.Enum): zero, one, same = range(3) State = namedtuple('State', ['write', 'move', 'transitions']) class SFParseError(Exception): pass def parse_smallfuck(s): def parse(s, index = 0, in_loop = False): parsed = [] while True: assert index <= len(s) if index == len(s): if in_loop: raise SFParseError('Unexpected end of text (expected "]")') else: break c = s[index] index += 1 if c == ']': if in_loop: break else: raise SFParseError('Unexpected "]"') elif c == '[': loop, index = parse(s, index, True) parsed.append(loop) elif c in ['<', '>', '*']: parsed.append(c) return parsed, index parsed, index = parse(s) return parsed def chunk(parsed): def append_change_move(): nonlocal change, move, chunked while change or move != 0: chunk = '*' if change else '' change = False if move < 0: chunk += '<' move += 1 elif move > 0: chunk += '>' move -= 1 if chunk != '': chunked.append(chunk) chunked = [] change = False move = 0 for c in parsed: if type(c) == list: append_change_move() chunked.append(chunk(c)) elif c == '*': if move == 0: change = not change else: append_change_move() change = True elif c == '<': move -= 1 elif c == '>': move += 1 else: assert not "Unreachable" append_change_move() return chunked def turingify(chunked): states = [] def add_state(write, move, transitions): nonlocal states assert write in writes assert -1 <= move <= 1 assert len(transitions) == 2 index = len(states) states.append(State(write, move, transitions)) return index def worker(chunked, end = None, in_loop = False): nonlocal states ifunset = ifset = end while len(chunked) > 0: c = chunked[-1] chunked = chunked[:-1] if type(c) == list: loop_test = add_state(writes.same, 0, [ifunset, None]) loop_body = worker(c, loop_test, True) # We want the loop to repeat if the cell is set states[loop_test].transitions[1] = loop_body ifunset = ifset = loop_test else: change = '*' in c move = (1 if '>' in c else 0) - (1 if '<' in c else 0) assert change or move != 0 if change: # Create a pair of states new_ifunset = add_state(writes.one, move, [ifunset, ifset]) new_ifset = add_state(writes.zero, move, [ifunset, ifset]) ifunset, ifset = new_ifunset, new_ifset else: # Create only one state ifunset = ifset = add_state(writes.same, move, [ifunset, ifset]) if in_loop: # At the start of the loop body, the cell will always be set return ifset else: # At the start of the program, the cell will always be unset return ifunset return worker(chunked), states def prettyprint_states(states): for i in range(len(states)): state = states[i] write = {writes.zero: '0 ', writes.one: '1 ', writes.same: ''}[state.write] move = {-1: '< ', 0: '', 1: '> '}[state.move] print('%i: %s%s%s' % (i, write, move, state.transitions)) def tree_shake(start, states): # Since we always end up with start at index 0, no need to create shaken_start shaken = [states[start]] translations = {start: 0, None: None} shaken_sweep = 0 while shaken_sweep < len(shaken): for i in range(len(shaken[shaken_sweep].transitions)): index_states = shaken[shaken_sweep].transitions[i] if index_states not in translations: index_shaken = len(shaken) shaken.append(states[index_states]) translations[index_states] = index_shaken shaken[shaken_sweep].transitions[i] = translations[index_states] shaken_sweep += 1 return shaken def main(): program = input('program: ') start_nonshaken, states_nonshaken = turingify(chunk(parse_smallfuck(program))) shaken = tree_shake(start_nonshaken, states_nonshaken) prettyprint_states(shaken) if __name__ == '__main__': main()