use crate::core::environment::{expr_desc, model_to_string};
use crate::core::eval::eval;
use crate::core::{Expr, ExprKind};
use crate::error::{Result, TemplateError};
use crate::template::utility::java_trim;
use crate::template::TModel;
use crate::value::{DateType, TNumber};
pub(crate) fn eval_item_cycle_direct(
env: &mut crate::core::Environment,
target: &Expr,
args: &[Expr],
) -> Result<TModel> {
if args.is_empty() {
return Err(TemplateError::misc(
"?itemCycle(...) expects 1 or more (unlimited) arguments but has received none.",
));
}
let target_var = match &target.kind {
ExprKind::Ident(n) => Some(n.as_str()),
_ => None,
};
let lc = env.get_loop_context(target_var).ok_or_else(|| {
TemplateError::misc(
"The target of ?itemCycle is not a loop variable (no enclosing loop in scope)",
)
})?;
let idx = lc.borrow().index % args.len();
let m = eval(env, &args[idx])?;
if m.is_nothing() {
return Err(TemplateError::invalid_reference(expr_desc(&args[idx])));
}
Ok(m)
}
pub(crate) struct NewConstructorFunction {
pub(crate) class_name: String,
}
impl crate::template::TemplateMethodModelEx for NewConstructorFunction {
fn exec(&self, _env: &mut crate::core::Environment, args: Vec<TModel>) -> Result<TModel> {
crate::template::utility_transforms::new_utility_class(&self.class_name, &args)
}
}
pub(crate) fn loop_state_builtin(
env: &mut crate::core::Environment,
target: &Expr,
name: &str,
args: &super::BuiltinArgs,
) -> Result<Option<TModel>> {
if !matches!(
name,
"index"
| "counter"
| "has_next"
| "has_previous"
| "is_first"
| "is_last"
| "is_odd"
| "is_even"
| "is_odd_item"
| "is_even_item"
| "item_parity"
| "item_parity_cap"
| "item_cycle"
) {
return Ok(None);
}
let target_var = match &target.kind {
ExprKind::Ident(n) => Some(n.as_str()),
_ => None,
};
let lc = env.get_loop_context(target_var).ok_or_else(|| {
TemplateError::misc(format!(
"The target of ?{name} is not a loop variable (no enclosing loop in scope)"
))
})?;
let lc = lc.borrow();
let b = match name {
"index" => {
return Ok(Some(TModel::from_number(TNumber::from_i64(
lc.index as i64,
))))
}
"counter" => {
return Ok(Some(TModel::from_number(TNumber::from_i64(
lc.index as i64 + 1,
))))
}
"has_next" => lc.has_next,
"has_previous" => lc.index > 0,
"is_first" => lc.index == 0,
"is_last" => !lc.has_next,
"is_odd" => lc.index % 2 == 1,
"is_even" => lc.index % 2 == 0,
"is_odd_item" => (lc.index + 1) % 2 == 1,
"is_even_item" => (lc.index + 1) % 2 == 0,
"item_parity" | "item_parity_cap" => {
let odd = (lc.index + 1) % 2 == 1;
let s = match name {
"item_parity" => {
if odd {
"odd"
} else {
"even"
}
}
_ => {
if odd {
"Odd"
} else {
"Even"
}
}
};
return Ok(Some(TModel::from_scalar(s.to_string())));
}
"item_cycle" => {
let args = args.exprs.unwrap_or(&[]);
if args.is_empty() {
return Err(TemplateError::misc(
"The ?itemCycle built-in requires at least one argument.",
));
}
let idx = lc.index % args.len();
let m = eval(env, &args[idx])?;
if m.is_nothing() {
return Err(TemplateError::invalid_reference(expr_desc(&args[idx])));
}
return Ok(Some(m));
}
_ => unreachable!(),
};
Ok(Some(TModel::from_boolean(b)))
}
pub(crate) fn builtin_interpret(
env: &mut crate::core::Environment,
target: &Expr,
) -> Result<TModel> {
let m = eval(env, target)?;
let (source, _id) = if let Some(seq) = &m.sequence {
let s0 = seq
.get(0)
.map_err(|_| TemplateError::misc("?interpret: the sequence is empty"))?;
let text = crate::core::environment::model_to_string(env, &s0)?;
(text, String::new())
} else if let Some(sc) = &m.scalar {
(sc.as_string()?, String::new())
} else {
return Err(TemplateError::type_mismatch(
"sequence or string",
m.type_name,
));
};
if source.is_empty() {
return Err(TemplateError::misc(
"?interpret: the template source is empty",
));
}
let cfg = env.template.configuration.clone();
let name = format!("{}->anonymous_interpreted", env.current_template_name);
let template = crate::parser::parse(&cfg, &name, &source).map_err(|e| {
TemplateError::misc(format!(
"Template parsing with \"?interpret\" has failed with this error:\n\n{e}"
))
})?;
Ok(TModel::from_transform(InterpretedTemplate(template)))
}
struct InterpretedTemplate(crate::template::Template);
impl crate::template::TemplateTransformModel for InterpretedTemplate {
fn transform(&self, env: &mut crate::core::Environment) -> Result<()> {
env.include_template(&self.0)
}
}
pub(crate) fn date_type_if_unknown(
env: &mut crate::core::Environment,
target: &Expr,
kind: DateType,
) -> Result<Option<TModel>> {
let m = eval(env, target)?;
if let Some(d) = &m.date {
let dv = d.as_date()?;
if dv.kind != DateType::Unknown {
return Ok(Some(m));
}
let mut nv = dv.clone();
nv.kind = kind;
return Ok(Some(TModel::from_date(nv)));
}
Err(TemplateError::type_mismatch("date", m.type_name))
}
pub(crate) fn is_type_test(
env: &mut crate::core::Environment,
target: &Expr,
test: impl Fn(&TModel) -> bool,
) -> Result<Option<TModel>> {
match eval(env, target) {
Ok(m) => Ok(Some(TModel::from_boolean(test(&m)))),
Err(TemplateError::InvalidReference { .. }) => Ok(Some(TModel::from_boolean(false))),
Err(e) => Err(e),
}
}
pub(crate) fn locale_case(s: &str, locale: &str, upper: bool) -> String {
let lang = locale.split(['_', '-']).next().unwrap_or("");
let tr = lang == "tr" || lang == "az";
if !tr {
return if upper {
s.to_uppercase()
} else {
s.to_lowercase()
};
}
let chars: Vec<char> = s.chars().collect();
let mut out = String::new();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
let next = chars.get(i + 1).copied();
if upper {
match c {
'i' if next == Some('\u{0307}') => {
out.push('i');
i += 1;
}
'i' => out.push('\u{0130}'),
_ => out.extend(c.to_uppercase()),
}
} else {
match c {
'I' if next == Some('\u{0307}') => {
out.push('i');
i += 1;
out.push('\u{0307}');
}
'I' => out.push('\u{0131}'),
_ => out.extend(c.to_lowercase()),
}
}
i += 1;
}
out
}
pub(crate) fn check_legacy_escaping_ban(env: &crate::core::Environment, name: &str) -> Result<()> {
let auto_escape = match env.settings.auto_escaping {
crate::core::AutoEscaping::On => true,
crate::core::AutoEscaping::Off => false,
crate::core::AutoEscaping::Default => env.settings.output_format.is_markup(),
};
if env.settings.output_format.is_markup() && auto_escape {
return Err(TemplateError::misc(format!(
"Using ?{name} (legacy escaping) is not allowed when auto-escaping is on with a markup output format ({}), to avoid double-escaping mistakes.",
env.settings.output_format.name()
)));
}
Ok(())
}
pub(crate) fn str_builtin(
env: &mut crate::core::Environment,
target: &Expr,
f: impl Fn(&str) -> String,
) -> Result<Option<TModel>> {
let m = eval(env, target)?;
if m.is_nothing() {
return Err(TemplateError::invalid_reference(
crate::core::environment::expr_desc(target),
));
}
let s = model_to_string(env, &m)?;
Ok(Some(TModel::from_scalar(f(&s))))
}
pub(crate) fn parse_number(s: &str) -> Result<TNumber> {
let t = java_trim(s);
match t {
"INF" | "Infinity" => return Ok(TNumber::Double(f64::INFINITY)),
"-INF" | "-Infinity" => return Ok(TNumber::Double(f64::NEG_INFINITY)),
"NaN" => return Ok(TNumber::Double(f64::NAN)),
_ => {}
}
if let Ok(i) = t.parse::<i64>() {
return Ok(TNumber::from_i64(i));
}
if let Ok(b) = t.parse::<num_bigint::BigInt>() {
return Ok(TNumber::BigInt(b));
}
if let Ok(d) = t.parse::<bigdecimal::BigDecimal>() {
return Ok(TNumber::Decimal(d));
}
Err(TemplateError::misc(format!("{s} is not a number")))
}
pub(crate) fn arg_expr<'a>(
args: &'a super::BuiltinArgs,
idx: usize,
err: &str,
) -> Result<&'a Expr> {
args.exprs
.and_then(|a| a.get(idx))
.ok_or_else(|| TemplateError::misc(err.to_string()))
}
pub(crate) fn char_index_from(s: &str, from: usize) -> Option<&str> {
let mut chars = 0;
for (i, _) in s.char_indices() {
if chars == from {
return Some(&s[i..]);
}
chars += 1;
}
if chars == from {
Some("")
} else {
None
}
}