microcad-lang 0.5.0

µcad language
Documentation
// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

//! Typed list of values evaluation entity

use crate::{ty::*, value::*};
use derive_more::{Deref, DerefMut};

/// Collection of values of the same type.
#[derive(Clone, Debug, Deref, DerefMut)]
pub struct Array {
    /// List of values
    #[deref]
    #[deref_mut]
    items: ValueList,
    /// Element type.
    ty: Type,
}

impl Array {
    /// Create new list
    pub fn new(ty: Type) -> Self {
        Self {
            items: ValueList::default(),
            ty,
        }
    }

    /// Create new list from `ValueList`.
    pub fn from_values(items: ValueList, ty: Type) -> Self {
        Self { items, ty }
    }
}

/// All builtin methods and builtin functions.
///
/// `len` and `contains` are already implemented and accessible via `impl Deref`.
impl Array {
    /// Get the first element, or None
    pub fn first(&self) -> Value {
        self.items.first().cloned().unwrap_or_default()
    }

    /// Get the last element, or None
    pub fn last(&self) -> Value {
        self.items.last().cloned().unwrap_or_default()
    }

    /// Get all elements but the first
    pub fn tail(&self) -> Array {
        Array::from_values(
            self.items.iter().skip(1).cloned().collect(),
            self.ty.clone(),
        )
    }

    /// Return a reversed version of the array.
    pub fn rev(&self) -> Array {
        Array::from_values(self.items.iter().rev().cloned().collect(), self.ty.clone())
    }

    /// Return a sorted version of this array.
    ///
    /// Only primitive types (quantities, integers, bools and string) can be sorted.
    pub fn sorted(&self) -> Array {
        let mut items = self.items.clone();
        match self.ty {
            Type::Integer | Type::Quantity(..) | Type::String | Type::Bool => {
                items.sort_by(|a, b| {
                    assert_eq!(a.ty(), b.ty());
                    match (a, b) {
                        (Value::Quantity(a), Value::Quantity(b)) => a.value.total_cmp(&b.value),
                        (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
                        (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
                        (Value::String(a), Value::String(b)) => a.cmp(b),
                        _ => unreachable!(),
                    }
                })
            }
            _ => {}
        };

        Array::from_values(items, self.ty.clone())
    }

    /// Check if all items are equal.
    pub fn all_equal(&self) -> bool {
        match self.first() {
            Value::None => true,
            value => self[1..].iter().all(|x| *x == value),
        }
    }

    /// Check if all items are sorted in ascending order.
    pub fn is_ascending(&self) -> bool {
        self.as_slice().windows(2).all(|w| w[0] <= w[1])
    }

    /// Check if all items are sorted in descending order.
    pub fn is_descending(&self) -> bool {
        self.as_slice().windows(2).all(|w| w[0] >= w[1])
    }
}

impl PartialEq for Array {
    fn eq(&self, other: &Self) -> bool {
        self.ty == other.ty && self.items == other.items
    }
}

impl IntoIterator for Array {
    type Item = Value;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.into_iter()
    }
}

impl TryFrom<ValueList> for Array {
    type Error = ValueError;
    fn try_from(items: ValueList) -> ValueResult<Array> {
        match items.types().common_type() {
            Some(ty) => Ok(Array::from_values(items, ty)),
            None => Err(ValueError::CommonTypeExpected),
        }
    }
}

impl FromIterator<Value> for Array {
    fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
        let items: ValueList = iter.into_iter().collect();
        let ty = items.types().common_type().expect("Common type");
        Self { ty, items }
    }
}

impl std::fmt::Display for Array {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "[{items}]",
            items = self
                .items
                .iter()
                .map(|v| v.to_string())
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

impl crate::ty::Ty for Array {
    fn ty(&self) -> Type {
        Type::Array(Box::new(self.ty.clone()))
    }
}

/// + operator. Adds a value to an array, e.g.: `[1,2] + 1 == [2,3]`.
impl std::ops::Add<Value> for Array {
    type Output = ValueResult;

    fn add(self, rhs: Value) -> Self::Output {
        if self.ty.is_compatible_to(&rhs.ty()) {
            Ok(Value::Array(Self::from_values(
                ValueList::new(
                    self.items
                        .iter()
                        .map(|value| value.clone() + rhs.clone())
                        .collect::<Result<Vec<_>, _>>()?,
                ),
                self.ty,
            )))
        } else {
            Err(ValueError::InvalidOperator("+".into()))
        }
    }
}

/// - operator. Subtracts a value from an array, e.g.: `[1,2] - 1 == [0,1]`.
impl std::ops::Sub<Value> for Array {
    type Output = ValueResult;

    fn sub(self, rhs: Value) -> Self::Output {
        if self.ty.is_compatible_to(&rhs.ty()) {
            Ok(Value::Array(Self::from_values(
                ValueList::new(
                    self.items
                        .iter()
                        .map(|value| value.clone() - rhs.clone())
                        .collect::<Result<Vec<_>, _>>()?,
                ),
                self.ty,
            )))
        } else {
            Err(ValueError::InvalidOperator("-".into()))
        }
    }
}

/// * operator. Multiply a value from an array, e.g.: `[1,2] * 2 == [2,4]`.
impl std::ops::Mul<Value> for Array {
    type Output = ValueResult;

    fn mul(self, rhs: Value) -> Self::Output {
        match self.ty {
            // List * Scalar or List * Integer
            Type::Quantity(_) | Type::Integer => Ok(Value::Array(Array::from_values(
                ValueList::new({
                    self.iter()
                        .map(|value| value.clone() * rhs.clone())
                        .collect::<Result<Vec<_>, _>>()?
                }),
                self.ty * rhs.ty().clone(),
            ))),
            _ => Err(ValueError::InvalidOperator("*".into())),
        }
    }
}

/// / operator. Divide an array by value, e.g.: `[2,4] / 2 == [1,2]`.
impl std::ops::Div<Value> for Array {
    type Output = ValueResult;

    fn div(self, rhs: Value) -> Self::Output {
        let values = ValueList::new(
            self.iter()
                .map(|value| value.clone() / rhs.clone())
                .collect::<Result<Vec<_>, _>>()?,
        );

        match (&self.ty, rhs.ty()) {
            // Integer / Integer => Scalar
            (Type::Integer, Type::Integer) => Ok(Value::Array(Array::from_values(
                values,
                self.ty / rhs.ty().clone(),
            ))),
            (Type::Quantity(_), _) => Ok(Value::Array(values.try_into()?)),
            _ => Err(ValueError::InvalidOperator("/".into())),
        }
    }
}

impl std::ops::Neg for Array {
    type Output = ValueResult;

    fn neg(self) -> Self::Output {
        let items = ValueList::new(
            self.iter()
                .map(|value| -value.clone())
                .collect::<Result<Vec<_>, _>>()?,
        );
        Ok(Value::Array(items.try_into()?))
    }
}

impl std::ops::Not for Array {
    type Output = ValueResult;

    fn not(self) -> Self::Output {
        let items = ValueList::new(
            self.iter()
                .map(|value| !value.clone())
                .collect::<Result<Vec<_>, _>>()?,
        );
        Ok(Value::Array(items.try_into()?))
    }
}