use std::collections::HashMap; use std::env; use std::fmt; use std::fs; use std::usize; mod eval; struct Program { data: Vec, pc: usize, vars: HashMap, funcs: HashMap } impl Program { fn from_string(program: String) -> Program { let mut op_list: Vec = Vec::new(); for opcode in program.split("\n").collect::>() { let new_op = opcode.to_owned(); if new_op.len() != 0 { op_list.push(new_op.to_owned()); } } return Program { data: op_list, pc: 0, vars: HashMap::new(), funcs: HashMap::new() }; } // Reads the arguments passed to an opcode, and inserts variables where necessary fn args_or_vars(&self, arguments: &str) -> String { let mut builder = String::from(""); // Empty string that will be rebuilt based on the loop let argument_vec: Vec = arguments.chars().collect(); // Deconstructed arguments // Iterate through each char for index in 0..argument_vec.len() { let current_char = argument_vec[index]; let str_to_push: String; if index > 0 { // Only test for the dollar sign if it's not the first character // This is because there can't be anything before the first character, otherwise it's not the first if argument_vec[index - 1] == '$' { // If the previous character is a dollar sign, we can skip this iteration because we know the variable has already been handled continue; } } if current_char == '$' { // If the current char is a $, we know that the next char should be a variable let variable = argument_vec[index + 1]; let key = self.vars.get(&variable); match key { Some(value) => str_to_push = value.to_string(), None => panic!("NotFoundError: Variable {} has not been defined", variable), } } else { // If there's no variable, then just push the char that was already there str_to_push = current_char.to_string(); } builder.push_str(&str_to_push); } builder } fn add_var(&mut self, arguments: &str) { let argument_vec: Vec = arguments.chars().collect(); let name = argument_vec[0]; let mut value: String = argument_vec[1..].into_iter().collect(); value = self.args_or_funcs(&value); self.vars.insert(name, value); } fn add_func(&mut self, arguments: &str) { let argument_vec: Vec = arguments.chars().collect(); let name = argument_vec[0]; let body: String = argument_vec[1..].into_iter().collect(); self.funcs.insert(name, body); } fn parse_funcs(&mut self, instruction: &String) -> u32 { // Opcode is the first character, arguments are everything after the first char let opcode = instruction.chars().collect::>()[0]; let arguments = &instruction[1..]; // Only a subset of opcodes, because the others don't make sense in a function match opcode { 'a' => eval::do_math(self.args_or_funcs(&self.args_or_vars(arguments)), '+'), 's' => eval::do_math(self.args_or_funcs(&self.args_or_vars(arguments)), '-'), 'm' => eval::do_math(self.args_or_funcs(&self.args_or_vars(arguments)), '*'), 'd' => eval::do_math(self.args_or_funcs(&self.args_or_vars(arguments)), '/'), 'l' => {self.add_var(arguments);0} _ => panic!("SyntaxError: No such opcode: {}", self.pc), } } fn args_or_funcs(&mut self, arguments: &str) -> String { let mut builder = String::from(""); let argument_vec: Vec = arguments.chars().collect(); for index in 0..argument_vec.len() { let current_char = argument_vec[index]; let str_to_push: String; if index > 0 { if argument_vec[index-1] == '*' { continue; } } if current_char == '*' { let func_name = argument_vec[index+1]; let body: String; let key = (self).funcs.get(&func_name); match key { Some(content) => body = content.to_owned(), None => panic!("ValueError: function {} has not been defined yet!", func_name) } str_to_push = self.parse_funcs(&body).to_string(); } else { str_to_push = current_char.to_string(); } builder.push_str(&str_to_push); } builder } fn run_external(&mut self, filename: String) { // Read contents of the provided file and construct a symbolic Program from it let contents = fs::read_to_string(filename).expect("Something went wrong reading the file"); let mut prog = Program::from_string(contents); prog.run(); } fn parse(&mut self, instruction: &String) { // Opcode is the first character, arguments are everything after the first char let opcode = instruction.chars().collect::>()[0]; let arguments = eval::args_or_comments(&instruction[1..]); if opcode != '#' { match opcode { 'p' => println!("{}", self.args_or_funcs(&self.args_or_vars(&arguments))), 'a' => println!("{}", eval::do_math(self.args_or_vars(&arguments), '+')), 's' => println!("{}", eval::do_math(self.args_or_vars(&arguments), '-')), 'm' => println!("{}", eval::do_math(self.args_or_vars(&arguments), '*')), 'd' => println!("{}", eval::do_math(self.args_or_vars(&arguments), '/')), 'l' => self.add_var(&arguments), 'f' => self.add_func(&arguments), 'i' => self.run_external(arguments), _ => panic!("SyntaxError at opcode {}: Unknown opcode {}", self.pc, opcode), } } } fn run(&mut self) { println!("{}", self); while self.pc < self.data.len() { // Grab instruction from op list and parse the instruction let instruction = self.data[self.pc].to_owned(); self.parse(&instruction); self.pc += 1; } } } impl fmt::Display for Program { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Program ({:?})", self.data) } } fn main() { // Grab args and a filename let args: Vec = env::args().collect(); if args.len() == 1 { // Args will always have at least 1 argument, which is the name of the executable. // That's why we're checking index 1, not index 0. panic!("You must provide a filename!"); } let filename = &args[1]; // Read contents of the provided file and construct a symbolic Program from it let contents = fs::read_to_string(filename).expect("Something went wrong reading the file"); let mut prog = Program::from_string(contents); prog.run(); } #[cfg(test)] mod tests { use super::*; fn make_program(contents: &str) -> Program { Program::from_string(contents.to_string()) } #[test] fn test_math() { assert_eq!(eval::do_math("2-2".to_string(), '+'), 4); } #[test] #[should_panic] fn test_undefined_opcode() { make_program("Hello\nWorld!").run(); } #[test] #[should_panic] fn test_undefined_variable() { make_program("p$v").run(); } #[test] #[should_panic] fn test_undefined_function() { make_program("p*x").run(); } #[test] fn test_factory() { let prog = make_program("lhHello\nlwWorld\np$h $w"); let vec_to_check: Vec = vec!["lhHello", "lwWorld", "p$h $w"].into_iter().map(|s| s.to_string()).collect(); assert_eq!(prog.data, vec_to_check); } #[test] fn test_args() { let mut prog = make_program("lhHello\nlwWorld\np$h $w"); prog.run(); let args_to_check: HashMap = [('h', String::from("Hello")), ('w', String::from("World"))].iter().cloned().collect(); assert_eq!(prog.vars, args_to_check); } #[test] fn test_funcs() { let mut prog = make_program("fxa10-10\nfys10-5\np*x *y"); prog.run(); let funcs_to_check: HashMap = [('x', String::from("a10-10")), ('y', String::from("s10-5"))].iter().cloned().collect(); assert_eq!(prog.funcs, funcs_to_check); } }