summaryrefslogblamecommitdiff
path: root/src/macro_processor/macro_processor.rs
blob: 7916148d1716c300c80431f317dffe267ffcdab2 (plain) (tree)
1
2
3
4
5
6
7
8
9
                         
                                                

                                                        
                                            

                              

                         
                          








                                   



















                                                                                   
                                    




                               
                       





                                                                          
                                          
     
                                            
                       
                                                
                                                        
            
                                                                 
     
                     
 
 













































                                                                          
                                                                                       




                               
                       





                                                                          
                                          




                                                                                        
     


                                           
                     
 
 
                                                                                           




                               
                       





                                                                          
                                          








                                                                                        
                     
 
 
                                                                                          




                               
                       





                                                                          
                                          
     

                                            





                                           
                     
 
 
                                                                                              




                               
                       





                                                                          
                                          
     

                                            





                                           
                             
 
 
                                                                                  




                               
                       





                                                                          
                                          
     

                                                                                                

                                          
 
                                                                   
                                




                               





                                                                          





                                                                             
                                            




                               
                       





                                                                          
                                          
     
                                            
                                                              
               

                                                                      








                                                                   

     
 
                                               




                               
                       





                                                                          
                                          


                                
                                        
     
 

                                                               
               

                                                                      








                                                                   


     









                                                                                           
                                                         






                                              








                                     




















                                                                       
     


                     




























                                                                                



                                                              















                                                                     

























                                                                      





























                                                                                      

                           
                             



                               





                                                                          

                                          
                                                 











                                                                              


                                          





























                                                                 
                                                  


                     












                                                         
                                                                                
                       

                                                               






                                      

                                               
                          

 



















                                                                  



                           
             

                

                             
        

 






















                                                                                                  

                                                               
                       

                                                           
                                           

                                                      

                                             
                             
                                  

 

                                                     



                                   
                                          
                                 
                                     
                                                  




                              

                              








                                                                      

                                                         
                                   
                          


                                                    
                          







                                                          


                                                   
                          


                                                    

                                                                                       


                                                   
                          


                                                     
                          

                                                              

                          


                                                   



                                                    
                                                                                       



                                                        



                                                        



                                                        



                                                     




                                                         




                                                                

                                                                                       





                                                         


                                       





                                                                                          
                                                                       
                                                         

     





                                                          
                                                                              

                                                  
 


                                                                                           








                                                        
                                                                                       




                                                                                                                
                                                                                                   


                                                                                            
                                                                 











                                                        

          




                                                  





                                                                                              



                                                         
                                                        

                                                                   
                                             
             

                                                    
             
                                                                                
         

     










                                                                     
                                                                              

                                            



                                           
 

                                              




                                                                                          
 
                                

                                 


                                           

                                                        
                                                         



                                           

                         




                                                    







                                                             






                                                       
                                                       

                                                      



                                                     



                                       
                                                  
                                                  
                                         

                                                 
                     
                                                  



                                                        
                                                     
                         
                                     




                                       


                                                  
                                       












                                                             



                                                        
                                          
                                                         
                            





                                                                                   


                                                                                                
                                                 
                                                              





                                                         
                                
                                                                                    




                                                       


                                             














                                                              
                                                          





                                                                                   
                                                                                            
                                         
                                                                     
                                                                                        

                                                    


                                           
                                                                 


                                                                     





                                              







                                                                          





                                                                       
                                                                       

         
                  

     
#[cfg(feature = "guile")]
use crate::guile::guile::{scm_undefined, Guile};
#[cfg(feature = "deadlinks")]
use crate::macro_processor::deadlinks::smp_builtin_wodl;
use crate::macro_processor::error::SMPError;
use std::collections::HashMap;
use std::fs;
#[cfg(feature = "guile")]
use std::os::raw::c_void;
use std::process::Command;

// print only with debug_assertions
macro_rules! dprint {
    ($($x:tt)*) => {
        #[cfg(debug_assertions)]
        print!($($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],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(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, MacroType::String(arg1));
    } else {
        smp.define_macro(arg0, MacroType::String(String::new()));
    }
    Ok(String::new())
}

/// Builtin for undefining a macro
fn smp_builtin_undefine(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(macro_name.to_string());
    }
    if let None = smp.macros.remove(&args[0]) {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Macro already not defined"),
            ));
    }
    Ok(String::new())
}

/// Builtin for defining a new macro
fn smp_builtin_define_array(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(macro_name.to_string());
    }
    let arg0 = smp.process_input(&args[0])?;
    smp.define_macro(arg0, MacroType::Array(Vec::new()));
    Ok(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],
) -> Result<String, SMPError> {
    if args.len() < 2 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 2"),
            ));
        return Ok(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]);
    }
    Ok(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],
) -> Result<String, SMPError> {
    if args.len() < 2 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 2"),
            ));
        return Ok(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]);
    }
    Ok(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],
) -> Result<String, SMPError> {
    if args.len() < 3 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 3"),
            ));
        return Ok(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]);
    }
    Ok(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],
) -> Result<String, SMPError> {
    if args.len() < 3 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 3"),
            ));
        return Ok(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 Ok(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],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(macro_name.to_string());
    }
    let arg0 = smp.process_input(&args[0])?;
    let input_file = fs::read_to_string(&arg0).map_err(|e| SMPError::IncludeError(2, e, arg0))?;
    return smp.process_input(&input_file);
}

/// Include a new file verbatim, don't do ANY additional processing
fn smp_builtin_include_verbatim(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(macro_name.to_string());
    }
    let arg0 = smp.process_input(&args[0])?;
    fs::read_to_string(&arg0).map_err(|e| SMPError::IncludeError(2, e, arg0))
}

/// Simply execute argument as shell command
fn smp_builtin_shell(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(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)
            .map_err(|e| SMPError::ShellCommandError(1, Box::new(e))),
        Err(e) => {
            smp.warnings
                .push(MacroProcessorWarning::from_macro_invocation(
                    macro_name,
                    args,
                    format!("Error running shell command ({})", e),
                ));
            Ok(String::new())
        }
    }
}

/// Would like one that is better than this tbh
fn smp_builtin_expr(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 1"),
            ));
        return Ok(macro_name.to_string());
    }

    for arg in args.iter_mut() {
        *arg = smp.process_input(&arg)?;
    }

    let args = args.to_vec();
    let res = Command::new("expr").args(args.clone()).output();
    match res {
        Ok(output) => String::from_utf8(output.stdout)
            .map_err(|e| SMPError::ShellCommandError(1, Box::new(e))),
        Err(e) => {
            smp.warnings
                .push(MacroProcessorWarning::from_macro_invocation(
                    macro_name,
                    &args,
                    format!("Error running shell command ({})", e),
                ));
            Ok(String::new())
        }
    }
}

/// Indent argument 2 by N spaces
fn smp_builtin_indent(
    smp: &mut MacroProcessor,
    _macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    let indent_size = args[0].parse::<u32>().unwrap_or(0);
    let mut out = String::with_capacity(args[1].len());
    for l in args[1].lines() {
        let mut lin = String::with_capacity(indent_size.try_into().unwrap_or(0) + l.len());
        if args.len() <= 2 || (args[2] != "skip_first") {
            for _ in 0..indent_size {
                lin.push(' ');
            }
        }
        lin.push_str(&smp.process_input(&l)?);
        out.push_str(&lin);
    }
    Ok(out)
}

/// Push any arguments to array macro
fn smp_builtin_array_push(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    let mut args_iter = args.iter();
    let Some(array_name) = args_iter.next() else {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Invalid arguments to array_push"),
            ));
        return Ok(String::new());
    };
    let mut def = Vec::new();
    while let Some(arg) = args_iter.next() {
        def.push(MacroType::String(smp.process_input(arg)?));
    }
    if let Err(e) = smp.array_push(array_name, MacroType::Array(def)) {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Error executing array_push ({:?})", e),
            ));
    }
    Ok(String::new())
}

/// Push any arguments to array macro
/// Process each element in a array as a macro-invokation on the second argument
/// Not the best way to do this, it is not sensibly recursive.
fn smp_builtin_array_each(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    let Some(macro_body) = smp.macros.get(&args[0]) else {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("{:?} is not a macro", args[0]),
            ));
        return Ok(String::new());
    };
    let MacroType::Array(array) = macro_body else {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("{:?} is not a macro of type array", args[0]),
            ));
        return Ok(String::new());
    };
    let mut out = String::new();
    for el in array.clone() {
        let exp = match el {
            MacroType::String(s) => {
                let n = smp.expand_macro(&args[1], &mut [s])?;
                smp.process_input(&n)?
            }
            MacroType::Array(a) => {
                let mut out = Vec::new();
                for _el in a {
                    out.push(_el.to_string());
                }
                let expanded = smp.expand_macro(&args[1], &mut out)?;
                smp.process_input(&expanded)?
            }
            _ => String::new(),
        };
        out.push_str(&exp);
    }

    Ok(out)
}

fn smp_builtin_array_size(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    let Some(macro_body) = smp.macros.get(&args[0]) else {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("{:?} is not a macro", args[0]),
            ));
        return Ok(String::new());
    };
    let MacroType::Array(array) = macro_body else {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("{:?} is not a macro of type array", args[0]),
            ));
        return Ok(String::new());
    };
    Ok(array.len().to_string())
}

/// Split input into array
fn smp_builtin_explode(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() != 3 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected 3"),
            ));
        return Ok(macro_name.to_string());
    }
    let split = smp.process_input(&args[1])?;
    for c in smp.process_input(&args[2])?.split(&split) {
        if let Err(e) = smp.array_push(&args[0], MacroType::String(String::from(c))) {
            smp.warnings
                .push(MacroProcessorWarning::from_macro_invocation(
                    macro_name,
                    args,
                    format!("Error executing array_push ({:?})", e),
                ));
        }
    }

    Ok(String::new())
}

#[cfg(feature = "time")]
fn smp_builtin_format_time(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 2 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected at least 2"),
            ));
        return Ok(macro_name.to_string());
    }
    let timestamp = smp.process_input(&args[1])?;
    let dt = match chrono::DateTime::parse_from_rfc3339(&timestamp) {
        Ok(dt) => dt,
        Err(e) => {
            smp.warnings
                .push(MacroProcessorWarning::from_macro_invocation(
                    macro_name,
                    args,
                    format!("Could not parse datetime {} ({})", timestamp, e),
                ));
            return Ok(timestamp);
        }
    };
    Ok(format!("{}", dt.format(&args[0])))
}

fn smp_builtin_html_from_markdown(
    smp: &mut MacroProcessor,
    macro_name: &str,
    args: &mut [String],
) -> Result<String, SMPError> {
    if args.len() < 1 {
        smp.warnings
            .push(MacroProcessorWarning::from_macro_invocation(
                macro_name,
                args,
                format!("Wrong number of arguments, expected 1"),
            ));
        return Ok(macro_name.to_string());
    }
    let content = smp.process_input(&args[0])?;
    let content = smp.process_input(&content)?;
    markdown::to_html_with_options(
        &content,
        &markdown::Options {
            parse: markdown::ParseOptions::gfm(),
            compile: markdown::CompileOptions {
                allow_dangerous_html: true,
                allow_dangerous_protocol: true,
                ..markdown::CompileOptions::default()
            },
        },
    )
    .map_err(|e| SMPError::MarkdownError(15, e))
}

fn macro_is_whitespace_deleting(s: &str) -> bool {
    if s.len() == 0 {
        return false;
    }
    s.chars().nth(s.len() - 1) == Some('_')
}

fn macro_name_clean<'a>(macro_name: &'a str) -> &'a str {
    let mut macro_name = macro_name;
    if macro_is_whitespace_deleting(macro_name) {
        let mut macro_chars = macro_name.chars();
        macro_chars.next_back();
        macro_name = macro_chars.as_str();
    }
    macro_name
}

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

use std::string::ToString;
impl ToString for MacroType {
    fn to_string(&self) -> String {
        match self {
            MacroType::String(s) => s.to_string(),
            MacroType::Array(a) => {
                let mut out = String::from("[");
                for (i, el) in a.iter().enumerate() {
                    out.push_str(&el.to_string());
                    if i < (a.len() - 1) {
                        out.push_str(", ");
                    }
                }
                out
            }
            MacroType::Function(_a) => String::from("Function()"),
        }
    }
}

/// Possible parser states
#[derive(Debug, PartialEq)]
enum ParserState {
    Normal,
    InQuotes,
    InMacro,
    InMacroArgs,
    #[cfg(feature = "guile")]
    InGuile,
    DNL,
}

#[derive(Clone, Debug)]
pub struct MacroProcessorWarning {
    pub description: String,
}

impl MacroProcessorWarning {
    pub fn new(description: String) -> Self {
        MacroProcessorWarning { description }
    }

    pub fn from_macro_invocation(macro_name: &str, args: &[String], description: String) -> Self {
        let mut desc = format!("{}(", macro_name);
        for (i, arg) in args.iter().enumerate() {
            desc.push_str(arg);
            if i < (args.len() - 1) {
                desc.push(',');
            }
        }
        desc.push_str(&format!(") -> {}", description));
        MacroProcessorWarning { description: desc }
    }
}

/// Defines a MacroProcessor object, with it's associated state
/// the state mostly includes the defined macros
#[derive(Debug, Clone)]
pub struct MacroProcessor {
    /// All currently defined macros in this MacroProcessor
    pub macros: HashMap<String, MacroType>,
    /// All macro invocations that has happened
    pub macro_invocations: Vec<(String, Vec<String>)>,
    /// Emitted warnings
    pub warnings: Vec<MacroProcessorWarning>,
    #[cfg(feature = "guile")]
    pub guile: std::rc::Rc<Guile>,
}

pub static mut GLOBS: Option<&MacroProcessor> = None;

impl MacroProcessor {
    pub fn new() -> Self {
        let mut smp = Self {
            macros: HashMap::new(),
            macro_invocations: Vec::new(),
            warnings: Vec::new(),
            #[cfg(feature = "guile")]
            guile: std::rc::Rc::new(Guile::new()),
        };
        smp.define_builtins();
        smp
    }

    #[cfg(feature = "guile")]
    /// Define own environment
    pub fn defself(&mut self) {
        // Find a better way to do this Rc-stuff
        let guile = std::rc::Rc::as_ptr(&self.guile);
        unsafe {
            let data_ptr: *mut c_void = self as *mut _ as *mut c_void;
            (*guile).define("smp_state_ptr", data_ptr);
        }
    }

    /// Bootstrapping-function for defining all builtins,
    /// the same way all other macros might be defined
    fn define_builtins(&mut self) {
        self.define_macro(
            String::from("define"),
            MacroType::Function(smp_builtin_define),
        );
        self.define_macro(
            String::from("define_array"),
            MacroType::Function(smp_builtin_define_array),
        );
        self.define_macro(
            String::from("undefine"),
            MacroType::Function(smp_builtin_undefine),
        );
        self.define_macro(
            String::from("ifdef"),
            MacroType::Function(smp_builtin_ifdef),
        );
        self.define_macro(
            String::from("ifndef"),
            MacroType::Function(smp_builtin_ifndef),
        );
        self.define_macro(String::from("ifeq"), MacroType::Function(smp_builtin_ifeq));
        self.define_macro(
            String::from("ifneq"),
            MacroType::Function(smp_builtin_ifneq),
        );
        self.define_macro(
            String::from("include"),
            MacroType::Function(smp_builtin_include),
        );
        self.define_macro(
            String::from("include_verbatim"),
            MacroType::Function(smp_builtin_include_verbatim),
        );
        self.define_macro(
            String::from("shell"),
            MacroType::Function(smp_builtin_shell),
        );
        self.define_macro(
            String::from("indent"),
            MacroType::Function(smp_builtin_indent),
        );
        self.define_macro(String::from("expr"), MacroType::Function(smp_builtin_expr));
        self.define_macro(
            String::from("array_push"),
            MacroType::Function(smp_builtin_array_push),
        );
        self.define_macro(
            String::from("array_each"),
            MacroType::Function(smp_builtin_array_each),
        );
        self.define_macro(
            String::from("array_size"),
            MacroType::Function(smp_builtin_array_size),
        );
        self.define_macro(
            String::from("explode"),
            MacroType::Function(smp_builtin_explode),
        );
        #[cfg(feature = "time")]
        self.define_macro(
            String::from("format_time"),
            MacroType::Function(smp_builtin_format_time),
        );
        #[cfg(feature = "markdown")]
        self.define_macro(
            String::from("html_from_markdown"),
            MacroType::Function(smp_builtin_html_from_markdown),
        );
        #[cfg(feature = "deadlinks")]
        self.define_macro(String::from("wodl"), MacroType::Function(smp_builtin_wodl));
        #[cfg(feature = "webring")]
        self.define_macro(
            String::from("webring_rss"),
            MacroType::Function(smp_builtin_webring_rss),
        );

        // 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_string(&mut self, name: String, body: String) {
        self.define_macro(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(&mut self, name: String, macro_expansion: MacroType) {
        self.macros.insert(name, macro_expansion);
    }

    /// Push a MacroType into a array, this allows creating special macros that can iterate
    pub fn array_push(&mut self, name: &str, element: MacroType) -> Result<(), SMPError> {
        let Some(macro_body) = self.macros.get_mut(name) else {
            return Err(SMPError::UnknownError(4, None));
        };
        let MacroType::Array(array) = macro_body else {
            return Err(SMPError::UnknownError(5, None));
        };
        array.push(element);
        Ok(())
    }

    /// 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]) -> Result<String, SMPError> {
        // Ignore trailing underscore in macro name, the parser will pop a space in front if
        // present, but we should ignore it for finding the macro.
        let macro_name = macro_name_clean(macro_name);
        let Some(macro_body) = self.macros.get(macro_name) else {
            if args.len() == 0 {
                return Ok(format!("{}", macro_name));
            }
            let mut out = format!("{}(", macro_name);
            for (i, arg) in args.iter().enumerate() {
                out.push_str(&self.process_input(arg)?);
                if i < (args.len() - 1) {
                    out.push(',');
                }
            }
            out.push(')');
            return Ok(out);
        };

        // Strip leading whitespace from arguments
        for arg in &mut *args {
            *arg = arg.trim().to_string();
        }

        // Log macro invokation
        // The fact that we are here, does not ensure that the macro is actually expanded into
        // something useful, just that it exists, and was invoked
        self.macro_invocations
            .push((macro_name.to_string(), args.to_vec()));

        match macro_body {
            MacroType::String(body) => {
                let mut expanded = body.clone();
                for (i, arg) in args.iter().enumerate() {
                    let placeholder = format!("${}", i);
                    expanded = expanded.replace(&placeholder, arg);
                }
                self.process_input(&expanded)
            }
            MacroType::Function(func) => {
                return func(self, macro_name, args);
            }
            MacroType::Array(vec) => return Ok(format!("Array[{}]", vec.len())),
        }
    }

    /// 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) -> Result<String, SMPError> {
        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 current_indent = 0;
        //let mut line_text_seen = false;

        // We should keep track of filename, linenumber, and character number on line here
        // So we can give sensible error messages

        let mut quote_level = 0;
        let mut parens_level = 0;

        #[cfg(feature = "guile")]
        let mut guile_expr = String::new();

        let mut chars = input.char_indices().peekable();
        while let Some((i, c)) = chars.next() {
            highlight_debug!(input, macro_name_start, i);
            let peek = match chars.peek() {
                Some((_, c)) => Some(c),
                None => None,
            };

            match state {
                ParserState::DNL => {
                    if c == '\n' {
                        state = ParserState::Normal;
                    }
                }
                ParserState::Normal => {
                    macro_name_start = i;

                    if skip_next_line_ending && (c == '\n') {
                        skip_next_line_ending = false;
                        continue;
                    }

                    #[cfg(feature = "guile")]
                    if c == '%' && peek == Some(&'(') {
                        state = ParserState::InGuile;
                        chars.next();
                        continue;
                    }

                    if c == '%' && peek == Some(&'"') {
                        state = ParserState::InQuotes;
                        quote_level += 1;
                        chars.next();
                    } else if c.is_alphanumeric() {
                        state = ParserState::InMacro;
                        macro_name.push(c);
                    } else {
                        output.push(c);
                    }
                }
                ParserState::InQuotes => match c {
                    '%' if peek == Some(&'"') => {
                        quote_level += 1;
                        chars.next();
                        output.push_str(r#"%""#);
                    }
                    '"' if peek == Some(&'%') => {
                        quote_level -= 1;
                        if quote_level == 0 {
                            state = ParserState::Normal;
                        } else {
                            output.push_str(r#""%"#);
                        }
                        chars.next();
                    }
                    _ => {
                        output.push(c);
                    }
                },
                #[cfg(feature = "guile")]
                ParserState::InGuile => match c {
                    ')' if peek == Some(&'%') => {
                        self.defself();
                        let r = self
                            .guile
                            .evaluate_expression(&guile_expr)
                            .expect("fatal guile error");
                        output.push_str(&r);
                        guile_expr.clear();
                        state = ParserState::Normal;
                        chars.next();
                    }
                    _ => {
                        guile_expr.push(c);
                    }
                },
                ParserState::InMacro => {
                    if c.is_alphanumeric() || c == '_' {
                        macro_name.push(c);
                    } else if c == '(' {
                        parens_level += 1;
                        state = ParserState::InMacroArgs;
                    } else {
                        if macro_is_whitespace_deleting(&macro_name) {
                            if output.chars().last() == Some(' ') {
                                output.pop();
                            }
                            macro_name = macro_name_clean(&macro_name).to_string();
                        }
                        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 if macro_name == "DNL" {
                            if c != '\n' {
                                state = ParserState::DNL;
                            }
                            macro_name.clear();
                            continue;
                        } 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 == '%' && peek == Some(&'"') {
                        quote_level += 1;
                        chars.next();
                        argument.push_str(r#"%""#);
                        continue;
                    } else if c == '"' && peek == Some(&'%') {
                        quote_level -= 1;
                        chars.next();
                        argument.push_str(r#""%"#);
                        continue;
                    } else if quote_level > 0 {
                        argument.push(c);
                        continue;
                    }

                    if (c == ')') && (parens_level == 1) {
                        if macro_is_whitespace_deleting(&macro_name) {
                            if output.chars().last() == Some(' ') {
                                output.pop();
                            }
                            macro_name = macro_name_clean(&macro_name).to_string();
                        }
                        highlight_debug!("\x1b[32m\x1b[7m", input, (macro_name_start -> i));
                        parens_level = 0;
                        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 == ',') && (parens_level == 1) {
                        macro_args.push(argument.trim().to_string());
                        argument.clear();
                    } else {
                        if c == '(' {
                            parens_level += 1;
                        }
                        if c == ')' {
                            parens_level -= 1;
                        }
                        argument.push(c);
                    }
                }
            }
        }

        // Handle cases where the text ends with a macro without arguments
        if !macro_name.is_empty() {
            if macro_is_whitespace_deleting(&macro_name) {
                if output.chars().last() == Some(' ') {
                    output.pop();
                }
                macro_name = macro_name_clean(&macro_name).to_string();
            }
            output.push_str(&self.expand_macro(&macro_name, &mut [])?);
        }

        Ok(output)
    }
}