Floater is a language in which the program is an image and pixels define the instructions. The instruction pointer can move in four directions. The memory layout is random-access and stack-based at the same time. Most instructions operate on the top of the stack, some instructions can access the stack using indirect addressing (random-access). The stack pointer defines how large the 'memory' is; addresses below 1 and above the stack pointer don't exist. The language is weak-typed and all memory location contains real numbers ℝ. The size of the stack is unbounded.
The program is self-modifying, and this is in fact the only way to make choices and branches. All 'amounts' are 1-base, meaning the value 1 is the lowest index in all data structures (memory, image, and instruction group). This does not apply to colors.
The author was inspired by PIET, and wanted to make something that didn't require remembering all the cycles, and to make both programming in it and running it easier. Strictly speaking, the former requirement (remembering lots of stuff) hasn't been solved. The amount of essential instructions is low, but there are a lot of exotic instructions (higher arithmetic, trigonometric, etc..). The subset of the language with only the essential instructions should, however, be Turing-complete. The set of instructions is primarily influenced by Assembly language and the memory model by the x87 FPU.
Syntax and Execution
Yes this section bloated a bit, i need to move stuff out here to a section below #Instruction table
The instruction pointer is like a turtle walking over the grid of the image. The upper-left pixel of the image is coordinate (1, 1), X increases to the right and Y increases downwards. Each pixel in the image is a discrete location. There are 16 instruction groups assigned to each of the 16 EGA colors (the 16 Windows colors can also be used). Images can however contain other colors, and when these are encountered the closest EGA color is calculated (allowing lossy compression).
Each instruction group (except NOP) needs a parameter (param) to define which instruction to execute or which literal value to take. The parameter is encoded in the area of all 4-connected pixels with the same instruction color. However, when two instructions from the same instruction group are in direct succession, this would cause a conflict, because the area of the entire connected blob would be the same for both instructions. To prevent this, a mechanism is built in to prevent the area of the next and previous instruction from contributing to the area of the current instruction. Instructions which are more than 1 step away, or not on the line of direction of the instruction pointer, are still counted, and this can be exploited as a feature.
|Here the param is 8|
|Here the param is 10|
|Here the param is 18|
|Here the param is 15|
The memory starts empty, and the stack pointer starts at 0. When you push a value, the stack pointer and memory size increases by 1. The stack pointer always points to the top value of the stack.
Some instructions need a data type more restricted than a float, so here are the conversion rules:
- Float: any possible value (theoretically unbounded and infinitely accurate; only limited by the implementation)
- Integer: A float is rounded to the closest integer (0.5 becomes 1).
- Boolean: Any non-zero value is true. True becomes −1.
- Sign: −1 means negative, 0 means zero, 1 means positive.
There are some configuration flags that modify certain behavior:
- logbase: The value such that log(logbase) = 1 and exp(1) = logbase. Default = e.
- angbase: The minimum value such that cos(ℤ*angbase) = 1 for any integer ℤ. Default = τ = 2π
- iomode: The way values are converted to/from strings when using STDIO. Default = 0.
- 0 = character mode
- 1 = float mode
- 2 = integer mode
- gfxmode: The way to convert values to/from colors when using GRAPHICS. Default = 0.
- 0 = 4 BPP, EGA (0..15) in rgbRGB format
- 1 = 8 BPP, 256 gray shades (0..255)
- 2 = 6 BPP (0..64) in RRGGBB format
- 3 = 12 BPP (0..4095) in RRRRGGGGBBBB format
- 4 = 18 BPP (0..262144) in RRRRRRGGGGGGBBBBBB format
- 5 = 24 BPP (0..16777215) in RRRRRRRRGGGGGGGGBBBBBBBB format
Any instruction that results in an illegal action or state is handled gracefully, using special values or by executing NOP instead. Examples:
- Popping a value when the stack is empty results in the value 0 and no change to the stack pointer.
- Writing a value to a non-existing memory location does NOP.
- Reading a pixel from a non-existing coordinate, e.g. (0, 0), returns 0.
- Dividing by zero returns Infinity or -Infinity. (NaN can also be generated in some cases)
- When param is higher than the highest instruction in the group, NOP is executed.
Start and end
The program starts at the first non-NOP instruction on the first line of the image, otherwise in the upper-left pixel. The instruction pointer always starts facing down. When the instruction pointer walks off of any edge of the image, the program halts.
There are three phases to running a program:
- Fetch instruction,
- Execute instruction,
- Update instruction pointer.
- Look at the color of the pixel under the instruction pointer,
- Find the closest matching color from the 16-color palette, the index in the palette is the 'instruction group'.
- Count the area (param) of the instruction, like so:
- Check if the next and/or previous instructions (in the line of direction of the instruction pointer) have the same color,
- If so, use a complex algorithm to mark cells from those instructions as unavailable,
- Apply a generic 4-connected flood fill algorithm to find the area.
The instruction can update the memory and/or the source image. Therefore execute should commence after fetch to prevent a paradox.
Update instruction pointer
- Depending on the specific instruction, the instruction pointer may be rotated in one of the four cardinal directions.
- It then takes one step in the current direction.
- If the new location does not fall with in the image, execution halts.
Note that the source image might be enlarged by certain instructions, but never by the instruction pointer.
|Color, param||Instruction group||Mnemonic||Instruction|
|0||NOP||NOP||No operation - use for traveling|
|1, param||STACK1||PUSH||Push the integer value equal to the param|
|2, ○||STACK2||DUP||Push the value at the top of the stack|
|2, ○○||SWAP||Swaps the two top elements of the stack|
|3, ○||RAM||GET||Pop address, if address < 0 then address = (SP + address), read value from memory at address, push value|
|3, ○○||SET||Pop address, if address < 0 then address = (SP + address + 1), pop value, write value to memory at address|
|4, ○||CONTROL||PAUSE||Stops the interpreter (no effect when running outside of the Designer)|
|5, ○||LOGIC||NOT||Pop value, push binary inverse (¬value)|
|5, ○○||AND||Pop value1, pop value2, push binary intersection (value1 ∧ value2)|
|5, ○○○||OR||Pop value1, pop value2, push binary union (value1 ∨ value2)|
|5, ○○○○||XOR||Pop value1, pop value2, push binary exclusion (value1 ⊕ value2)|
|6, ○||COMPARE||EQ||Pop value, if value = 0 then push -1 else push 0|
|6, ○○||LT||Pop value, if value < 0 then push -1 else push 0|
|6, ○○○||GT||Pop value, if value > 0 then push -1 else push 0|
|6, ○○○○||SIGN||Pop value, if value < 0 push -1 else if value = 0 push 0 else if value > 0 push 1|
|7, ○||STDIO||Pop value, write value to stdout using the current iomode|
|7, ○○||INPUT||Read value from stdin using current iomode, push value|
|7, ○○○||IOMODE||(Configuration) Pop iomode|
|8, ○||GRAPHICS||SET PIXEL||Pop Y, pop X, pop color, put color to pixel to source image (X, Y) using current gfxmode|
|8, ○○||GET PIXEL||Pop Y, pop X, push color of pixel in source image (X, Y) using current gfxmode|
|8, ○○○○||GFXMODE||(Configuration) Pop gfxmode|
|9, ○||NUMBERS||ZERO||Push 0|
|9, ○○||COMPLEX||Pop value, push (value × i)|
|9, ○○○||RND||Generate random value between 0 (inclusive) and 1 (exclusive), push value(2π)|
|10, ○||ROUND||ROUND||Pop value, push value rounded to closest integer|
|10, ○○||FLOOR||Pop value, push value rounded to next lower integer (⌊value⌋)|
|10, ○○○||CEIL||Pop value, push value rounded to next higher integer (⌈value⌉)|
|10, ○○○○||TRUNC||Pop value, push value rounded to integer closest to zero|
|11, ○||ARITHMETIC||ADD||Pop value1, pop value2, push (value2 + value1)|
|11, ○○||SUB||Pop value1, pop value2, push (value2 − value1)|
|11, ○○○||MUL||Pop value1, pop value2, push (value2 × value1)|
|11, ○○○○||DIV||Pop value1, pop value2, push (value2 ÷ value1)|
|12, ○||SCIENTIFIC||POW||Pop value1, pop value2, push the power (value2value1)|
|12, ○○||SQRT||Pop value, push square root (√value)|
|12, ○○○||EXP||Pop value, push exponent of value|
|12, ○○○○||LOG||Pop value, push logarithm of value|
|12, ○○○○○||LOGBASE||(Configuration) Pop logbase|
|13, ○||TRIGONOMETRIC||SIN||Pop value, push sine of value|
|13, ○○||COS||Pop value, push cosine of value|
|13, ○○○||TAN||Pop value, push tangent of value|
|13, ○○○○||ATN||Pop value, push arc-tangent of value|
|13, ○○○○○||ANGBASE||(Configuration) Pop angbase|
|15, ○||FLOW||FORWARD||Executes NOP|
|15, ○○||DEFLECT||Rotate CW or CCW (avoids the other white pixel)|
PUSH 33 #'!' PUSH 100 #'d' PUSH 108 #'l' PUSH 114 #'r' PUSH 111 #'o' PUSH 119 #'w' PUSH 32 #' ' PUSH 44 #',' PUSH 111 #'o' PUSH 108 #'l' PUSH 108 #'l' PUSH 101 #'e' PUSH 72 #'H' PRINT PRINT PRINT PRINT PRINT PRINT PRINT PRINT PRINT PRINT PRINT PRINT PRINT
Turing-complete because it can interpret Brainfuck. This has not been tested yet, but if for some reason it can not interpret Brainfuck, then, by definition, the specification of this language is erroneous and should be fixed.
- Source and binaries: https://github.com/Zom-B/Floater