summaryrefslogblamecommitdiff
path: root/src/macro_processor/macro_processor.rs
blob: 306aa04deadd3165ef0805c3c3a08bbb03836fae (plain) (tree)



































































































































































































































































                                                                                                      
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(&macro_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(&macro_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(&macro_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(&macro_name, &mut []));
        }

        output
    }
}

#[derive(Debug, PartialEq)]
enum ParserState {
    Normal,
    InMacro,
    InMacroArgs,
}