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;  | 
