hwcalc_lib 0.2.0

Backend for the hwcalc calculator
Documentation
use super::Span;
use super::Sym;

pub(crate) type ExprList = Vec<Expr>;

#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Expr {
    pub kind: ExprKind,
    pub span: Span,
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ExprKind {
    Num(Num),
    Ident(Sym),
    Un(UnOp, Box<Expr>),
    Bin(BinOp, Box<Expr>, Box<Expr>),
    Cast { ty: Ty, expr: Box<Expr> },
    Assign(Sym, Box<Expr>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UnOp {
    Neg,
    Not,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BinOp {
    Add,
    Sub,
    Mul,
    Div,
    Rem,
    And,
    Or,
    Xor,
    Shl,
    Shr,
    Pow,
}

impl UnOp {
    pub fn precedence(self) -> u8 {
        match self {
            Self::Neg | Self::Not => 7,
        }
    }
}

impl BinOp {
    pub fn precedence(self) -> u8 {
        match self {
            Self::Pow => 7,
            Self::Mul | Self::Div | Self::Rem => 6,
            Self::Add | Self::Sub => 5,
            Self::Shl | Self::Shr => 4,
            Self::And => 3,
            Self::Xor => 2,
            Self::Or => 1,
        }
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Ty {
    pub signed: Option<bool>,
    pub width: Option<W>,
    pub width_frac: Option<W>,
}

impl Ty {
    pub fn new(signed: Option<bool>, width: Option<W>, width_frac: Option<W>) -> Option<Self> {
        if signed.unwrap_or(false) && matches!(width, Some(0)) {
            return None;
        }
        if let (Some(w), Some(wf)) = (width, width_frac) {
            match w.checked_add(wf) {
                None | Some(0) => return None,
                _ => {}
            }
        }
        Some(Self {
            signed,
            width,
            width_frac,
        })
    }

    fn cast(self, ty: Ty) -> Option<Self> {
        let signed = ty.signed.or(self.signed);
        let width = ty.width.or(self.width);
        let width_frac = ty.width_frac.or(self.width_frac);
        Self::new(signed, width, width_frac)
    }

    /// Signed
    pub fn s(self) -> Self {
        Self::new(Some(true), self.width, self.width_frac).unwrap()
    }

    /// Unsigned
    pub fn u(self) -> Self {
        Self::new(Some(false), self.width, self.width_frac).unwrap()
    }

    /// Integer width
    pub fn w(self, w: W) -> Self {
        Self::new(self.signed, Some(w), self.width_frac).unwrap()
    }

    /// Fractional width
    pub fn wf(self, w: W) -> Self {
        Self::new(self.signed, self.width, Some(w)).unwrap()
    }
}

/// The default signedness for numbers, when signed is set to None.
const SIGNED_DEFAULT: bool = true;

pub type Int = num_bigint::BigInt;
pub type Val = num_rational::Ratio<Int>;

/// Type for bit width
#[cfg(not(any(test, small_max)))]
pub type W = u32;
#[cfg(any(test, small_max))]
pub type W = u8;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Num {
    pub(crate) val: Val,
    pub(crate) ty: Ty,
}

impl Num {
    pub(crate) fn new(val: Val) -> Option<Self> {
        if val.to_integer().bits() <= W::MAX.into() {
            let ty = Ty::default();
            Some(Self { val, ty })
        } else {
            None
        }
    }

    #[must_use]
    pub fn value(&self) -> &Val {
        &self.val
    }

    #[must_use]
    pub fn negative(&self) -> bool {
        self.val < Val::default()
    }

    #[must_use]
    pub fn signed(&self) -> bool {
        self.ty.signed.unwrap_or(SIGNED_DEFAULT)
    }

    #[must_use]
    pub fn integer(&self) -> bool {
        self.val.fract() == Val::default()
    }

    #[must_use]
    pub fn width(&self) -> Option<W> {
        self.ty.width
    }

    #[must_use]
    pub fn to_signed(&self) -> Option<Self> {
        self.ty
            .cast(Ty::default().s())
            .map(|t| self.clone().with_ty(t))
    }

    #[must_use]
    pub fn to_unsigned(&self) -> Self {
        self.clone()
            .with_ty(self.ty.cast(Ty::default().u()).unwrap())
    }

    pub(crate) fn cast(self, ty: Ty) -> Option<Self> {
        self.ty.cast(ty).map(|ty| self.clone().with_ty(ty))
    }

    pub(crate) fn with_ty(self, ty: Ty) -> Self {
        let val = self.val;

        // truncate fractional part
        let val = if let Some(w) = ty.width_frac {
            let denom = Int::from(1) << w;
            Val::from_integer((val * Val::from_integer(denom.clone())).to_integer()) / denom
        } else {
            val
        };

        // truncate/extend integer part
        let val = if let Some(w) = ty.width {
            let int = val.to_integer() & mask(w);
            let int = if ty.signed.unwrap_or(SIGNED_DEFAULT)
                && w > 0
                && val.to_integer().bit(u64::from(w) - 1)
            {
                int - (Int::from(1) << w)
            } else {
                int
            };
            let val = Val::from_integer(int) + val.fract();
            if matches!(ty.signed, Some(false)) && val < Val::default() {
                Val::from_integer(Int::from(1) << w) + val
            } else {
                val
            }
        } else {
            val
        };

        Self { val, ty }
    }
}

fn mask(n: W) -> Int {
    (Int::from(1) << n) - 1
}

#[cfg(test)]
mod test {
    use super::Num;
    use super::Ty;
    use super::Val;

    impl Num {
        pub(crate) fn new_ty(val: Val, ty: Ty) -> Self {
            Self::new(val).unwrap().with_ty(ty)
        }

        /// Number
        pub fn n(n: i128) -> Self {
            Self::new_ty(Val::from_integer(n.into()), Ty::default())
        }

        /// Signed integer
        pub fn i(n: i128) -> Self {
            Self::new_ty(Val::from_integer(n.into()), Ty::default().s().wf(0))
        }

        /// Unsigned integer
        pub fn u(n: i128) -> Self {
            Self::new_ty(Val::from_integer(n.into()), Ty::default().u().wf(0))
        }

        /// Rational number
        pub fn nd(n: i128, d: u128) -> Self {
            Self::new_ty(Val::new(n.into(), d.into()), Ty::default())
        }

        /// Signed fixed point number
        pub fn q(n: i128, d: u128) -> Self {
            Self::new_ty(Val::new(n.into(), d.into()), Ty::default().s())
        }

        /// Unsigned fixed point number
        pub fn uq(n: i128, d: u128) -> Self {
            Self::new_ty(Val::new(n.into(), d.into()), Ty::default().u())
        }

        /// Integer width
        pub fn w(self, w: super::W) -> Self {
            self.cast(Ty::default().w(w)).unwrap()
        }

        /// Fractional width
        pub fn wf(self, w: super::W) -> Self {
            self.cast(Ty::default().wf(w)).unwrap()
        }
    }

    #[test]
    fn to_signed() {
        assert_eq!(Num::u(4).to_signed(), Some(Num::i(4)));
        assert_eq!(Num::i(4).to_signed(), Some(Num::i(4)));
        assert_eq!(Num::i(-4).to_signed(), Some(Num::i(-4)));
        assert_eq!(Num::u(4).w(4).to_signed(), Some(Num::i(4).w(4)));
        assert_eq!(Num::u(12).w(4).to_signed(), Some(Num::i(-4).w(4)));
        assert_eq!(Num::i(4).w(4).to_signed(), Some(Num::i(4).w(4)));
        assert_eq!(Num::i(-4).w(4).to_signed(), Some(Num::i(-4).w(4)));
    }

    #[test]
    fn to_signed_zero() {
        assert_eq!(Num::uq(1, 2).w(0).to_signed(), None);
    }

    #[test]
    fn to_unsigned() {
        assert_eq!(Num::u(4).to_unsigned(), Num::u(4));
        assert_eq!(Num::i(4).to_unsigned(), Num::u(4));
        assert_eq!(Num::i(-4).to_unsigned(), Num::u(-4));
        assert_eq!(Num::u(4).w(4).to_unsigned(), Num::u(4).w(4));
        assert_eq!(Num::u(12).w(4).to_unsigned(), Num::u(12).w(4));
        assert_eq!(Num::i(4).w(4).to_unsigned(), Num::u(4).w(4));
        assert_eq!(Num::i(-4).w(4).to_unsigned(), Num::u(12).w(4));
    }

    #[test]
    fn to_unsigned_fix() {
        assert_eq!(
            Num::nd(-9, 4).w(4).to_unsigned(),
            Num::uq(0b1101_11, 0b1_00).w(4),
        );
        assert_eq!(
            Num::nd(-23, 4).w(4).to_unsigned(),
            Num::uq(10_25, 1_00).w(4),
        );
        assert_eq!(
            Num::nd(-3, 4).w(3).to_unsigned(),
            Num::uq(0b111_01, 0b1_00).w(3),
        );
        assert_eq!(
            Num::nd(-0_3, 1_0).w(3).to_unsigned(),
            Num::uq(15_7, 1_0).w(3),
        );
    }
}