csa-rhdl 0.1.0

Carry-save adder compressor trees composed via comp-cat-rs, with hdl-cat backend
Documentation
//! Hand-rolled error type for the CSA crate.
//!
//! All enum matches on [`CsaError`] are exhaustive; no `_` wildcards
//! are used anywhere in the crate.

use comp_cat_rs::collapse::free_category::FreeCategoryError;

use crate::shape::Shape;

/// All errors surfaced by the `csa-rhdl` crate.
#[derive(Debug)]
pub enum CsaError {
    /// The compressor tree was asked to reduce zero operands.
    TreeSizeZero,
    /// Two bit widths that should have agreed did not.
    BitWidthMismatch {
        /// The width the caller expected.
        expected: usize,
        /// The width actually observed.
        actual: usize,
    },
    /// Two shapes that should have agreed did not.
    ShapeMismatch {
        /// The left-hand shape.
        left: Shape,
        /// The right-hand shape.
        right: Shape,
    },
    /// A CSA grouping had an invalid bundle count (not 3).
    InvalidGrouping {
        /// The bundle count observed.
        count: usize,
    },
    /// An error bubbled up from the free-category layer.
    UpstreamGraph(FreeCategoryError),
    /// An error bubbled up from the hdl-cat backend.
    #[cfg(feature = "hdl-cat-gates")]
    HdlCat(hdl_cat::Error),
}

impl From<FreeCategoryError> for CsaError {
    fn from(e: FreeCategoryError) -> Self {
        Self::UpstreamGraph(e)
    }
}

#[cfg(feature = "hdl-cat-gates")]
impl From<hdl_cat::Error> for CsaError {
    fn from(e: hdl_cat::Error) -> Self {
        Self::HdlCat(e)
    }
}

impl core::fmt::Display for CsaError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::TreeSizeZero => write!(f, "tree size must be positive"),
            Self::BitWidthMismatch { expected, actual } => write!(
                f,
                "bit-width mismatch: expected {expected}, got {actual}"
            ),
            Self::ShapeMismatch { left, right } => {
                write!(f, "shape mismatch: {left:?} vs {right:?}")
            }
            Self::InvalidGrouping { count } => {
                write!(f, "invalid CSA grouping size: {count} (expected 3)")
            }
            Self::UpstreamGraph(e) => write!(f, "graph error: {e}"),
            #[cfg(feature = "hdl-cat-gates")]
            Self::HdlCat(e) => write!(f, "hdl-cat error: {e}"),
        }
    }
}

impl std::error::Error for CsaError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::TreeSizeZero
            | Self::BitWidthMismatch { .. }
            | Self::ShapeMismatch { .. }
            | Self::InvalidGrouping { .. } => None,
            Self::UpstreamGraph(e) => Some(e),
            #[cfg(feature = "hdl-cat-gates")]
            Self::HdlCat(e) => Some(e),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{CsaError, Shape};
    use comp_cat_rs::collapse::free_category::{Edge, FreeCategoryError};
    use std::error::Error;

    #[test]
    fn display_covers_all_variants() {
        let shape = Shape::new(3, 8);
        let cases: Vec<CsaError> = vec![
            CsaError::TreeSizeZero,
            CsaError::BitWidthMismatch { expected: 8, actual: 16 },
            CsaError::ShapeMismatch { left: shape, right: shape },
            CsaError::InvalidGrouping { count: 4 },
            CsaError::UpstreamGraph(FreeCategoryError::EdgeOutOfBounds {
                edge: Edge::new(0),
                count: 0,
            }),
        ];
        cases.iter().for_each(|e| {
            let rendered = format!("{e}");
            assert!(!rendered.is_empty());
        });
    }

    #[test]
    fn source_only_for_upstream() {
        let edge = Edge::new(0);
        let upstream = CsaError::UpstreamGraph(FreeCategoryError::EdgeOutOfBounds {
            edge,
            count: 0,
        });
        assert!(upstream.source().is_some());
        assert!(CsaError::TreeSizeZero.source().is_none());
    }

    #[test]
    fn from_free_category_error_works() {
        let e: CsaError = FreeCategoryError::CompositionMismatch {
            target: comp_cat_rs::collapse::free_category::Vertex::new(0),
            source: comp_cat_rs::collapse::free_category::Vertex::new(1),
        }
        .into();
        match e {
            CsaError::UpstreamGraph(_) => {}
            CsaError::TreeSizeZero
            | CsaError::BitWidthMismatch { .. }
            | CsaError::ShapeMismatch { .. }
            | CsaError::InvalidGrouping { .. } => panic!("wrong variant"),
            #[cfg(feature = "hdl-cat-gates")]
            CsaError::HdlCat(_) => panic!("wrong variant"),
        }
    }
}