use std::env; use std::fs; use std::fs::File; use std::io::Write; use std::io::{self, BufRead}; use std::path::Path; use std::ffi::OsStr; enum Command { Arithmetic(ArithmeticCommand), MemoryAccess(MemoryAccessCommand), ProgramFlow(ProgramFlowCommand), FunctionCalling(FunctionCallingCommand), Error(String), NoCommand, } enum ArithmeticCommand { Add, Sub, Neg, Eq, Gt, Lt, And, Or, Not, } struct MemoryAccessCommand { access_type: MemoryAccessType, segment: Segment, index: String, // TODO: might be better to use &str here? } #[derive(Debug)] enum MemoryAccessType { Push, Pop, } #[derive(Debug)] enum Segment { Argument, Local, Static, Constant, This, That, Pointer, Temp, } enum ProgramFlowCommand { Label(String), Goto(String), IfGoto(String) } enum FunctionCallingCommand { Function { name: String, n: String }, Call { name: String, m: String }, Return, } struct CodeGen { acc: String, // accumulator for code comp_counter: u32, // counter to create unique labels for comparison jump instructions call_counter: u32, // counter to create unique labels for return addresses vm_ref: String, // Name of VM, e.g. "Foo" for "Foo.vm" - used for push/pop static current_function: String, // name of the last Function command } fn make_error_command(error: &str, line: &String) -> Command { let mut s = String::new(); s.push_str("// "); s.push_str(&error); s.push_str(": '"); s.push_str(line); s.push_str("'."); println!("{}", s); Command::Error(s) } fn parse_single_token(tokens: &Vec<&str>, line: &String) -> Command { use crate::ArithmeticCommand::*; use crate::Command::Arithmetic; use crate::Command::FunctionCalling; use crate::FunctionCallingCommand::Return; return match tokens[0] { "add" => Arithmetic(Add), "sub" => Arithmetic(Sub), "neg" => Arithmetic(Neg), "eq" => Arithmetic(Eq), "gt" => Arithmetic(Gt), "lt" => Arithmetic(Lt), "and" => Arithmetic(And), "or" => Arithmetic(Or), "not" => Arithmetic(Not), "return" => FunctionCalling(Return), _ => make_error_command("Unrecognized single token command", &line), }; } fn parse_two_tokens(tokens: &Vec<&str>, line: &String) -> Command { use crate::ProgramFlowCommand::*; use crate::Command::ProgramFlow; return match (tokens[0], tokens[1]) { ("label", symbol) => ProgramFlow(Label(symbol.to_string())), ("goto", symbol) => ProgramFlow(Goto(symbol.to_string())), ("if-goto", symbol) => ProgramFlow(IfGoto(symbol.to_string())), _ => make_error_command("Unrecognized two tokens command", &line), }; } fn parse_three_tokens(tokens: &Vec<&str>, line: &String) -> Command { use crate::Command::MemoryAccess; use crate::Command::FunctionCalling; use crate::MemoryAccessType::*; use crate::Segment::*; use crate::FunctionCallingCommand::*; fn make(access_type: MemoryAccessType, segment: Segment, index: &str) -> Command { return MemoryAccess(MemoryAccessCommand { access_type: access_type, segment: segment, index: index.to_string(), }) } return match (tokens[0], tokens[1], tokens[2]) { ("push", "argument", index) => make(Push, Argument, index), ("pop", "argument", index) => make(Pop, Argument, index), ("push", "local", index) => make(Push, Local, index), ("pop", "local", index) => make(Pop, Local, index), ("push", "static", index) => make(Push, Static, index), ("pop", "static", index) => make(Pop, Static, index), ("push", "constant", index) => make(Push, Constant, index), ("pop", "constant", index) => make(Pop, Constant, index), ("push", "this", index) => make(Push, This, index), ("pop", "this", index) => make(Pop, This, index), ("push", "that", index) => make(Push, That, index), ("pop", "that", index) => make(Pop, That, index), ("push", "pointer", index) => make(Push, Pointer, index), ("pop", "pointer", index) => make(Pop, Pointer, index), ("push", "temp", index) => make(Push, Temp, index), ("pop", "temp", index) => make(Pop, Temp, index), ("function", name, narg) => FunctionCalling(Function { name: name.to_string(), n: narg.to_string() }), ("call", name, narg) => FunctionCalling(Call { name: name.to_string(), m: narg.to_string() }), _ => make_error_command("Unexpected three tokens", line) }; } fn parse_line(line: &String) -> Command { let mut tokens: Vec<&str> = Vec::new(); let mut iter = line.split_whitespace(); while let Some(token) = iter.next() { if token == "//" { break; } tokens.push(token); } return match tokens.len() { 0 => Command::NoCommand, 1 => parse_single_token(&tokens, &line), 2 => parse_two_tokens(&tokens, &line), 3 => parse_three_tokens(&tokens, &line), _ => make_error_command("Unexpected number of tokens", &line), }; } fn parse_file(filename: &String) -> Vec { let filename = Path::new(filename); let mut commands: Vec = Vec::new(); if let Ok(file) = File::open(filename) { for line in io::BufReader::new(file).lines() { let command = match line { Ok(ok) => parse_line(&ok), Err(err) => make_error_command("Error reading line", &err.to_string()), }; commands.push(command); } } else { panic!("Could not open {:?}!", filename); } commands } fn generate_code_arithmetic(command: &ArithmeticCommand, mut code_gen: &mut CodeGen) { fn binary_operator(command: &str, operator: &str, code_gen: &mut CodeGen) { let s = format!( "\ // {}\n\ @SP\n\ A = M\n\ A = A - 1\n\ A = A - 1\n\ D = M\n\ A = A + 1\n\ D = D {} M\n\ A = A - 1\n\ M = D\n\ @SP\n\ M = M - 1\n\n\ ", command, operator ); code_gen.acc.push_str(&s); } fn unary_operator(command: &str, operator: &str, code_gen: &mut CodeGen) { let s = format!( "\ // {}\n\ @SP\n\ A = M\n\ A = A - 1\n\ M = {}M\n\n\ ", command, operator ); code_gen.acc.push_str(&s) } fn comparison_operator(command: &str, operator: &str, code_gen: &mut CodeGen) { code_gen.comp_counter += 1; let s = format!( "\ // {cmd}\n\ @SP\n\ A = M\n\ A = A - 1\n\ A = A - 1\n\ D = M\n\ A = A + 1\n\ D = D - M\n\ @IF_{op}_{index}\n\ D;{op}\n\ @ELSE_{op}_{index}\n\ 0;JMP\n\ (IF_{op}_{index})\n\ D = -1\n\ @END_{op}_{index}\n\ 0;JMP\n\ (ELSE_{op}_{index})\n\ D = 0\n\ (END_{op}_{index})\n\ @SP\n\ A = M\n\ A = A - 1\n\ A = A - 1\n\ M = D\n\ @SP\n\ M = M - 1\n\ \n", cmd = command, op = operator, index = code_gen.comp_counter ); code_gen.acc.push_str(&s); } use crate::ArithmeticCommand::*; match command { Add => binary_operator("add", "+", &mut code_gen), Sub => binary_operator("sub", "-", &mut code_gen), Neg => unary_operator("neg", "-", &mut code_gen), Eq => comparison_operator("eq", "JEQ", &mut code_gen), Gt => comparison_operator("gt", "JGT", &mut code_gen), Lt => comparison_operator("lt", "JLT", &mut code_gen), And => binary_operator("and", "&", &mut code_gen), Or => binary_operator("or", "|", &mut code_gen), Not => unary_operator("not", "!", &mut code_gen), }; } fn generate_code_memory_access(command: &MemoryAccessCommand, code_gen: &mut CodeGen) { use crate::MemoryAccessType::*; use crate::Segment::*; fn pop_regular(segment_name: &str, segment_id: &str, index: &String, code_gen: &mut CodeGen) { let s = format!( "\ // pop {segment_name} {index}\n\ @{index}\n\ D = A\n\ @{segment_id}\n\ A = M\n\ D = D + A\n\ @R13\n\ M = D\n\ // ^ R13 = {segment_name} + index\n\ @SP\n\ A = M\n\ A = A - 1\n\ D = M\n\ @SP\n\ M = M - 1\n\ // ^ pop into D\n\ @R13\n\ A = M\n\ M = D\n\ // ^ *R13 = D\n\n\ ", segment_name = segment_name, segment_id = segment_id, index = index ); code_gen.acc.push_str(&s); } fn push_regular(segment_name: &str, segment_id: &str, index: &String, code_gen: &mut CodeGen) { let s = format!( "\ // push {segment_name} {index}\n\ @{index}\n\ D = A\n\ @{segment_id}\n\ A = M\n\ A = D + A\n\ D = M\n\ // ^ D = *({segment_id} + index)\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push D\n\n\ ", segment_name = segment_name, segment_id = segment_id, index = index ); code_gen.acc.push_str(&s); } fn push_constant(index: &String, code_gen: &mut CodeGen) { let s = format!( "\ // push constant {}\n\ @{}\n\ D = A\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\n\ ", index, index ); code_gen.acc.push_str(&s); } fn pop_static(index: &String, code_gen: &mut CodeGen) { let mut symbol = String::from(code_gen.vm_ref.as_str()); symbol.push_str("."); symbol.push_str(index); let s = format!("\ // pop static {symbol}\n\ @SP\n\ A = M\n\ A = A - 1\n\ D = M\n\ @SP\n\ M = M - 1\n\ // ^ pop into D\n\ @{symbol}\n\ M = D\n\ // ^ {symbol} = D\n\ \n", symbol=symbol); code_gen.acc.push_str(&s); } fn push_static(index: &String, code_gen: &mut CodeGen) { let mut symbol = String::from(code_gen.vm_ref.as_str()); symbol.push_str("."); symbol.push_str(index); let s = format!("\ // push static {symbol}\n\ @{symbol}\n\ D = M\n\ // ^ D = {symbol}\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push from D\n\ \n", symbol=symbol); code_gen.acc.push_str(&s); } fn pop_temp(index: &String, code_gen: &mut CodeGen) { let s = format!( "\ // pop temp {index}\n\ @{index}\n\ D = A\n\ @5\n\ A = D + A\n\ D = A\n\ @R13\n\ M = D\n\ // ^ R13 = temp + index\n\ @SP\n\ A = M\n\ A = A - 1\n\ D = M\n\ @SP\n\ M = M - 1\n\ // ^ pop into D\n\ @R13\n\ A = M\n\ M = D\n\ // ^ *R13 = D\n\n\ ", index = index ); code_gen.acc.push_str(&s); } fn push_temp(index: &String, code_gen: &mut CodeGen) { let s = format!( "\ // push temp {index}\n\ @{index} D = A @5 A = D + A D = M // ^ D = *(temp + index)\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push D\n\n\ ", index = index ); code_gen.acc.push_str(&s); } fn push_pointer(index: &String, code_gen: &mut CodeGen) { let segment = match index.as_str() { "0" => "THIS", "1" => "THAT", _ => "INVALID" }; let s = format!("\ // push pointer {segment}\n\ @{segment}\n\ D = M\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push {segment}\n\n", segment=segment ); code_gen.acc.push_str(&s); } fn pop_pointer(index: &String, code_gen: &mut CodeGen) { let segment = match index.as_str() { "0" => "THIS", "1" => "THAT", _ => "INVALID" }; let s = format!("\ // pop pointer {segment}\n\ @SP\n\ A = M\n\ A = A - 1\n\ D = M\n\ @SP\n\ M = M - 1\n\ @{segment}\n\ M = D\n\ // ^ pop into {segment}\n\ \n", segment=segment ); code_gen.acc.push_str(&s); } match command { MemoryAccessCommand { access_type: Push, segment: Constant, index } => push_constant(index, code_gen), MemoryAccessCommand { access_type: Pop, segment: Local, index } => pop_regular("local", "LCL", index, code_gen), MemoryAccessCommand { access_type: Push, segment: Local, index } => push_regular("local", "LCL", index, code_gen), MemoryAccessCommand { access_type: Pop, segment: Argument, index } => pop_regular("argument", "ARG", index, code_gen), MemoryAccessCommand { access_type: Push, segment: Argument, index } => push_regular("argument", "ARG", index, code_gen), MemoryAccessCommand { access_type: Pop, segment: This, index } => pop_regular("this", "THIS", index, code_gen), MemoryAccessCommand { access_type: Push, segment: This, index } => push_regular("this", "THIS", index, code_gen), MemoryAccessCommand { access_type: Pop, segment: That, index } => pop_regular("that", "THAT", index, code_gen), MemoryAccessCommand { access_type: Push, segment: That, index } => push_regular("that", "THAT", index, code_gen), MemoryAccessCommand { access_type: Pop, segment: Static, index } => pop_static(index, code_gen), MemoryAccessCommand { access_type: Push, segment: Static, index } => push_static(index, code_gen), MemoryAccessCommand { access_type: Pop, segment: Temp, index } => pop_temp(index, code_gen), MemoryAccessCommand { access_type: Push, segment: Temp, index } => push_temp(index, code_gen), MemoryAccessCommand { access_type: Pop, segment: Pointer, index } => pop_pointer(index, code_gen), MemoryAccessCommand { access_type: Push, segment: Pointer, index } => push_pointer(index, code_gen), MemoryAccessCommand { access_type, segment, index, } => { let s = format!( "// warning: {:?} {:?} {} not implemented.\n\n", access_type, segment, index ); code_gen.acc.push_str(&s); } } } fn generate_code_program_flow(command: &ProgramFlowCommand, code_gen: &mut CodeGen) { fn get_flow_label(label: &String, code_gen: &mut CodeGen) -> std::string::String { if code_gen.current_function != "" { return format!("{}:{}", code_gen.current_function, label); } else { return label.to_string(); } } use crate::ProgramFlowCommand::*; match command { Label(label) => { let label = get_flow_label(label, code_gen); let s = format!("// label {}\n({})\n\n", label, label); code_gen.acc.push_str(&s); }, Goto(label) => { let label = get_flow_label(label, code_gen); let s = format!("// goto {}\n@{}\n0;JMP\n\n", label, label); code_gen.acc.push_str(&s); }, IfGoto(label) => { let label = get_flow_label(label, code_gen); let s = format!("\ // if-goto {label}\n\ @SP\n\ A = M\n\ A = A - 1\n\ D = M\n\ @SP\n\ M = M - 1\n\ @{label}\n\ D;JNE\n\ ", label=label); code_gen.acc.push_str(&s); } } } fn generate_code_function_calling(command: &FunctionCallingCommand, code_gen: &mut CodeGen) { use crate::FunctionCallingCommand::*; fn call(function_name: &String, nargs: &String, code_gen: &mut CodeGen) { let return_label = format!("{}:return:{}", function_name, code_gen.call_counter); code_gen.call_counter += 1; let s = format!( "\ // call {function_name} {nargs}\n\ @{return_label}\n\ D = A\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push return-address\n\ @LCL\n\ D = M\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push lcl\n\ @ARG\n\ D = M\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push arg\n\ @THIS\n\ D = M\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push this\n\ @THAT\n\ D = M\n\ @SP\n\ A = M\n\ M = D\n\ @SP\n\ M = M + 1\n\ // ^ push that\n\ @SP\n\ D = M\n\ @{nargs}\n\ D = D - A\n\ @5\n\ D = D - A\n\ @ARG\n\ M = D\n\ // ^ ARG = SP - {nargs} - 5\n\ @SP\n\ D = M\n\ @LCL\n\ M = D\n\ // ^ LCL = SP\n\ @{function_name}\n\ 0;JMP\n\ // ^ goto {function_name}\n\ ({return_label})\n\ \n\n", nargs=nargs, function_name=function_name, return_label=return_label, ); code_gen.acc.push_str(&s); } fn function(function_name: &String, nargs: &String, code_gen: &mut CodeGen) { code_gen.current_function = function_name.to_string(); let s = format!( "\ // function {function_name} {nargs}\n\ ({function_name})\n\ @0\n\ D = A\n\ ", function_name=function_name, nargs=nargs, ); code_gen.acc.push_str(&s); let nargs: u32 = nargs.parse().unwrap(); for _ in 0..nargs { code_gen.acc.push_str("@SP\nA = M\nM = D\n@SP\nM = M + 1\n// ^ push 0\n"); } let s = format!("// ^ push 0 * {nargs}\n\n", nargs=nargs); code_gen.acc.push_str(&s); } fn fnreturn(code_gen: &mut CodeGen) { let s = format!( "\ // return\n\ @LCL\n\ D = M\n\ @R13\n\ M = D\n\ // ^ R13 = FRAME = LCL\n\ @5\n\ D = A\n\ @R13\n\ A = M - D\n\ D = M\n\ @R14\n\ M = D\n\ // ^ R14 = RET = *(FRAME - 5)\n\ @SP\n\ A = M\n\ A = A - 1\n\ D = M\n\ @SP\n\ M = M - 1\n\ @ARG\n\ A = M\n\ M = D\n\ // ^ POP into *ARG\n\ @ARG\n\ D = M + 1\n\ @SP\n\ M = D\n\ // ^ SP = ARG + 1\n\ @1\n\ D = A\n\ @R13\n\ A = M - D\n\ D = M\n\ @THAT\n\ M = D\n\ // ^ THAT = *(FRAME - 1)\n\ @2\n\ D = A\n\ @R13\n\ A = M - D\n\ D = M\n\ @THIS\n\ M = D\n\ // ^ THIS = *(FRAME - 2)\n\ @3\n\ D = A\n\ @R13\n\ A = M - D\n\ D = M\n\ @ARG\n\ M = D\n\ // ^ ARG = *(FRAME - 3)\n\ @4\n\ D = A\n\ @R13\n\ A = M - D\n\ D = M\n\ @LCL\n\ M = D\n\ // ^ LCL = *(FRAME - 4)\n\ @R14\n\ A = M\n\ 0;JMP\n\ // ^ goto RET\n\ \n\n", ); code_gen.acc.push_str(&s); } match command { Call { name, m } => call(name, m, code_gen), Function { name, n, } => function(name, n, code_gen), Return => fnreturn(code_gen), }; } fn generate_code_error(error: &String, code_gen: &mut CodeGen) { code_gen.acc.push_str(error); code_gen.acc.push_str("\n"); } fn generate_code(commands: &Vec, mut code_gen: &mut CodeGen) { use crate::Command::*; for command in commands { match command { Arithmetic(c) => generate_code_arithmetic(c, &mut code_gen), MemoryAccess(c) => generate_code_memory_access(c, &mut code_gen), ProgramFlow(c) => generate_code_program_flow(c, &mut code_gen), FunctionCalling(c) => generate_code_function_calling(c, &mut code_gen), Error(string) => generate_code_error(string, &mut code_gen), NoCommand => (), }; } } fn generate_bootstrap_code(code_gen: &mut CodeGen) { use crate::Command::FunctionCalling; use crate::FunctionCallingCommand::Call; let s = format!( "\ // Bootstrap code\n\ @256\n\ D = A\n\ @SP\n\ M = D\n\ // ^ SP = 256\n\n"); code_gen.acc.push_str(&s); let v = vec![FunctionCalling(Call { name: "Sys.init".to_string(), m: "0".to_string() })]; generate_code(&v, code_gen); } fn main() { fn write_output_file(filename: &String, output: &String) { let path = Path::new(&filename); let display = path.display(); // Open a file in write-only mode, returns `io::Result` let mut file = match File::create(&path) { Err(why) => panic!("Couldn't create {}: {}", display, why.to_string()), Ok(file) => file, }; match file.write_all(output.as_bytes()) { Err(why) => panic!("Couldn't write to {}: {}", display, why.to_string()), Ok(_) => println!("<= {}", display), } } fn filename_to_vm_ref(filename: &String) -> String { let p = Path::new(filename); p.file_stem().and_then(OsStr::to_str).unwrap().to_string() } fn translate_single_file(filename: &String) { let mut code_gen = CodeGen { acc: String::new(), comp_counter: 0, call_counter: 0, vm_ref: filename_to_vm_ref(filename), current_function: String::new(), }; let commands = parse_file(filename); generate_code(&commands, &mut code_gen); let filename = str::replace(filename, ".vm", ".asm"); write_output_file(&filename, &code_gen.acc); } fn translate_file(filename: &String, mut code_gen: &mut CodeGen) { let commands = parse_file(filename); generate_code(&commands, &mut code_gen); } fn translate_dir(dirname: &String) { println!("=> {}", dirname); let mut code_gen = CodeGen { acc: String::new(), comp_counter: 0, call_counter: 0, vm_ref: String::new(), current_function: String::new(), }; generate_bootstrap_code(&mut code_gen); let paths = fs::read_dir(dirname).unwrap(); for path in paths { let filename = path.unwrap().path().to_str().unwrap().to_string(); if is_vm_file(&filename) { println!(" -> {}", filename); let vm_ref = filename_to_vm_ref(&filename); let s = format!("// Start {}\n\n", filename); code_gen.vm_ref = vm_ref; code_gen.current_function = "".to_string(); code_gen.acc.push_str(&s); translate_file(&filename, &mut code_gen); } } let filename = format!("{}/{}.asm", dirname, filename_to_vm_ref(dirname)); write_output_file(&filename, &code_gen.acc); } fn is_vm_file(filename: &String) -> bool { let p = Path::new(filename); if p.is_file() && (p.extension().unwrap() == OsStr::new("vm")) { return true; } return false; } let args: Vec = env::args().collect(); for arg in &args[1..] { if is_vm_file(arg) { translate_single_file(&arg); } else if Path::new(arg).is_dir() { translate_dir(&arg); } else { println!("{} is not a *.vm file or directory!", arg); } } }