use crate::core::environment::model_to_string;
use crate::core::eval::{eval, trunc_i64};
use crate::core::expression::dot_builtin_chain;
use crate::core::expression::eval_builtin;
use crate::core::{Expr, ExprKind};
use crate::error::{Result, TemplateError};
use crate::template::TModel;
pub struct DynamicKeyName {
pub target: Expr,
pub key: Expr,
}
impl DynamicKeyName {
pub fn new(target: Expr, key: Expr) -> Self {
DynamicKeyName { target, key }
}
pub(crate) fn eval(&self, env: &mut crate::core::Environment) -> Result<TModel> {
eval_dyn_key(env, &self.target, &self.key)
}
}
fn eval_dyn_key(env: &mut crate::core::Environment, target: &Expr, key: &Expr) -> Result<TModel> {
if let Some((inner, bname, mut names)) = dot_builtin_chain(target) {
if let Ok(k) = eval(env, key) {
if let Ok(k) = k.get_scalar() {
names.push(k);
let args: Vec<Expr> = names
.iter()
.map(|n| Expr::new(ExprKind::Str(n.clone()), target.span))
.collect();
return eval_builtin(env, &inner, &bname, &Some(args));
}
}
}
let t = eval(env, target)?;
if t.is_nothing() {
if env.settings.classic_compatible {
return Ok(TModel::nothing());
}
return Err(TemplateError::invalid_reference(
crate::core::environment::expr_desc(target),
));
}
let k = eval(env, key)?;
if let Ok(n) = k.get_number() {
let idx = trunc_i64(&n).ok_or_else(|| {
TemplateError::misc(format!(
"The index {} is out of the range of representable integers",
n.to_plain_string()
))
})?;
if idx < 0 {
if t.range.is_some() {
return Err(TemplateError::misc(format!(
"Range item index {idx} is out of bounds."
)));
}
if t.sequence.is_some() {
return Err(TemplateError::invalid_reference(format!(
"{}[{}]",
crate::core::environment::expr_desc(target),
crate::core::environment::expr_desc(key)
)));
}
return Err(TemplateError::misc(format!(
"Negative index not allowed: {idx}"
)));
}
let i = idx as usize;
if let Some(seq) = &t.sequence {
let size = seq.size()?;
if i < size {
return seq.get(i);
}
return Ok(TModel::nothing());
}
if t.node.is_some() {
return if i == 0 {
Ok(t.clone())
} else {
Ok(TModel::nothing())
};
}
let text = match model_to_string(env, &t) {
Ok(s) => s,
Err(_e @ TemplateError::TypeMismatch { .. }) => {
let mut err = TemplateError::type_mismatch("sequence-or-string", t.type_name)
.with_expected_phrase(
"a sequence or string or something automatically convertible to string (number, date or boolean)",
)
.with_blame_at(
"...[...]",
"left-hand operand",
&crate::core::environment::expr_desc(target),
&env.current_template_name,
target.span,
);
if t.hash.is_some() {
err = err.with_tip("You had a numerical value inside the []. Currently that's only supported for sequences (lists) and strings. To get a Map item with a non-string key, use myMap?api.get(myKey).");
}
if t.collection.is_some() {
err = err.with_tip("As the problematic value contains a collection of items, you could convert it to a sequence like someValue?sequence. Be sure though that you won't have a large number of items, as all will be held in memory the same time.");
}
return Err(err);
}
Err(e) => return Err(e),
};
return match text.chars().nth(i) {
Some(c) => Ok(TModel::from_scalar(c.to_string())),
None => Err(TemplateError::misc(format!(
"String index out of range: The index was {} (0-based), but the length of the string is only {}.",
i,
text.chars().count()
))),
};
}
if let Some(r) = &k.range {
return slice_with_range(
&t,
r,
&crate::core::environment::expr_desc(target),
&crate::core::environment::expr_desc(key),
);
}
if let Ok(s) = k.get_scalar() {
if let Some(nh) = &t.node_hash {
return Ok(nh.get(env, &s)?.unwrap_or_else(TModel::nothing));
}
if let Some(h) = &t.hash {
return Ok(h.get(&s)?.unwrap_or_else(TModel::nothing));
}
return Err(TemplateError::type_mismatch("hash", t.type_name));
}
Err(TemplateError::type_mismatch(
"number, range, or string",
k.type_name,
))
}
fn slice_with_range(
t: &TModel,
r: &crate::core::RangeSpec,
_td: &str,
_kd: &str,
) -> Result<TModel> {
let (target_size, is_str): (i64, bool) = if let Some(seq) = &t.sequence {
(seq.size()? as i64, false)
} else if let Some(s) = &t.scalar {
(s.as_string()?.chars().count() as i64, true)
} else {
return Err(TemplateError::type_mismatch("sequence", t.type_name));
};
let step: i64 = if r.ascending { 1 } else { -1 };
if !r.unbounded && r.count == 0 {
return Ok(empty_slice_result(t));
}
let first = r.start;
if first < 0 {
return Err(TemplateError::misc(format!(
"Negative range start index ({first}) isn't allowed for a range used for slicing."
)));
}
let start_ok = if r.adaptive && step == 1 {
first <= target_size
} else {
first < target_size
};
if !start_ok {
return Err(TemplateError::misc(format!(
"Range start index {first} is out of bounds, because the sliced {} has only {target_size} {}(s). (Note that indices are 0-based).",
if is_str { "string" } else { "sequence" },
if is_str { "character" } else { "element" }
)));
}
let result_size: i64 = if r.unbounded {
target_size - first
} else {
let last = first + (r.count as i64 - 1) * step;
if last < 0 {
if !r.adaptive {
return Err(TemplateError::misc(format!(
"Negative range end index ({last}) isn't allowed for a range used for slicing."
)));
}
first + 1
} else if last >= target_size {
if !r.adaptive {
return Err(TemplateError::misc(format!(
"Range end index {last} is out of bounds, because the sliced {} has only {target_size} {}(s). (Note that indices are 0-based).",
if is_str { "string" } else { "sequence" },
if is_str { "character" } else { "element" }
)));
}
(target_size - first).abs()
} else {
r.count as i64
}
};
if result_size == 0 {
return Ok(empty_slice_result(t));
}
if is_str && step < 0 && result_size > 1 {
if r.affected_by_string_slicing_bug && result_size == 2 {
return Ok(TModel::from_scalar(String::new()));
}
return Err(TemplateError::misc(format!(
"Decreasing ranges aren't allowed for slicing strings (as it would give reversed text). The index range was: first = {first}, last = {}",
first + (result_size - 1) * step
)));
}
if is_str {
let text = t.get_scalar()?;
let chars: Vec<char> = text.chars().collect();
let mut out = String::new();
let mut idx = first;
for _ in 0..result_size {
out.push(chars[idx as usize]);
idx += step;
}
return Ok(TModel::from_scalar(out));
}
if let Some(seq) = &t.sequence {
let mut out = Vec::new();
let mut idx = first;
for _ in 0..result_size {
out.push(seq.get(idx as usize)?);
idx += step;
}
return Ok(TModel::from_sequence(out));
}
Err(TemplateError::type_mismatch("sequence", t.type_name))
}
fn empty_slice_result(t: &TModel) -> TModel {
if t.is_scalar() {
TModel::from_scalar(String::new())
} else {
TModel::from_sequence(vec![])
}
}