Emblia

Emblia is a minimal esolanguage by Keymaker, designed in 2018 but not released until 2020.

Language description

An Emblia program is a finite array of non-negative integers. There is a pointer that indicates the current location in the array, initially the cell 0. There is a finite number of unbounded registers (all initially 0), defined by the given program.

A program in its visible/written format consists of characters '_' and '1' (all other characters are ignored) that encode the program array in a simple way. Initially the array has only one cell, which is 0. '_' appends a new 0-cell, while '1' increases the current last cell of the array by 1. For example, the program _111__11 would define the internal array / program (0 3 0 2).

An essential concept to this language is the insignia-numbers, which I have in context of Natyre (an earlier language of mine) called event-numbers, but it is the same set of numbers: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, and so forth. It is easy to generate these numbers: Take integers s and n, both initially 0. Increase n by 1, add n to s. Now s is the first insignia-number. Repeat (n=n+1, s=s+n).

After the program is set in memory, it is executed. There is only one instruction and it does the following: Increase the register Rx by 1, where x is the current cell's (the cell under the pointer) value. For instance, if the current cell has value 2, then the register R2 is increased. Then it is time to move the pointer - if the new value of the register that was just manipulated is an insignia-number, then the pointer is moved left by the value in the current cell (2, in this example), otherwise, in case of a non-insignia-number, the pointer is moved right by the current cell's value (2, again). The pointer wraps if it moves past the bounds of the program. If the new location of the pointer is equal to its location before the movement, instead of getting stuck in an infinite loop the program halts (successfully).

Example program and techniques

The program data 11_1_1_111_1_1_1 defines the program (2 1 1 3 1 1 1). This simple program has 7 cells and uses 3 registers (since it has values 1, 2, and 3 in it): R1, R2, and R3.

Execution begins at cell 0, and first increases R2, which changes from 0 to 1. As 1 is an insignia-number, the pointer will move left, by 2 (the cell's value). The pointer has now wrapped to cell 5, which is 1. R1 is increased, changing from 0 to 1, which is an insignia-number, which moves the pointer left by 1 (the cell's value). Now the pointer is at cell 4. R1 is increased, becoming 2, which is an ordinary number, and the pointer is thus moved right by 1 (the cell's value). Now the pointer is at cell 5 again. R1 is changed from 2 to 3, which is an insignia-number. Pointer moves 1 left again. R1 is increased from 3 to 4, which results in moving 1 right. Eventually the pointer gets to cell 6, from which it moves 1 right and is wrapped to cell 0. Inspected from some distance, essentially this program increases R2, and if R2 becomes an insignia-number, the pointer wraps on the other side, from which it always returns back to cell 0. If R2 became an ordinary number instead, the pointer would move to cell 2, from which it would eventually, inevitably move to cell 3. The value of cell 3 is 3, so R3 would be increased, and depending on its value, the pointer would either jump left (insignia) to cell 0 or right (ordinary) to cell 6, from which it would continue eventually and inevitably to cell 0. This is looped forever, but the times the pointer moves left get rarer and rarer, because the distance between each successive insignia-number grows and grows.

It is simpler to show a part of the program execution. Each segment shows the current state of the program (the values of registers and where the pointer is).

Initial:
1=0, 2=0, 3=0
[2] 1 1 3 1 1 1

1=0, 2=1, 3=0
2 1 1 3 1 [1] 1

1=1, 2=1, 3=0
2 1 1 3 [1] 1 1

1=2, 2=1, 3=0
2 1 1 3 1 [1] 1

1=3, 2=1, 3=0
2 1 1 3 [1] 1 1

1=4, 2=1, 3=0
2 1 1 3 1 [1] 1

1=5, 2=1, 3=0
2 1 1 3 1 1 [1]

1=6, 2=1, 3=0
2 1 1 3 1 [1] 1

1=7, 2=1, 3=0
2 1 1 3 1 1 [1]

1=8, 2=1, 3=0
[2] 1 1 3 1 1 1

1=8, 2=2, 3=0
2 1 [1] 3 1 1 1

1=9, 2=2, 3=0
2 1 1 [3] 1 1 1

1=9, 2=2, 3=1
[2] 1 1 3 1 1 1

...and so on...

The above showcases some of the programming methods that are useful (or outright necessary). Since there is an increasing tendency to move right (because the distance between insignia-numbers keeps growing and the number of ordinary numbers is much higher), designing the control flow to favour moving right will make the programs function ever more frictionlessly - and it might be the only possible way, since moving right more often is inevitable with the larger amount of ordinary numbers. Creating paths of 1s is a useful technique for moving the pointer towards a specific cell somewhere on right. Sometimes R1 will be an insignia-number, and the pointer moves 1 left, which is good to keep in mind to avoid the pointer accidentally moving to a cell it should not move to. This is often solved by adding an extra 1 on left as padding - no two successive numbers are insignia-numbers, so it is certain that if the pointer steps left into the extra 1-cell, it will step out of it to right during the next step because it is guaranteed that the value of R1 will always be an ordinary number after being an insignia-number. The translation method (see below) uses these techniques, and more.

Proof of Turing-completeness via Natyre translation

The language is Turing-complete. This is shown via Natyre to Emblia translation. Natyre is Turing-complete with four counters/registers, and my translation scheme (which I tried to keep as simple as possible) only allows four registers. The translation tool recognizes them by the names regA, zeroA, regB, and zeroB - which are the names the Minsky Machine to Natyre translator uses (assuming that the MM program uses two counters (or less) with the names 'A' and 'B' - so be sure to use them in the MM program), which is on purpose, to enable easily translating a two-counter Minsky Machine program (that does not use the 'halt' instruction) into a Natyre program that uses the four registers (or fewer, in some cases), and then translate that into Emblia. The translation scheme requires the Natyre program to have at least 5 instructions; if less, the translator will add meaningless instructions that are never executed, to get the size right.

Some idea of how the translation works might be gained by viewing the program execution in the interpreter (or rather, printing some of its output into a file and viewing it with as wide a screen as possible). Essentially, the translation creates a block of code for each Natyre instruction. The pointer moves on a certain path of numbers to find which block it needs to execute, and within the block a register (regA, zeroA, regB, zeroB - cell values 2, 3, 4, 5 respectively) is increased, and based on whether its new value is an insignia-number or not, the pointer is moved onto a path that moves it to the block that represents the corresponding Natyre instruction that is to be executed next.

F F F F F F aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa k ssssssssssssssssssssssssssssss x R zzzzzzzzzzzzzz y nnnnnnn { F F F F F F }

Above is a line from my design files. The letter choices are purely arbitrary. This is a block for a translation where the number of Natyre instructions is 6 (the size of individual blocks grows as the instruction count grows). Each non-space character here marks a number. The 'F's mark the path used to move the pointer from block to block and eventually get into the correct block to execute it. 'a's are 1s, used for moving the pointer. 'k' moves the pointer to 'R'. If the register of 'k' happens to become insignia-number, it throws the pointer backwards, from where it must travel to 'k' again. Once the pointer jumps to 'R', there it manipulates one of the four registers (2, 3, 4, 5), and moves, depending on the register, 2, 3, 4, or 5 units left (in case the register now has an insignia-number as its value) or right (in case of an ordinary number in the register). If the pointer was moved left, it moves along the line of 's' (which are 1s) until it reaches 'x', which sends it onto the path (on right) that takes it to the block that is executed next. If the pointer is thrown backwards, it lands on an 's' and moves to 'x' again until it is moved to one of the 'F's. Similarly, if 'R' moved the pointer right instead, it will move along 'z' (which are 1s) to 'y', from which it will jump to the correct 'F' path on right. (The 'F' on right inside {} are not part of this block, they are the beginning 'F's of the next block, used here only as a visual aid.) If 'y' throws the pointer left, it travels the 'z's until it reaches 'y' again and is moved where it is meant to go. The 'n' cells return the pointer back to the correct block if a case of insignia-number throws the pointer in the wrong direction and out of the block that is meant to be executed, in the beginning of a block's execution.

Interpreter and translator

emblia.py - Emblia interpreter. Displays the program execution in a similar but slightly more verbose way as in the example above.

natyretoemblia.py - Natyre to Emblia translator. Given an extra argument (such as "abc") the program will output the results of the translation also as an array of integers, which makes inspecting the program data much easier than the final Emblia code.

mmtonatyre.py - A copy of the Minsky Machine to Natyre translator.

-- Keymaker, 23.3.2020