sf2xed/sf2xed.py

149 lines
3.6 KiB
Python

#!/usr/bin/env python3
import enum
from collections import namedtuple
class actions(enum.Enum):
change, left, right, nothing = range(4)
State = namedtuple('State', ['action', 'transitions', 'reachable'])
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 turingify(parsed):
states = []
def add_state(action, transitions):
nonlocal states
assert action in actions
assert len(transitions) == 2
index = len(states)
states.append(State(action, transitions, [False, False]))
return index
def worker(parsed, end = None):
nonlocal states
while len(parsed) > 0:
c = parsed[-1]
parsed = parsed[:-1]
if type(c) == list:
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
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:
assert not "Unreachable"
return end
return worker(parsed), states
def prettyprint_states(states):
for i in range(len(states)):
state = states[i]
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 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)
# 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_processed = len(processed)
processed.append(copy_state(index_states))
translations[index_states] = index_processed
# Fix transition to the state
processed[processed_sweep].transitions[i] = translations[index_states]
# Mark how we got to the state
processed[translations[index_states]].reachable[i] = True
processed_sweep += 1
return processed
def main():
program = input('program: ')
start_noreachability, states_noreachability = turingify(parse_smallfuck(program))
processed = reachability(start_noreachability, states_noreachability)
prettyprint_states(processed)
if __name__ == '__main__':
main()