vizh
vizh is a visual esoteric programming language that takes image files as input and is based on a multi-tape Turing machine. It is designed for compatibility with C and was created by User:TartanLlama in 2021.
The "parser" has to use computer vision and OCR to process the user's code. Here is an implementation of memcpy:
Here's how the parser understands the program (produced if you pass --debug-parser
when compiling):
More complex programs can be written by splitting the symbols across multiple lines, such as this function which counts nested square braces:
Abstract Machine
The vizh abstract machine consists of:
- Some number of tapes which are contiguous groups of 8-bit unsigned integers
- A read/write head with storage for a single 8-bit unsigned integer
The initial state of the abstract machine is:
- A single tape of size 4096 is allocated with all cells initialised to 0
- The read/write head is initialised to the left-most cell of this tape
Programs and Functions
A vizh program consists of a number of functions, each in its own image file.
The entry point to a vizh program is a function called main
. (Note that the main function gets mangled as vizh_main
. For all other functions the symbol name is the same as the vizh name).
A vizh function is an image file containing:
- The name of the function at the top left of the image
- The number of arguments (tapes) it takes at the top right of the image
- A sequence of instructions in a horizontal lines
Function names are alphanumeric: [a-zA-Z][a-zA-Z0-9]*
.
The tapes available to a vizh function consist of its tape arguments. A function returns when control flow reaches the end of its instructions. Any tapes allocated by a function are automatically deallocated when the function exits.
When you call a function subsequent pointer arguments are taken from the currently active tape onwards.
For example, given the following state of the abstract machine where ^
is the last position of the r/w head on that tape and $
is the active tape:
t1 01234 ^ $t2 99999 ^ t3 00000 ^
then a call to a function that takes two tapes would supply the arguments t2, t3.
Instruction Set
Instruction | Description |
---|---|
Left arrow | Move the r/w head left. |
Right arrow | Move the r/w head right. |
Up arrow | Move the r/w head to the tape above the current one. |
Down arrow | Move the r/w head to the tape below the current one. |
Function name in a circle | Call the named function. |
Plus | Increment the value pointed to by the r/w head by 1 .
|
Minus | Decrement the value pointed to by the r/w head by 1 .
|
Equilateral triangle with the point at the top | Read the cell pointed to by the r/w head into the r/w head storage. |
Equilateral triangle with the point at the bottom | Write the value stored in r/w head storage into the cell pointed to by the r/w head. |
[<instructions>] |
Loop over the instructions between the brackets until the value pointed to by the r/w head at the start of the loop is 0. |
When you move the r/w head up or down, the position it was last at for the previous tape is saved. E.g. given this state of the abstract where ^ is the last position of the r/w head on that tape and $ is the active tape:
$t0 01234 ^ t1 01234 ^
The sequence of instructions "right right right down" would result in this state:
t0 01234 ^ $t1 01234 ^
Comments in vizh are anything enclosed in a rectangle. Stick what you want in there.
Standard Library
The vizh standard library is called libv and the majority of it can be implemented in pure vizh.
Function | Description |
---|---|
readin |
Read an ASCII character from stdin and write its integral representation into the cell pointed to by the r/w head. |
print |
Print the value of the cell pointed to by the r/w head to stout, interpreted as an ASCII character. |
putstr |
Write the null-terminated ASCII string starting at the position pointed to by the r/w head to stdout. |
geta |
Puts the character a at the current position of the r/w head.
|
getA |
Puts the character A at the current position of the r/w head. |
add |
Given tape cells a,b from the r/w head, results in a+b,0 .
|
minus |
Given tape cells a,b from the r/w head, results in a-b,0 .
|
mul |
Given tape cells a,b,c from the r/w head, results in a*b,0,0 .
|
zero |
Given tape cell a from the r/w head, results in 0 .
|
compl |
Given tape cells a,b from the r/w head, results in 1,0 if a==0 , otherwise 0,0 .
|
equal |
Given tape cells a,b from the r/w head, results in a==b?1:0,0 .
|
newtape |
Allocate a new tape underneath the last one currently allocated for this function. |
freetape |
Deallocate the bottom-most secondary tape for this function (no-op if there are not any). |
Computability and Usability
Since vizh is essentially a multi-tape Turing machine, it is Turing-complete.
The ability to allocate multiple tapes and call functions makes it much more useable than something like Brainfuck. You can find an implementation of a Brainfuck interpreter written in vizh on GitHub.
Implementation
An implementation written in Python which uses OpenCV and Tesseract OCR to parse code and outputs C is available on GitHub.