From 62f93875c1edaed83fe810a9d92043bd31d4f366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Thu, 12 Jul 2018 15:12:27 +0300 Subject: [PATCH] First commit --- .gitignore | 2 + README | 1 + UNLICENSE | 24 ++++++++ sf2xed.py | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 UNLICENSE create mode 100644 sf2xed.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fffc64d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.swp diff --git a/README b/README new file mode 100644 index 0000000..f863fd8 --- /dev/null +++ b/README @@ -0,0 +1 @@ +Project to translate smallfuck (with infinite tape) to xed diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..69843e4 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to [http://unlicense.org] diff --git a/sf2xed.py b/sf2xed.py new file mode 100644 index 0000000..73930da --- /dev/null +++ b/sf2xed.py @@ -0,0 +1,178 @@ +#!/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()