Skip to content

A single-instruction esoteric programming language, inspired by Chris Domas' "MovFuscator"

Notifications You must be signed in to change notification settings

eriknyquist/addlad

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 

Repository files navigation

AddLad is an esoteric programming language that emulates a One-Instruction-Set-Computer (OISC) with transport-triggered I/O, inspired partially by Brainf**k and partially by Chris Domas' movfuscator.

AddLad programs operate on a "memory tape", a fixed-size array of 8-bit unsigned integers. There is only a single operation available in AddLad, which reads a value from one array index (the source index), and adds it to the value stored in another array index (the destination index). A single operation consists of an integer (the destination array index, where the result will go), followed by a comma, followed by another integer (the source array index), and finally a semicolon:

6, 7;   # Read from array index 7, and add to value stored in index 6
6, 8;   # Read from array index 8, and add to value stored in index 6

Enclosing an array index with square brackets [] will cause the value at that array index to be used as the array index (i.e. a pointer to another array index):

6, [7]; # Read from array index stored at array index 7, and add to value stored in index 6

In order to facilitate some of the features that programmers are accustomed to (specifically: input, output, modifying the instruction pointer, and non-zero values), 4 specific negative array indices (-1 through -4) are reserved for special purposes, and are referred to as "registers":

  • array index -1: Output register Using index -1 as the destination index will write the value at the source index to stdout. Using index -1 as the source index will add 1 to the value stored in the destination index. This is the only way you can change any initial value in the array from 0 to 1, and most programs will start by adding array index -1 to some other array index.
  • array index -2: Input register Using index -2 as the source index will read 1 character from stdin, and then add the read value to the value stored at the destination index. Using index -2 as the destination index will result in no change to the value at the source index.
  • array index -3: Instruction pointer increment register Using index -3 as the destination index will add the value at the source index to the current instruction pointer value. If the value at the source index is zero, then the instruction pointer will not be changed and execution will continue normally. Using index -3 as the source index will result in no change to value at the destination index.
  • array index -4: Instruction pointer decrement register Using index -4 as the destination index will subtract the value at the source index from the current instruction pointer value. If the value at the source index is zero, then the instruction pointer will not be changed and execution will continue normally. Using index -4 as the source index will result in no change to the value at the destination index.

NOTE: None of the registers can be used with pointer syntax (e.g. [-1]), and this will be a parsing failure.

  • All array values (including registers) are 8-bit unsigned integers, so values are fixed between 0-255. Integer overflow/underflow will wrap around.
  • Using the same array index for the source and destination is allowed, and will cause the value stored at the array index to be doubled.
  • Instruction pointer increments or decrements are allowed to overflow/underflow. They will wrap around to the first/last instruction in this case.
  • Default array size is 100k elements
  • All whitespace is ignored. Single-line comments are available with the # character.

Install with pip:

pip install addlad

To run the AddLad interpreter, use the addlad command and pass your AddLad source file as an argument:

addlad my_addlad_code.ps

This section contains some example AddLad programs, and showcases a couple of interesting ways that standard programming constructs can be expressed with AddLad.

263,-1;     # Add 1 to index 263
263,263;    # Put '2' in index 263, for arithmetic later
264,263;
264,264;    # Put '4' in index 264, for arithmetic later
265,264;
265,265;    # Put '8' in index 265, for arithmetic later
25,265;     # Index 25, space character
25,25;
25,25;
20,25;      # Index 20, first character 'H'
20,20;
20,265;
30,20;      # Index 30,  last character 'd'
30,265;
30,265;
30,265;
30,264;
21,30;      # Index 21, 'e' character
21,-1;
22,21;      # Index 22, first 'l' character
22,264;
22,263;
22,-1;
23,22;      # Index 23, second 'l' character
29,23;      # Index 29, last 'l' character
24,29;      # Index 24, first 'o' character
24,263;
24,-1;
27,24;      # Index 27, last 'o' character
28,27;      # Index 28, 'r' character
28,263;
28,-1;
26,28;      # Index 26, 'w' character
26,264;
26,-1;
31,265;     # Index 31, newline character
31,263;
-1,20;      # Print 'H'
-1,21;      # Print 'e'
-1,22;      # Print 'l'
-1,23;      # Print 'l'
-1,24;      # Print 'o'
-1,25;      # Print ' '
-1,26;      # Print 'w'
-1,27;      # Print 'o'
-1,28;      # Print 'r'
-1,29;      # Print 'l'
-1,30;      # Print 'd'
-1,31;      # Print '\n'

It may seem like AddLad isn't capable of constructs like this:

if ((value >= LOWER_BOUND) && (value <= UPPER_BOUND))
{
    // Some conditional code
}

But if we take some inspiration from Stephen Dolan's "mov is Turing-complete" paper, and Chris Domas' MovFuscator project, we can do some interesting things.

This section shows how to create an AddLad program that reads 1 byte from stdin, and prints uppercase if the read byte is an ASCII uppercase letter, and prints lowercase otherwise. The full example program is available in the Github repo

  1. Change the values stored at array indices 65 through 90 (ASCII 'A' through 'Z') from 0 to 1:

    65,-1;
    66,-1;
    67,-1;
    68,-1;
    69,-1;
    70,-1;
    71,-1;
    72,-1;
    73,-1;
    74,-1;
    75,-1;
    76,-1;
    77,-1;
    78,-1;
    79,-1;
    80,-1;
    81,-1;
    82,-1;
    83,-1;
    84,-1;
    85,-1;
    86,-1;
    87,-1;
    88,-1;
    89,-1;
    90,-1;
    

    It's important that all other array indices in the 0-255 range remain at their initial default value of 0. After this step, array indices 0-64 should hold a value of 0, array indices 65-90 should hold a value of 1, and finally array indices 91-255 should hold a value of 0. You'll see why in the following steps.

  2. Read 1 byte from stdin, into array index 260:

    260,-2;
    
  3. Interpret the byte read from stdin as an array index, and add the value stored in that array index to the value stored in array index 261:

    261,[260];
    

    After this step, if the value stored in array index 261 is 1, then we know that the byte read from stdin is an uppercase letter, since it's within the range of array indices that we set to a value of 1 in step #1.

    If the value stord in array index 261 is 0, then we know that the byte read from stdin is not an uppercase letter, since it's outside the range of array indices that we set to a value of 1 in step #1, and those array indices will still be at their default value of 0.

  4. You can now use the value of "0" or "1" obtained in step #3 to (for example) switch between different array indices which contain different instruction pointer increment values. This is how the examples/condition.ps program decides which characters to print.