diff options
Diffstat (limited to 'src/macro_processor')
-rw-r--r-- | src/macro_processor/macro_processor.rs | 260 | ||||
-rw-r--r-- | src/macro_processor/main.rs | 12 | ||||
-rw-r--r-- | src/macro_processor/mod.rs | 2 |
3 files changed, 274 insertions, 0 deletions
diff --git a/src/macro_processor/macro_processor.rs b/src/macro_processor/macro_processor.rs new file mode 100644 index 0000000..306aa04 --- /dev/null +++ b/src/macro_processor/macro_processor.rs @@ -0,0 +1,260 @@ +use std::collections::HashMap; +use std::fs; + +// 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) + }; +} + +#[derive(Debug)] +pub struct MacroProcessor { + macros: HashMap<String, String>, +} + +impl MacroProcessor { + pub fn new() -> Self { + Self { + macros: HashMap::new(), + } + } + + pub fn define_macro(&mut self, name: String, body: String) { + self.macros.insert(name, body); + } + + /// This expands a macro definition, and it executes builtin functions, like define + /// Currently, you cannot overwrite builtin's. + /// This is partly by design, and I don't currently see why I would want that. + /// In the future, this may change + /// + /// (The HashMap might become a HashMap<String, Box<dyn FnMut>> or something similar. + /// Then, all builtins would also be functions, and "normal" macros, would simply + /// be closures returning the string) + fn expand_macro(&mut self, macro_name: &str, args: &mut [String]) -> String { + if macro_name == "define" { + if args.len() < 1 { + println!("Missing argument(s) to `define`, found {} but expected 1 or 2", args.len()); + return String::new(); + } + let arg0 = self.process_input(&args[0]); + if args.len() > 1 { + let arg1 = self.process_input(&args[1]); + self.define_macro(arg0, arg1); + } else { + self.define_macro(arg0, String::new()); + } + return String::new(); + } + + if macro_name == "ifdef" { + if args.len() < 2 { + println!("Missing argument(s) to `ifdef`, found {} but expected 2 or 3", args.len()); + return String::new(); + } + // We need to expand the first argument here as well, but we need to make the parser + // support literal, and phrase strings + if self.macros.contains_key(&args[0]) { + return self.process_input(&args[1]); + } + if args.len() > 2 { + return self.process_input(&args[2]); + } + return String::new(); + } + + if macro_name == "ifndef" { + if args.len() < 2 { + println!("Missing argument(s) to `ifndef`, found {} but expected 2 or 3", args.len()); + return String::new(); + } + // We need to expand the first argument here as well, but we need to make the parser + // support literal, and phrase strings + if !self.macros.contains_key(&args[0]) { + return self.process_input(&args[1]); + } + if args.len() > 2 { + return self.process_input(&args[2]); + } + return String::new(); + } + + if macro_name == "ifeq" { + if args.len() < 3 { + println!("Missing argument(s) to `ifeq`, found {} but expected 3 or 4", args.len()); + return String::new(); + } + let arg0 = self.process_input(&args[0]); + let arg1 = self.process_input(&args[1]); + if arg0 == arg1 { + return self.process_input(&args[2]); + } + if args.len() > 3 { + return self.process_input(&args[3]); + } + return String::new(); + } + + if macro_name == "ifneq" { + if args.len() < 3 { + println!("Missing argument(s) to `ifneq`, found {} but expected 3 or 4", args.len()); + return String::new(); + } + let arg0 = self.process_input(&args[0]); + let arg1 = self.process_input(&args[1]); + if arg0 != arg1 { + return self.process_input(&args[2]); + } + if args.len() > 3 { + return self.process_input(&args[3]); + } + return String::new(); + } + + if macro_name == "include" { + if args.len() < 1 { + println!("Missing argument(s) to `include`, found {} but expected 1", args.len()); + return String::new(); + } + let arg0 = self.process_input(&args[0]); + let input_file = fs::read_to_string(&arg0).expect("Failed to read input file"); + return self.process_input(&input_file); + } + + //Expand macro name? somwhat unsure on how to do this safely + //let macro_name = self.process_input(macro_name); + + let Some(macro_body) = self.macros.get(macro_name) else { + return format!("{}", macro_name); + }; + + let mut expanded = macro_body.clone(); + for (i, arg) in args.iter().enumerate() { + let placeholder = format!("${}", i + 1); + expanded = expanded.replace(&placeholder, arg); + } + expanded + } + + pub fn process_input(&mut self, input: &str) -> String { + let mut output = String::new(); + let mut state = ParserState::Normal; + let mut state_previous = 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; + state_previous = ParserState::Normal; + 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; + state_previous = ParserState::InMacro; + } else { + if self.macros.contains_key(¯o_name) { + highlight_debug!("\x1b[32m\x1b[7m", input, (macro_name_start -> i)); + } + if macro_name == "DNL" { + 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; + state_previous = ParserState::InMacro; + } + } + 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; + state_previous = ParserState::InMacroArgs; + 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, +} diff --git a/src/macro_processor/main.rs b/src/macro_processor/main.rs new file mode 100644 index 0000000..5575aa6 --- /dev/null +++ b/src/macro_processor/main.rs @@ -0,0 +1,12 @@ +use std::env; +use std::fs; +use skaldpress::macro_processor::MacroProcessor; + +fn main() { + let args: Vec<String> = env::args().collect(); + let input_file = fs::read_to_string(&args[1]).expect("Failed to read input file"); + + let mut macro_processor = MacroProcessor::new(); + let final_output = macro_processor.process_input(&input_file); + println!("{}", final_output); +} diff --git a/src/macro_processor/mod.rs b/src/macro_processor/mod.rs new file mode 100644 index 0000000..2fccbda --- /dev/null +++ b/src/macro_processor/mod.rs @@ -0,0 +1,2 @@ +pub mod macro_processor; +pub use macro_processor::MacroProcessor; |