We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.

Vines

From Esolang
Jump to navigation Jump to search
Vines
Paradigm(s) Imperative
Designed by User:AeroPurple
Appeared in 2026
Memory system Cell-based
Dimensions two-dimensional
Computational class Turing complete
Reference implementation WIP

Vines is an esoteric programming language created by User:AeroPurple. It is a two-dimensional interpreted language with multiple program counters, each with a 4-bit register.

Syntactic elements

Seeds

Seeds are the variables and program counters of Vines. They can be assigned any one-digit hexadecimal value, ranging from 0 to F. They have to be sown into the ground of the file, which is its very last line. This is done like so:



 5   F  0 3

Vine growth

Each seed, after being sown, will attempt to grow along its vine path upwards, ascending character by character. If it fails to find a path, it will cease to grow.

|
|    this one will not continue its path
5  F

Vines can also turn.

|    |
\   /
 |  |
 5  F

Note that the | character counts as being adjacent to the left side of the character grid, instead of the middle. Therefore, the following configuration is invalid, as it will be considered disconnected:

|    |
 \   /
 |  |
 5  F

Also having sharp turns is not allowed, as the path will be deemed "creased", and will also cease to grow.

 |               |
 |              /
/ won't grow!   | will grow
\               \
 |               |
 5               5

Additionally, any characters that are not on the vine path, even if they are path characters themselves, will be ignored and treated as comments. The valid basic vine path characters are \, |, /, or, alternatively, , , , , (the latter is not currently implemented). More characters will be discussed later. This also means that when any non-path character is placed on the path, including a space , the seed will cease to grow. This can be used for placing comments above the end of the vines.

|  unreachable by both vines
|     counts as comments
|   |
1   2

Branching and intersections

Vines can also branch, and in this case, the seed is duplicated, and the variable arrives at the end of both paths.

result: 5   5

        |   |
        \  /
         \/
          |
          |
          5

Vines can also merge back together. The seeds merge into one, and their values effectively go through a NOR gate. This is how all calculations will be performed.

result: A (0001 NOR 0101 = 1010)

        |
        |
       /\ NOR
      /  \
      |   |
      1   5

Thus, due to the NOR gate being a universal gate, the rest of the gates can be constructed from it, such as this NOT gate.

result: 9

        |
       /\                |  |    /
       | |               |  |   o
       \/                O  o  /
        | alternatively: |  |  |  - more compact, retaining a similar shape (also works diagonally)
        6                6  6  6

Other logic gates thus look like this:

results:
  2         7        A           5

  |         |        |           |
  |         |        |           o
  |         |        |           |
  |         |       /\          /\
  |         |       | \         | \
  |         |       |  |        |  |
  |         |      /| /|       /| /|
  |         |      |\/ |       |\/ |
  |         o      | | |       | | |
 /\         |      \/\/        \/\/ 
 o o AND   /\  OR   | |  XNOR   | |  XOR
 | |       | |      | |         | |
 6 3       6 3      6 3         6 3

Leaves

Unfortunately, the current system would not be turing-complete; the individual bytes in one byte cannot interact with each other in any way, and thus, mathematical functions like addition cannot be performed.

Leaves solve this. Adding them besides a vine will bitshift the value in the corresponding direction. Note that bitshifting the value too far will cause data loss, as bitshifting it back will always shift in a 0.

 |
<|  value is 4 (0100) - last bit was lost
 |
 |> value is 2 (0010)
 |
 |  value is 5 (0101)
 5

Stem types

The beginning of a vine, or a "stem", doesn't have to be straight. Different types of stems have different effects.

normal (no effects)    sets memory size to $64 (100) bytes   checks if character ID $65 (A) is pressed

      |                            |      |                                 |     |
      |                            \      \                                /     /
      2                             6      4                               6     5

The normal stem (|) does not cause any effects when it starts growing.

The memory stem (\) will set the size of the memory plane to the specified number of bytes. The memory plane will be discussed later.

The entanglement stem (/) will check two-digit hexadecimal patterns at a time, and if they match a currently pressed keyboard key (Note: currently not implemented). If it does, the affected vine seeds will change their values to F; otherwise they will become 0. The checking process occurs digit by digit, so if only one digit of the two matches, it will still change to F like so:

$65 (A) is held   $68 (D) is held   $85 (U) is held
results:
   F   F             F   0             0   F

   |   |             |   |             |   |
  /   /             /   /             /   /
  6   5             6   5             6   5

Also, different stem types can be interweaved with no issue:

 | | |    || - will set the memory plane size to $FFF and will check for key $65 being pressed
/  \ \   / \
6   F F  5  F

Buds and flowers

Once the memory plane size has been assigned, the buds and the flowers will be able to interact with it. Vines are only able to work with one hexadecimal digit at a time, which is only half of a byte. On the memory plane, these digits are called "cells".

Every vine starts out at the first cell on the memory plane (cell #0), and can be moved left/right along it using buds (d and b). Going too far along the memory plane will cause it to wrap around.

|
b points at cell #0
d points at cell #17 (wrapped around)
d points at cell #0
b points at cell #1
| points at cell #0  \  memory size is 9 bytes (18 cells)
0                     9

To go to a specific cell, the seeds have to merge using the ^ character (currently not implemented):

           |
           ^
          / \
memory   ^   \
size    / \   | the "5" vine will now move to cell $60 (#96)
   \ \  |  |  |
    F F 5  6  0

For this operation, the order does matter, as the left value will have its pointer adjusted and the right value will "vanish".

To read/write values into the selected memory cell, the vine has to grow flowers (* "blooms" and @ "blossoms" respectively). The latter is the only way to communicate the information back to the user. The former also completely overwrites the vine seed's current value, so make sure to store it somewhere first if you'll need it later.

                         |                                     /  can be used diagonally, just like the NOT gate
|                        @ set cell #1 to current value (5)   *
* gets value at cell #0  b move to cell #1                   /
|                        |                                   |         \  dummy memory size
0                        5                                   0          1

Note that if the memory size is not set or is 0, the blooms (*) will retrieve the value F, and the blossoms (@) will not do anything for you.

Loops

Generations

Since vines cannot turn back on themselves to repeat their actions, all vines will instead regrow each time all of them finish growing. Each time this happens, it is called a new "generation". The state of the memory plane is preserved between generations, but the vines aren't, so their selected memory cells will also be reset.

Gen 1:
@ writes $8 to cell #0
|   * gets value at cell #0, retrieves the default $0
|   |    \  dummy memory size
8   0     1
Gen 2:
@ writes $8 to cell #0 again (no visible effect)
|   * gets value at cell #0, retrieves the previously written $8
|   |    \  dummy memory size
8   0     1

Basics of making a loop

To make a finite loop, the following can be done:

  • Use two memory cells: one for the loop counter, one as a flag to determine if the loop is finished or not. Both are initially 0
  • Set loop counter to the desired value if the loop has not yet been executed (as denoted by the flag)
  • Perform the necessary actions (if the flag has not yet been set)
  • Decrease the loop counter at the end of the generation
  • If the loop counter decreases to 0 at the end of a generation, also set the "finished" flag
  • Once the loop begins again, the flag being set would prevent it from being executed, essentially "stopping" the loop

Adding the flag may seem unnecessary, but keep in mind that the loop counter will be 0 both before and after the loop. So the flag is added to prevent the same "code" running in both cases.

But what is important to keep in mind is that the only way to "stop" the execution is to make sure there is no difference between any following generations.

Conditional statements

You might have noticed above that making a loop requires conditional execution, or in simpler terms, "if/then" statements, and the booleans that need to go along with them. No worries, these can be created quite easily.

Booleans

Booleans in Vines are not explicitly defined, but the standard is F for True, and 0 for False.

Syntax

Conditional statements can be thought of more abstractly as filters, receiving a single boolean, and returning one value in one case, and a different in another. Here's an example of a conditional statement in pseudocode:

((boolean AND value1) OR (NOT boolean AND value2))

Here's a sample of what happens depending on the boolean input:

((boolean & 0x1) | (!boolean & 0x2))

boolean=True (0xF)            | boolean=False (0x0)
 ((0xF & 0x1) | (!0xF & 0x2)) |  ((0x0 & 0x1) | (!0x0 & 0x2))
=((0xF & 0x1) | (0x0 & 0x2))  | =((0x0 & 0x1) | (0xF & 0x2))
=((0x1) | (0x0))              | =((0x0) | (0x2))
=0x1 | 0x0                    | =0x0 | 0x2
=0x1                          | =0x2

So, depending on the input boolean, different values are received.

This will look roughly like this (Note that this is a code snippet, and will not execute correctly on its own; B stands for "boolean"):

     |
     o
     |
    /\ OR gate
   /  \
  /    \
  |     |
 /|     |                                                   |
/ \    /|                                                  /|
o  o  / \              [ more readable representation     o \
|  | /   | AND gates - [ of (!boolean & 0x2)             o   |
|  \/    o             [ though the two NOT subgates    /    o
|   |    |             [ would obviously cancel out ->  |    |
1  [b]   2                                             [b]   2

So, to summarize, in a coniditonal statement, all of the conditions will be executed, and it's the conditional statement's job to filter out any conditions that shouldn't have occurred using booleans. This means that if you're planning to overwrite a variable with a new one if the condition succeeds, but want to keep it unchanged if it fails, you need to plug in its value into the "False" part of the conditional statement.

Calculating booleans

In order to use booleans properly, we must calculate them in the first place. For example, if you want to check if the most significant bit is set (which results in values 8-F), a "smearing" technique can be used, which involves bitshifting.

1011 - binary value we're interested in

1011 & 1000 = 1000 - remove the irrelevant values

 1000 | (1000 >> 1)
=1000 | 0100
=1100 - halfway there

 1100 | (1100 >> 2)
=1100 | 0011
=1111 - True, which means the first bit was set

Now, this resulting value can be plugged into a conditional statement.

To check equivalence between two values, the check can be done four times per each bit, and the resulting values could be ANDed together. But a more compact way is to bitshift all of the different bits into the same bit, AND them together, and smear the value into a boolean later.

Interpreters

The main interpreter is written in Java, but is not yet fully complete (evidenced by the italic notes). Once it is completed, the interpreter source code will be uploaded to an online repository.