use alloc::{borrow::Cow, string::String};
use super::{condition::eval_condition, segments::render_interpolated_str};
use crate::{
compiled::MatchArm,
error::TemplateError,
scope::{CompiledPath, Scope},
value::Value,
};
#[cfg(feature = "std")]
pub(super) fn render_match(
expr: &CompiledPath,
arms: &[MatchArm],
is_option: bool,
scope: &mut Scope<'_>,
base_dir: Option<&std::path::Path>,
output: &mut String,
) -> Result<(), TemplateError> {
let active_variant = resolve_match_variant(expr, is_option, scope)?;
for arm in arms {
let variant_matches = arm_matches(&active_variant, &arm.variants, scope);
if variant_matches {
if let Some(ref guard) = arm.guard {
if !eval_condition(guard, scope)? {
continue;
}
}
let narrowed = is_option && active_variant == crate::consts::OPTION_SOME;
if narrowed {
scope.narrow_option(expr.as_str());
}
let result = super::segments::render_segments_into(&arm.body, scope, base_dir, output);
if narrowed {
scope.unnarrow_option(expr.as_str());
}
return result;
}
}
Ok(())
}
#[cfg(not(feature = "std"))]
pub(super) fn render_match_no_std(
expr: &CompiledPath,
arms: &[MatchArm],
is_option: bool,
scope: &mut Scope<'_>,
output: &mut String,
) -> Result<(), TemplateError> {
let active_variant = resolve_match_variant(expr, is_option, scope)?;
for arm in arms {
let variant_matches = arm_matches(&active_variant, &arm.variants, scope);
if variant_matches {
if let Some(ref guard) = arm.guard {
if !eval_condition(guard, scope)? {
continue;
}
}
let narrowed = is_option && active_variant == crate::consts::OPTION_SOME;
if narrowed {
scope.narrow_option(expr.as_str());
}
let result = super::segments::render_segments_into_no_std(&arm.body, scope, output);
if narrowed {
scope.unnarrow_option(expr.as_str());
}
return result;
}
}
Ok(())
}
fn arm_matches(active_variant: &str, variants: &[Cow<'_, str>], scope: &Scope<'_>) -> bool {
for v in variants {
let label = v.as_ref();
if label == crate::consts::MATCH_DEFAULT {
return true;
}
if let Some(inner) = crate::consts::strip_string_literal(label) {
let inner = crate::consts::unescape_string_literal(inner);
if inner.contains(crate::consts::EXPR_START) {
if let Ok(segments) = crate::compiled::compile_body(&inner) {
if let Ok(rendered) = render_interpolated_str(&segments, scope) {
if active_variant == rendered.as_str() {
return true;
}
}
}
} else if active_variant == inner.as_str() {
return true;
}
continue;
}
if active_variant == label {
return true;
}
if let Some(Value::Str(s)) = scope.resolve(label) {
if active_variant == s.as_str() {
return true;
}
}
}
false
}
pub(super) fn resolve_match_variant<'a>(
expr: &CompiledPath,
is_option: bool,
scope: &'a Scope<'_>,
) -> Result<Cow<'a, str>, TemplateError> {
let value = scope.resolve_path(expr)?;
match value {
Value::None => Ok(Cow::Borrowed(crate::consts::OPTION_NONE)),
_ if is_option => Ok(Cow::Borrowed(crate::consts::OPTION_SOME)),
Value::Str(s) => Ok(Cow::Borrowed(s.as_str())),
Value::Struct(map) => {
let tag_key = crate::consts::ENUM_TAG_KEY;
match map.get(tag_key) {
Some(Value::Str(tag)) => Ok(Cow::Borrowed(tag.as_str())),
_ => Err(TemplateError::syntax(alloc::format!(
"match: '{}' is a dict without a 'tag' field",
expr.as_str()
))),
}
}
Value::Int(n) => Ok(Cow::Owned(alloc::format!("{n}"))),
Value::Float(f) => Ok(Cow::Owned(alloc::format!("{f}"))),
Value::Bool(b) => Ok(Cow::Borrowed(if *b {
crate::consts::LIT_TRUE
} else {
crate::consts::LIT_FALSE
})),
Value::List(_) => Err(TemplateError::syntax(alloc::format!(
"match: '{}' is a list — match requires a scalar or enum value",
expr.as_str(),
))),
Value::Tmpl(_) => Err(TemplateError::syntax(alloc::format!(
"match: '{}' is not an enum value (got {})",
expr.as_str(),
value.type_name()
))),
}
}