EarScript
EarScript is a programming language originally designed to power the procedural generation behind the rhythm game [EternAlgoRhythm]. It can be considered both an esoteric programming language, as well as a [domain specific language].
It is based on the question "what if brainfuck had goto statements?"
It is designed to only directly work with integers, has many built-in features to facilitate procedural generation (such as sequencers and randomizers), and to mainly be interpreted by EarScript machines.
Its official documentation is in [this wiki]. You can try EarScript online in [this link]!
History
Agecaf, a mathematician and game developer, had been interested in creating a new programming language. One idea they had was to enhance brainfuck with "quality of life features" such as being able to replace
+++++
with +5
, arrays of integers being two dimensional, and being able to have multiple arrays of integers. This idea was tentatively named brainteger, but never fully defined or implemented.
Then, while creating their rhythm game EternAlgoRhythm, and while reading the Bytecode chapter in the Game Programming Patterns book by Bob Nystrom [[1]], Agecaf decided to create and implement a three-layered programming language consisting of,
- A bytecode, EarByte, designed to only work with integers.
- A programming language, EarScript, which compiles to EarByte.
- A graphical programming language, EarVis, which compiles to EarScript.
EarScript was first implemented, both its compiler and the EarByte virtual machine, in GDScript, which was not efficient enough for real-time use until it all was elegantly shoved into a parallel thread, which allowed for running of the language to generate music and notes in parallel to the rhythm gameplay of EternAlgoRhythm.
EarScript was later given its formal documentation, as well as a compiler and interpreter written in JavaScript, to allow interested users to try it online.
A proper compiler in C or C++ remains yet to be written.
Description
EarScript is compiled into EarByte, which is then run by an EarScript virtual machine, which will be called a machine. In practice, EarScript might be directly interpreted, or compiled into binaries.
Each machine consists of one or more tables. Each table is a 2 dimensional array of integers. By default, tables have a 1x1 size, but they can be resized, and new tables can be created. Each table also has a pointer, pointing at a particular cell within the table, which works in a similar way to brainfuck's pointer. At every point in the execution of code, there is an active table, whose pointer points to the cell actively being worked on.
Other than comments, which start with a #
and go all the way to the end of the line, EarScript code is a sequence of tokens, where each token has a head and a tail. Tokens can be placed together without whitespace, or separated by whitespace; identation is ignored, however a token's head and tail cannot be separated by whitespace.
Heads
The head of a token includes its operator character, which is the first character (and sometimes only character) in the head. Here's a description of heads, note that many of them take an argument x given by the tail.
$ | Change table to table x. |
> | Move pointer to the right by x. |
< | Move pointer to the left by x. |
^ | Move pointer upwards by x. |
` | Move pointer downwards by x. |
: | Move pointer to column x. |
; | Move pointer to row x. |
\ncol | Change number of columns to x. |
\nrow | Change number of rows to x. |
For operations, a is given by the current cell value, while b is given by the tail.
= | Set cell value to b. |
. | Output the current cell value. |
, | Get input and set the cell value to it. |
+ | Set cell value to a + b. |
- | Set cell value to a - b. |
* | Set cell value to a * b. |
/ | Set cell value to a / b, using integer division. |
% | Set cell value to a % b, using positive modulo. |
! | Set cell value to the bitwise not of a. |
& | Set cell value to the bitwise and of a and b. |
? | Set cell value to the bitwise or of a or b. |
\pow | Set cell value to the power of a by b. |
\min | Set cell value to the maximum between a and b. |
\max | Set cell value to the minimum between a and b. |
The flow operators allow one to jump to different points of the code, for these the tail is the label to create or to jump to.
@ | Set a label in the code. |
' | Jump to label. |
" | Jump to label but keep track of where the jump started, for a future return. |
~ | Return to the source of the last " jump, or end program. |
There is one last category of heads, which will be explained later.
Tails
Tails are used to give operators some additional information as to what to do. There are three types of tails; integers, table names, and label names. Label names are exclusively used by the flow operators @, ' and ". Table name tails are only used by the table change operator $, used to both create new tables and swap between tables.
There are many different types of integer tails;
- An empty tail is considered to be 1, so for example
+
adds 1,*
multiplies by 1, etc. - Positive numbers can be written directly, such as
+5
to add 5. - Negative numbers can be written using _, such as
*_1
to multiply by -1. - Table names can be used to use the table's current cell (the one pointed at by the table's pointer), this allows using other table's values while manipulating a table, such as
-otherTable
to subtract the otherTable's current value. - Relative references can be used, such as
+l
to add the value of the cell to the left to the current cell.
Control flow
The operator characters [ ] { } ( ) |
are used for control flow.
[ ]
are used for loops.{ }
are used for sequencers, which may have multiple branches separated by|
.( )
are used for conditionals, which may have one or two branches, separated by|
.] } )
will always close a corresponding operator character and have no variations, however[ { (
have variations giving different functionality.
[ | Or [1 runs the loop until once the corresponding [ is reached, the current cell value is 0.
|
[n | Where n is a positive integer given by the tail, loops n times. |
[i | Infinite loop, can be broken out of through flow operators. |
[r | Repeats the loop with probability b/(1+b), where b is the value of the tail, the total number of loops is thus a geometric distribution with an average of b loops. |
Conditionals essentially branch over a true and a false branches as follows;
(condition true_branch | false_branch)
, though the false branch can be omitted as follows; (condition true_branch )
, in which case the true branch is only executed if the conditional is true, and otherwise the true_branch is skipped. Here b is the value of the tail, and a is the current cell value.
( | Also (1 , true if a is non-zero.
|
(eq | True if a is equal to b. |
(ne | True if a is not equal to b. |
(le | True if a is less than or equal to b. |
(lt | True if a is less than b. |
(ge | True if a is equal to b. |
(gt | True is a is greater than or equal to b. |
(div | True is a is divisible by b. |
(x | True the first b times it is executed. |
(c | True is b times, then false once, then repeats the cycle. |
(r | True with probability 1/(1+b), false with probability b/(1+b). |
Sequencers have multiple branches and each time the code reaches the sequencer, it chooses a different (or the same) branch. Branches are separated by |
, for example
{ branch1 | branch2 | branch3 }
. Once again,a is the current cell value, b is the value of the tail, and now n is the number of branches. Note that there exists a maximum number of branches, dependent on the machine implementation (usually 16).
{ | Repeats each branch b times, then moves to the next branch. |
{r | Chooses a branch randomly, then repeats it b times, then chooses randomly again. |
{s | Shuffles the branches, repeats them each b times moving to the next branch in the shuffled order, and once all are done, shuffles again. |
{m | Selects a branch based on the value of a modulo n. |
Relation to brainfuck
Note that []
in brainfuck corresponds to ([])
in EarScript, as []
in EarScript only has the looping behavior, whereas ()
in EarScript has the conditional behavior. Other than this, EarScript is a superset of brainfuck, so any brainfuck code can be transformed into EarScript by
- Adding
\nrowX
with the desired array length at the start of the code. - Replacing
[
with([
and replacing]
with])
. - Making sure all brainfuck comments are placed behind a
#
.
Examples
Hello World
By default, EarScript does not work with characters, so instead we'll output 42.
# This is a comment =42 . # Assignment to 42, output.
Fibonacci Numbers
This example showcases the usefulness of having tables of custom sizes, as well as being able to use relative references in tails.
# Fibonacci Example \ncol2 =1 > =1 > # Initialize a table to [1 1] [10 . +l > ] # Loop 10 times, output, add the cell to the left, move right
Sequencers and Randomizers
Sequencers and randomizers are simple to use, and very powerful for procedural generation.
# Sequencers [12 {=1|=2|=3} .] # Output: 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3,
# Randomizers [12 {r=1|=2|=3} .] # Output Example: 1, 2, 1, 1, 1, 3, 3, 1, 2, 1, 2, 2,
Flow operators
Through flow operators one can have "functions", as well as use flow to jump over function declarations.
'start # Jumps to label start @func # Label func +. # Increases value, outputs ~ # Return @start # Label Start =42 # Set value to 42 [3 "func ] # Loop 3 times, call func and return
Table manipulation
Tables can be quite useful, one can use 1x1 tables like variables.
$main # Name default table $other # Create table other $ # Return to main table $other=4$ # Move to table other, set it to 4, return to main table +other*other*other # Use table value . # Output: 64 = 4^3
EarVis
EarVis is a node-based visual programming language that compiles to EarScript. It relies on meta comments to describe the nodes' position and linkage.
The idea is that each node is a @label ... ~
block, and where node links become goto statements such as 'label
.
This alleviates the one problem of goto statements, the fact that they are really hard to visualize. This is a fundamental principle called;
- Making code spaghetti look like actual spaghetti.
The free demo of EternAlgoRhythm contains an editor for EarVis setup for the generation of music, and video tutorials are available [[2]].
Implementations
There is an implementation of the EarScript compiler and Earbyte machines in GDScript, which is available in the EternAlgoRhythm game.
An open source implementation in JavaScript is available [[3]], which is the implementation powering the "Try EarScript online!".
Agecaf claimed to have been working on a C++ implementation, but such implementation has yet to be shown to actually exist.
Category:Languages Category:2024 Category:Probabilistic Category:Cell-based Category:Brainfuck derivatives Category:Implemented Category:Low-level