Repository: ealter/vim_turing_machine Branch: master Commit: 7a7647f3e5b2 Files: 36 Total size: 66.4 KB Directory structure: gitextract_mhyv690l/ ├── .coveragerc ├── .deactivate.sh ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── Makefile ├── README.md ├── decode_hours.sh ├── requirements-dev.txt ├── setup.cfg ├── setup.py ├── tests/ │ ├── __init__.py │ ├── machines/ │ │ ├── __init__.py │ │ ├── is_number_even_test.py │ │ └── merge_overlapping_intervals/ │ │ ├── __init__.py │ │ ├── decode_intervals_test.py │ │ ├── encode_intervals_test.py │ │ ├── merge_overlapping_intervals_test.py │ │ └── vim_merge_overlapping_intervals_test.py │ └── turing_machine_test.py ├── tox.ini ├── vim_turing_machine/ │ ├── __init__.py │ ├── constants.py │ ├── machines/ │ │ ├── __init__.py │ │ ├── is_number_even.py │ │ ├── merge_overlapping_intervals/ │ │ │ ├── __init__.py │ │ │ ├── decode_intervals.py │ │ │ ├── encode_intervals.py │ │ │ ├── merge_overlapping_intervals.py │ │ │ └── vim_merge_overlapping_intervals.py │ │ └── vim_is_number_even.py │ ├── struct.py │ ├── turing_machine.py │ ├── vim_constants.py │ └── vim_machine.py └── vimrc ================================================ FILE CONTENTS ================================================ ================================================ FILE: .coveragerc ================================================ [run] branch = True source = . omit = .tox/* /usr/* setup.py [report] show_missing = True skip_covered = True exclude_lines = # Have to re-enable the standard pragma \#\s*pragma: no cover # Don't complain if tests don't hit defensive assertion code: ^\s*raise AssertionError\b ^\s*raise NotImplementedError\b ^\s*return NotImplemented\b ^\s*raise$ # Don't complain if non-runnable code isn't run: ^if __name__ == ['"]__main__['"]:$ [html] directory = coverage-html # vim:ft=dosini ================================================ FILE: .deactivate.sh ================================================ deactivate ================================================ FILE: .gitignore ================================================ *.egg-info *.py[co] /.cache /.coverage /.tox /coverage-html /dist /venv machine.vim *.swp ================================================ FILE: .pre-commit-config.yaml ================================================ - repo: https://github.com/pre-commit/pre-commit-hooks sha: v0.9.2 hooks: - id: trailing-whitespace language_version: python3.6 - id: end-of-file-fixer language_version: python3.6 exclude: ^\.activate\.sh$ - id: autopep8-wrapper language_version: python3.6 - id: check-docstring-first language_version: python3.6 - id: check-executables-have-shebangs language_version: python3.6 - id: check-merge-conflict language_version: python3.6 - id: check-yaml language_version: python3.6 - id: debug-statements language_version: python3.6 - id: double-quote-string-fixer language_version: python3.6 - id: name-tests-test language_version: python3.6 - id: flake8 language_version: python3.6 - id: check-added-large-files language_version: python3.6 exclude: ^\.activate\.sh$ - id: check-byte-order-marker language_version: python3.6 - repo: https://github.com/asottile/reorder_python_imports sha: v0.3.5 hooks: - id: reorder-python-imports language_version: python3.6 - repo: https://github.com/asottile/pyupgrade sha: v1.2.0 hooks: - id: pyupgrade language_version: python3.6 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) [year] [fullname] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 OR COPYRIGHT HOLDERS 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. ================================================ FILE: Makefile ================================================ .PHONY: minimal minimal: venv venv: tox -e venv .PHONY: install-hooks install-hooks: venv venv/bin/pre-commit install-hooks venv/bin/pre-commit install .PHONY: test test: tox .PHONY: clean clean: find . -name '*.pyc' -delete find . -name '__pycache__' -delete rm -rf .tox rm -rf venv rm machine.vim .PHONY: run run: venv venv/bin/python -m vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals '[[1,2],[2,3],[5,8]]' 5 .PHONY: run-vim build-vim: venv venv/bin/python -m vim_turing_machine.machines.merge_overlapping_intervals.vim_merge_overlapping_intervals '[[1,2],[2,3],[5,8]]' 5 open-vim-machine: build-vim vim -u vimrc machine.vim run-vim-machine: build-vim vim -u vimrc machine.vim -c ':normal gg0yy@"' ================================================ FILE: README.md ================================================ Vim Turing Machine ================== Ever wish you could run your code in your editor? Tired of installing huge dependencies like bash or python to run your scripts? Love Vim so much that you never want to leave it? Why not run your code... in your editor itself? Enter vim_turing_machine: a tool to allow you to run a Turing machine using only normal mode Vim commands. And now you might ask, but what can we do on a Turing machine! To demonstrate its capabilities, we implemented a solution to the Merge Overlapping Intervals question and defined all the state transitions needed to solve this glorious problem. So next time you need to merge some intervals, don't hand-write a 10-line python program. Instead, take out your favorite editor and watch it solve the problem in less than a minute with 1400 state transitions! But a simple naysayer may say, 'We already have vimscript! Why in God's name would I want to use a Turing machine instead?' To that, we retort: our Turing machine only uses normal mode. So you could theoretically just type in the program and then execute it without running a single script! No ex mode either! This project proves that normal mode in Vim is as powerful as any computer! Merging your favorite intervals =============================== Given a set of sorted potentially overlapping open/close intervals, merge the overlapping intervals together. Example: ``` [[1, 5], [6, 7]] -> [[1, 5], [6, 7]] [[1, 5], [2, 3], [5, 7], [12, 15]] -> [[1, 7], [12, 15]] ``` Running the Python Turing Machine: `make run` Opening the Vim Turing Machine without running it: `make open-vim-machine` Opening and then running the Vim Turing Machine: `make run-vim-machine` So Vim did what? Wait. How does it even? ======================================== So you run this program, and it works. Great! So what happened? Well the most common thing you're going to see is `y$@"`. What this does is yank from the current cursor to the end of the line and then executes the default register as a macro. This allows us to encode motions in lines and then execute them. We then chain lines together by ending lines with moving to a mark, or a search result, and then yanking and executing that line. Using that nifty trick, we begin by yanking the first line and executing it. That then sets off our mark initialization. We then search for `_` and then mark that position with the corresponding letter. Generally the first initial of whatever the thing we're marking is. Once everything is marked, we then begin the state transitions. We begin a state transition by executing a long command (located at `_n:`) which jumps to the tape marker, yanks it, then jumps to the current state marker, yanks that too, and then searches for some transition that contains both the state and tape values. Once it gets to that line, it jumps to the command string and then executes our trusty `y$@"` to execute it. To make sure we keep transitioning, each state transition ends with `` `ny$@ `` which tells it to jump to our "next state" marker and then execute it again, which kicks off the search for the next state. The execution halts when it can't find a new state to transition to. The state search includes an "or" operator where it will fall back to matching `---`, which tells it to print the current state and halt. Most transitions themselves involve changing a tape value or a state value and then moving in some direction on the tape. Changing values consists of jumping to the tape or state marks (`` `t `` or `` `k `` respectively) and then using `cw` or `C` to change the value. We then move the pointer by jumping to the tape position (`` `t ``) and then moving a word forward (`W`) or backward (`B`), and then marking the new tape position. The last real piece of complication is extending the tape. We're living in a world with unlimited tapes! What a time to be alive! This is done through a series of nifty hacks. First, we have a modeline that sets `whichwrap+b,s`. This allows us to move across line breaks and keep the tape all in the screen. Next, the line directly under the tape contains a "fake" value that, when added to our state search, will prevent it from matching any real state transitions and instead match a "transition" for adding a line to the tape. This line tells us to jump to the end of the tape, and then insert a full line of empty values (we use `X`), and then go back to our original tape location and execute the next state transition! And there you have it! A simple `ggyy@"` will kick off all of these sequences until execution completes. The cool thing is that this isn't special to the intervals problem. In fact, you can write your own state machine and use the provided Vim adapter to create a new Vim machine to solve any problem that can be solved by a Turing Machine! To see some more details about various common commands, you can take a look at `vim_turing_machine/vim_constants.py`. That file contains some constants that are used repeatedly in the generated `machine.vim` file and their names are fairly descriptive. Also, if you'd like to step through manually, you can edit the Vim machine in `vim_turing_machine/machines/vim_is_number_even.py` and tell it not to auto step and then step through manually using `y$@"`. Happy hacking! Dependencies ============ To run this code, you will need `python3.6`, `tox`, and `vim` installed on your machine. This code hasn't been tested on other versions of python3, but they'll probably work if you change the pinned version in `tox.ini`. This code is not python2 compatible. Contributors ============ eliot and ifij wrote this project in July 2017 for Yelp's Hackathon 23. It was inspired by [vimmmex](https://github.com/xoreaxeaxeax/vimmmex): a Brainfuck interpretor written in Vim. [modeline]: # ( vim: set fenc=utf-8 spell spl=en textwidth=80: ) ================================================ FILE: decode_hours.sh ================================================ #! /bin/bash venv/bin/python -m vim_turing_machine.machines.merge_overlapping_intervals.decode_intervals "$1" $2 ================================================ FILE: requirements-dev.txt ================================================ coverage flake8 pre-commit>=0.15 pytest ================================================ FILE: setup.cfg ================================================ [wheel] universal = True ================================================ FILE: setup.py ================================================ from setuptools import find_packages from setuptools import setup setup( name='vim-turing-machine', version='1.0.0', classifiers=[ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', ], install_requires=[ 'colored', ], packages=find_packages(exclude=('tests*', 'testing*')), ) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/machines/__init__.py ================================================ ================================================ FILE: tests/machines/is_number_even_test.py ================================================ import pytest from vim_turing_machine.constants import BLANK_CHARACTER from vim_turing_machine.constants import NO_FINAL_STATE from vim_turing_machine.constants import YES_FINAL_STATE from vim_turing_machine.machines.is_number_even import number_is_even_state_transitions from vim_turing_machine.turing_machine import TuringMachine def assert_tape(machine, expected_tape): # Ignore any blanks at the end assert expected_tape == ''.join(machine.tape).rstrip(BLANK_CHARACTER) def run_machine(transitions, tape): machine = TuringMachine(list(transitions), quiet=True) machine.run(tape[:], max_steps=10000) assert_tape(machine, tape) return machine @pytest.mark.parametrize('tape, is_even', [ ('', False), ('1', False), ('0', True), ('1001', False), ('1010', True), ]) def test_is_number_even(tape, is_even): machine = run_machine(number_is_even_state_transitions, tape) if is_even: assert machine.current_state == YES_FINAL_STATE else: assert machine.current_state == NO_FINAL_STATE ================================================ FILE: tests/machines/merge_overlapping_intervals/__init__.py ================================================ ================================================ FILE: tests/machines/merge_overlapping_intervals/decode_intervals_test.py ================================================ from vim_turing_machine.machines.merge_overlapping_intervals.decode_intervals import decode_intervals def test_encode_intervals(): assert decode_intervals('{}{}'.format('01010', '11111'), 5) == [[10, 31]] ================================================ FILE: tests/machines/merge_overlapping_intervals/encode_intervals_test.py ================================================ import pytest from vim_turing_machine.machines.merge_overlapping_intervals.encode_intervals import encode_in_x_bits from vim_turing_machine.machines.merge_overlapping_intervals.encode_intervals import encode_intervals @pytest.mark.parametrize('number, encoded', [ (0, '00000'), (10, '01010'), (31, '11111'), ]) def test_encode_in_x_bits(number, encoded): assert encode_in_x_bits(number, num_bits=5) == encoded def test_encode_intervals(): assert encode_intervals([(10, 31)], num_bits=5) == '{}{}'.format('01010', '11111') ================================================ FILE: tests/machines/merge_overlapping_intervals/merge_overlapping_intervals_test.py ================================================ from unittest import mock import pytest import vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals import vim_turing_machine.struct import vim_turing_machine.turing_machine from vim_turing_machine.constants import INITIAL_STATE from vim_turing_machine.constants import NO_FINAL_STATE from vim_turing_machine.constants import YES_FINAL_STATE from vim_turing_machine.machines.merge_overlapping_intervals.decode_intervals import decode_intervals from vim_turing_machine.machines.merge_overlapping_intervals.encode_intervals import encode_intervals from vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals import invert_bit from vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals import invert_direction from vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals import MergeOverlappingIntervalsGenerator from vim_turing_machine.struct import BACKWARDS from vim_turing_machine.struct import FORWARDS from vim_turing_machine.turing_machine import TuringMachine @pytest.yield_fixture(autouse=True) def mock_blank_character(): """Change the blank character to be a space so that it's easier to write test cases.""" with mock.patch.object( vim_turing_machine.turing_machine, 'BLANK_CHARACTER', ' ', ): with mock.patch.object( vim_turing_machine.struct, 'VALID_CHARACTERS', ('0', '1', ' '), ): with mock.patch.object( vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals, 'BLANK_CHARACTER', ' ', ): with mock.patch.object( vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals, 'VALID_CHARACTERS', ('0', '1', ' '), ): yield @pytest.fixture def merger(): return MergeOverlappingIntervalsGenerator(num_bits=3) def run_machine(transitions, tape, initial_position=0, assert_tape_not_changed=False): machine = TuringMachine(list(transitions), quiet=True) machine.run(tape[:], max_steps=10000, initial_cursor_position=initial_position) if assert_tape_not_changed: assert_tape(machine, tape) return machine def assert_cursor_at_end_of_output(machine): end = len(machine.tape) - 1 while end > 0 and machine.tape[end] == ' ': end -= 1 assert machine.cursor_position == end def assert_cursor_is_at_beginning_of_input(machine): i = 0 while i < len(machine.tape) and machine.tape[i] == ' ': i += 1 assert machine.cursor_position == i def assert_tape(machine, expected_tape): # Ignore any blanks at the end assert expected_tape == ''.join(machine.tape).rstrip() def test_invert_bit(): assert invert_bit('0') == '1' assert invert_bit('1') == '0' with pytest.raises(AssertionError): invert_bit('not_valid') def test_invert_direction(): assert invert_direction(FORWARDS) == BACKWARDS assert invert_direction(BACKWARDS) == FORWARDS with pytest.raises(AssertionError): invert_direction('not_valid') def test_move_n_bits(merger): machine = run_machine( merger.move_n_bits( initial_state=INITIAL_STATE, direction=FORWARDS, final_state=YES_FINAL_STATE, num_bits=4, ), tape='01010111', assert_tape_not_changed=True, ) assert machine.cursor_position == 4 def test_move_to_blank_spaces(merger): machine = run_machine( merger.move_to_blank_spaces( initial_state=INITIAL_STATE, direction=FORWARDS, final_state=YES_FINAL_STATE, final_character=' ', final_direction=BACKWARDS, num_blanks=2, ), tape='01 1111 10', assert_tape_not_changed=True, ) assert machine.cursor_position == 6 # End of the 1111 def test_copy_bits_to_end_of_output(merger): machine = run_machine( merger.copy_bits_to_end_of_output( initial_state=INITIAL_STATE, num_bits=3, final_state=YES_FINAL_STATE, ), tape='10111 01', ) assert_tape(machine, ' 11 01101') assert_cursor_at_end_of_output(machine) @pytest.mark.parametrize('tape, final_state', [ ('101 100110', NO_FINAL_STATE), ('101 100100', YES_FINAL_STATE), ('101 111100', YES_FINAL_STATE), ]) def test_compare_two_sequential_numbers(merger, tape, final_state): machine = run_machine( merger.compare_two_sequential_numbers( initial_state=INITIAL_STATE, greater_than_or_equal_to_state=YES_FINAL_STATE, less_than_state=NO_FINAL_STATE, ), tape=tape, initial_position=len(tape) - 1, assert_tape_not_changed=True, ) assert_cursor_at_end_of_output(machine) assert machine.current_state == final_state def test_erase_number(merger): machine = run_machine( merger.erase_number( initial_state=INITIAL_STATE, final_state=YES_FINAL_STATE, ), tape='100101110', initial_position=5, # end of 101 ) assert machine.cursor_position == 2 assert_tape(machine, '100 110') def test_replace_number(merger): tape = '100101110' machine = run_machine( merger.replace_number( initial_state=INITIAL_STATE, final_state=YES_FINAL_STATE, ), tape=tape, initial_position=len(tape) - 1, ) assert_tape(machine, '100110') assert_cursor_at_end_of_output(machine) @pytest.mark.parametrize('tape, final_state', [ (' 100 101101', NO_FINAL_STATE), (' 100101101', YES_FINAL_STATE), ]) def test_check_if_there_is_any_input_left(merger, tape, final_state): machine = run_machine( merger.check_if_there_is_any_input_left( initial_state=INITIAL_STATE, final_state=NO_FINAL_STATE, # The machine exits with Yes if there is no input left. ), tape=tape, initial_position=len(tape) - 1, assert_tape_not_changed=True, ) assert_cursor_is_at_beginning_of_input(machine) assert machine.current_state == final_state @pytest.mark.parametrize('initial_tape, final_tape', [ (' 100 001010001', ' 001100'), # 2nd pair's closing value is larger (' 010 001110001', ' 001110'), # 2nd pair's closing value is smaller (' 110 001110001', ' 001110'), # 2nd pair's closing value is equal ]) def test_copy_closing_value_and_merge(merger, initial_tape, final_tape): machine = run_machine( merger.copy_closing_value_and_merge( initial_state=INITIAL_STATE, final_state=YES_FINAL_STATE, ), tape=initial_tape, initial_position=len(initial_tape) - 1, ) assert_cursor_at_end_of_output(machine) assert_tape(machine, final_tape) def test_copy_closing_value_without_merging(merger): tape = ' 111 000010110' machine = run_machine( merger.copy_closing_value_without_merging( initial_state=INITIAL_STATE, final_state=YES_FINAL_STATE, ), tape=tape, initial_position=len(tape) - 1, ) assert_cursor_at_end_of_output(machine) assert_tape(machine, ' 000010110111') @pytest.mark.parametrize( 'initial_intervals, final_intervals', [ ( [[0, 1]], [[0, 1]], ), ( [[0, 1], [5, 6]], [[0, 1], [5, 6]], ), ( [[0, 5], [2, 3]], [[0, 5]], ), ( [[1, 3], [3, 4], [4, 5], [6, 7]], [[1, 5], [6, 7]], ) ] ) def test_merge_overlapping_intervals(merger, initial_intervals, final_intervals): """The true integration test!""" tape = encode_intervals(initial_intervals, num_bits=3) machine = run_machine( merger.merge_overlapping_intervals_transitions(), tape=tape, ) assert final_intervals == decode_intervals(''.join(machine.tape), num_bits=3) ================================================ FILE: tests/machines/merge_overlapping_intervals/vim_merge_overlapping_intervals_test.py ================================================ import subprocess from vim_turing_machine.machines.merge_overlapping_intervals.decode_intervals import decode_intervals from vim_turing_machine.machines.merge_overlapping_intervals.encode_intervals import encode_intervals from vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals import MergeOverlappingIntervalsGenerator from vim_turing_machine.vim_constants import VIM_MACHINE_FILENAME from vim_turing_machine.vim_machine import VimTuringMachine NUM_BITS = 3 def run_vim_machine(intervals): initial_tape = encode_intervals(intervals, NUM_BITS) gen = MergeOverlappingIntervalsGenerator(NUM_BITS) merge_overlapping_intervals = VimTuringMachine(gen.merge_overlapping_intervals_transitions(), debug=False) # Write to the vim machine file merge_overlapping_intervals.run(initial_tape=initial_tape) subprocess.run( [ 'vim', '-u', 'vimrc', VIM_MACHINE_FILENAME, '-c', # Execute the vim machine and then save the resulting file ":execute 'normal gg0yy@\"' | :x", ], timeout=10, check=True, ) def read_contents_of_tape(): with open(VIM_MACHINE_FILENAME, 'r') as f: tape_lines = [] found_beginning_of_tape = False for line in f: # Look for the lines between '_t:' and 'notvalid' if line.startswith('_t:'): found_beginning_of_tape = True elif line.startswith('notvalid'): return convert_tape_to_string(tape_lines) elif found_beginning_of_tape: tape_lines.append(line) raise AssertionError('Could not find the tape') def convert_tape_to_string(tape_lines): return ''.join(tape_lines).replace(' ', '').replace('\n', '') def test_merge_intervals_in_vim(): run_vim_machine([[1, 2], [2, 3], [5, 7]]) tape = read_contents_of_tape() intervals = decode_intervals(tape, num_bits=NUM_BITS) assert intervals == [[1, 3], [5, 7]] ================================================ FILE: tests/turing_machine_test.py ================================================ import pytest from vim_turing_machine.constants import FORWARDS from vim_turing_machine.struct import StateTransition from vim_turing_machine.turing_machine import DuplicateStateTransitionException from vim_turing_machine.turing_machine import validate_state_transitions def test_invalid_state_transition(): with pytest.raises(AssertionError): validate_state_transitions([ StateTransition( previous_state='foo', previous_character='Not a valid character', next_state='bar', next_character='0', tape_pointer_direction=FORWARDS, ) ]) def test_valid_states(): validate_state_transitions( [ StateTransition( previous_state='foo', previous_character='0', next_state='bar', next_character='0', tape_pointer_direction=FORWARDS, ), StateTransition( previous_state='foo', previous_character='1', next_state='bar', next_character='1', tape_pointer_direction=FORWARDS, ), ] ) def test_duplicate_states(): state = StateTransition( previous_state='foo', previous_character='0', next_state='bar', next_character='0', tape_pointer_direction=FORWARDS, ) with pytest.raises(DuplicateStateTransitionException): validate_state_transitions([state, state]) ================================================ FILE: tox.ini ================================================ [tox] envlist = py3 tox_pip_extensions_ext_pip_custom_platform = true tox_pip_extensions_ext_venv_update = true [testenv] deps = -rrequirements-dev.txt passenv = HOME SSH_AUTH_SOCK USER commands = coverage erase coverage run -m pytest {posargs:tests} coverage report pre-commit install -f --install-hooks pre-commit run --all-files [testenv:venv] basepython = python3 envdir = venv commands = [flake8] max-line-length = 131 [pep8] ignore = E265,E309,E501 ================================================ FILE: vim_turing_machine/__init__.py ================================================ ================================================ FILE: vim_turing_machine/constants.py ================================================ BLANK_CHARACTER = 'X' INITIAL_STATE = 'InitialState' YES_FINAL_STATE = 'YES' NO_FINAL_STATE = 'NO' BITS_PER_NUMBER = 5 FINAL_STATES = [YES_FINAL_STATE, NO_FINAL_STATE] # Tape pointer direction FORWARDS = 1 DO_NOT_MOVE = 0 BACKWARDS = -1 VALID_CHARACTERS = {'0', '1', BLANK_CHARACTER} INVALID_STATE_CHARACTERS = ['_', '-', ':'] ================================================ FILE: vim_turing_machine/machines/__init__.py ================================================ ================================================ FILE: vim_turing_machine/machines/is_number_even.py ================================================ import sys from vim_turing_machine.constants import BLANK_CHARACTER from vim_turing_machine.constants import INITIAL_STATE from vim_turing_machine.constants import NO_FINAL_STATE from vim_turing_machine.constants import YES_FINAL_STATE from vim_turing_machine.struct import BACKWARDS from vim_turing_machine.struct import FORWARDS from vim_turing_machine.struct import StateTransition from vim_turing_machine.turing_machine import TuringMachine ADVANCE_TO_END_OF_NUMBER = 'onward!' FOUND_END_OF_NUMBER = 'eof' number_is_even_state_transitions = ( # A number is not even if the initial tape is blank. StateTransition( previous_state=INITIAL_STATE, previous_character=BLANK_CHARACTER, next_state=NO_FINAL_STATE, next_character=BLANK_CHARACTER, tape_pointer_direction=FORWARDS, ), # But any other number initially means that we should go find the end of the array StateTransition( previous_state=INITIAL_STATE, previous_character='0', next_character='0', next_state=ADVANCE_TO_END_OF_NUMBER, tape_pointer_direction=FORWARDS, ), StateTransition( previous_state=INITIAL_STATE, previous_character='1', next_character='1', next_state=ADVANCE_TO_END_OF_NUMBER, tape_pointer_direction=FORWARDS, ), # Once we're looking for the end of the number, go until we hit a blank StateTransition( previous_state=ADVANCE_TO_END_OF_NUMBER, previous_character='0', next_character='0', next_state=ADVANCE_TO_END_OF_NUMBER, tape_pointer_direction=FORWARDS, ), StateTransition( previous_state=ADVANCE_TO_END_OF_NUMBER, previous_character='1', next_character='1', next_state=ADVANCE_TO_END_OF_NUMBER, tape_pointer_direction=FORWARDS, ), # Once we're looking for the end of the number, go until we hit a blank. # Then backtrack 1 space. StateTransition( previous_state=ADVANCE_TO_END_OF_NUMBER, previous_character=BLANK_CHARACTER, next_character=BLANK_CHARACTER, next_state=FOUND_END_OF_NUMBER, tape_pointer_direction=BACKWARDS, ), # Now that we're on the last character, check if it is even or odd StateTransition( previous_state=FOUND_END_OF_NUMBER, previous_character='0', next_character='0', next_state=YES_FINAL_STATE, tape_pointer_direction=FORWARDS, ), StateTransition( previous_state=FOUND_END_OF_NUMBER, previous_character='1', next_character='1', next_state=NO_FINAL_STATE, tape_pointer_direction=FORWARDS, ), ) if __name__ == '__main__': even_odd_turing_machine = TuringMachine(number_is_even_state_transitions, debug=True) even_odd_turing_machine.run(initial_tape=sys.argv[1]) ================================================ FILE: vim_turing_machine/machines/merge_overlapping_intervals/__init__.py ================================================ ================================================ FILE: vim_turing_machine/machines/merge_overlapping_intervals/decode_intervals.py ================================================ """Decodes a binary string to a json representation of the intervals after the merge overlapping intervals turing machine have processed them. Reads json from the command line and outputs the initial tape.""" import json import sys from vim_turing_machine.constants import BITS_PER_NUMBER from vim_turing_machine.constants import BLANK_CHARACTER def decode_intervals(intervals, num_bits=BITS_PER_NUMBER): result = [] clean_intervals = intervals.replace(BLANK_CHARACTER, '').replace(' ', '') index = 0 while index < len(clean_intervals): begin = clean_intervals[index:index + num_bits] begin = int(begin, 2) index += num_bits end = clean_intervals[index:index + num_bits] end = int(end, 2) index += num_bits result.append([begin, end]) return result if __name__ == '__main__': print(json.dumps(decode_intervals(sys.argv[1], int(sys.argv[2])))) ================================================ FILE: vim_turing_machine/machines/merge_overlapping_intervals/encode_intervals.py ================================================ """Encodes a json representation of the intervals into the 5-bit binary representation used by the merge overlapping intervals turing machine. It takes input from stdin and outputs the initial tape.""" import json import sys from vim_turing_machine.constants import BITS_PER_NUMBER def encode_intervals(intervals, num_bits=BITS_PER_NUMBER): result = '' for (begin, end) in intervals: result += encode_in_x_bits(begin, num_bits) result += encode_in_x_bits(end, num_bits) return result def encode_in_x_bits(number, num_bits): encoded = '{:b}'.format(number) assert len(encoded) <= num_bits # Add leading zeros return '0' * (num_bits - len(encoded)) + encoded if __name__ == '__main__': print(encode_intervals(json.load(sys.stdin))) ================================================ FILE: vim_turing_machine/machines/merge_overlapping_intervals/merge_overlapping_intervals.py ================================================ """Our tape is defined in multiple segments, each separated by a blank character. INPUTOUTPUT This program solves the "Merge Overlapping Intervals" problem: Given a sorted list of possibly overlapping (opening, closing) intervals, merge them. Example: [[1, 5], [2, 3], [4, 6], [9, 11]] -> [[1, 6], [9, 11]] We do this... on a turing machine. """ import itertools import json import sys from vim_turing_machine.constants import BITS_PER_NUMBER from vim_turing_machine.constants import BLANK_CHARACTER from vim_turing_machine.constants import INITIAL_STATE from vim_turing_machine.constants import VALID_CHARACTERS from vim_turing_machine.constants import YES_FINAL_STATE from vim_turing_machine.machines.merge_overlapping_intervals.decode_intervals import decode_intervals from vim_turing_machine.machines.merge_overlapping_intervals.encode_intervals import encode_intervals from vim_turing_machine.struct import BACKWARDS from vim_turing_machine.struct import DO_NOT_MOVE from vim_turing_machine.struct import FORWARDS from vim_turing_machine.struct import StateTransition from vim_turing_machine.turing_machine import TuringMachine class MergeOverlappingIntervalsGenerator(object): def __init__(self, num_bits=BITS_PER_NUMBER): self._num_bits = num_bits def merge_overlapping_intervals_transitions(self): """This is the main orchestration point of the program""" # This is the beginning of the loop that goes through the rest of the intervals. CHECK_NEXT_SET_OF_HOURS = 'CheckNextSetOfHours' # We begin the program by copying the first intervals pair into the output # array. At the end of this, the cursor will be at the end of the # output array. transitions = list( self.copy_bits_to_end_of_output( initial_state=INITIAL_STATE, num_bits=self._num_bits * 2, final_state=CHECK_NEXT_SET_OF_HOURS, ) ) BEGIN_COPY_NEXT_SET_OF_HOURS = 'CopyNextSetOfHours' BEGIN_COMPARISON = 'BeginComparison' # Then move back to the beginning of the input while checking if there is any input left transitions.extend( self.check_if_there_is_any_input_left( initial_state=CHECK_NEXT_SET_OF_HOURS, final_state=BEGIN_COPY_NEXT_SET_OF_HOURS, ) ) # Now it's time to copy the opening intervals of the next pair. transitions.extend( self.copy_bits_to_end_of_output( initial_state=BEGIN_COPY_NEXT_SET_OF_HOURS, num_bits=self._num_bits, final_state=BEGIN_COMPARISON, ) ) OPEN_HOUR_IS_LESS_THAN = 'OpeningLessThan' OPEN_HOUR_IS_GREATER_THAN = 'OpeningGreaterThan' # Next we compare the closing intervals of the previous pair with the # opening intervals of the current pair. transitions.extend( self.compare_two_sequential_numbers( initial_state=BEGIN_COMPARISON, greater_than_or_equal_to_state=OPEN_HOUR_IS_LESS_THAN, less_than_state=OPEN_HOUR_IS_GREATER_THAN, ) ) transitions.extend( self.copy_closing_value_without_merging( initial_state=OPEN_HOUR_IS_GREATER_THAN, final_state=CHECK_NEXT_SET_OF_HOURS, ) ) transitions.extend( self.copy_closing_value_and_merge( initial_state=OPEN_HOUR_IS_LESS_THAN, final_state=CHECK_NEXT_SET_OF_HOURS, ) ) return transitions def copy_closing_value_without_merging(self, initial_state, final_state): """Things are super simple if we don't need to merge the intervals. We just need to copy over the closing intervals from the input array. Precondition: we are at the end of the output array. The opening intervals have already been copied. The opening value is greater than the previous pair's closing value. Postcondition: we are at the end of the output array """ COPY_CLOSING_HOUR_WITHOUT_MERGING = 'CopyClosingHourWithoutMerging' # First move back to the beginning of the input array since the copy # function requires that. transitions = self.move_to_blank_spaces( initial_state=initial_state, final_state=COPY_CLOSING_HOUR_WITHOUT_MERGING, final_character=BLANK_CHARACTER, final_direction=FORWARDS, direction=BACKWARDS, num_blanks=2, ) # Then just copy the closing value from the input array to the output array. transitions.extend( self.copy_bits_to_end_of_output( initial_state=COPY_CLOSING_HOUR_WITHOUT_MERGING, num_bits=self._num_bits, final_state=final_state, ) ) return transitions def copy_closing_value_and_merge(self, initial_state, final_state): """Call this if you need to merge in the 2nd set of intervals. Precondition: we are at the end of the output array. The opening intervals have already been copied. The opening value is less than or equal to the previous pair's closing value. Postcondition: we are at the end of the output array """ MOVE_BACK_TO_BEGINNING_TO_COPY_CLOSING_HOUR = 'MoveToBeginningToCopyClosingHour' COPY_OVER_CLOSING_HOUR = 'CopyClosingHour' COMPARE_CLOSING_HOUR = 'CompareClosingHour' CLOSING_HOUR_IS_LARGER = 'ClosingHourIsLarger' CLOSING_HOUR_IS_NOT_LARGER = 'ClosingHourIsNotLarger' # If the opening value is less than the closing value of the previous pair, # then we discard that opening value. So essentially, [2, 7, 5] becomes [2, 7]. transitions = self.erase_number( initial_state=initial_state, final_state=MOVE_BACK_TO_BEGINNING_TO_COPY_CLOSING_HOUR, ) # Move back to the beginning of the array. transitions.extend( self.move_to_blank_spaces( initial_state=MOVE_BACK_TO_BEGINNING_TO_COPY_CLOSING_HOUR, final_state=COPY_OVER_CLOSING_HOUR, final_character=BLANK_CHARACTER, final_direction=FORWARDS, direction=BACKWARDS, num_blanks=2, ) ) # Now after erasing that number, we need to copy over the closing value so # that we can merge it in. transitions.extend( self.copy_bits_to_end_of_output( initial_state=COPY_OVER_CLOSING_HOUR, num_bits=self._num_bits, final_state=COMPARE_CLOSING_HOUR, ) ) # Now we take the max of the 2 pairs' closing intervals. transitions.extend( self.compare_two_sequential_numbers( initial_state=COMPARE_CLOSING_HOUR, less_than_state=CLOSING_HOUR_IS_LARGER, greater_than_or_equal_to_state=CLOSING_HOUR_IS_NOT_LARGER, ) ) # If the closing value is less than or equal to the previous closing value, just nuke it. transitions.extend( self.erase_number( initial_state=CLOSING_HOUR_IS_NOT_LARGER, final_state=final_state, ) ) # But if the closing value is greater than the previous closing value, we # should overwrite that closing value with our larger value. transitions.extend( self.replace_number( initial_state=CLOSING_HOUR_IS_LARGER, final_state=final_state, ) ) return transitions def noop_when_non_blank(self, state, direction): return ( StateTransition( previous_state=state, previous_character='0', next_state=state, next_character='0', tape_pointer_direction=direction, ), StateTransition( previous_state=state, previous_character='1', next_state=state, next_character='1', tape_pointer_direction=direction, ), ) def move_n_bits(self, initial_state, direction, final_state, num_bits): """Moves 'num_bits' in the specified direction. Errors if it encounters a blank space while doing so. Ends in the final_state.""" def state_name(bit_index): if bit_index == 0: return initial_state elif bit_index == num_bits: return final_state else: return '{}MovingBit{}'.format(initial_state, bit_index) return itertools.chain.from_iterable([ [ StateTransition( previous_state=state_name(bit_index), previous_character=bit_value, next_state=state_name(bit_index + 1), next_character=bit_value, tape_pointer_direction=direction, ) for bit_index in range(num_bits) ] for bit_value in ['0', '1'] ]) def move_to_blank_spaces( self, initial_state, final_state, final_character, final_direction, direction, num_blanks, ): """Moves along the array until it hits a certain number of blank spaces. :param str initial_state: The state used to trigger this code :param str final_state: The state we should finish with :param str final_character: The character we should write on that state transition :param int final_direction: Which direction we should move at the end :param int direction: Which direction we should search in :param int num_blanks: How many blanks to search for """ def state_name(blank_num): return '{}Searching{}'.format(initial_state, blank_num) transitions = [ # Rename our current state StateTransition( previous_state=initial_state, previous_character=character, next_state=state_name(blank_num=0), next_character=character, tape_pointer_direction=DO_NOT_MOVE, ) for character in VALID_CHARACTERS ] for blank_num in range(num_blanks): transitions.extend( # If we're looking for the first blank, then keep going until we hit it self.noop_when_non_blank(state_name(blank_num=blank_num), direction=direction) ) if blank_num == num_blanks - 1: # This is the last blank transitions.append( StateTransition( previous_state=state_name(blank_num), previous_character=BLANK_CHARACTER, next_state=final_state, next_character=final_character, tape_pointer_direction=final_direction, ) ) else: # This is not the last blank transitions.append( StateTransition( previous_state=state_name(blank_num), previous_character=BLANK_CHARACTER, next_state=state_name(blank_num + 1), next_character=BLANK_CHARACTER, tape_pointer_direction=direction, ) ) return transitions def copy_bits_to_end_of_output(self, initial_state, num_bits, final_state): """ :param string initial_state: The state used before we start to move :param int num_bits: The number of bits to copy :param StateTransition final_state: The state to finish with when we are done copying Note: This overwrites the copied section with blanks. Precondition: We are at the beginning of the input array Postcondition: We are at the end of the output array :rtype: [StateTransition] """ def state_name(bit_index): if bit_index == 0: return initial_state else: return '{}Copy{}'.format(initial_state, bit_index) def copy_bit(bit_index, bit_value): base_copying_state = '{}Bit{}'.format(state_name(bit_index + 1), bit_value) return [ # Let's start copying the character. Note how we replace it with a blank. StateTransition( previous_state=state_name(bit_index), previous_character=bit_value, next_state='{}Forward'.format(base_copying_state), next_character=BLANK_CHARACTER, tape_pointer_direction=FORWARDS, ), *self.move_to_blank_spaces( initial_state='{}Forward'.format(base_copying_state), # If we're on the last character, don't go backwards final_state=( '{}Backwards'.format(base_copying_state) if bit_index < num_bits - 1 else final_state ), final_character=bit_value, final_direction=DO_NOT_MOVE, direction=FORWARDS, num_blanks=2, ), *self.move_to_blank_spaces( initial_state='{}Backwards'.format(base_copying_state), final_state=state_name(bit_index + 1), final_character=BLANK_CHARACTER, final_direction=FORWARDS, direction=BACKWARDS, num_blanks=2, ), ] return itertools.chain.from_iterable( ( *copy_bit(bit_index, bit_value='0'), *copy_bit(bit_index, bit_value='1'), ) for bit_index in range(num_bits) ) def compare_two_sequential_numbers(self, initial_state, greater_than_or_equal_to_state, less_than_state): """ If the earlier number is greater than or equal to the later number, this will end in the greater_than_or_equal_to_state. If the earlier number is less than the later number, this will end in the less_than_state. Precondition: The cursor is at the end of the output array Postcondition: The cursor is at the end of the output array """ # We can't directly transition into the >= or < states since we need to end # up at the end of the output array. FOUND_GREATER_THAN_OR_EQUAL_TO_STATE = '{}FoundGreaterThanOrEqualTo'.format(initial_state) FOUND_LESS_THAN_STATE = '{}FoundLessThan'.format(initial_state) def already_have_one_bit_state(bit_index, bit_value): """This means that we've read a 'bit_value' at 'bit_index'. We are currently searching for the equivalent bit in the other number.""" return '{}BitIndex{}BitValue{}'.format(initial_state, bit_index, bit_value) def about_to_read_first_bit_state(bit_index): """This means that we're about to start reading/comparing the next bit index. We always immediately transition from this state to the 'already_have_one_bit_state' after reading its value.""" if bit_index == self._num_bits: # At this point, we know that the numbers are equal since we've compared every bit. return FOUND_GREATER_THAN_OR_EQUAL_TO_STATE else: return '{}BitIndex{}'.format(initial_state, bit_index) def about_to_compare_bits_state(bit_index, bit_value): """Our cursor is over the other bit we want to compare this one too.""" return '{}BitIndex{}CompareWithBitValue{}'.format(initial_state, bit_index, bit_value) # Begin by moving to the beginning of the 2nd number. transitions = list( self.move_n_bits( initial_state=initial_state, direction=BACKWARDS, final_state=about_to_read_first_bit_state(bit_index=0), num_bits=self._num_bits - 1, ) ) direction = BACKWARDS # Then begin comparing the digits one by one from largest to smallest for bit_index in range(self._num_bits): for bit_value in ['0', '1']: transitions.append( # Read the current bit StateTransition( previous_state=about_to_read_first_bit_state(bit_index), previous_character=bit_value, next_state=already_have_one_bit_state(bit_index, bit_value), next_character=bit_value, tape_pointer_direction=direction, ) ) # Then go to the equivalent bit in the other number. We already # moved 1 space in that direction. transitions.extend( self.move_n_bits( initial_state=already_have_one_bit_state(bit_index, bit_value), direction=direction, final_state=about_to_compare_bits_state(bit_index, bit_value), num_bits=self._num_bits - 1, ) ) # We've already read the 2nd number and now we're comparing it to # the first. Now finally do the comparison transitions.append( # If the numbers are equal StateTransition( previous_state=about_to_compare_bits_state(bit_index, bit_value), previous_character=bit_value, next_state=about_to_read_first_bit_state(bit_index + 1), next_character=bit_value, tape_pointer_direction=FORWARDS, ) ) transitions.append( # If the numbers are not equal StateTransition( previous_state=about_to_compare_bits_state(bit_index, bit_value), previous_character=invert_bit(bit_value), next_state=( FOUND_GREATER_THAN_OR_EQUAL_TO_STATE if ( (bit_value == '1' and direction == FORWARDS) or (bit_value == '0' and direction == BACKWARDS) ) else FOUND_LESS_THAN_STATE ), next_character=invert_bit(bit_value), tape_pointer_direction=invert_direction(direction), ) ) direction = invert_direction(direction) # After we've determined the answer, we need to move to the end of the output array transitions.extend( self.move_to_blank_spaces( initial_state=FOUND_GREATER_THAN_OR_EQUAL_TO_STATE, final_state=greater_than_or_equal_to_state, final_character=BLANK_CHARACTER, final_direction=BACKWARDS, direction=FORWARDS, num_blanks=1, ) ) transitions.extend( self.move_to_blank_spaces( initial_state=FOUND_LESS_THAN_STATE, final_state=less_than_state, final_character=BLANK_CHARACTER, final_direction=BACKWARDS, direction=FORWARDS, num_blanks=1, ) ) return transitions def erase_number(self, initial_state, final_state): """Erases the number under the cursor by replacing it with blanks. Precondition: The cursor is at the end of that number Postcondition: The cursor is right before the beginning of that number """ def state_name(bit_index): if bit_index == 0: return initial_state elif bit_index == self._num_bits: return final_state else: return '{}ErasingBit{}'.format(initial_state, bit_index) transitions = [] for bit_index in range(self._num_bits): for bit_value in ['0', '1']: transitions.append( StateTransition( previous_state=state_name(bit_index), previous_character=bit_value, next_state=state_name(bit_index + 1), next_character=BLANK_CHARACTER, tape_pointer_direction=BACKWARDS, ) ) return transitions def replace_number(self, initial_state, final_state): """Replaces the 2nd to last number with the last number. So, [1, 5, 7] would become [1, 7]. Precondition: The cursor is at the end of the output array Postcondition: The cursor is at the end of the output array """ def need_to_read_bit(bit_index): if bit_index == 0: return initial_state elif bit_index == self._num_bits: return final_state else: return '{}ReadingBitIndexToMove{}'.format(initial_state, bit_index) def read_bit(bit_index, bit_value): return '{}ReadingBitIndexToMove{}Bit{}'.format(initial_state, bit_index, bit_value) def overwrite_bit(bit_index, bit_value): return '{}OverwritingBitIndex{}Bit{}'.format(initial_state, bit_index, bit_value) def move_back_to_end(bit_index): return '{}ReplacingNumberMovingBackToEnd{}'.format(initial_state, bit_index) transitions = [] for bit_index in range(self._num_bits): for bit_value in ['0', '1']: # Start by reading the bit under the cursor. Replace it with a blank. transitions.append( StateTransition( previous_state=need_to_read_bit(bit_index), previous_character=bit_value, next_state=read_bit(bit_index, bit_value), next_character=BLANK_CHARACTER, tape_pointer_direction=BACKWARDS, ) ) # Then go to the equivalent bit in the other number. transitions.extend( self.move_n_bits( initial_state=read_bit(bit_index, bit_value), direction=BACKWARDS, final_state=overwrite_bit(bit_index, bit_value), num_bits=self._num_bits - 1, ) ) # Then overwrite the current bit with the stored bit transitions.extend( StateTransition( previous_state=overwrite_bit(bit_index, bit_value), previous_character=bit_value_we_are_reading, next_state=move_back_to_end(bit_index), next_character=bit_value, tape_pointer_direction=FORWARDS, ) for bit_value_we_are_reading in ['0', '1'] ) # Lastly move back to the end of the output array transitions.extend( self.move_to_blank_spaces( initial_state=move_back_to_end(bit_index), final_state=need_to_read_bit(bit_index + 1), final_character=BLANK_CHARACTER, final_direction=BACKWARDS, direction=FORWARDS, num_blanks=1, ) ) return transitions def check_if_there_is_any_input_left(self, initial_state, final_state): """ Precondition: We are at the end of the output array Postcondition: We are at the beginning of the input array If there is no more input left, this ends the program """ CHECK_IF_ANY_HOURS_LEFT = '{}CheckIfAnyHoursLeft'.format(initial_state) # Then move back to the beginning of the input transitions = self.move_to_blank_spaces( initial_state=initial_state, final_state=CHECK_IF_ANY_HOURS_LEFT, final_character=BLANK_CHARACTER, final_direction=FORWARDS, direction=BACKWARDS, num_blanks=2, ) # If we moved back 2 blanks and still ended on a blank, then there is # nothing left in the input because we hit 2 blanks in a row. transitions.append( StateTransition( previous_state=CHECK_IF_ANY_HOURS_LEFT, previous_character=BLANK_CHARACTER, next_state=YES_FINAL_STATE, next_character=BLANK_CHARACTER, tape_pointer_direction=FORWARDS, ) ) # If we did not find a blank, then we just chill where we are transitions.extend( StateTransition( previous_state=CHECK_IF_ANY_HOURS_LEFT, previous_character=bit_value, next_state=final_state, next_character=bit_value, tape_pointer_direction=DO_NOT_MOVE, ) for bit_value in ['0', '1'] ) return transitions def invert_bit(bit_value): if bit_value == '0': return '1' elif bit_value == '1': return '0' else: raise AssertionError('Invalid bit {}'.format(bit_value)) def invert_direction(direction): if direction == BACKWARDS: return FORWARDS elif direction == FORWARDS: return BACKWARDS else: # pragma: no cover raise AssertionError('Invalid direction {}'.format(direction)) if __name__ == '__main__': input_string = json.loads(sys.argv[1]) num_bits = int(sys.argv[2]) initial_tape = encode_intervals(input_string, num_bits) gen = MergeOverlappingIntervalsGenerator(num_bits) merge_overlapping_intervals = TuringMachine(gen.merge_overlapping_intervals_transitions(), debug=True) merge_overlapping_intervals.run(initial_tape=initial_tape, max_steps=5000) print(decode_intervals(''.join(merge_overlapping_intervals.tape), num_bits)) ================================================ FILE: vim_turing_machine/machines/merge_overlapping_intervals/vim_merge_overlapping_intervals.py ================================================ import json import sys from vim_turing_machine.machines.merge_overlapping_intervals.encode_intervals import encode_intervals from vim_turing_machine.machines.merge_overlapping_intervals.merge_overlapping_intervals import MergeOverlappingIntervalsGenerator from vim_turing_machine.vim_machine import VimTuringMachine if __name__ == '__main__': input_string = json.loads(sys.argv[1]) num_bits = int(sys.argv[2]) initial_tape = encode_intervals(input_string, num_bits) gen = MergeOverlappingIntervalsGenerator(num_bits) merge_overlapping_intervals = VimTuringMachine(gen.merge_overlapping_intervals_transitions(), debug=False) merge_overlapping_intervals.run(initial_tape=initial_tape) ================================================ FILE: vim_turing_machine/machines/vim_is_number_even.py ================================================ import sys from vim_turing_machine.machines.is_number_even import number_is_even_state_transitions from vim_turing_machine.vim_machine import VimTuringMachine if __name__ == '__main__': initial_tape = sys.argv[1] even_odd_turing_machine = VimTuringMachine(number_is_even_state_transitions, debug=False) even_odd_turing_machine.run(initial_tape=initial_tape) ================================================ FILE: vim_turing_machine/struct.py ================================================ from collections import namedtuple from vim_turing_machine.constants import BACKWARDS from vim_turing_machine.constants import DO_NOT_MOVE from vim_turing_machine.constants import FORWARDS from vim_turing_machine.constants import INVALID_STATE_CHARACTERS from vim_turing_machine.constants import VALID_CHARACTERS class StateTransition(namedtuple('StateTransition', [ 'previous_state', 'previous_character', 'next_state', 'next_character', 'tape_pointer_direction', ])): def validate(self): assert self.tape_pointer_direction in (FORWARDS, DO_NOT_MOVE, BACKWARDS) assert self.previous_character in VALID_CHARACTERS assert self.next_character in VALID_CHARACTERS for invalid_char in INVALID_STATE_CHARACTERS: if invalid_char in self.previous_state: raise AssertionError('{} is in {}'.format(invalid_char, self.previous_state)) if invalid_char in self.next_state: raise AssertionError('{} is in {}'.format(invalid_char, self.next_state)) ================================================ FILE: vim_turing_machine/turing_machine.py ================================================ from collections import defaultdict import colored from vim_turing_machine.constants import BLANK_CHARACTER from vim_turing_machine.constants import FINAL_STATES from vim_turing_machine.constants import INITIAL_STATE class NegativeTapePositionException(Exception): pass class MissingStateTransition(Exception): pass class DuplicateStateTransitionException(Exception): pass class TooManyStepsException(Exception): pass class TuringMachine(object): def __init__(self, state_transitions, debug=False, quiet=False): validate_state_transitions(state_transitions) self._state_transitions = state_transitions self._state_transition_mapping = { (state.previous_state, state.previous_character): state for state in state_transitions } self._debug = debug self._quiet = quiet self.initialize_machine(tape=[]) def initialize_machine(self, tape, initial_cursor_position=0): if tape: self.tape = list(tape)[:] # Copy the initial tape since we mutate it else: self.tape = [BLANK_CHARACTER] self.cursor_position = initial_cursor_position self.current_state = INITIAL_STATE self._num_steps = 0 def get_state_transition(self): try: return self._state_transition_mapping[ (self.current_state, self.tape[self.cursor_position]) ] except KeyError: raise MissingStateTransition( (self.current_state, self.tape[self.cursor_position]) ) def step(self): """This implements an infinitely long tape in the right direction, but will error if you go beyond position 0""" transition = self.get_state_transition() self.tape[self.cursor_position] = transition.next_character self.cursor_position += transition.tape_pointer_direction if self.cursor_position < 0: raise NegativeTapePositionException # Make sure we haven't run more than 1 past the end of the tape. This # should never happen since we append to the tape over time. assert self.cursor_position <= len(self.tape) if self.cursor_position >= len(self.tape): # Fake the infinite tape by adding a blank character under the # cursor. self.tape.append(BLANK_CHARACTER) self.current_state = transition.next_state if self.current_state in FINAL_STATES: self.final_state() elif self._debug: self.print_tape() def final_state(self): if not self._quiet: print('Program complete. Final state: {}'.format(self.current_state)) print( 'The program completed in {} steps using a machine with {} transitions'.format( self._num_steps, len(self._state_transitions) ) ) self.print_tape() raise StopIteration def run(self, initial_tape, max_steps=None, initial_cursor_position=0): self.initialize_machine(initial_tape, initial_cursor_position=initial_cursor_position) if self._debug: self.print_tape() try: while(True): self.step() self._num_steps += 1 if max_steps is not None and self._num_steps >= max_steps: raise TooManyStepsException except StopIteration: pass def print_tape(self): tape = '' for i, character in enumerate(self.tape): if i == self.cursor_position: tape += '{}{}{}{}'.format(colored.bg('red'), colored.fg('white'), character, colored.attr('reset')) else: tape += character if i != len(self.tape) - 1: tape += ' | ' print(tape) print('State: {}'.format(self.current_state)) print() # Add empty line def validate_state_transitions(state_transitions): seen = defaultdict(list) for transition in state_transitions: transition.validate() key = (transition.previous_state, transition.previous_character) seen[key].append(transition) for transitions in seen.values(): if len(transitions) > 1: raise DuplicateStateTransitionException(transitions) ================================================ FILE: vim_turing_machine/vim_constants.py ================================================ VIM_MACHINE_FILENAME = 'machine.vim' VIM_NEXT_STATE = '`ny$@"' VIM_MOVE_TAPE_FORWARDS = '`tWmt' VIM_MOVE_TAPE_BACKWARDS = '`tBmt' VIM_RUN_REGISTER = '@"' VIM_TAPE_WRAP_POSITION = 40 VIM_LOG_TAPE_AND_STATE = '`ly$@"' def create_pointer(name, direction='j'): """Creates a mark to a particular place on the tape.""" return '`h/_{name}:\rn{direction}m{name}'.format(name=name, direction=direction) VIM_POINTERS = ''.join([ create_pointer('t'), create_pointer('l'), create_pointer('k'), create_pointer('o'), create_pointer('p'), create_pointer('s', direction=''), create_pointer('n'), create_pointer('e', direction='k'), ]) VIM_TEMPLATE = """0/_v1\rnf-ly$@" ### launch with ggyy@" ### # Init pointers _v1-gg0mh{pointers}`ny$@" _o: # Output _k: # Current state {initial_state} _t: # Current tape {initial_tape} notvalid\|--addlinetotape _e: # End of tape. Pointer is 1 line above this _n: # Next state transition. Usage: `ny$@" {logging}`t"tyiW`ky$`s/_"-t\|---\rf:ly$@" _p: # Print state. Usage: `py$@" `ky$`op _l: # Log the tape and state Usage: `ly$@" `tyipGopdd`kyyGp _s: # State transitions {state_transitions} # End State transitions # Add an extra line to the end of the tape _--addlinetotape: `eO{characters_per_line}i{blank_character} 0mt`ny$@" # Print state when unknown transition _---: `py$@" # vim: set whichwrap+=b,s """ ================================================ FILE: vim_turing_machine/vim_machine.py ================================================ from vim_turing_machine.constants import BACKWARDS from vim_turing_machine.constants import BLANK_CHARACTER from vim_turing_machine.constants import FORWARDS from vim_turing_machine.turing_machine import TuringMachine from vim_turing_machine.vim_constants import VIM_LOG_TAPE_AND_STATE from vim_turing_machine.vim_constants import VIM_MACHINE_FILENAME from vim_turing_machine.vim_constants import VIM_MOVE_TAPE_BACKWARDS from vim_turing_machine.vim_constants import VIM_MOVE_TAPE_FORWARDS from vim_turing_machine.vim_constants import VIM_NEXT_STATE from vim_turing_machine.vim_constants import VIM_POINTERS from vim_turing_machine.vim_constants import VIM_RUN_REGISTER from vim_turing_machine.vim_constants import VIM_TAPE_WRAP_POSITION from vim_turing_machine.vim_constants import VIM_TEMPLATE def create_initial_tape(input_tape): """Generates the initial tape by padding the input and wrapping""" padding_length = VIM_TAPE_WRAP_POSITION - len(input_tape) % VIM_TAPE_WRAP_POSITION input_tape += padding_length * [BLANK_CHARACTER] initial_tape = [] for index, value in enumerate(input_tape): if index % VIM_TAPE_WRAP_POSITION == 0: initial_tape.append([]) initial_tape[index // VIM_TAPE_WRAP_POSITION].append(value) return '\n'.join(' '.join(row) for row in initial_tape) class VimStateTransitionAdapter(object): def __init__(self, state_transition): self.st = state_transition def to_vim(self): """Returns vim command mapping of this transition""" return ( '_{}-{}:{}{}{}{}' ).format( self.st.previous_state, self.st.previous_character, self._change_state_to(), self._change_tape_to(), self._move_pointer(), VIM_NEXT_STATE, ) def _change_state_to(self): """Returns the vim commands to change current state to next""" return '`k"_C{}'.format(self.st.next_state) def _change_tape_to(self): """Returns the vim commands to change current tape value to next""" return '`t"_cw{}'.format(self.st.next_character) def _move_pointer(self): """Returns the vim commands to move the tape after transition""" if self.st.tape_pointer_direction == FORWARDS: return VIM_MOVE_TAPE_FORWARDS elif self.st.tape_pointer_direction == BACKWARDS: return VIM_MOVE_TAPE_BACKWARDS else: return '' class VimTuringMachine(TuringMachine): def run(self, initial_tape, auto_step=True): """Generates vim machine in an output file""" self.initialize_machine(initial_tape) with open(VIM_MACHINE_FILENAME, 'w') as machine: machine.write(VIM_TEMPLATE.format( initial_state=self.current_state, initial_tape=create_initial_tape(self.tape), characters_per_line=VIM_TAPE_WRAP_POSITION, pointers=VIM_POINTERS, blank_character=BLANK_CHARACTER, logging=( VIM_LOG_TAPE_AND_STATE if auto_step and self._debug else '' ), state_transitions='\n'.join( VimStateTransitionAdapter(state_transition).to_vim() for state_transition in self._state_transitions ), ).replace(VIM_RUN_REGISTER, VIM_RUN_REGISTER if auto_step else '')) print('Machine written to {}'.format(VIM_MACHINE_FILENAME)) ================================================ FILE: vimrc ================================================ set whichwrap+=b,s set cursorline set cursorcolumn