From e510661d9c32cb2e52332daedf41894b782274a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Thu, 12 Jul 2018 16:17:50 +0300 Subject: [PATCH] Use a simple IR that should also produce better code --- sf2xed.py | 176 ++++++++++++++++++++++-------------------------------- 1 file changed, 73 insertions(+), 103 deletions(-) diff --git a/sf2xed.py b/sf2xed.py index 73930da..d15f190 100644 --- a/sf2xed.py +++ b/sf2xed.py @@ -2,10 +2,10 @@ import enum from collections import namedtuple -class writes(enum.Enum): - zero, one, same = range(3) +class actions(enum.Enum): + change, left, right, nothing = range(4) -State = namedtuple('State', ['write', 'move', 'transitions']) +State = namedtuple('State', ['action', 'transitions', 'reachable']) class SFParseError(Exception): pass @@ -41,138 +41,108 @@ def parse_smallfuck(s): 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): +def turingify(parsed): states = [] - def add_state(write, move, transitions): + def add_state(action, transitions): nonlocal states - assert write in writes - assert -1 <= move <= 1 + assert action in actions assert len(transitions) == 2 index = len(states) - states.append(State(write, move, transitions)) + states.append(State(action, transitions, [False, False])) return index - def worker(chunked, end = None, in_loop = False): + def worker(parsed, end = None): nonlocal states - ifunset = ifset = end - while len(chunked) > 0: - c = chunked[-1] - chunked = chunked[:-1] + while len(parsed) > 0: + c = parsed[-1] + parsed = parsed[:-1] if type(c) == list: - loop_test = add_state(writes.same, 0, [ifunset, None]) - loop_body = worker(c, loop_test, True) + loop_test = add_state(actions.nothing, [end, None]) + loop_body = worker(c, loop_test) # We want the loop to repeat if the cell is set states[loop_test].transitions[1] = loop_body - ifunset = ifset = loop_test + end = loop_test + + elif c == '*': + end = add_state(actions.change, [end, end]) + + elif c == '<': + end = add_state(actions.left, [end, end]) + + elif c == '>': + end = add_state(actions.right, [end, end]) else: - change = '*' in c - move = (1 if '>' in c else 0) - (1 if '<' in c else 0) + assert not "Unreachable" - assert change or move != 0 + return end - 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 + return worker(parsed), 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)) + action = {actions.change: '* ', actions.left: '< ', actions.right: '> ', actions.nothing: ''}[state.action] + if state.reachable == [True, True]: + reachable = '(*)' + elif state.reachable == [True, False]: + reachable = '(0)' + elif state.reachable == [False, True]: + reachable = '(1)' + elif state.reachable == [False, False]: + reachable = '(!)' + else: + reachable = '' + print('%4i%s: %s%s' % (i, reachable, action, 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} +def reachability(start, states): + def copy_state(index): + state = states[index] + action = state.action + transitions = state.transitions[:] + reachable = [False if i is None else i for i in state.reachable] + return State(action, transitions, reachable) - shaken_sweep = 0 - while shaken_sweep < len(shaken): - for i in range(len(shaken[shaken_sweep].transitions)): - index_states = shaken[shaken_sweep].transitions[i] + # Since we always end up with start at index 0, no need to create processed_start + processed = [copy_state(start)] + processed[0].reachable[0] = True + processed[0].reachable[1] = True + translations = {start: 0} + processed_sweep = 0 + while processed_sweep < len(processed): + for i in range(len(processed[processed_sweep].transitions)): + index_states = processed[processed_sweep].transitions[i] + + # Nothing to be done if we point to halt state + if index_states is None: + continue + + # Copy the state to processed if it isn't already there if index_states not in translations: - index_shaken = len(shaken) - shaken.append(states[index_states]) - translations[index_states] = index_shaken + index_processed = len(processed) + processed.append(copy_state(index_states)) + translations[index_states] = index_processed - shaken[shaken_sweep].transitions[i] = translations[index_states] + # Fix transition to the state + processed[processed_sweep].transitions[i] = translations[index_states] - shaken_sweep += 1 + # Mark how we got to the state + processed[translations[index_states]].reachable[i] = True - return shaken + processed_sweep += 1 + + return processed def main(): program = input('program: ') - start_nonshaken, states_nonshaken = turingify(chunk(parse_smallfuck(program))) - shaken = tree_shake(start_nonshaken, states_nonshaken) - prettyprint_states(shaken) + start_noreachability, states_noreachability = turingify(parse_smallfuck(program)) + processed = reachability(start_noreachability, states_noreachability) + prettyprint_states(processed) if __name__ == '__main__': main()