sf2xed/sf2xed.py

179 lines
3.9 KiB
Python

#!/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()