Skip to main content

microcad_lang/value/
tuple.rs

1// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Named tuple evaluation entity
5
6use microcad_core::hash::HashMap;
7
8use microcad_lang_base::{Identifier, SrcReferrer};
9use microcad_lang_proc_macros::SrcReferrer;
10
11use crate::{ty::*, value::*};
12
13/// Tuple with named values
14///
15/// Names are optional, which means Identifiers can be empty.
16#[derive(Clone, Debug, Default, PartialEq, SrcReferrer)]
17pub struct Tuple {
18    pub(crate) named: HashMap<ir::Identifier, Value>,
19    pub(crate) unnamed: HashMap<Type, Value>,
20    pub(crate) src_ref: SrcRef,
21}
22
23/// Create a Value::Tuple from items
24#[macro_export]
25macro_rules! create_tuple_value {
26    ($($key:ident = $value:expr),*) => {
27        Value::Tuple(Box::new($crate::create_tuple!($( $key = $value ),*)))
28    };
29}
30
31/// Create a Tuple from items
32#[macro_export]
33macro_rules! create_tuple {
34        ($($key:ident = $value:expr),*) => {
35                [$( (stringify!($key), $crate::value::Value::try_from($value).expect("Valid value")) ),* ]
36                    .iter()
37                    .into()
38    };
39}
40
41impl Tuple {
42    /// Create new named tuple.
43    pub fn new_named(
44        named: microcad_core::hash::HashMap<ir::Identifier, Value>,
45        src_ref: SrcRef,
46    ) -> Self {
47        Self {
48            named,
49            unnamed: HashMap::default(),
50            src_ref,
51        }
52    }
53
54    /// Insert new (or overwrite existing) value into tuple
55    pub fn insert(&mut self, id: ir::Identifier, value: Value) {
56        if id.is_empty() {
57            self.unnamed.insert(value.ty(), value);
58        } else {
59            self.named.insert(id, value);
60        }
61    }
62
63    /// Return an iterator over all named values
64    pub fn named_iter(&self) -> std::collections::hash_map::Iter<'_, ir::Identifier, Value> {
65        if !self.unnamed.is_empty() {
66            log::warn!("using named_iter() on a tuple which has unnamed items too")
67        }
68        self.named.iter()
69    }
70
71    /// Return the tuple type.
72    pub fn tuple_type(&self) -> TupleType {
73        TupleType {
74            named: self
75                .named
76                .iter()
77                .map(|(id, v)| (id.clone(), v.ty()))
78                .collect(),
79            unnamed: self.unnamed.values().map(|v| v.ty()).collect(),
80        }
81    }
82
83    /// Combine two tuples of the same type with an operation.
84    ///
85    /// This function is used for `+` and `-` builtin operators.
86    pub fn combine(
87        self,
88        rhs: Tuple,
89        op: impl Fn(Value, Value) -> ValueResult,
90    ) -> ValueResult<Self> {
91        if self.ty() == rhs.ty() {
92            let mut named = self.named;
93
94            for (key, rhs_val) in rhs.named {
95                named
96                    .entry(key)
97                    .and_modify(|lhs_val| {
98                        *lhs_val = op(lhs_val.clone(), rhs_val.clone()).unwrap_or_default()
99                    })
100                    .or_insert(rhs_val);
101            }
102
103            let mut unnamed = self.unnamed;
104
105            for (key, rhs_val) in rhs.unnamed {
106                unnamed
107                    .entry(key)
108                    .and_modify(|lhs_val| {
109                        *lhs_val = op(lhs_val.clone(), rhs_val.clone()).unwrap_or_default()
110                    })
111                    .or_insert(rhs_val);
112            }
113
114            Ok(Tuple {
115                named,
116                unnamed,
117                src_ref: self.src_ref,
118            })
119        } else {
120            Err(ValueError::TupleTypeMismatch {
121                lhs: self.ty(),
122                rhs: rhs.ty(),
123            })
124        }
125    }
126
127    /// Apply value with an operation to a tuple.
128    ///
129    /// This function is used for `*` and `/` builtin operators.
130    pub fn apply(
131        self,
132        value: Value,
133        op: impl Fn(Value, Value) -> ValueResult,
134    ) -> ValueResult<Self> {
135        let mut named = HashMap::default();
136        for (key, lhs_val) in self.named {
137            named.insert(key, op(lhs_val, value.clone()).unwrap_or_default());
138        }
139
140        let mut unnamed = HashMap::default();
141        for (key, lhs_val) in self.unnamed {
142            unnamed.insert(key, op(lhs_val, value.clone()).unwrap_or_default());
143        }
144
145        Ok(Tuple {
146            named,
147            unnamed,
148            src_ref: self.src_ref,
149        })
150    }
151
152    /// Transform each value in the tuple.
153    pub fn transform(self, op: impl Fn(Value) -> ValueResult) -> ValueResult<Self> {
154        let mut named = HashMap::default();
155        for (key, value) in self.named {
156            named.insert(key, op(value).unwrap_or_default());
157        }
158
159        let mut unnamed = HashMap::default();
160        for (key, value) in self.unnamed {
161            unnamed.insert(key, op(value).unwrap_or_default());
162        }
163
164        Ok(Tuple {
165            named,
166            unnamed,
167            src_ref: self.src_ref,
168        })
169    }
170
171    /// Call a predicate for each tuple multiplicity.
172    ///
173    /// - `ids`: Items to multiply.
174    /// - `p`: Predicate to call for each resulting tuple.
175    ///
176    /// # Example
177    ///
178    /// | Input           | Predicate's Parameters |
179    /// |-----------------|------------------------|
180    /// | `([x₀, x₁], y)` | `(x₀, y)`, `(x₁, y)`   |
181    ///
182    pub fn multiplicity<P: FnMut(Tuple)>(&self, mut ids: ir::IdentifierList, mut p: P) {
183        log::trace!("combining: {ids:?}:");
184
185        // sort ids for persistent order
186        ids.sort();
187
188        // count array indexes for items which shall be multiplied and number of overall combinations
189        let mut combinations = 1;
190        let mut counts: HashMap<Identifier, (_, _)> = ids
191            .into_iter()
192            .map(|id| {
193                let counter = if let Some(Value::Array(array)) = &self.named.get(&id) {
194                    let len = array.len();
195                    combinations *= len;
196                    (0, len)
197                } else {
198                    panic!("{id:?} found in tuple but no list:\n{self:#?}");
199                };
200                (id, counter)
201            })
202            .collect();
203
204        log::trace!("multiplicity: {combinations} combinations:");
205
206        // call predicate for each version of the tuple
207        for _ in 0..combinations {
208            let mut counted = false;
209
210            // sort multiplier ids for persistent order
211            let mut named: Vec<_> = self.named.iter().collect();
212            named.sort_by(|lhs, rhs| lhs.0.cmp(rhs.0));
213
214            let tuple = named
215                .into_iter()
216                .map(|(id, v)| match v {
217                    Value::Array(array) => {
218                        if let Some((count, len)) = counts.get_mut(id) {
219                            let item = (
220                                id.clone(),
221                                array.get(*count).expect("array index not found").clone(),
222                            );
223                            if !counted {
224                                *count += 1;
225                                if *count == *len {
226                                    *count = 0
227                                } else {
228                                    counted = true;
229                                }
230                            }
231                            item
232                        } else {
233                            panic!("{id:?} found in tuple but no list");
234                        }
235                    }
236                    _ => (id.clone(), v.clone()),
237                })
238                .collect();
239            p(tuple);
240        }
241    }
242}
243
244impl ValueAccess for Tuple {
245    fn by_id(&self, id: &Identifier) -> Option<&Value> {
246        self.named.get(id)
247    }
248
249    fn by_ty(&self, ty: &Type) -> Option<&Value> {
250        self.unnamed.get(ty)
251    }
252}
253
254// TODO impl FromIterator instead
255impl<T> From<std::slice::Iter<'_, (&'static str, T)>> for Tuple
256where
257    T: Into<Value> + Clone + std::fmt::Debug,
258{
259    fn from(iter: std::slice::Iter<'_, (&'static str, T)>) -> Self {
260        let (unnamed, named): (Vec<_>, _) = iter
261            .map(|(k, v)| (Identifier::no_ref(k), (*v).clone().into()))
262            .partition(|(k, _)| k.is_empty());
263        Self {
264            src_ref: SrcRef::none(),
265            named: named.into_iter().collect(),
266            unnamed: unnamed.into_iter().map(|(_, v)| (v.ty(), v)).collect(),
267        }
268    }
269}
270
271impl FromIterator<(Identifier, Value)> for Tuple {
272    fn from_iter<T: IntoIterator<Item = (Identifier, Value)>>(iter: T) -> Self {
273        let (unnamed, named): (Vec<_>, _) = iter
274            .into_iter()
275            .map(|(k, v)| (k, v.clone()))
276            .partition(|(k, _)| k.is_empty());
277        Self {
278            src_ref: SrcRef::merge_all(
279                named
280                    .iter()
281                    .map(|(id, _)| id.src_ref())
282                    .chain(unnamed.iter().map(|(id, _)| id.src_ref())),
283            ),
284            named: named.into_iter().collect(),
285            unnamed: unnamed.into_iter().map(|(_, v)| (v.ty(), v)).collect(),
286        }
287    }
288}
289
290impl From<Vec2> for Tuple {
291    fn from(v: Vec2) -> Self {
292        create_tuple!(x = v.x, y = v.y)
293    }
294}
295
296impl From<Vec3> for Tuple {
297    fn from(v: Vec3) -> Self {
298        create_tuple!(x = v.x, y = v.y, z = v.z)
299    }
300}
301
302impl From<Color> for Tuple {
303    fn from(color: Color) -> Self {
304        create_tuple!(r = color.r, g = color.g, b = color.b, a = color.a)
305    }
306}
307
308impl From<Size2> for Tuple {
309    fn from(size: Size2) -> Self {
310        create_tuple!(
311            width = Value::from(Quantity::length(size.width)),
312            height = Value::from(Quantity::length(size.height))
313        )
314    }
315}
316
317impl From<Tuple> for Value {
318    fn from(tuple: Tuple) -> Self {
319        Value::Tuple(Box::new(tuple))
320    }
321}
322
323impl FromIterator<Tuple> for Tuple {
324    fn from_iter<T: IntoIterator<Item = Tuple>>(iter: T) -> Self {
325        let tuples: Vec<_> = iter.into_iter().collect();
326        Self {
327            src_ref: SrcRef::merge_all(tuples.iter().map(|t| t.src_ref())),
328            named: Default::default(),
329            unnamed: tuples
330                .into_iter()
331                .map(|t| (Type::Tuple(t.tuple_type().into()), Value::Tuple(t.into())))
332                .collect(),
333        }
334    }
335}
336
337impl IntoIterator for Tuple {
338    type Item = (Identifier, Value);
339    type IntoIter = std::collections::hash_map::IntoIter<Identifier, Value>;
340
341    fn into_iter(self) -> Self::IntoIter {
342        if !self.unnamed.is_empty() {
343            log::warn!("trying to iterate Tuple with unnamed items");
344        }
345        self.named.into_iter()
346    }
347}
348
349impl<'a> TryFrom<&'a Value> for &'a Tuple {
350    type Error = ValueError;
351
352    fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
353        match value {
354            Value::Tuple(tuple) => Ok(tuple),
355            _ => Err(ValueError::CannotConvert(
356                value.to_string(),
357                "Tuple".to_string(),
358            )),
359        }
360    }
361}
362
363impl TryFrom<&Tuple> for Color {
364    type Error = ValueError;
365
366    fn try_from(tuple: &Tuple) -> Result<Self, Self::Error> {
367        let (r, g, b, a) = (
368            tuple.by_id(&Identifier::no_ref("r")),
369            tuple.by_id(&Identifier::no_ref("g")),
370            tuple.by_id(&Identifier::no_ref("b")),
371            tuple
372                .by_id(&Identifier::no_ref("a"))
373                .unwrap_or(&Value::Quantity(Quantity::new(1.0, QuantityType::Scalar)))
374                .clone(),
375        );
376
377        match (r, g, b, a) {
378            (
379                Some(Value::Quantity(Quantity {
380                    value: r,
381                    quantity_type: QuantityType::Scalar,
382                    ..
383                })),
384                Some(Value::Quantity(Quantity {
385                    value: g,
386                    quantity_type: QuantityType::Scalar,
387                    ..
388                })),
389                Some(Value::Quantity(Quantity {
390                    value: b,
391                    quantity_type: QuantityType::Scalar,
392                    ..
393                })),
394                Value::Quantity(Quantity {
395                    value: a,
396                    quantity_type: QuantityType::Scalar,
397                    ..
398                }),
399            ) => Ok(Color::new(*r as f32, *g as f32, *b as f32, a as f32)),
400            _ => Err(ValueError::CannotConvertToColor(tuple.to_string())),
401        }
402    }
403}
404
405impl TryFrom<&Tuple> for Size2 {
406    type Error = ValueError;
407
408    fn try_from(tuple: &Tuple) -> Result<Self, Self::Error> {
409        let (width, height) = (
410            tuple.by_id(&Identifier::no_ref("width")),
411            tuple.by_id(&Identifier::no_ref("height")),
412        );
413
414        match (width, height) {
415            (
416                Some(Value::Quantity(Quantity {
417                    value: width,
418                    quantity_type: QuantityType::Length,
419                    ..
420                })),
421                Some(Value::Quantity(Quantity {
422                    value: height,
423                    quantity_type: QuantityType::Length,
424                    ..
425                })),
426            ) => Ok(Size2 {
427                width: *width,
428                height: *height,
429            }),
430            _ => Err(ValueError::CannotConvert(tuple.to_string(), "Size2".into())),
431        }
432    }
433}
434
435impl std::fmt::Display for Tuple {
436    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
437        write!(
438            f,
439            "({items})",
440            items = {
441                let mut items = self
442                    .named
443                    .iter()
444                    .map(|(id, v)| format!("{id}={v}"))
445                    .chain(self.unnamed.values().map(|v| format!("{v}")))
446                    .collect::<Vec<String>>();
447                items.sort();
448                items.join(", ")
449            }
450        )
451    }
452}
453
454impl std::ops::Add<Tuple> for Tuple {
455    type Output = ValueResult<Tuple>;
456
457    fn add(self, rhs: Tuple) -> Self::Output {
458        self.combine(rhs, |lhs, rhs| lhs.clone() + rhs.clone())
459    }
460}
461
462impl std::ops::Sub<Tuple> for Tuple {
463    type Output = ValueResult<Tuple>;
464
465    fn sub(self, rhs: Tuple) -> Self::Output {
466        self.combine(rhs, |lhs, rhs| lhs.clone() - rhs.clone())
467    }
468}
469
470impl std::ops::Mul<Value> for Tuple {
471    type Output = ValueResult<Tuple>;
472
473    fn mul(self, rhs: Value) -> Self::Output {
474        self.apply(rhs, |lhs, rhs| lhs * rhs)
475    }
476}
477
478impl std::ops::Div<Value> for Tuple {
479    type Output = ValueResult<Tuple>;
480
481    fn div(self, rhs: Value) -> Self::Output {
482        self.apply(rhs, |lhs, rhs| lhs / rhs)
483    }
484}
485
486impl std::ops::Neg for Tuple {
487    type Output = ValueResult;
488
489    fn neg(self) -> Self::Output {
490        Ok(Value::Tuple(Box::new(self.transform(|value| -value)?)))
491    }
492}
493
494impl std::ops::Not for Tuple {
495    type Output = ValueResult;
496
497    fn not(self) -> Self::Output {
498        Ok(Value::Tuple(Box::new(self.transform(|value| !value)?)))
499    }
500}
501
502impl std::hash::Hash for Tuple {
503    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
504        self.unnamed.iter().for_each(|(ty, value)| {
505            ty.hash(state);
506            value.hash(state);
507        });
508        self.named.iter().for_each(|(id, value)| {
509            id.hash(state);
510            value.hash(state);
511        });
512    }
513}
514
515impl Ty for Tuple {
516    fn ty(&self) -> Type {
517        Type::Tuple(Box::new(self.tuple_type()))
518    }
519}