## The first program A simple example would be to print all $2^{16}$ possible colors to the screen. We make our lives easier, by mapping each index of the screen-buffer to the color which is encoded with the index. Here, we use the names of the opcodes instead of their numbers. ``` Set 501 1 0 // Write the value 1 to address 501 Set 502 65535 0 // Write the largest possible value to 502 Print 500 500 0 // Display color=@500 at screen-index=@500 Add 500 501 500 // Increment the color/screen-index Cmp 500 502 503 // See if we are not at the max number Xor 503 501 503 // Negate it Skip 0 4 503 // Unless we are at the max number, go back 4 instructions Sync 0 0 0 // Sync GoTo 0 0 0 // Repeat to keep the window open ``` We could rely on the fact that the value at index 500 starts at zero and we did not have to initialize it. To build a program that we can execute, we could use python: ```python import struct code = [ 0, 501, 1, 0, #Opcodes replaced with numbers 0, 502, 65535, 0, 11, 500, 500, 0, # ... ] with open("all_colors.svc16", "wb") as f: for value in code: f.write(struct.pack(" Add variable "1" variable Negate variable -> Xor variable "1" variable JumpTo location condition -> GoTo "0" location condition // where location is not a variable JumpAfter location condition -> GoTo "1" location condition // where location is not a variable ``` ### Labels As it stands, we still have to count or remember line numbers for the `Skip` and `GoTo` instructions. One simple Idea would be to allow a label (or multiple) for each line. ``` Print color color 0 Incr color Cmp color "65535" condition Negate condition JumpTo @start condition Sync 0 0 0 JumpTo @start "0" ``` References can then be resolved by counting the lines (and multiplying by 4 to get the memory address). ### Including Data We might want to include large sequences of data for things like sprites. This can be done in a way very similar to constants. The syntax could look something like this ``` Print "path/to/binary.dat" index 0 ``` This does two things: - 1. It places the content of the binary file into the program. - 2. It creates a constant that stores the first address of this content. This way, we can iterate over the data at runtime, using the `Deref` instruction. ### Enabling memory management So far, we can only use variables of fixed size. If we want to allocate memory at runtime, we have to know what part of memory is not used by the program (or variables, constants, data ...). This can be easily done by adding a constant called `"free"` which resolves to an address where the first free address is stored. ## The first compiler We want to design a simple programming language that is then compiled down to the assembly syntax we just made up. We will not give any examples for the syntax here as this can be easily changed according to personal preference.In order to get a working version that will suffice for our next project, we need three basic things: conditionals, loops and routines. ### Conditionals The easiest kind of conditional would be of the form `if(condition_variable) {block}`. We can compile this in three steps: - 1. Compile the block to lines of instructions. - 2. Generate a unique label for the if block and add it to the last instruction of the block. - 3. Add the instruction `JumpAfter @unique_label condition_variable` to the front of the block. ### Loops There are, of course, different kinds of loop we want to consider. A loop without a condition can be compiled as follows: - 1. Generate two unique labels for the loop: `unique_start` and `unique_end` - 2. Compile the inner block of the loop with the labels as additional contexts so that statements like `break` and `continue` can be handled. - 3. Label the first instruction of the block `unique_start`. - 4. Add the instruction `JumpTo @unique_start "0" ` to the end. ### Routines There are many different ways calling a routine can be implemented. Here is a very simple way this might be done. At the callsite, we do the following: - 1. Copy each of the arguments into the corresponding variable of the function. - 2. Write the current value of the instruction pointer into a variable scoped to the function. - 3. Jump to the first instruction of the function. - 4. Copy the variables from the function back into the local variables. When compiling the function, we just have to add instructions to the end to jump back to the first instruction of 4. This method, while simple, has some downsides, for example recursion is not possible. It also relies on the fact, that we know the addresses of the function's arguments. Ideally, we would like to include functions that are already compiled.