packages feed

clash-shockwaves-1.0.0: src/Clash/Shockwaves/Internal/BitList.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeAbstractions #-}

{- |
Copyright  :  (C) 2025-2026, QBayLogic B.V.
License    :  BSD2 (see the file LICENSE)
Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>

Dynamically sized bitvectors.
-}
module Clash.Shockwaves.Internal.BitList where

import Clash.Prelude hiding (concat, drop, split, take)
import Clash.Sized.Internal.BitVector
import Data.Aeson hiding (Value)
import Data.Aeson.Types (toJSONKeyText)
import Data.String (IsString (fromString))
import qualified Data.Text as Text

{- | A type like 'BitVector', but with a dynamic size.
It is meant to make type-independent handling of binary representations possible.
-}
data BitList = BL
  { unsafeMask :: !Natural
  , unsafeToNatural :: !Natural
  , bitLength :: !Int
  }
  deriving (Eq, Ord)

instance Show BitList where
  show BL{unsafeMask, unsafeToNatural, bitLength} = go bitLength unsafeMask unsafeToNatural []
   where
    go 0 _ _ s = s
    go n m0 v0 s =
      let
        (!v1, !vBit) = quotRem v0 2
        (!m1, !mBit) = quotRem m0 2
        !renderedBit = showBit mBit vBit
       in
        go (n - 1) m1 v1 (renderedBit : s)

    showBit 0 0 = '0'
    showBit 0 1 = '1'
    showBit _ _ = 'x'

-- | Convert a 'BitVector' into a 'BitList'.
bvToBl :: (KnownNat n) => BitVector n -> BitList
bvToBl (BV @n m i) = BL m i (natToNum @n)

{- | Convert a 'BitList' into a 'BitVector', provided that is has the right number
of bits
-}
blToBv :: forall n. (KnownNat n) => BitList -> BitVector n
blToBv (BL m i l) | natToNum @n == l = BV m i
blToBv _ = errorX "BitList does not match BitVector size"

-- | Pack a value into a 'BitList'.
binPack :: (BitPack a) => a -> BitList
binPack = bvToBl . pack

-- | Unpack a value from a 'BitList'.
binUnpack :: (BitPack a) => BitList -> a
binUnpack = unpack . blToBv

-- | Discard the /n/ most significant bits.
drop :: Int -> BitList -> BitList
drop x = snd . split x

-- | Take only the /n/ most significant bits.
take :: Int -> BitList -> BitList
take n (BL m i l)
  | n > l || n < 0 =
      error ("Attempt to take " <> show n <> " from BitList of size " <> show l)
  | otherwise = BL m' i' n
 where
  s = l - n
  m' = shiftR m s
  i' = shiftR i s

{- | Split a 'BitList' into the /n/ most significant bits,
and the rest of the bits
-}
split :: Int -> BitList -> (BitList, BitList)
split n bv@(BL mm ii l) = (a, b)
 where
  a@(BL m i _n) = take n bv
  m' = shiftL m (l - n)
  i' = shiftL i (l - n)
  b = BL (mm - m') (ii - i') (l - n)

-- | Concatenate two 'BitList's.
concat :: BitList -> BitList -> BitList
concat (BL ma ia la) (BL mb ib lb) = BL m i l
 where
  m = (ma `shiftL` lb) .|. mb
  i = (ia `shiftL` lb) .|. ib
  l = la + lb

-- | Take a range (exclusive) of a 'BitList'.
slice :: (Int, Int) -> BitList -> BitList
slice (from, to) = drop from . take to

-- | Convert a 'BitList' into an 'Integer' if it has no undefined bits.
toInteger :: BitList -> Maybe Integer
toInteger (BL m i _) | m == 0 = Just $ fromIntegral i
toInteger _ = Nothing

instance Semigroup BitList where
  (<>) = concat

instance ToJSON BitList where
  toJSON = toJSON . show

instance ToJSONKey BitList where
  toJSONKey = toJSONKeyText (Text.pack . show)

{- FOURMOLU_DISABLE -}
-- | When converting from a string, `0` and `1` are interpreted as bits, and
-- `_` is treated as a spacer (is ignored). Any other characters are interpreted
-- as undefined bits.
instance IsString BitList where
  fromString ss = go ss (BL 0 0 0)
    where
      go ""       bl         = bl
      go ('_': s) bl         = go s bl
      go ('0': s) (BL m i l) = go s (BL (2*m  ) (2*i  ) (l+1))
      go ('1': s) (BL m i l) = go s (BL (2*m  ) (2*i+1) (l+1))
      go ( _ : s) (BL m i l) = go s (BL (2*m+1) (2*i  ) (l+1))
{- FOURMOLU_ENABLE -}