summaryrefslogblamecommitdiff
path: root/src/macro_processor/macro_processor.rs
blob: 4a46a62ca9148efc81c3adf8e7301fc5418a5ffe (plain) (tree)
1
2
3

                              
                          




































                                                                                   
                                    









                                                                                                  
     

                 
 
                                                                                       







                                                                                                 
     




                                           
 
                                                                                           













                                                                                                  
 
                                                                                          













                                                                                                
 
                                                                                              













                                                                                                 
 
                                                                                  







                                                                                                   
 
                                            




                                                                                                 
                                                              

                                                                      
                                

     
 








                                                                                                
 
                                                       

                                                                      
                                


     






















                                                                                            
                     
 







                                   

                                                         
                                   











                                                    
                                                                                          











                                                     
                                                                                          


                                       





                                                                                          



                                                                





                                                          


                                                                                 
 
                                                                                       




                                                                                                                
                                                                                 



                                                                 


                                                


                                                                                                  




                                                                   
             

                                                    
             
         

     










                                                                     


                                                            



                                           
 


















                                                             









                                                         



                                                                                                
                                                 







                                                                                   









                                                                                            




















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

/// Builtin for defining a new macro
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()
}

/// If macro is defined, return second argument, else return third argument if provided
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()
}

/// If macro is not defined, return second argument, else return third argument if provided
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()
}

/// If arguments are equal, return third argument, else return fourth argument if provided
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()
}

/// If arguments are not equal, return third argument, else return fourth argument if provided
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();
}

/// Include a new file, and process it normally. There is no loop protection here!
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);
}

/// Simply execute argument as shell command
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(),
    }
}

/// Types of macros, this is to make it easy to store both functions and strings
pub enum MacroType {
    /// When expanded, the associated function will be expanded
    Function(fn(smp: &mut MacroProcessor, macro_name: &str, args: &mut [String]) -> String),
    /// Will be expanded in-place to the String
    String(String),
}

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

/// Defines a MacroProcessor object, with it's associated state
/// the state mostly includes the defined macros
pub struct MacroProcessor {
    /// All currently defined macros in this MacroProcessor
    macros: HashMap<String, MacroType>,
}

impl MacroProcessor {

    pub fn new() -> Self {
        let mut smp = Self {
            macros: HashMap::new(),
        };
        smp.define_builtins();
        smp
    }

    /// Bootstrapping-function for defining all builtins,
    /// the same way all other macros might be defined
    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));
        // format('Result id %d', 3282)
    }

    /// Define a new macro as a string that will be expanded in-place
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the new macro
    /// * `body` - The body of the new macro, this will be expanded when macro is executed
    pub fn define_macro(&mut self, name: String, body: String) {
        self.macros.insert(name, MacroType::String(body));
    }

    /// Define a new macro as any MacroType
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the new macro
    /// * `macro_expansion` - The MacroType struct to use.
    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
    ///
    /// # Arguments
    ///
    /// * `macro_name` - Name of macro to expand if it exists
    /// * `args` - List of arguments parsed along with macro invokation (empty list if no arguments were parsed)
    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();
                // The expanded macro, should _probably_ be expanded again
                // The below is a okay _idea_, but I am not sure if I want to have this syntax for
                // functions defined in normal smp code
                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);
            }
        }
    }

    /// Do macro processing of a input string
    ///
    /// This is the main function used for processing a input string,
    /// will return the processed string.
    /// Will be called recursively if needed.
    /// Subsequent calls will keep the state from the previous call.
    /// This includes macro definitions.
    ///
    /// # Arguments
    ///
    /// * `input` - The text to process
    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(&macro_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(&macro_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(&macro_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(&macro_name, &mut []));
        }

        output
    }
}