use std::collections::HashMap; use std::fs; use std::process::Command; // print only with debug_assertions macro_rules! dprint { ($($x:tt)*) => { #[cfg(debug_assertions)] print!($($x)*) } } // // println only with debug_assertions // macro_rules! dprintln { // ($($x:tt)*) => { // #[cfg(debug_assertions)] // println!($($x)*) // } // } // Point to one or more ranges of a string, useful for highlighting parts of string macro_rules! highlight_debug { ($hi_col:expr, $str:expr $(, ($pos:tt -> $endpos:tt))*) => { for (i, _c) in $str.char_indices() { if false $(|| (i >= $pos) && (i < $endpos))* { dprint!("{}{}\x1b[0m", $hi_col, _c); } else { dprint!("{}", _c); } } dprint!("\n"); }; ($str:expr, $pos:expr, $endpos:expr) => { highlight_debug!("\x1b[7m", $str, ($pos -> $endpos)) }; ($str:expr, $pos:expr) => { highlight_debug!($str, $pos, $pos+1) }; } pub enum MacroType { Function(fn(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String), String(String) } pub struct MacroProcessor { macros: HashMap, } fn smp_builtin_define(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 1 { return macro_name.to_string(); } let arg0 = smp.process_input(&args[0]); if args.len() > 1 { let arg1 = smp.process_input(&args[1]); smp.define_macro(arg0, arg1); } else { smp.define_macro(arg0, String::new()); } String::new() } fn smp_builtin_ifdef(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 2 { return macro_name.to_string(); } // We need to expand the first argument here as well, but we need to make the parser // support literal, and phrase strings if smp.macros.contains_key(&args[0]) { return smp.process_input(&args[1]); } if args.len() > 2 { return smp.process_input(&args[2]); } String::new() } fn smp_builtin_ifndef(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 2 { return macro_name.to_string(); } // We need to expand the first argument here as well, but we need to make the parser // support literal, and phrase strings if !smp.macros.contains_key(&args[0]) { return smp.process_input(&args[1]); } if args.len() > 2 { return smp.process_input(&args[2]); } String::new() } fn smp_builtin_ifeq(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 3 { return macro_name.to_string(); } let arg0 = smp.process_input(&args[0]); let arg1 = smp.process_input(&args[1]); if arg0 == arg1 { return smp.process_input(&args[2]); } if args.len() > 3 { return smp.process_input(&args[3]); } String::new() } fn smp_builtin_ifneq(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 3 { return macro_name.to_string(); } let arg0 = smp.process_input(&args[0]); let arg1 = smp.process_input(&args[1]); if arg0 != arg1 { return smp.process_input(&args[2]); } if args.len() > 3 { return smp.process_input(&args[3]); } return String::new(); } fn smp_builtin_include(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 1 { return macro_name.to_string(); } let arg0 = smp.process_input(&args[0]); let input_file = fs::read_to_string(&arg0).expect("Failed to read input file"); return smp.process_input(&input_file); } fn smp_builtin_shell(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 1 { return macro_name.to_string(); } let arg0 = smp.process_input(&args[0]); let res = Command::new("sh") .arg("-c") .arg(arg0) .output(); match res { Ok(output) => String::from_utf8(output.stdout).expect("SMP1"), Err(_) => String::new() } } /// Would like one that is better than this tbh fn smp_builtin_expr(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String { if args.len() < 1 { return macro_name.to_string(); } for arg in args.iter_mut() { *arg = smp.process_input(&arg); } let res = Command::new("expr") .args(args) .output(); match res { Ok(output) => String::from_utf8(output.stdout).expect("SMP1"), Err(_) => String::new() } } impl MacroProcessor { pub fn new() -> Self { let mut smp = Self { macros: HashMap::new(), }; smp.define_builtins(); smp } fn define_builtins(&mut self) { self.define_macro_fn(String::from("define"), MacroType::Function(smp_builtin_define)); self.define_macro_fn(String::from("ifdef"), MacroType::Function(smp_builtin_ifdef)); self.define_macro_fn(String::from("ifndef"), MacroType::Function(smp_builtin_ifndef)); self.define_macro_fn(String::from("ifeq"), MacroType::Function(smp_builtin_ifeq)); self.define_macro_fn(String::from("ifneq"), MacroType::Function(smp_builtin_ifneq)); self.define_macro_fn(String::from("include"), MacroType::Function(smp_builtin_include)); self.define_macro_fn(String::from("shell"), MacroType::Function(smp_builtin_shell)); self.define_macro_fn(String::from("expr"), MacroType::Function(smp_builtin_expr)); // TODO // format('Result id %d', 3282) } pub fn define_macro(&mut self, name: String, body: String) { self.macros.insert(name, MacroType::String(body)); } pub fn define_macro_fn(&mut self, name: String, macro_expansion: MacroType) { self.macros.insert(name, macro_expansion); } /// This expands a macro definition, and it executes builtin functions, like define fn expand_macro(&mut self, macro_name: &str, args: &mut [String]) -> String { let Some(macro_body) = self.macros.get(macro_name) else { return format!("{}", macro_name); }; match macro_body { MacroType::String(body) => { let mut expanded = body.clone(); for (i, arg) in args.iter().enumerate() { let placeholder = format!("${}", i + 1); expanded = expanded.replace(&placeholder, arg); } return expanded; }, MacroType::Function(func) => { return func(self, macro_name, args); }, } } pub fn process_input(&mut self, input: &str) -> String { let mut output = String::new(); let mut state = ParserState::Normal; let mut macro_name = String::new(); let mut macro_args = Vec::new(); let mut argument = String::new(); let mut macro_name_start = 0; let mut skip_next_line_ending = false; let mut in_quote_single = false; let mut in_quote_double = false; for (i, c) in input.char_indices() { highlight_debug!(input, macro_name_start, i); match state { ParserState::Normal => { macro_name_start = i; if skip_next_line_ending && (c == '\n') { skip_next_line_ending = false; continue; } if c.is_alphanumeric() { state = ParserState::InMacro; macro_name.push(c); } else { output.push(c); } } ParserState::InMacro => { if c.is_alphanumeric() || c == '_' { macro_name.push(c); } else if c == '(' { state = ParserState::InMacroArgs; } else { if self.macros.contains_key(¯o_name) { highlight_debug!("\x1b[32m\x1b[7m", input, (macro_name_start -> i)); } if macro_name == "SNNL" { skip_next_line_ending = c != '\n'; } else { let expanded = self.expand_macro(¯o_name, &mut []); output.push_str(&expanded); output.push(c); } macro_name.clear(); state = ParserState::Normal; } } ParserState::InMacroArgs => { if c == ')' { highlight_debug!("\x1b[32m\x1b[7m", input, (macro_name_start -> i)); macro_args.push(argument.trim().to_string()); let expanded = self.expand_macro(¯o_name, &mut macro_args); output.push_str(&expanded); state = ParserState::Normal; macro_name.clear(); macro_args.clear(); argument.clear(); } else if c == ',' { macro_args.push(argument.trim().to_string()); argument.clear(); } else { argument.push(c); } } } } // Handle cases where the text ends with a macro without arguments if !macro_name.is_empty() { output.push_str(&self.expand_macro(¯o_name, &mut [])); } output } } #[derive(Debug, PartialEq)] enum ParserState { Normal, InMacro, InMacroArgs, }