use crate::ty::*;
use microcad_lang_base::Identifier;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TupleType {
pub named: microcad_core::hash::HashMap<Identifier, Type>,
pub unnamed: microcad_core::hash::HashSet<Type>,
}
impl TupleType {
pub fn new_vec2() -> Self {
[("x", Type::scalar()), ("y", Type::scalar())]
.into_iter()
.collect()
}
pub fn new_vec3() -> Self {
[
("x", Type::scalar()),
("y", Type::scalar()),
("z", Type::scalar()),
]
.into_iter()
.collect()
}
pub fn new_color() -> Self {
[
("r", Type::scalar()),
("g", Type::scalar()),
("b", Type::scalar()),
("a", Type::scalar()),
]
.into_iter()
.collect()
}
pub fn new_size2() -> Self {
[("width", Type::length()), ("height", Type::length())]
.into_iter()
.collect()
}
pub(crate) fn is_matching(&self, params: &TupleType) -> bool {
if self == params {
true
} else if self.unnamed.is_empty()
&& params.unnamed.is_empty()
&& self.named.len() == params.named.len()
{
self.named.iter().all(|arg| {
if let Some(ty) = params.named.get(arg.0) {
arg.1 == ty || arg.1.is_array_of(ty)
} else {
false
}
})
} else {
false
}
}
fn matches_keys(&self, keys: &[&str]) -> bool {
if !self.unnamed.is_empty() || self.named.len() != keys.len() {
return false;
}
keys.iter()
.all(|k| self.named.contains_key(&Identifier::no_ref(k)))
}
fn is_scalar_only(&self) -> bool {
self.common_type().is_some_and(|ty| *ty == Type::scalar())
}
fn is_length_only(&self) -> bool {
self.common_type().is_some_and(|ty| *ty == Type::length())
}
pub(crate) fn common_type(&self) -> Option<&Type> {
let mut iter = self.unnamed.iter().chain(self.named.values());
if let Some(first) = iter.next() {
if iter.all(|x| x == first) {
return Some(first);
}
}
None
}
pub(crate) fn is_color(&self) -> bool {
self.is_scalar_only() && self.matches_keys(&["r", "g", "b", "a"])
}
pub(crate) fn is_vec2(&self) -> bool {
self.is_scalar_only() && self.matches_keys(&["x", "y"])
}
pub(crate) fn is_vec3(&self) -> bool {
self.is_scalar_only() && self.matches_keys(&["x", "y", "z"])
}
pub(crate) fn is_size2(&self) -> bool {
self.is_length_only() && self.matches_keys(&["width", "height"])
}
}
impl std::hash::Hash for TupleType {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.named.iter().for_each(|(id, ty)| {
id.hash(state);
ty.hash(state)
});
self.unnamed.iter().for_each(|ty| ty.hash(state));
}
}
impl FromIterator<(Identifier, Type)> for TupleType {
fn from_iter<T: IntoIterator<Item = (Identifier, Type)>>(iter: T) -> Self {
let (unnamed, named) = iter.into_iter().partition(|(id, _)| id.is_empty());
Self {
named,
unnamed: unnamed.into_values().collect(),
}
}
}
impl<'a> FromIterator<(&'a str, Type)> for TupleType {
fn from_iter<T: IntoIterator<Item = (&'a str, Type)>>(iter: T) -> Self {
let (unnamed, named) = iter
.into_iter()
.map(|(id, ty)| (Identifier::no_ref(id), ty))
.partition(|(id, _)| id.is_empty());
Self {
named,
unnamed: unnamed.into_values().collect(),
}
}
}
impl std::fmt::Display for TupleType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.is_color() {
return write!(f, "Color");
}
if self.is_vec2() {
return write!(f, "Vec2");
}
if self.is_vec3() {
return write!(f, "Vec3");
}
if self.is_size2() {
return write!(f, "Size2");
}
write!(f, "({})", {
let mut types = self
.named
.iter()
.map(|(id, ty)| format!("{id}: {ty}"))
.chain(self.unnamed.iter().map(|ty| ty.to_string()))
.collect::<Vec<_>>();
types.sort();
types.join(", ")
})
}
}
#[test]
fn test_tuple_type_eq() {
assert_eq!(TupleType::new_color(), TupleType::new_color());
}
#[test]
fn test_tuple_type_match() {
let args = TupleType {
named: [
(Identifier::no_ref("x"), Type::Integer),
(
Identifier::no_ref("y"),
Type::Array(Box::new(Type::Integer)),
),
]
.into_iter()
.collect(),
unnamed: Default::default(),
};
let params = TupleType {
named: [
(Identifier::no_ref("x"), Type::Integer),
(Identifier::no_ref("y"), Type::Integer),
]
.into_iter()
.collect(),
unnamed: Default::default(),
};
assert!(args.is_matching(¶ms));
}