From 6af9f573fce9c167487e10c7327feff357327d6a Mon Sep 17 00:00:00 2001 From: Qrius Date: Thu, 26 Sep 2024 00:11:05 +0200 Subject: Change around some things, don't clone MacroProcessor for nested templates, so we can keep state from parents --- src/macro_processor/macro_processor.rs | 188 +++++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 11 deletions(-) (limited to 'src/macro_processor/macro_processor.rs') diff --git a/src/macro_processor/macro_processor.rs b/src/macro_processor/macro_processor.rs index 7839963..ee7e269 100644 --- a/src/macro_processor/macro_processor.rs +++ b/src/macro_processor/macro_processor.rs @@ -56,6 +56,52 @@ fn smp_builtin_define( Ok(String::new()) } +/// Builtin for undefining a macro +fn smp_builtin_undefine( + smp: &mut MacroProcessor, + macro_name: &str, + args: &mut [String], +) -> Result { + 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 { + 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, @@ -295,19 +341,77 @@ fn smp_builtin_array_push( macro_name: &str, args: &mut [String], ) -> Result { - for arg in args.iter() { - if let Err(e) = smp.array_push(macro_name, MacroType::String(arg.to_string())) { - smp.warnings - .push(MacroProcessorWarning::from_macro_invocation( - macro_name, - args, - format!("Error executing array_push ({:?})", e), - )); - } + 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 { + 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) => smp.expand_macro(&args[1], &mut [s])?, + 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) +} + #[cfg(feature = "time")] fn smp_builtin_format_time( smp: &mut MacroProcessor, @@ -354,7 +458,7 @@ fn macro_name_clean<'a>(macro_name: &'a str) -> &'a str { } /// Types of macros, this is to make it easy to store both functions and strings -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum MacroType { /// When expanded, the associated function will be expanded Function( @@ -369,10 +473,31 @@ pub enum MacroType { Array(Vec), } +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, DNL, @@ -431,6 +556,14 @@ impl MacroProcessor { 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), @@ -465,11 +598,21 @@ impl MacroProcessor { String::from("array_push"), MacroType::Function(smp_builtin_array_push), ); + self.define_macro( + String::from("array_each"), + MacroType::Function(smp_builtin_array_each), + ); #[cfg(feature = "time")] self.define_macro( String::from("format_time"), MacroType::Function(smp_builtin_format_time), ); + #[cfg(feature = "webring")] + self.define_macro( + String::from("webring_rss"), + MacroType::Function(smp_builtin_webring_rss), + ); + // format('Result id %d', 3282) } @@ -548,7 +691,7 @@ impl MacroProcessor { // 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); + let placeholder = format!("${}", i); expanded = expanded.replace(&placeholder, arg); } self.process_input(&expanded) @@ -584,6 +727,9 @@ impl MacroProcessor { let mut in_quote_single = false; let mut in_quote_double = false; + const QUOTE_START: char = '`'; + const QUOTE_END: char = '\''; + let mut quote_level = 0; let mut parens_level = 0; for (i, c) in input.char_indices() { @@ -606,10 +752,30 @@ impl MacroProcessor { if c.is_alphanumeric() { state = ParserState::InMacro; macro_name.push(c); + } else if c == QUOTE_START { + state = ParserState::InQuotes; + quote_level += 1; } else { output.push(c); } } + ParserState::InQuotes => match c { + QUOTE_START => { + quote_level += 1; + output.push(c); + } + QUOTE_END => { + quote_level -= 1; + if quote_level == 0 { + state = ParserState::Normal; + } else { + output.push(c); + } + } + _ => { + output.push(c); + } + }, ParserState::InMacro => { if c.is_alphanumeric() || c == '_' { macro_name.push(c); -- cgit v1.2.3