diff --git a/src/main.rs b/src/main.rs index e01909b..c165fe3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,141 @@ +use std::cell::RefCell; +use std::collections::HashMap; use std::env; use std::ffi; use std::fs; use std::io::Write; use std::path; -use std::process; +use std::path::PathBuf; use regex::Regex; +use types::Address; use types::Instruction; mod code; mod parser; mod types; -fn process(instruction: String) -> String { - match parser::parse(&instruction) { - Instruction::AInstruction(parsed_instruction) => { - match parsed_instruction.decimal.parse::() { - Ok(decimal) => { - let translated = code::decimal_to_fifteen_bits_binary(&decimal); +fn remove_whitespace_and_comments(content: String) -> String { + let re = Regex::new(r"\s*\/\/.*").unwrap(); + + content + .lines() + .map(|line| line.trim()) + .filter(|line| !line.starts_with("//") && !line.is_empty()) + .map(|line| re.replace_all(line, "")) + .map(|line| format!("{}\n", line)) + .collect::() +} - String::from(format!("0{}", translated)) - } - Err(_) => panic!("Failed to parse A instruction {}", instruction), +fn first_pass(content: String, table: &RefCell>) -> String { + let mut mutable_table = table.borrow_mut(); + + content + .lines() + .enumerate() + .filter(|(_, line)| line.starts_with('(') && line.ends_with(')')) + .collect::>() + .iter() + .enumerate() + .for_each(|(index, (line_number, instruction))| { + let symbol = instruction[1..instruction.len() - 1].to_string(); + + if index > 0 { + mutable_table.insert(symbol, ((*line_number - index) as i32, Address::ROM)); + } else { + mutable_table.insert(symbol, (*line_number as i32, Address::ROM)); } - } - Instruction::CInstruction(parsed_instruction) => { - String::from("111") - + code::translate_comp(&parsed_instruction.comp.unwrap_or_else(|| "".to_string())) - .as_str() - + code::translate_dest(&parsed_instruction.dest.unwrap_or_else(|| "".to_string())) - .as_str() - + code::translate_jump(&parsed_instruction.jump.unwrap_or_else(|| "".to_string())) - .as_str() - } - } + }); + + content + .lines() + .filter(|line| !line.starts_with('(') && !line.ends_with(')')) + .map(|line| format!("{}\n", line)) + .collect() +} + +fn second_pass(content: String, table: &RefCell>) -> String { + content + .lines() + .map(|instruction| { + let mut mutable_table = table.borrow_mut(); + + if instruction.is_empty() { + return instruction.to_string(); + } + + match parser::parse(&instruction.to_string(), &mut mutable_table) { + Instruction::AInstruction(parsed_instruction) => { + match parsed_instruction.decimal.parse::() { + Ok(decimal) => { + let translated = code::decimal_to_fifteen_bits_binary(&decimal); + + String::from(format!("0{}", translated)) + } + Err(_) => panic!("Failed to parse A instruction {}", instruction), + } + } + Instruction::CInstruction(parsed_instruction) => { + String::from("111") + + code::translate_comp( + &parsed_instruction.comp.unwrap_or_else(|| "".to_string()), + ) + .as_str() + + code::translate_dest( + &parsed_instruction.dest.unwrap_or_else(|| "".to_string()), + ) + .as_str() + + code::translate_jump( + &parsed_instruction.jump.unwrap_or_else(|| "".to_string()), + ) + .as_str() + } + } + }) + .filter(|line| !line.is_empty()) + .map(|line| format!("{}\n", line)) + .collect::() +} + +fn process(path: &PathBuf) { + let filepath = path + .canonicalize() + .unwrap() + .to_str() + .unwrap() + .replace("asm", "hack"); + let mut file = fs::File::create(filepath).unwrap(); + let content = fs::read_to_string(&path).expect("You must provide a correct filepath!"); + let symbol_table = RefCell::new(HashMap::from([ + (String::from("SP"), (0, Address::ROM)), + (String::from("LCL"), (1, Address::ROM)), + (String::from("ARG"), (2, Address::ROM)), + (String::from("THIS"), (3, Address::ROM)), + (String::from("THAT"), (4, Address::ROM)), + (String::from("R0"), (0, Address::RAM)), + (String::from("R1"), (1, Address::RAM)), + (String::from("R2"), (2, Address::RAM)), + (String::from("R3"), (3, Address::RAM)), + (String::from("R4"), (4, Address::RAM)), + (String::from("R5"), (5, Address::RAM)), + (String::from("R6"), (6, Address::RAM)), + (String::from("R7"), (7, Address::RAM)), + (String::from("R8"), (8, Address::RAM)), + (String::from("R9"), (9, Address::RAM)), + (String::from("R10"), (10, Address::RAM)), + (String::from("R11"), (11, Address::RAM)), + (String::from("R12"), (12, Address::RAM)), + (String::from("R13"), (13, Address::RAM)), + (String::from("R14"), (14, Address::RAM)), + (String::from("R15"), (15, Address::RAM)), + (String::from("SCREEN"), (16384, Address::RAM)), + (String::from("KBD"), (24576, Address::RAM)), + ])); + let without_whitespace_and_comments = remove_whitespace_and_comments(content); + let ran_through_first_pass = first_pass(without_whitespace_and_comments, &symbol_table); + let ran_through_second_pass = second_pass(ran_through_first_pass, &symbol_table); + + file.write_all(ran_through_second_pass.as_bytes()).unwrap(); } fn main() { @@ -48,24 +150,7 @@ fn main() { .to_str() .unwrap_or("") { - "asm" => { - let filepath = path.canonicalize().unwrap().to_str().unwrap().replace("asm", "hack"); - let mut file = fs::File::create(filepath).unwrap(); - let content = fs::read_to_string(&path).expect("You must provide a correct filepath!"); - let re = Regex::new(r"\s*\/\/.*").unwrap(); - let processed: String = content - .lines() - .filter(|line| !line.starts_with("//") && !line.is_empty()) - .map(|line| re.replace_all(line, "")) - .map(|line| process(line.to_string())) - .map(|line| format!("{}\n", line)) - .collect(); - - file.write_all(processed.as_bytes()).unwrap(); - } - _ => { - println!("The file extension must be asm!"); - process::exit(1) - } + "asm" => process(&path), + _ => panic!("The file extension must be asm!"), } } diff --git a/src/parser.rs b/src/parser.rs index ea9e306..0dd30d4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,15 +1,42 @@ -use crate::types::{AInstruction, CInstruction, Instruction}; +use std::collections::HashMap; + +use crate::types::{AInstruction, Address, CInstruction, Instruction}; use regex::Regex; -pub fn parse(instruction: &String) -> Instruction { +pub fn parse(instruction: &String, table: &mut HashMap) -> Instruction { let mut cloned = instruction.clone(); if cloned.starts_with('@') { cloned.remove(0); - return Instruction::AInstruction(AInstruction { - decimal: cloned.trim().to_string(), - }); + if cloned.parse::().is_ok() { + return Instruction::AInstruction(AInstruction { decimal: cloned }); + } else { + if table.contains_key(&cloned) { + return Instruction::AInstruction(AInstruction { + decimal: table.get(&cloned).copied().unwrap().0.to_string(), + }); + } else { + let mut temp_table = table.clone(); + + temp_table.remove_entry(&String::from("SCREEN")); + temp_table.remove_entry(&String::from("KBD")); + + let address = temp_table + .iter() + .filter(|(_, (_, address))| matches!(address, Address::RAM)) + .fold( + 0, + |acc, (_, (addr, _))| if *addr > acc { *addr } else { acc }, + ) + + 1; + + table.insert(cloned, (address, Address::RAM)); + return Instruction::AInstruction(AInstruction { + decimal: address.to_string(), + }); + } + } } else { let re_dest = Regex::new(r"=").unwrap(); let re_jump = Regex::new(r";").unwrap(); diff --git a/src/types.rs b/src/types.rs index 84b859b..1cc1b2a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,6 +8,12 @@ pub struct CInstruction { pub jump: Option, } + #[derive(Clone, Copy)] +pub enum Address { + RAM, + ROM +} + pub enum Instruction { AInstruction(AInstruction), CInstruction(CInstruction),