Last active
January 16, 2017 13:53
-
-
Save learnopengles/834b6fdedbd605c5853093ac76be22f7 to your computer and use it in GitHub Desktop.
Assembler for nand2tetris project 6 written in Rust.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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