Compiler, assembler and VM for the intcode computer from Advent of Code 2019. This is mainly for fun and to try out some different Rust parsing libraries.
There are four crates in this project:
intcode
: Wrapper binary for executing the compiler, assembler or VMvm
: An intcode VM that can run intcode files:intcode run input.int
asm
: An intcode assembler that can assemble intcode programs from intcode assembly:intcode asm input.asm
compiler
: An intcode compiler that can compile intcode programs from a higher-level language:intcode compile input.ic
Building or installing requires a working Rust Installation.
Build from source and install:
$ git clone https://github.com/benediktwerner/intcode
$ cd intcode
$ cargo install --path intcode
$ intcode
To just build from source:
$ git clone https://github.com/benediktwerner/intcode
$ cd intcode
$ cargo build
$ ./target/debug/intcode
The compiler can compile code written in a simple high-level language to intcode:
// Comment
var x; // Variables must be declared before their first use. Globals are initialized to zero.
var y = 13; // but they can also be declared on the first assignment
var z = input(); // Get input
print(y); // Produce output
func fib(x) { // Arguments and variables declared in functions are seperate for each call
y = 42; // Modify a global variabl
if x < 2 {
return 1;
}
return fib(x - 1) * x; // Recursion is possible
}
print(fib(z));
print(x); // Still zero because the function has its own scope
print(y); // Changed to 42
const LENGTH = 6 * 7; // Constants are computed at compile time
array a[LENGTH]; // Declare a new array with the given length. Only constant expressions can be used to specify the length
var index = 0;
while index < LENGTH {
a[index] = fib(index);
}
print(a[LENGTH - 1]);
An extension for syntax highlighting in Visual Studio Code can be found in vscode-syntax-highlighting
.
It works if the file has the .ic
extension.
This program computes Day 1 Part 1 in intcode:
# Comments start with '#'
start: # Label for jump
in x # Read to memory location x. The assembler automatically 'allocates' this memory after the program.
eq x 0 tmp # Check if input == 0. If yes, stop and print the result.
jmp_true tmp :end # Label targets must be prefixed with a ':' (to get the address instead of the value)
div x 3 x
sub x 2 x
add total x total
jmp :start
end:
out total
hlt
# Initialize 'total' to 0. The assembler does this automatically so
# this isn't really neccessary, but it shows the concept.
total: data 0
More examples can be found in the examples
directory.
Operation | Effect | Note |
---|---|---|
mov a target |
target = a |
|
add a b target |
target = a + b |
|
sub a b target |
target = a - b |
|
mul a b target |
target = a * b |
|
div a b target |
target = a // b |
Can be quite slow, only works for positive numbers atm. |
mod a b target |
target = a % b |
Can be quite slow, only works for positive numbers atm |
divmod a b target rest |
target, rest = divmod(a, b) |
Can be quite slow, only works for positive numbers atm |
in target |
target = input() |
|
out a |
print(a) |
|
jmp target |
goto target |
|
jnz a target |
if a != 0: goto target |
Alias: jtrue |
jz a target |
if a == 0: goto target |
Alias: jfalse |
eq a b target |
target = a == b |
|
neq a b target |
target = a != b |
|
lt a b target |
target = a < b |
|
leq a b target |
target = a <= b |
|
gt a b target |
target = a > b |
|
geq a b target |
target = a >= b |
|
and a b target |
target = a and b |
|
or a b target |
target = a and b |
|
not a target |
target = not a |
|
add_rel_base a |
rel_base += a |
|
load a target |
target = memory[a] |
|
store a target |
memory[target] = a |
|
hlt |
exit() |
Alias: halt |
data x |
stores x as raw data |
Accepts multiple arguments, e.g. data 1 5 13 42 |
array val len |
stores val len times as raw data |
|
push val |
memory[rel_base] = val; rel_base += 1 |
|
pop target |
rel_base -= 1; target = memory[rel_base] |
|
call target |
push(ip) |
|
ret |
goto pop(ip) |
The predifined label __end
can be used to get the address after all the generated code.
This is useful for putting a stack after the program: add_rel_base :__end
. Simply putting
a label at the end of the program will not work if the program contains
undeclared labels/variables because they will be put after the program.
- Identifier positional:
some_name
- Identifier immediate:
:some_name
- Identifier relative:
%some_name
- Value positional:
[42]
- Value immediate:
42
- Value relative:
%42
All the code in this repository is in the public domain. Or if you prefer, you may also use it under the MIT license or CC0 license.
Note though that some parts of this code use external libraries which have their own licenses.