microcad_lang/ty/
type.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! µcad Type
5
6use crate::ty::*;
7
8/// µcad Basic Types
9#[derive(Clone, PartialEq, Eq, Hash)]
10pub enum Type {
11    /// Invalid type (used for error handling)
12    Invalid,
13    /// A 64-bit integer number: `Integer: 10`.
14    Integer,
15    /// A quantity type: `Scalar: 1.0`, `Length: 1.0mm`
16    Quantity(QuantityType),
17    /// A string.
18    String,
19    /// A boolean: `true`, `false`.
20    Bool,
21    /// An array of elements of the same type: `[Scalar]`.
22    Array(Box<Type>),
23    /// A named tuple of elements: `(x: Scalar, y: String)`.
24    Tuple(Box<TupleType>),
25    /// Matrix type: `Matrix3x3`.
26    Matrix(MatrixType),
27    /// Models.
28    Models,
29    /// used for assert_valid() and assert_invalid()
30    Target,
31}
32
33impl Type {
34    /// Shortcut to create a scalar type.
35    pub fn scalar() -> Self {
36        Self::Quantity(QuantityType::Scalar)
37    }
38
39    /// Shortcut to create a length type.
40    pub fn length() -> Self {
41        Self::Quantity(QuantityType::Length)
42    }
43
44    /// Check if the type is an array of the given type `ty`
45    pub fn is_array_of(&self, ty: &Type) -> bool {
46        match self {
47            Self::Array(array_type) => array_type.as_ref() == ty,
48            _ => false,
49        }
50    }
51
52    /// Check if types are compatible.
53    pub fn is_compatible_to(&self, rhs: &Self) -> bool {
54        rhs == self
55            || (*self == Type::Integer && *rhs == Type::scalar())
56            || (*rhs == Type::Integer && *self == Type::scalar())
57    }
58
59    /// Returns if the given type or it's inner type matches the given parameter type.
60    pub fn is_matching(&self, param_type: &Type) -> bool {
61        match (self, param_type) {
62            (_, Type::Quantity(QuantityType::Scalar)) => {
63                self == &Type::scalar()
64                    || self == &Type::Integer
65                    || self.is_array_of(&Type::scalar())
66                    || self.is_array_of(&Type::Integer)
67            }
68            (Type::Tuple(ty_s), Type::Tuple(ty_p)) => ty_s.matches(ty_p),
69            _ => self == param_type || self.is_array_of(param_type),
70        }
71    }
72}
73
74impl std::ops::Mul for Type {
75    type Output = Type;
76
77    fn mul(self, rhs: Self) -> Self::Output {
78        if self == Self::Invalid || rhs == Self::Invalid {
79            return Self::Invalid;
80        }
81
82        match (self, rhs) {
83            (Type::Integer, ty) | (ty, Type::Integer) => ty,
84            (Type::Quantity(lhs), Type::Quantity(rhs)) => Type::Quantity(lhs * rhs),
85            (ty, Type::Array(array_type)) | (Type::Array(array_type), ty) => *array_type * ty,
86            (Type::Tuple(_), _) | (_, Type::Tuple(_)) => todo!(),
87            (Type::Matrix(_), _) | (_, Type::Matrix(_)) => todo!(),
88            (lhs, rhs) => unimplemented!("Multiplication for {lhs} * {rhs}"),
89        }
90    }
91}
92
93impl std::ops::Div for Type {
94    type Output = Type;
95
96    fn div(self, rhs: Self) -> Self::Output {
97        if self == Self::Invalid || rhs == Self::Invalid {
98            return Self::Invalid;
99        }
100
101        match (self, rhs) {
102            (Type::Integer, ty) | (ty, Type::Integer) => ty,
103            (Type::Quantity(lhs), Type::Quantity(rhs)) => Type::Quantity(lhs / rhs),
104            (Type::Array(array_type), ty) => *array_type / ty,
105            (Type::Tuple(_), _) => todo!(),
106            (Type::Matrix(_), _) | (_, Type::Matrix(_)) => todo!(),
107            (lhs, rhs) => unimplemented!("Division for {lhs} * {rhs}"),
108        }
109    }
110}
111
112impl From<QuantityType> for Type {
113    fn from(value: QuantityType) -> Self {
114        Type::Quantity(value)
115    }
116}
117
118impl std::fmt::Display for Type {
119    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
120        match self {
121            Self::Invalid => write!(f, crate::invalid_no_ansi!(TYPE)),
122            Self::Integer => write!(f, "Integer"),
123            Self::Quantity(quantity) => write!(f, "{quantity}"),
124            Self::String => write!(f, "String"),
125            Self::Bool => write!(f, "Bool"),
126            Self::Array(t) => write!(f, "[{t}]"),
127            Self::Tuple(t) => write!(f, "{t}"),
128            Self::Matrix(t) => write!(f, "{t}"),
129            Self::Models => write!(f, "Models"),
130            Self::Target => write!(f, "Target"),
131        }
132    }
133}
134
135impl std::fmt::Debug for Type {
136    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
137        match self {
138            Self::Invalid => write!(f, crate::invalid!(TYPE)),
139            Self::Integer => write!(f, "Integer"),
140            Self::Quantity(quantity) => write!(f, "{quantity}"),
141            Self::String => write!(f, "String"),
142            Self::Bool => write!(f, "Bool"),
143            Self::Array(t) => write!(f, "[{t}]"),
144            Self::Tuple(t) => write!(f, "{t}"),
145            Self::Matrix(t) => write!(f, "{t}"),
146            Self::Models => write!(f, "Models"),
147            Self::Target => write!(f, "Target"),
148        }
149    }
150}
151
152#[test]
153fn builtin_type() {
154    use crate::parser::*;
155    use crate::syntax::*;
156
157    let ty = Parser::parse_rule::<TypeAnnotation>(Rule::r#type, "Integer", 0).expect("test error");
158    assert_eq!(ty.0.to_string(), "Integer");
159    assert_eq!(ty.0.value, Type::Integer);
160}
161
162#[test]
163fn type_matching() {
164    assert!(Type::scalar().is_matching(&Type::scalar()));
165    assert!(!Type::scalar().is_matching(&Type::Integer));
166    assert!(Type::Integer.is_matching(&Type::scalar()));
167    assert!(!Type::scalar().is_matching(&Type::String));
168    assert!(!Type::String.is_matching(&Type::scalar()));
169}