packages feed

checked-literals-0.1.2: src-nums/CheckedLiterals/Nums/Signed.hs

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module CheckedLiterals.Nums.Signed (
  Signed (..),
) where

import CheckedLiterals (
  CheckedNegativeIntegerLiteral,
  CheckedPositiveIntegerLiteral,
 )
import Data.Bits (Bits (..), FiniteBits (..))
import Data.Proxy (Proxy (..))
import Data.Type.Bool (type If)
import GHC.TypeError (Assert, ErrorMessage (ShowType, Text, (:$$:), (:<>:)), TypeError)
import GHC.TypeLits (KnownNat, Nat, natVal, type (+), type (-), type (<=?), type (^))
import GHC.TypeLits.Extra (CLog)
import Prelude

-- | Signed integer with @n@ bits (including sign bit)
newtype Signed (n :: Nat) = Signed Integer
  deriving (Eq, Ord)

instance (KnownNat n) => Show (Signed n) where
  show = show . toInteger

instance (KnownNat n) => Bounded (Signed n) where
  minBound =
    let n = natVal (Proxy @n)
     in Signed $
          if n == 0
            then 0
            else negate (2 ^ (n - 1))
  maxBound =
    let n = natVal (Proxy @n)
     in Signed $
          if n == 0
            then 0
            else 2 ^ (n - 1) - 1

instance (KnownNat n) => Num (Signed n) where
  Signed a + Signed b = fromInteger (a + b)
  Signed a * Signed b = fromInteger (a * b)
  Signed a - Signed b = fromInteger (a - b)
  negate (Signed a) = fromInteger (negate a)
  abs (Signed a) = fromInteger (abs a)
  signum (Signed a) = fromInteger (signum a)
  fromInteger i =
    let s = Signed i'
        Signed minB = minBound `asTypeOf` s
        Signed maxB = maxBound `asTypeOf` s
        i'
          | i < minB = minB
          | i > maxB = maxB
          | otherwise = i
     in s

instance (KnownNat n) => Real (Signed n) where
  toRational (Signed i) = toRational i

instance (KnownNat n) => Enum (Signed n) where
  toEnum = fromInteger . toInteger
  fromEnum (Signed i) = fromInteger i

instance (KnownNat n) => Integral (Signed n) where
  quot (Signed a) (Signed b) = Signed (quot a b)
  rem (Signed a) (Signed b) = Signed (rem a b)
  div (Signed a) (Signed b) = Signed (div a b)
  mod (Signed a) (Signed b) = Signed (mod a b)
  quotRem (Signed a) (Signed b) = let (q, r) = quotRem a b in (Signed q, Signed r)
  divMod (Signed a) (Signed b) = let (q, r) = divMod a b in (Signed q, Signed r)
  toInteger (Signed i) = i

instance (KnownNat n) => Bits (Signed n) where
  Signed a .&. Signed b = fromInteger (a .&. b)
  Signed a .|. Signed b = fromInteger (a .|. b)
  Signed a `xor` Signed b = fromInteger (a `xor` b)
  complement (Signed a) = fromInteger (complement a)
  shift (Signed a) i = fromInteger (shift a i)
  rotate = error "rotate not implemented for Signed"
  bitSize _s = fromInteger (natVal (Proxy @n))
  bitSizeMaybe _s = Just (fromInteger (natVal (Proxy @n)))
  isSigned _ = True
  testBit (Signed a) i = testBit a i
  bit i = fromInteger (bit i)
  popCount (Signed a) = popCount a

instance (KnownNat n) => FiniteBits (Signed n) where
  finiteBitSize _ = fromInteger (natVal (Proxy @n))

type PositiveSignedError lit n typ minVal maxVal =
  TypeError
    ( 'Text "Literal "
        ':<>: 'ShowType lit
        ':<>: 'Text " is (potentially) out of bounds."
        ':$$: 'ShowType typ
          ':<>: 'Text " has bounds: [-"
          ':<>: 'ShowType minVal
          ':<>: 'Text " .. "
          ':<>: 'ShowType maxVal
          ':<>: 'Text "]."
        ':$$: 'Text "Possible fix: add a constraint: "
          ':<>: 'ShowType (CLog 2 (lit + 1) + 1)
          ':<>: 'Text " <= "
          ':<>: 'ShowType n
          ':<>: 'Text "."
        ':$$: 'Text "Possible fix: use 'uncheckedLiteral' from 'CheckedLiterals' to bypass this check."
    )

instance
  ( Assert
      (If (lit <=? 0) (lit <=? 0) (CLog 2 (lit + 1) + 1 <=? n))
      (PositiveSignedError lit n (Signed n) (2 ^ (n - 1)) ((2 ^ (n - 1)) - 1))
  ) =>
  CheckedPositiveIntegerLiteral lit (Signed n)

type NegativeSignedError lit n typ minVal maxVal =
  TypeError
    ( 'Text "Literal -"
        ':<>: 'ShowType lit
        ':<>: 'Text " is (potentially) out of bounds."
        ':$$: 'ShowType typ
          ':<>: 'Text " has bounds: [-"
          ':<>: 'ShowType minVal
          ':<>: 'Text " .. "
          ':<>: 'ShowType maxVal
          ':<>: 'Text "]."
        ':$$: 'Text "Possible fix: add a constraint: "
          ':<>: 'ShowType (CLog 2 lit + 1)
          ':<>: 'Text " <= "
          ':<>: 'ShowType n
          ':<>: 'Text "."
        ':$$: 'Text "Possible fix: use 'uncheckedLiteral' from 'CheckedLiterals' to bypass this check."
    )

instance
  ( Assert
      (If (lit <=? 0) (lit <=? 0) (CLog 2 lit + 1 <=? n))
      (NegativeSignedError lit n (Signed n) (2 ^ (n - 1)) ((2 ^ (n - 1)) - 1))
  ) =>
  CheckedNegativeIntegerLiteral lit (Signed n)