use crate::formatter::Formatter;
use crate::models::MacroType;
use crate::models::RadResult;
use crate::models::{ExtMacroBody, ExtMacroBuilder};
use crate::utils::Utils;
use crate::ArgParser;
use crate::Processor;
use crate::{AuthType, RadError};
use std::collections::HashMap;
use std::iter::FromIterator;
pub(crate) type DFunctionMacroType = fn(&str, usize, &mut Processor) -> RadResult<Option<String>>;
#[derive(Clone)]
pub struct DeterredMacroMap {
pub(crate) macros: HashMap<String, DMacroSign>,
}
impl DeterredMacroMap {
pub fn empty() -> Self {
Self {
macros: HashMap::new(),
}
}
pub fn new() -> Self {
let mut map = HashMap::from_iter(IntoIterator::into_iter([
(
"exec".to_owned(),
DMacroSign::new(
"exec",
["macro_name", "macro_args"],
DeterredMacroMap::execute_macro,
Some("Execute a macro with arguments".to_string()),
),
),
(
"fassert".to_owned(),
DMacroSign::new(
"fassert",
["a_lvalue", "a_rvalue"],
DeterredMacroMap::assert_fail,
Some("Assert succeedes when fails".to_string()),
),
),
(
"foreach".to_owned(),
DMacroSign::new(
"foreach",
["a_array", "a_body"],
DeterredMacroMap::foreach,
Some("Loop around given array".to_string()),
),
),
(
"forline".to_owned(),
DMacroSign::new(
"forline",
["a_iterable", "a_body"],
DeterredMacroMap::forline,
Some("Loop around given lines".to_string()),
),
),
(
"forloop".to_owned(),
DMacroSign::new(
"forloop",
["a_min", "a_max", "a_body"],
DeterredMacroMap::forloop,
Some("Loop around given range".to_string()),
),
),
(
"from".to_owned(),
DMacroSign::new(
"from",
["a_macro_name", "a_csv_value"],
Self::from_data,
Some("Execute macro multiple times with given data chunk".to_string()),
),
),
(
"if".to_owned(),
DMacroSign::new(
"if",
["a_boolean", "a_if_expr"],
DeterredMacroMap::if_cond,
Some("Check condition and then execute".to_string()),
),
),
(
"ifelse".to_owned(),
DMacroSign::new(
"ifelse",
["a_boolean", "a_if_expr", "a_else_expr"],
DeterredMacroMap::ifelse,
Some("Check condition and execute different expressions".to_string()),
),
),
(
"ifdef".to_owned(),
DMacroSign::new(
"ifdef",
["a_macro_name", "a_if_expr"],
DeterredMacroMap::ifdef,
Some("Execute expression if macro is defined".to_string()),
),
),
(
"ifdefel".to_owned(),
DMacroSign::new(
"ifdefel",
["a_macro_name", "a_if_expr", "a_else_expr"],
DeterredMacroMap::ifdefel,
Some("Execute expressions whether macro is defined or not".to_string()),
),
),
(
"que".to_owned(),
DMacroSign::new(
"que",
["a_content"],
DeterredMacroMap::queue_content,
Some("Que expressions".to_string()),
),
),
(
"ifque".to_owned(),
DMacroSign::new(
"ifque",
["a_bool", "a_content"],
DeterredMacroMap::if_queue_content,
Some("If true, then queue expressions".to_string()),
),
),
]));
#[cfg(not(feature = "wasm"))]
{
map.insert(
"ifenv".to_owned(),
DMacroSign::new(
"ifenv",
["a_env_name", "a_if_expr"],
DeterredMacroMap::ifenv,
Some("Execute expression if environment variable is set".to_string()),
),
);
map.insert(
"ifenvel".to_owned(),
DMacroSign::new(
"ifenvel",
["a_env_name", "a_if_expr", "a_else_expr"],
DeterredMacroMap::ifenvel,
Some(
"Execute expression by whether environment variable is set or not"
.to_string(),
),
),
);
}
#[cfg(feature = "evalexpr")]
{
map.insert(
"ieval".to_owned(),
DMacroSign::new(
"ieval",
["a_macro", "a_expression"],
Self::eval_inplace,
Some("Eval expression in-place for macro".to_string()),
),
);
}
Self { macros: map }
}
pub fn get_deterred_macro(&self, name: &str) -> Option<&DFunctionMacroType> {
if let Some(mac) = self.macros.get(name) {
Some(&mac.logic)
} else {
None
}
}
pub fn contains(&self, name: &str) -> bool {
self.macros.contains_key(name)
}
pub fn undefine(&mut self, name: &str) {
self.macros.remove(name);
}
pub fn rename(&mut self, name: &str, target: &str) {
let func = self.macros.remove(name).unwrap();
self.macros.insert(target.to_owned(), func);
}
pub fn new_ext_macro(&mut self, ext: ExtMacroBuilder) {
if let Some(ExtMacroBody::Deterred(mac_ref)) = ext.macro_body {
let sign = DMacroSign::new(&ext.macro_name, &ext.args, mac_ref, ext.macro_desc);
self.macros.insert(ext.macro_name, sign);
}
}
fn foreach(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let mut sums = String::new();
let loopable = &processor.parse_chunk_args(level, "", &args[0])?;
for (count, value) in loopable.split(',').enumerate() {
processor.add_new_local_macro(level, "a_LN", &count.to_string());
processor.add_new_local_macro(level, ":", value);
let result = processor.parse_chunk_args(level, "", &args[1])?;
sums.push_str(&result);
}
processor.remove_local_macro(level, ":");
Ok(Some(sums))
} else {
Err(RadError::InvalidArgument(
"Foreach requires two argument".to_owned(),
))
}
}
fn forline(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let mut sums = String::new();
let loopable = &processor.parse_chunk_args(level, "", &args[0])?;
let mut count = 1;
for value in loopable.lines() {
processor.add_new_local_macro(level, "a_LN", &count.to_string());
processor.add_new_local_macro(level, ":", value);
let result = processor.parse_chunk_args(level, "", &args[1])?;
sums.push_str(&result);
count += 1;
}
Ok(Some(sums))
} else {
Err(RadError::InvalidArgument(
"Forline requires two argument".to_owned(),
))
}
}
fn forloop(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
let mut sums = String::new();
let min_src = processor.parse_chunk_args(level, "", &Utils::trim(&args[0]))?;
let max_src = processor.parse_chunk_args(level, "", &Utils::trim(&args[1]))?;
let min = if let Ok(num) = min_src.parse::<usize>() {
num
} else {
return Err(RadError::InvalidArgument(format!(
"Forloop's min value should be non zero positive integer but given {}",
min_src
)));
};
let max = if let Ok(num) = max_src.parse::<usize>() {
num
} else {
return Err(RadError::InvalidArgument(format!(
"Forloop's max value should be non zero positive integer but given \"{}\"",
max_src
)));
};
let mut result: String;
for value in min..=max {
processor.add_new_local_macro(level, ":", &value.to_string());
result = processor.parse_chunk_args(level, "", &args[2])?;
sums.push_str(&result);
result.clear();
}
processor.remove_local_macro(level, ":");
Ok(Some(sums))
} else {
Err(RadError::InvalidArgument(
"Forloop requires two argument".to_owned(),
))
}
}
fn if_cond(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let boolean = &processor.parse_chunk_args(level, "", &args[0])?;
let cond = Utils::is_arg_true(&Utils::trim(boolean));
if let Ok(cond) = cond {
if cond {
let if_expr = processor.parse_chunk_args(level, "", &args[1])?;
return Ok(Some(if_expr));
}
} else {
return Err(RadError::InvalidArgument(format!(
"If requires either true/false or zero/nonzero integer but given \"{}\"",
boolean
)));
}
Ok(None)
} else {
Err(RadError::InvalidArgument(
"if requires two arguments".to_owned(),
))
}
}
fn ifelse(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
let boolean = &processor.parse_chunk_args(level, "", &args[0])?;
let cond = Utils::is_arg_true(&Utils::trim(boolean));
if let Ok(cond) = cond {
if cond {
let if_expr = processor.parse_chunk_args(level, "", &args[1])?;
return Ok(Some(if_expr));
}
} else {
return Err(RadError::InvalidArgument(format!(
"Ifelse requires either true/false or zero/nonzero integer but given \"{}\"",
boolean
)));
}
let else_expr = processor.parse_chunk_args(level, "", &args[2])?;
Ok(Some(else_expr))
} else {
Err(RadError::InvalidArgument(
"ifelse requires three argument".to_owned(),
))
}
}
fn ifdef(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = processor.parse_chunk_args(level, "", &Utils::trim(&args[0]))?;
let boolean = processor.contains_macro(&name, MacroType::Any);
if boolean {
let if_expr = processor.parse_chunk_args(level, "", &args[1])?;
return Ok(Some(if_expr));
}
Ok(None)
} else {
Err(RadError::InvalidArgument(
"ifdef requires two arguments".to_owned(),
))
}
}
fn ifdefel(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
let name = processor.parse_chunk_args(level, "", &Utils::trim(&args[0]))?;
let boolean = processor.contains_macro(&name, MacroType::Any);
if boolean {
let if_expr = processor.parse_chunk_args(level, "", &args[1])?;
Ok(Some(if_expr))
} else {
let else_expr = processor.parse_chunk_args(level, "", &args[2])?;
Ok(Some(else_expr))
}
} else {
Err(RadError::InvalidArgument(
"ifdefel requires three arguments".to_owned(),
))
}
}
fn ifenv(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("ifenv", AuthType::ENV, processor)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let name = processor.parse_chunk_args(level, "", &Utils::trim(&args[0]))?;
let boolean = std::env::var(name).is_ok();
if boolean {
let if_expr = processor.parse_chunk_args(level, "", &args[1])?;
return Ok(Some(if_expr));
}
Ok(None)
} else {
Err(RadError::InvalidArgument(
"ifenv requires two arguments".to_owned(),
))
}
}
fn ifenvel(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if !Utils::is_granted("ifenvel", AuthType::ENV, processor)? {
return Ok(None);
}
if let Some(args) = ArgParser::new().args_with_len(args, 3) {
let name = processor.parse_chunk_args(level, "", &Utils::trim(&args[0]))?;
let boolean = std::env::var(name).is_ok();
if boolean {
let if_expr = processor.parse_chunk_args(level, "", &args[1])?;
Ok(Some(if_expr))
} else {
let else_expr = processor.parse_chunk_args(level, "", &args[2])?;
Ok(Some(else_expr))
}
} else {
Err(RadError::InvalidArgument(
"ifenvel requires three arguments".to_owned(),
))
}
}
fn assert_fail(
args: &str,
level: usize,
processor: &mut Processor,
) -> RadResult<Option<String>> {
let result = processor.parse_chunk_args(level, "", args);
if result.is_err() {
processor.track_assertion(true)?;
Ok(None)
} else {
processor.track_assertion(false)?;
Err(RadError::AssertFail)
}
}
fn queue_content(args: &str, _: usize, processor: &mut Processor) -> RadResult<Option<String>> {
processor.insert_queue(args);
Ok(None)
}
fn if_queue_content(
args: &str,
level: usize,
processor: &mut Processor,
) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let boolean = &processor.parse_chunk_args(level, "", &args[0])?;
let cond = Utils::is_arg_true(boolean)?;
if cond {
processor.insert_queue(&args[1]);
}
Ok(None)
} else {
Err(RadError::InvalidArgument(
"ifque requires two argument".to_owned(),
))
}
}
fn execute_macro(
args: &str,
level: usize,
processor: &mut Processor,
) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let macro_name = &processor.parse_chunk_args(level, "", &args[0])?;
if !processor.contains_macro(macro_name, MacroType::Any) {
return Err(RadError::InvalidArgument(format!(
"Macro \"{}\" doesn't exist",
macro_name
)));
}
let args = &args[1];
let result =
processor.parse_chunk_args(level, "", &format!("${}({})", macro_name, args))?;
Ok(Some(result))
} else {
Err(RadError::InvalidArgument(
"exec requires two argument".to_owned(),
))
}
}
fn from_data(args: &str, level: usize, processor: &mut Processor) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let macro_name = Utils::trim(&args[0]);
let macro_data = &args[1];
let result =
Formatter::csv_to_macros(¯o_name, macro_data, &processor.state.newline)?;
#[cfg(feature = "debug")]
let original = processor.is_debug();
#[cfg(feature = "debug")]
processor.set_debug(false);
let result = processor.parse_chunk_args(level, "", &result)?;
#[cfg(feature = "debug")]
{
use crate::debugger::DebugSwitch;
processor.set_debug(original);
match processor.get_debug_switch() {
DebugSwitch::StepMacro | DebugSwitch::NextMacro => {
processor.set_prompt_log("\"From macro\"")
}
_ => (),
}
}
Ok(Some(result))
} else {
Err(RadError::InvalidArgument(
"From requires two arguments".to_owned(),
))
}
}
#[cfg(feature = "evalexpr")]
fn eval_inplace(
args: &str,
level: usize,
processor: &mut Processor,
) -> RadResult<Option<String>> {
if let Some(args) = ArgParser::new().args_with_len(args, 2) {
let macro_name = Utils::trim(&args[0]);
if !processor.contains_macro(¯o_name, MacroType::Runtime) {
return Err(RadError::InvalidArgument(format!(
"Macro \"{}\" doesn't exist",
macro_name
)));
}
let expr = Utils::trim(&args[1]);
let chunk = format!("$eval( ${}() {} )", macro_name, expr);
let result = processor.parse_chunk_args(level, "", &chunk)?;
processor.replace_macro(¯o_name, &result);
Ok(None)
} else {
Err(RadError::InvalidArgument(
"Ieval requires two arguments".to_owned(),
))
}
}
}
#[derive(Clone)]
pub(crate) struct DMacroSign {
name: String,
args: Vec<String>,
pub logic: DFunctionMacroType,
#[allow(dead_code)]
desc: Option<String>,
}
impl DMacroSign {
pub fn new(
name: &str,
args: impl IntoIterator<Item = impl AsRef<str>>,
logic: DFunctionMacroType,
desc: Option<String>,
) -> Self {
let args = args
.into_iter()
.map(|s| s.as_ref().to_owned())
.collect::<Vec<String>>();
Self {
name: name.to_owned(),
args,
logic,
desc,
}
}
}
impl std::fmt::Display for DMacroSign {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut inner = self
.args
.iter()
.fold(String::new(), |acc, arg| acc + arg + ",");
inner.pop();
write!(f, "${}({})", self.name, inner)
}
}
#[cfg(feature = "signature")]
impl From<&DMacroSign> for crate::sigmap::MacroSignature {
fn from(ms: &DMacroSign) -> Self {
Self {
variant: crate::sigmap::MacroVariant::Deterred,
name: ms.name.to_owned(),
args: ms.args.to_owned(),
expr: ms.to_string(),
desc: ms.desc.clone(),
}
}
}