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