Skip to content

Instantly share code, notes, and snippets.

@PurpleMyst
Created December 22, 2017 19:46
Show Gist options
  • Save PurpleMyst/908f94abebabda64ead72711c9b6e3f5 to your computer and use it in GitHub Desktop.
Save PurpleMyst/908f94abebabda64ead72711c9b6e3f5 to your computer and use it in GitHub Desktop.
An Assembunny interpreter. (AoC 2016 Day 12)
#[derive(Debug, Clone)]
enum Instruction {
Unary(String, String),
Binary(String, String, String)
}
#[derive(Debug)]
struct CPU {
a: isize,
b: isize,
c: isize,
d: isize,
pc: isize,
code: Vec<Instruction>
}
impl Instruction {
fn from_string(instruction: String) -> Self {
let mut instruction_ws = instruction.split_whitespace();
macro_rules! next_argument {
() => {
instruction_ws.next().expect("Expected more arguments.").to_owned()
};
}
macro_rules! instruction {
(unary, $mnemonic: ident) => {
Instruction::Unary($mnemonic.to_owned(), next_argument!());
};
(binary, $mnemonic: ident) => {
Instruction::Binary($mnemonic.to_owned(), next_argument!(), next_argument!());
}
}
let mnemonic = instruction_ws.next().expect("No mnemonic found.");
match mnemonic {
"cpy" => instruction!(binary, mnemonic),
"inc" => instruction!(unary, mnemonic),
"dec" => instruction!(unary, mnemonic),
"jnz" => instruction!(binary, mnemonic),
"tgl" => instruction!(unary, mnemonic),
_ => panic!("Unknown instruction!")
}
}
}
impl CPU {
fn new<I: IntoIterator<Item = String>>(code: I) -> CPU {
CPU {
a: 0,
b: 0,
c: 0,
d: 0,
pc: 0,
code: code.into_iter().map(Instruction::from_string).collect()
}
}
fn get_value(&self, argument: &str) -> isize {
match argument {
"a" => self.a,
"b" => self.b,
"c" => self.c,
"d" => self.d,
_ => argument.parse().expect("Could not parse argument.")
}
}
fn set_value(&mut self, register: &str, value: isize) {
match register {
"a" => self.a = value,
"b" => self.b = value,
"c" => self.c = value,
"d" => self.d = value,
_ => panic!("Tried to set unknown register {:?}.", register)
}
}
fn execute_instruction(&mut self) -> bool {
if self.pc < 0 || self.pc >= (self.code.len() as isize) {
return false;
}
let instruction = &self.code[self.pc as usize].clone();
match *instruction {
Instruction::Unary(ref mnemonic, ref x) => match mnemonic.as_ref() {
"inc" => {
let value = self.get_value(x);
self.set_value(x, value + 1);
},
"dec" => {
let value = self.get_value(x);
self.set_value(x, value - 1);
}
_ => panic!("Unknown unary instruction!")
},
Instruction::Binary(ref mnemonic, ref x, ref y) => match mnemonic.as_ref() {
"cpy" => {
let value = self.get_value(x);
self.set_value(y, value);
},
"jnz" => {
let x_value = self.get_value(x);
let y_value = self.get_value(y);
if x_value != 0 {
// We offset y_value by one to account for the `self.pc += 1` later.
self.pc += y_value - 1;
}
},
_ => panic!("Unknown binary instruction!")
}
}
self.pc += 1;
true
}
fn execute_all_instructions(&mut self) {
while self.execute_instruction() {}
}
}
fn main() {
let code = include_str!("assembunny_code.txt").split('\n')
.map(|s| s.to_owned())
.filter(|s| s.len() > 0);
let mut cpu = CPU::new(code);
cpu.execute_all_instructions();
println!("{}", cpu.a);
}
cpy 1 a
cpy 1 b
cpy 26 d
jnz c 2
jnz 1 5
cpy 7 c
inc d
dec c
jnz c -2
cpy a c
inc a
dec b
jnz b -2
cpy c b
dec d
jnz d -6
cpy 13 c
cpy 14 d
inc a
dec d
jnz d -2
dec c
jnz c -5
The assembunny code you've extracted operates on four registers (a, b, c, and d) that start at 0 and can hold any integer. However, it seems to make use of only a few instructions:
cpy x y copies x (either an integer or the value of a register) into register y.
inc x increases the value of register x by one.
dec x decreases the value of register x by one.
jnz x y jumps to an instruction y away (positive means forward; negative means backward), but only if x is not zero.
The jnz instruction moves relative to itself: an offset of -1 would continue at the previous instruction, while an offset of 2 would skip over the next instruction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment