use std::collections::HashMap; use std::env; use std::fmt; use std::fs; use std::usize; use regex::Regex; mod eval; struct Program { data: Vec, pc: usize, vars: HashMap, funcs: HashMap } macro_rules! create_program { ($filename:expr, $prog_name:ident) => { let contents = fs::read_to_string($filename).expect("Something went wrong reading the file"); let mut $prog_name = Program::from_string(contents); }; } 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); value = eval::inp_or_normal(&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, arguments: String) { // Split arguments by - let argument_vec: Vec<&str> = arguments.split("-").collect(); println!("{}", argument_vec.len()); let filename = argument_vec[0]; // Read contents of the provided file and construct a symbolic Program from it create_program!(filename, prog); prog.run(); if argument_vec.len() > 1 { // Start from the second element for name in argument_vec[1..].iter() { let kind = match name.chars().nth(0) { Some(content) => content, None => panic!("SyntaxError: {}: Invalid syntax", arguments) }; let name_to_import = match name.chars().nth(1) { Some(content) => content, None => panic!("SyntaxError: {}: Invalid syntax", arguments) }; if kind == 'v' { let key = prog.vars.get(&name_to_import); match key { Some(value) => self.vars.insert(name_to_import, value.to_string()), None => panic!("ValueError: variable {} has not been defined in {}!", name_to_import, filename) }; } else if kind == 'f' { let key = prog.funcs.get(&name_to_import); match key { Some(value) => {self.funcs.insert(name_to_import, value.to_string());()}, None => panic!("ValueError: function {} has not been defined in {}!", name_to_import, filename) } } else { // Skip unknown types continue; } } } else { // implied catch-all, might be unintuitive for (key, value) in prog.vars.iter() { self.vars.insert(*key, value.to_string()); } for (key, value) in prog.funcs.iter() { self.funcs.insert(*key, value.to_string()); } } } fn handle_if_statement(&mut self, arguments: String) { let argument_vec: Vec<&str> = arguments.split(":").collect(); let condition = argument_vec[0]; let success = argument_vec[1]; let failure = argument_vec[2]; let re = Regex::new(r"(?P\d+)(?P==|!=|>|<|>=|<=)(?P\d+)").unwrap(); let caps = re.captures(condition).unwrap(); let num1 = &caps["num1"]; let operator = &caps["operator"]; let num2 = &caps["num2"]; let meets_condition: bool; match operator { "==" => meets_condition = num1 == num2, "!=" => meets_condition = num1 != num2, ">" => meets_condition = num1 > num2, "<" => meets_condition = num1 < num2, ">=" => meets_condition = num1 >= num2, "<=" => meets_condition = num1 <= num2, _ => panic!("SyntaxError: Unknown operator at opcode {}: {}", self.pc, operator) }; if meets_condition { self.parse(&(success.to_string())); } else { self.parse(&(failure.to_string())); } } fn parse(&mut self, instruction: &String) { // Opcode is the first character, arguments are everything after the first char let opcode = instruction.chars().collect::>()[0]; if opcode != '#' { let arguments = self.args_or_funcs(&self.args_or_vars(&eval::args_or_comments(&instruction[1..]))); match opcode { 'p' => println!("{}", arguments), 'a' => println!("{}", eval::do_math(arguments, '+')), 's' => println!("{}", eval::do_math(arguments, '-')), 'm' => println!("{}", eval::do_math(arguments, '*')), 'd' => println!("{}", eval::do_math(arguments, '/')), 'l' => self.add_var(&arguments), 'f' => self.add_func(&arguments), 'i' => self.run_external(arguments), '?' => self.handle_if_statement(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 create_program!(filename, prog); prog.run(); } #[cfg(test)] mod tests;