packages feed

hasmtlib-2.6.0: src/Language/Hasmtlib/Type/Bitvec.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE RoleAnnotations #-}

module Language.Hasmtlib.Type.Bitvec
( BvEnc(..), SBvEnc(..), KnownBvEnc(..)
, bvEncSing', bvEncSing''
, Bitvec(..)
, asUnsigned, asSigned
, bitvecConcat, bitvecFromListN, bitvecFromListN'
)
where

import Prelude hiding ((&&), (||), not)
import Language.Hasmtlib.Boolean
import Language.Hasmtlib.Internal.Render
import Data.GADT.Compare
import Data.ByteString.Builder
import Data.Bit
import Data.Bits
import Data.Coerce
import Data.Finite hiding (shift)
import Data.Proxy
import Data.Bifunctor
import Data.Type.Equality
import qualified Data.Vector.Unboxed.Sized as V
import GHC.TypeNats

-- | Type of Bitvector encoding - used as promoted type (data-kind).
data BvEnc = Unsigned | Signed deriving (Show, Eq, Ord)

-- | Singleton for 'BvEnc'.
data SBvEnc (enc :: BvEnc) where
  SUnsigned :: SBvEnc Unsigned
  SSigned   :: SBvEnc Signed

deriving instance Show (SBvEnc enc)
deriving instance Eq   (SBvEnc enc)
deriving instance Ord  (SBvEnc enc)

-- | Compute singleton 'SBvEnc' from it's promoted type 'BvEnc'.
class KnownBvEnc (enc :: BvEnc) where bvEncSing :: SBvEnc enc
instance KnownBvEnc Unsigned    where bvEncSing = SUnsigned
instance KnownBvEnc Signed      where bvEncSing = SSigned

-- | Wrapper for 'bvEncSing' which takes a 'Proxy'.
bvEncSing' :: forall enc prxy. KnownBvEnc enc => prxy enc -> SBvEnc enc
bvEncSing' _ = bvEncSing @enc

-- | Wrapper for 'bvEncSing' which takes a 'Proxy' and some ballast.
--   This is helpful for singing on values of type 'Bitvec' where the ballst is a 'Nat'.
bvEncSing'' :: forall enc a prxy. KnownBvEnc enc => prxy enc a -> SBvEnc enc
bvEncSing'' _ = bvEncSing @enc

instance GEq SBvEnc where
  geq SUnsigned SUnsigned = Just Refl
  geq SSigned SSigned = Just Refl
  geq _ _ = Nothing

instance GCompare SBvEnc where
  gcompare SUnsigned SUnsigned = GEQ
  gcompare SUnsigned _  = GLT
  gcompare _ SUnsigned  = GGT
  gcompare SSigned SSigned = GEQ
  -- gcompare SSigned _  = GLT
  -- gcompare _ SSigned  = GGT

-- | Length-indexed bitvector ('V.Vector') carrying a phantom type-level 'BvEnc'.
--   Most significant bit is first (index 0) for unsigned bitvectors.
--   Signed bitvectors have their sign bit first (index 0) and their most significant bit second (index 1).
type role Bitvec phantom phantom
newtype Bitvec (enc :: BvEnc) (n :: Nat) = Bitvec { unBitvec :: V.Vector n Bit }
  deriving newtype (Eq, Ord, Boolean)

-- | Convert 'Bitvec' with any encoding 'BvEnc' to 'Unsigned'.
asUnsigned :: forall enc n. Bitvec enc n -> Bitvec Unsigned n
asUnsigned = coerce . coerce @(Bitvec enc n) @(V.Vector n Bit)

-- | Convert 'Bitvec' with any encoding 'BvEnc' to 'Signed'.
asSigned :: forall enc n. Bitvec enc n -> Bitvec Signed n
asSigned = coerce . coerce @(Bitvec enc n) @(V.Vector n Bit)

instance Show (Bitvec enc n) where
  show = V.toList . V.map (\b -> if coerce b then '1' else '0') . coerce @_ @(V.Vector n Bit)
  {-# INLINEABLE show #-}

instance Render (Bitvec enc n) where
  render = stringUtf8 . show
  {-# INLINE render #-}

instance (KnownBvEnc enc, KnownNat n) => Bits (Bitvec enc n) where
  (.&.) = (&&)
  (.|.) = (||)
  xor = Language.Hasmtlib.Boolean.xor
  complement = not
  shift bv i  = coerce $ shift (coerce @_ @(V.Vector n Bit) bv) (negate i)
  rotate bv i = coerce $ rotate (coerce @_ @(V.Vector n Bit) bv) (negate i)
  bitSize _ = fromIntegral $ natVal (Proxy @n)
  bitSizeMaybe _ = Just $ fromIntegral $ natVal (Proxy @n)
  isSigned _ = case bvEncSing @enc of
    SUnsigned -> False
    SSigned -> True
  testBit bv = testBit (V.reverse (coerce @_ @(V.Vector n Bit) bv))
  bit (toInteger -> i) = coerce $ V.reverse $ V.replicate @n (Bit False) V.// [(finite i, Bit True)]
  popCount = coerce . popCount . coerce @_ @(V.Vector n Bit)

instance (KnownBvEnc enc, KnownNat n) => Num (Bitvec enc n) where
   fromInteger x = coerce . V.reverse $ V.generate @n (coerce . testBit x . fromInteger . getFinite)
   negate        = case bvEncSing @enc of
    SUnsigned -> id
    SSigned -> (+1) . not
   abs x         = if signum x < 0 then negate x else x
   signum x      = case bvEncSing @enc of
    SUnsigned -> 0
    SSigned -> if testBit x 0 then -1 else 1
   x + y = fromInteger $ toInteger x + toInteger y
   x - y = fromInteger $ toInteger x - toInteger y
   x * y = fromInteger $ toInteger x * toInteger y

instance (KnownBvEnc enc, KnownNat n) => Bounded (Bitvec enc n) where
  minBound = case bvEncSing @enc of
    SUnsigned -> coerce $ V.replicate @n false
    SSigned -> coerce $ setBit (V.replicate @n false) 0
  maxBound = case bvEncSing @enc of
    SUnsigned -> coerce $ V.replicate @n true
    SSigned -> coerce $ clearBit (V.replicate @n true) 0

instance (KnownBvEnc enc, KnownNat n) => Enum (Bitvec enc n) where
  toEnum     = fromInteger . toInteger
  fromEnum x = case bvEncSing @enc of
    SUnsigned -> V.sum . V.imap (\i b -> if coerce b then 2 ^ getFinite i else 0) . V.reverse $ coerce @_ @(V.Vector n Bit) x
    SSigned -> if testBit x 0
          then negate . (+1) . V.sum . V.imap (\i b -> if coerce b then 2 ^ getFinite i else 0) . V.reverse $ coerce @_ @(V.Vector n Bit) $ not x
          else V.sum . V.imap (\i b -> if coerce b then 2 ^ getFinite i else 0) . V.reverse $ coerce @_ @(V.Vector n Bit) x

instance (KnownBvEnc enc, KnownNat n) => Real (Bitvec enc n) where
  toRational = toRational . fromEnum

instance (KnownBvEnc enc, KnownNat n) => Integral (Bitvec enc n) where
  toInteger = fromIntegral . fromEnum
  quotRem x y = bimap fromInteger fromInteger $ quotRem (toInteger x) (toInteger y)

bitvecConcat :: Bitvec enc n -> Bitvec enc m -> Bitvec enc (n + m)
bitvecConcat (coerce -> x) (coerce -> y) = coerce $ x V.++ y

bitvecFromListN :: forall n enc. KnownNat n => [Bit] -> Maybe (Bitvec enc n)
bitvecFromListN = coerce . V.fromListN @n

bitvecFromListN' :: KnownNat n => Proxy n -> [Bit] -> Maybe (Bitvec enc n)
bitvecFromListN' _ = bitvecFromListN