Skip to main content

somni_template/
value.rs

1//! Values that can be handed to a template render.
2//!
3//! [`IntoValue`] converts common Rust types into Somni values. Scalars convert directly;
4//! iterables must be wrapped in [`Iter`] to become a loop source (a Somni iterator).
5//!
6//! The templating engine uses the [`WithIterator<DefaultTypeSet>`](somni_expr::WithIterator)
7//! type set, exposed here as [`TemplateTypes`].
8
9use somni_expr::{DefaultTypeSet, SomniIterator, SomniStruct, TypedValue, WithIterator};
10
11/// The Somni type set used by the templating engine.
12///
13/// It is [`DefaultTypeSet`] (`u64`/`i64`/`f64`) extended with iterator support so that
14/// `for` loops can iterate host-provided collections.
15pub type TemplateTypes = WithIterator<DefaultTypeSet>;
16
17/// A Rust value that can be registered on an [`Env`](crate::Env).
18///
19/// Implemented for scalars directly; wrap iterables in [`Iter`] to register a loop source.
20pub trait IntoValue {
21    /// Converts `self` into a Somni value.
22    fn into_value(self) -> TypedValue<TemplateTypes>;
23}
24
25impl IntoValue for u64 {
26    fn into_value(self) -> TypedValue<TemplateTypes> {
27        TypedValue::Int(self)
28    }
29}
30impl IntoValue for i64 {
31    fn into_value(self) -> TypedValue<TemplateTypes> {
32        TypedValue::SignedInt(self)
33    }
34}
35impl IntoValue for f64 {
36    fn into_value(self) -> TypedValue<TemplateTypes> {
37        TypedValue::Float(self)
38    }
39}
40impl IntoValue for bool {
41    fn into_value(self) -> TypedValue<TemplateTypes> {
42        TypedValue::Bool(self)
43    }
44}
45impl IntoValue for &str {
46    fn into_value(self) -> TypedValue<TemplateTypes> {
47        TypedValue::String(self.into())
48    }
49}
50impl IntoValue for String {
51    fn into_value(self) -> TypedValue<TemplateTypes> {
52        TypedValue::String(self.into_boxed_str())
53    }
54}
55impl IntoValue for TypedValue<TemplateTypes> {
56    fn into_value(self) -> TypedValue<TemplateTypes> {
57        self
58    }
59}
60impl IntoValue for SomniStruct<TemplateTypes> {
61    fn into_value(self) -> TypedValue<TemplateTypes> {
62        TypedValue::Struct(self)
63    }
64}
65
66/// Wraps an iterable so it becomes a loop source (a Somni iterator value).
67///
68/// ```ignore
69/// env.value("items", Iter(vec![1u64, 2, 3]));
70/// // #for x: int in items ...
71/// ```
72///
73/// Elements must themselves be scalar [`IntoValue`]s; iterators of iterators are not
74/// supported (the element type set has no iterator type).
75///
76/// **Single-pass:** a value registered with `Iter` is backed by a one-shot iterator, so it
77/// can only be consumed by a single `for` loop. To iterate the same source repeatedly (e.g.
78/// an inner loop nested inside an outer loop), register a host **function** that returns a
79/// fresh iterator each call and invoke it in the loop header (`#for x: int in items()`).
80pub struct Iter<I>(pub I);
81
82impl<I> IntoValue for Iter<I>
83where
84    I: IntoIterator,
85    I::Item: IntoValue,
86{
87    fn into_value(self) -> TypedValue<TemplateTypes> {
88        let items: Vec<TypedValue<DefaultTypeSet>> = self
89            .0
90            .into_iter()
91            .map(|e| to_inner(e.into_value()))
92            .collect();
93        TypedValue::Iter(SomniIterator::new(items))
94    }
95}
96
97/// Down-converts a value from the engine's type set to the inner (non-iterator) type set,
98/// used to build iterator elements. Non-scalars collapse to `Void` (unsupported as elements).
99fn to_inner(v: TypedValue<TemplateTypes>) -> TypedValue<DefaultTypeSet> {
100    match v {
101        TypedValue::Void => TypedValue::Void,
102        TypedValue::MaybeSignedInt(x) => TypedValue::MaybeSignedInt(x),
103        TypedValue::Int(x) => TypedValue::Int(x),
104        TypedValue::SignedInt(x) => TypedValue::SignedInt(x),
105        TypedValue::Float(x) => TypedValue::Float(x),
106        TypedValue::Bool(b) => TypedValue::Bool(b),
107        TypedValue::String(s) => TypedValue::String(s),
108        // Structs are carried through as iterator elements by down-converting their
109        // fields recursively.
110        TypedValue::Struct(s) => {
111            let (name, fields) = s.into_parts();
112            TypedValue::Struct(SomniStruct::new(
113                name,
114                fields.into_iter().map(|(k, v)| (k, to_inner(v))).collect(),
115            ))
116        }
117        // Nested iterators and references cannot be represented as iterator elements.
118        TypedValue::Iter(_) | TypedValue::Ref(_) => TypedValue::Void,
119    }
120}
121
122/// The default `str` conversion registered on every [`Env`](crate::Env).
123pub(crate) fn default_str(v: TypedValue<TemplateTypes>) -> String {
124    match v {
125        TypedValue::Int(i) => i.to_string(),
126        TypedValue::MaybeSignedInt(i) => i.to_string(),
127        TypedValue::SignedInt(i) => i.to_string(),
128        TypedValue::Float(f) => f.to_string(),
129        TypedValue::Bool(b) => b.to_string(),
130        TypedValue::String(s) => String::from(s),
131        TypedValue::Void => String::new(),
132        TypedValue::Iter(_) => String::from("<iter>"),
133        TypedValue::Struct(s) => format!("<struct {}>", s.name()),
134        TypedValue::Ref(_) => String::from("<ref>"),
135    }
136}