Skip to content

Instantly share code, notes, and snippets.

@learnopengles
Last active January 16, 2017 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save learnopengles/834b6fdedbd605c5853093ac76be22f7 to your computer and use it in GitHub Desktop.
Save learnopengles/834b6fdedbd605c5853093ac76be22f7 to your computer and use it in GitHub Desktop.
Assembler for nand2tetris project 6 written in Rust.
// Assember for nand2tetris project 6 written in Rust.
// CC BY-SA 4.0 -- https://creativecommons.org/licenses/by-sa/4.0/
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
fn main() {
let args: Vec<String> = env::args().collect();
match args.len() {
3 => {
let in_name = &args[1];
let out_name = &args[2];
assemble(in_name, out_name).unwrap();
}
_ => {
println!("Usage: hack-asm input_file output_file");
}
}
}
fn assemble(in_name: &str, out_name: &str) -> io::Result<()> {
let in_file = try!(File::open(in_name));
let out_file = try!(File::create(out_name));
let mut reader = BufReader::new(in_file);
let mut writer = BufWriter::new(out_file);
// First, build up all labels
let mut label_map: HashMap<String, i32> = HashMap::new();
let mut instruction_address = 0;
for line in reader.by_ref().lines() {
let line = line.unwrap();
let line = get_trimmed_line(&line);
if line.is_empty() {
// Not an instruction, skip.
} else if line.starts_with('(') && line.ends_with(')') {
let label = &line[1..line.len() - 1];
if label.is_empty() {
panic!("Empty label");
} else if let Some(label) = label_map.insert(label.to_string(), instruction_address) {
panic!("Label {:?} was already defined.", label);
}
} else {
// Assume it's an instruction. If it's not, validation will fail further below anyways.
instruction_address += 1;
}
}
// Now parse
let mut var_map: HashMap<String, i32> = HashMap::new();
let mut next_var_address = 16;
reader.seek(SeekFrom::Start(0)).unwrap();
for (num, line) in reader.lines().enumerate() {
let line = line.unwrap();
println!("Parsing line {}: {}", num, line);
let line = get_trimmed_line(&line);
if line.is_empty() {
// Skip
} else if line.starts_with('(') {
// We already handled labels earlier, skip.
} else if line.starts_with('@') {
// "A" instruction
let address = &line[1..];
let address_number = match address {
"SP" => 0,
"LCL" => 1,
"ARG" => 2,
"THIS" => 3,
"THAT" => 4,
"R0" => 0,
"R1" => 1,
"R2" => 2,
"R3" => 3,
"R4" => 4,
"R5" => 5,
"R6" => 6,
"R7" => 7,
"R8" => 8,
"R9" => 9,
"R10" => 10,
"R11" => 11,
"R12" => 12,
"R13" => 13,
"R14" => 14,
"R15" => 15,
"SCREEN" => 16384,
"KBD" => 24576,
_ => {
if let Ok(address) = address.parse::<i32>() {
address
} else {
if let Some(label_index) = label_map.get(address) {
*label_index
} else {
// Just assume it's a variable
if var_map.contains_key(address) {
*var_map.get(address).unwrap()
} else {
let var_address = next_var_address;
next_var_address += 1;
var_map.insert(address.to_string(), var_address);
var_address
}
}
}
}
};
writeln!(&mut writer, "{:016b}", address_number).unwrap();
} else {
// "C" instruction
// The format of the C instruction is dest=comp;jump
// Either dest or jump fields may be empty
let idx_dest = line.find('=');
let idx_jump = line.find(';');
let dest = idx_dest.map(|idx| &line[0..idx]);
let jump = idx_jump.map(|idx| &line[idx + 1..]);
let idx_comp_start = idx_dest.map_or(0, |idx| idx + 1);
let idx_comp_end = idx_jump.map_or(line.len(), |idx| idx);
let comp = &line[idx_comp_start..idx_comp_end];
let comp_binary = match comp {
"0" => "0101010",
"1" => "0111111",
"-1" => "0111010",
"D" => "0001100",
"A" => "0110000",
"M" => "1110000",
"!D" => "0001101",
"!A" => "0110001",
"!M" => "1110001",
"-D" => "0001111",
"-A" => "0110011",
"-M" => "1110011",
"D+1" => "0011111",
"A+1" => "0110111",
"M+1" => "1110111",
"D-1" => "0001110",
"A-1" => "0110010",
"M-1" => "1110010",
"D+A" => "0000010",
"D+M" => "1000010",
"D-A" => "0010011",
"D-M" => "1010011",
"A-D" => "0000111",
"M-D" => "1000111",
"D&A" => "0000000",
"D&M" => "1000000",
"D|A" => "0010101",
"D|M" => "1010101",
_ => panic!("Unrecognized comparison: {:?}", comp),
};
let dest_binary = match dest {
None => "000",
Some(d) => {
match d {
"M" => "001",
"D" => "010",
"MD" => "011",
"A" => "100",
"AM" => "101",
"AD" => "110",
"AMD" => "111",
_ => panic!("Unrecognized destination: {:?}", d),
}
}
};
let jump_binary = match jump {
None => "000",
Some(j) => {
match j {
"JGT" => "001",
"JEQ" => "010",
"JGE" => "011",
"JLT" => "100",
"JNE" => "101",
"JLE" => "110",
"JMP" => "111",
_ => panic!("Unrecognized jump: {:?}", j),
}
}
};
writeln!(&mut writer, "111{}{}{}", comp_binary, dest_binary, jump_binary).unwrap();
}
}
Ok(())
}
fn get_trimmed_line(line: &str) -> &str {
let mut line = line.trim();
// Strip any comments
if let Some(idx_comment) = line.find("//") {
line = &line[0..idx_comment].trim();
}
return line;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment