summaryrefslogtreecommitdiff
path: root/src/macro_processor
diff options
context:
space:
mode:
authorQrius <[email protected]>2024-09-26 00:11:05 +0200
committerQrius <[email protected]>2024-09-26 00:11:05 +0200
commit6af9f573fce9c167487e10c7327feff357327d6a (patch)
tree1969c1bfad6de89ad7da3b9904c20ef78b14df9c /src/macro_processor
parentc7e3570f90ddd495c0a27969e738de5a21bbccff (diff)
downloadskaldpress-6af9f573fce9c167487e10c7327feff357327d6a.tar.gz
skaldpress-6af9f573fce9c167487e10c7327feff357327d6a.zip
Change around some things, don't clone MacroProcessor for nested templates, so we can keep state from parents
Diffstat (limited to 'src/macro_processor')
-rw-r--r--src/macro_processor/macro_processor.rs188
1 files changed, 177 insertions, 11 deletions
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<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,
@@ -295,19 +341,77 @@ fn smp_builtin_array_push(
macro_name: &str,
args: &mut [String],
) -> Result<String, SMPError> {
- 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<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) => 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<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,
DNL,
@@ -432,6 +557,14 @@ impl MacroProcessor {
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);