microcad_lang/value/
tuple.rs

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