If you take every action that Vim provides then it is possible to create a turing complete sequence of actions. This is because both vimscript and the shell are turing complete. This is an exploration of what minimal set of vim motions and actions are required to support turing completeness.
This README will look broken on github because it makes extensive use of literal escapes. It would be best to read this in Vim itself:
vim https://raw.githubusercontent.com/matthewfranglen/vim-turing-complete/master/README.md
The definition of a turing machine is available here.
The turing machine requires the following:
- An internal state
- An infinite tape of symbols
- A map of state and symbol to next state and action
It is perfectly possible to use buffers to represent all of these. It's also convenient to have a fourth buffer to work over.
A simple list of requirements is available here.
The turing complete language requires the following:
- A way to perform conditional repetition
- A way to read and write some form of storage
It is possible to do a great deal with a relatively small number of actions. For example:
This script needs to be yanked into the a
register. You can do this with 0"ay$
.
Ihello world!�@a
When executed (@a
) this will endlessly repeat hello world!.
This is because it inserts that text and then calls itself again.
This allows recursion.
This script needs to be yanked into the a
register. You can do this with 0"ay$
.
oIhello world!�0"by$dd@b�@a
When executed (@a
) this will endlessly repeat hello world!.
This is because it populates the b
register with Ihello world!
and then calls it.
This uses <ESC>
after calling @b
to demonstrate that the complex repeat invocation
remains in insert mode after leaving the b
complex repeat. It would be possible to end
insert mode within the b
complex repeat by ending it with <C-V><C-V><C-V><ESC>
.
With careful construction conditional complex repeat invocation can be achieved.
This can be done by reducing and mapping a block of text into a complex repeat
using actions such as :g
, :v
and :s
.
This script needs to be yanked into the a
register. You can do this with 0"ay$
.
�@a
You need to execute this while the cursor sits on or before a number.
When executed (@a
) this will endlessly increment the number.
By using <C-A>
the current buffer has become readable as well as writeable.
This functionality has already been demonstrated by the other examples.
This just makes it explicit.
There are other ways to do this which permit greater power (e.g. multi-repeat).
To implement the turing machine in vim the most important thing is the definition of the mapping.
This will be mapped in the mapping
buffer.
To make this easy the mapping will fit on one line:
STATE SYMBOL NEXT-STATE ACTION
a 0 a j
a 1 b r0j
b 0 a j
b 1 c oend�
If you are in state a
and you are over a 0
then you stay in state a
and
use j
to move down one line.
This is held in the state
buffer and must be initialized.
This is held in the tape
buffer and has a symbol per line. The cursor rests on the first line.
To execute a single round the following process is performed:
- Move to the
state
buffer and read the state into registers
- Move to the
tape
buffer and read into registert
- Initialize the
mapping
registerm
with the content:@s @t
- Clear the
work
buffer - Copy the
mapping
buffer into thework
buffer - Reduce the
work
buffer down to the single line that matches the current state and symbol combination - (find a space to exit early)
- Delete the first two words (current state and current symbol)
- Read the next word and replace the
state
buffer with it - Read the remaining words and execute them as a complex repeat over the
tape
buffer - Repeat
Move to the machine
folder and execute the following:
vim *
Then within vim open the init
buffer and follow the instructions.
This example takes a tape containing the following content:
0
1
0
1
1
And a mapping of the following state and symbol transitions:
STATE | SYMBOL | NEXT-STATE | ACTION |
---|---|---|---|
a | 0 | a | j |
a | 1 | b | r0j |
b | 0 | a | j |
b | 1 | c | oend� |
It starts in state a
on the first row. It then proceeds as follows:
- The state is
a
and the tape is0
so it transitions to statea
and executesj
(move one row down) - The state is
a
and the tape is1
so it transitions to stateb
and executesr0j
(replace the 1 with a zero and move one row down) - The state is
b
and the tape is0
so it transitions to statea
and executesj
(move one row down) - The state is
a
and the tape is1
so it transitions to stateb
and executesr0j
(replace the 1 with a zero and move one row down) - The state is
b
and the tape is1
so it transitions to statec
and executesoend<ESC>
(writeend
after the current line and exit insert mode) - The state is
c
and the tape isend
so it fails to find a matching mapping line. It stops at this point
This results in a tape with the following content:
0
0
0
0
1
end