protospec-build 0.3.0

One binary format language to rule them all, One binary format language to find them, One binary format language to bring them all and in the darkness bind them.
Documentation
use super::*;

mod array;
pub use array::*;

mod container;
pub use container::*;

mod enum_;
pub use enum_::*;

mod bitfield;
pub use bitfield::*;

mod foreign;
pub use foreign::*;

mod type_ref;
pub use type_ref::*;

#[derive(Clone, Debug)]
pub enum Type {
    Container(Box<ContainerType>),
    Enum(EnumType),
    Bitfield(BitfieldType),
    Scalar(ScalarType),
    Array(Box<ArrayType>),
    Foreign(Arc<ForeignType>),
    F32,
    F64,
    Bool,
    Ref(TypeRef),
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Type::Container(c) => {
                write!(f, "container ")?;
                if let Some(length) = c.length.as_ref() {
                    write!(f, "[{}] ", length)?;
                }
                write!(f, "{{\n")?;
                for (name, field) in c.items.iter() {
                    write!(f, "  {}: {}", name, field.type_.borrow())?;
                }
                write!(f, "\n}}\n")
            }
            Type::Enum(c) => {
                write!(f, "enum {} {{\n", c.rep)?;
                for (name, cons) in c.items.iter() {
                    write!(f, "  {} = {}", name, cons.value)?;
                }
                write!(f, "\n}}\n")
            }
            Type::Bitfield(c) => {
                write!(f, "bitfield {} {{\n", c.rep)?;
                for (name, cons) in c.items.iter() {
                    write!(f, "  {} = {}", name, cons.value)?;
                }
                write!(f, "\n}}\n")
            }
            Type::Scalar(c) => c.fmt(f),
            Type::Array(c) => {
                write!(f, "{}[{}]", c.element, c.length)
            }
            Type::Foreign(c) => {
                write!(f, "{}", c.name)
            }
            Type::F32 => write!(f, "f32"),
            Type::F64 => write!(f, "f64"),
            Type::Bool => write!(f, "bool"),
            Type::Ref(field) => {
                write!(f, "{}", field.target.name)?;
                if field.arguments.len() > 0 {
                    write!(f, "(")?;
                    for argument in field.arguments.iter() {
                        argument.fmt(f)?;
                        write!(f, ",")?;
                    }
                    write!(f, ")")?;
                }
                Ok(())
            }
        }
    }
}

impl Type {
    pub fn resolved(&self) -> Cow<'_, Type> {
        match self {
            Type::Ref(x) => Cow::Owned(x.target.type_.borrow().resolved().into_owned()),
            x => Cow::Borrowed(x),
        }
    }

    pub fn assignable_from(&self, other: &Type) -> bool {
        match (self.resolved().as_ref(), other.resolved().as_ref()) {
            (Type::Ref(field1), Type::Ref(field2)) => field1
                .target
                .type_
                .borrow()
                .assignable_from(&field2.target.type_.borrow()),
            (Type::Ref(field), x) => field.target.type_.borrow().assignable_from(x),
            (x, Type::Ref(field)) => x.assignable_from(&field.target.type_.borrow()),
            (t1, Type::Foreign(f2)) => f2.obj.assignable_to(t1),
            (Type::Foreign(f1), t2) => f1.obj.assignable_from(t2),
            (Type::Container(c1), Type::Container(c2)) => c1 == c2,
            (Type::Enum(e1), Type::Enum(e2)) => e1 == e2,
            (Type::Bitfield(e1), Type::Bitfield(e2)) => e1 == e2,
            (Type::Scalar(s1), Type::Scalar(s2)) => s2 == s1,
            (Type::Array(a1), Type::Array(a2)) => a1 == a2,
            (Type::F32, Type::F32) => true,
            (Type::F64, Type::F32) => true,
            (Type::Bool, Type::Bool) => true,
            (_, _) => false,
        }
    }

    pub fn can_coerce_to(&self, other: &Type) -> bool {
        match (self.resolved().as_ref(), other.resolved().as_ref()) {
            (Type::Enum(e1), Type::Scalar(scalar_type))
                if e1.rep.can_implicit_cast_to(scalar_type) =>
            {
                true
            }
            (Type::Bitfield(e1), Type::Scalar(scalar_type))
                if e1.rep.can_implicit_cast_to(scalar_type) =>
            {
                true
            }
            (Type::Scalar(scalar_type), Type::Enum(e1))
                if scalar_type.can_implicit_cast_to(&e1.rep) =>
            {
                true
            }
            (Type::Scalar(scalar_type), Type::Bitfield(e1))
                if scalar_type.can_implicit_cast_to(&e1.rep) =>
            {
                true
            }
            (Type::Scalar(s1), Type::Scalar(s2)) => s1.can_implicit_cast_to(s2),
            (_, _) => false,
        }
    }

    pub fn can_cast_to(&self, to: &Type) -> bool {
        if to.assignable_from(self) || to.can_coerce_to(self) {
            return true;
        }
        match (self.resolved().as_ref(), to.resolved().as_ref()) {
            (Type::Scalar(_), Type::Scalar(_)) => true,
            (Type::F32, Type::F64) => true,
            (Type::F64, Type::F32) => true,
            (Type::F32, Type::Scalar(_)) => true,
            (Type::F64, Type::Scalar(_)) => true,
            (Type::Scalar(_), Type::F32) => true,
            (Type::Scalar(_), Type::F64) => true,
            _ => false,
        }
    }
}

impl PartialEq for Type {
    fn eq(&self, other: &Type) -> bool {
        self.assignable_from(other)
    }
}