- This is still a work in progress. It may be changed in the future.
Bytemap is a self-modifying language that stores all source and data in the same place and format. All commands and data are written onto an indefinitely large 2D grid of bytes. Bytes may be read as instructions, read as data, or written over by other instructions.
Input is an ascii text file - where a single byte is specified in hex by two letters (range 00 - FF) Any bytes that are not specified are automatically assumed to be FF. Execution starts at the first position in the file, however, programs may read/write at places before that. Input files should contain only the 0-F symbols, and have an even number of rows Program terminates when it reads the end program command FF (if its read as a command)
Note on inputs
Since Bytemap's 'code' consists of a 2d byte grid, an interpreter can be made to read either straight up binary files, provided their row width is specified in the beginning. Alternativly (And more interestingly perhaps) a Bytemap interpreter can be made to read any 8bpp image files as program inputs
The following perform actions if they are executed as commands. Note that they can also be read as data
5# Byte Jump 58 Jump Up 54 Jump Left 56 Jump Right 52 Jump Down (correspond to Numpad Directions) All of these take the next 1 byte as an input: 1 The number of bytes to jump in that direction
If executed by itself (i.e. not part of another command), control continues from the jumped to Byte. This allows for jumps to be daisy-chained together (as long as they're not executes as part of another command) Jumps are treated as offset from the start of the command. This means if a jump is part of another command, it will jump to a location relative to the start of its PARENT command (not relative to itself)
0# Console output - sends to the console 00 Output as a decimal (reads all specified bytes, interprets it as two's compliment) 0A Output as an ASCII 0F Output as a hexadecimal (padded with 0 if necessary) All of these take 3 bytes as inputs - the next 3 bytes following (to the right) 1-2 A Byte Jump, indicating from where to read data. If this isn't a Byte Jump, the whole instruction is ignored 3 Number of Bytes to print (starting from the Byte jump, right). If this is 0, instruction is ignored
After execution, control returns to the byte immidiately following the last input
1# Console input - interprets input from the console and writes it to the byte map 10 Read a single whole integer number from console, writes it as a 2's compliment integer 1A Read a single character from console, interpreting it as an ASCII 1F Read pairs of characters, interpreting each pair as a hex value (byte) Both these take 3 Bytes as input: 1-2 A Byte Jump, indicating where to start writing 3 Maximum number of Bytes to write For whole integers, if the bytes necessary to represent the input integer are larger than the bytes allowed to write, the most significant bytes are trunkated. If the integer is smaller, the unecessary bytes are padded with 0s For characters, this determines how many characters to read (1 char = 1 byte) For hex values, this determines how many pairs to read. Any invalid hex numbers read are instead written as FF
After execution, control returns to the byte immidiately following the last input
A# Perform Integer Arithmetic operations. Treats inputs as 2s compliment integers A0 Addition A1 Subtraction (byte1 - byte2) A2 Multiplication (byte1 * byte2) - overflow is thrown away (not written) A3 Integer Division (byte1 / byte2) A4 Modulus operation (byte1 % byte2) All of these take 7 bytes as input 1-2 Byte Jump to first value (byte1) (if this is an invalid jump, treats value as 0) 3-4 Byte Jump to second value (byte2) (if this is an invalid jump, treats value as 1) 5-6 Byte Jump to where to write the result 7 Length - how many bytes to read to perform the operation on (i.e. 4bytes for c-like ints)
C# Comparison operators C1 Less than (byte1 < byte2) C2 Less or equal than (byte1 <= byte2) C3 Equal (byte1 == byte2) C4 Greater or equal than (byte1 >= byte2) C5 Greater than (byte1 > byte2) C6 Different than (byte1 != byte2) All of these take 9 bytes as input 1-2 Byte Jump for first value (if its an invalid byte jump, treats this value as 0 instead) 3-4 Byte Jump for the 2nd value (if its an invalid byte jump, treats this value as 0 instead) 5 Length of byte values to compare (if specified as 0, the TRUE path is always taken) 6-7 Byte Jump to take if comparison is TRUE (if invalid, command is ignored) 8-9 Byte Jump to take if comparison is FALSE (if invalid, command is ignored)
After this command, execution continues either at the TRUE location, the FALSE location, or if either of those are invalid, it continues immidiately after the command
FF Terminates program (if read as a command)
Note: Since no working interpreter exists yet, these were written and verified by hand to get an idea of how well this idea could work
0A56050CFF48656C6C6F20576F726C6421 Explanation: 0A - Output as ASCII text 5605 - Starting at 5 bytes to the right (56 = right). Relative to the start of the output command, i.e. 0A 0C - Number of bytes to read from code and output - in this case 12 (Hello World!) FF - Terminate Program. After reading the entire hex string and outputting it, control returns here. The rest of the code is simply the ASCII representation of 'Hello World!' including the space and !
Output 1 continously
005603015404 Explanation: 00 - Output decimal (read as two's compliment) 5603 - Start 3 bytes right of the output command 01 - Read 1 byte. Note that this is also read as the data to output, illustrating how commands can read themselves 5404 - Byte Jump command - jumps 4 bytes to the left of it, essentially creating a loop
Quine (with explicit termination)
0F540005FF Explanation: 0F - output bytes in hex (basically same encoding as this file) 5400 - starting from 0 spaces to the left of 0F, meaning start at 0F. This is the equivalent of 5800 or 5600 or 5200 05 - Reading 5 bytes from the starting point FF - After the code outputs itself, this explicitly terminates the program
Quine (with implicit program termination)
0F540004 Explanation: 0F - output bytes in hex (basically same encoding as this file) 5400 - starting without an offset from the start of 0F 04 - Reading 4 bytes from the starting point
After that the execution pointer tries to read past the specified info. This is completely legal, and since any unspecified bytes are assumed to be FF, it receives FF as its input, and terminates, implicitly.
Truth Machine (inputs an integer, stored as 4-bytes), if the input is 0 it prints 0 and exits. Otherwise, it continously prints 1
105201045202 FFFFFFFFFFFF 5201FFFF5404 C3580200000452015202 00540001FF 005603015404 Explanation: 105201045202 - (10) input a single int, (5201) write at location 1 byte down, (04) write 4 bytes, (5202) new cmd, jump down 2 FFFFFFFFFFFF - Empty line for writing the inputted int to. 5201FFFF5404 - (5201) jump down 1 byte, (FFFF) filler, (5404) jump left 4 bytes (the 5202 command from row 1 jumps here) C3580200000452015202 - (C3) Equality comparison, (5802) - jump up 2 bytes to read the inputted int, (0000) invalid jump, value assumed 0 (04) Read 4 bytes, (5201) jump executed if comparison is TRUE, jump down 1 byte, (5202) jump executed if comparison is FASLE, jump down 2 bytes 00540001FF - (00) output, (5400) starting at 0 bytes left (i.e. starting at 00), (01) read 1 byte of data, (FF) terminate 005603015404 - Output 1 continuously - see the explained example above
Self-modificiation and Self-replication
A simple self-replicating pattern (copies itself as the next command to execute, and does so indefinitely along a single row)
A000005400560808 Explanation: A0 - Perform integer Addition 0000 - First offset - this is an invalid byte jump, so the value is automatically assumed to be 0 (see spec above) 5400 - Second byte offset, basically starts at A0 (since it says 'move left by 0 bytes') 5608 - Offset of where to write the result - in this case 56=Right by 08 bytes. This places result immidiately after command. 08 - How many bytes to read/write - in this case 8, which is the length of the command
This illustrates how the Arithmetic commands can be used to copy data, by simply performing addition to 0. After the command is executed, a perfect copy of it is placed immediately after it, which is where the execution pointer goes to read, and repeats the whole thing again, and again...
A column-wise self-replicating pattern (copies itself downwards, and moves the command pointer there)
A00000540052010FA000005403540D025410 Explanation: A0 - Perform integer Addition 0000 - First offset - invalid, so value assumed to be 0 5400 - Second offset - starting at the beginning of the command at A0 5201 - Where to write the output, in this case 52=Down, 01 byte - meaning directly below the A0 byte 0F - How many bytes to read/write. 0F = 15 bytes, which is the entire length of the line (in bytes) After this is executed, a perfect copy of the line is placed below it. However, the execution pointer continues RIGHT. The next thing it reads is the A0 command after the 0B byte A0 - Perform addition again 0000 - Assuming argument is 0 (standard way to copy something) 5403 - 2nd offset - 3 bytes to the left (54=left). This starts reading at the 52 byte (which is 3 left of the second A0) 540D - Where to write the result - in this case 13 (D=13) bytes left of the 2nd A0. This is right at the start of the line. 02 - How many bytes to read/write - only 2 are needed. This has now placed the command 5201 at the front of the line. 5201 is coincidentally the command to jump down by 1 byte. Execution pointer finishes the command, and still continues right, where it reads the last 2 bytes: 5410 5410 - Byte Jump - Jumps to the left (54) by 16 bytes. This places the execution pointer at the start of the line. Here the execution pointer now reads the newly written 5201 command, causing it to jump directly down to the clean-copied line.
Since the 2nd line is completely unmodified (identical to the original code), it repeats the whole process again, cloning itself below, on the 3rd line, and proceeding indefinitely. This example shows again how parts of a command can be used as data by another command, and overriding of the commands can be used to achieve some interesting effects.