packages feed

packstream-bolt-0.1.0.0: src/Data/PackStream/Integer.hs

{-# LANGUAGE PatternSynonyms     #-}

-- | PackStream integer type covering the full @[-2^63, 2^64-1]@ range.
module Data.PackStream.Integer
    ( PSInteger
    , ToPSInteger(..)
    , FromPSInteger(..)
    , fromIntegerTry

      -- ** Internal helper
    , tryPSInteger
    ) where

import           Compat.Prelude

import           Control.Exception     (ArithException (DivideByZero, Overflow, Underflow),
                                        throw)
import           Data.Kind             (Constraint, Type)
import           TextShow
import TextShow.Generic
-- import GHC.Generics

import           Compat.Binary
import           Data.PackStream.Tags


-- | Integer type that represents the value range of integral numbers in PackStream; i.e. \( \left[ -2^{63}, 2^{64}-1 \right] \).
-- In other words, `PSInteger` provides the union of the value ranges of `Word64` and `Int64`.
--
-- This type can be unboxed (i.e. via @{-# UNPACK #-}@).
type PSInteger :: Type
data PSInteger = PSInteger {-# UNPACK #-} !Int64
  deriving stock (Eq, Ord, Generic)
  deriving TextShow via FromGeneric (PSInteger)

-- NOTE: Internal invariant of 'PSInteger'
--
-- 'isW64' MUST be true IFF the value range of `Int64` cannot represent the semantic value of 'value'
--
-- Consequently, when 'isW64' is true, 'value :: Int64' must be negative.

-- NB: only valid if isW64 is true
-- toW64 :: Int64 -> Word64
-- toW64 = intCastIso

-- | Types that can be losslessly converted to 'PSInteger'.
type ToPSInteger :: Type -> Constraint
class ToPSInteger a where
  toPSInteger :: a -> PSInteger

instance ToPSInteger Int8  where toPSInteger i = PSInteger (intCast i)
instance ToPSInteger Int16 where toPSInteger i = PSInteger (intCast i)
instance ToPSInteger Int32 where toPSInteger i = PSInteger (intCast i)
instance ToPSInteger Int64 where toPSInteger   = PSInteger 
instance ToPSInteger Int   where toPSInteger i = PSInteger (intCast i)

instance ToPSInteger Word8  where toPSInteger w = PSInteger (intCast w)
instance ToPSInteger Word16 where toPSInteger w = PSInteger (intCast w)
instance ToPSInteger Word32 where toPSInteger w = PSInteger (intCast w)
-- instance ToPSInteger Word64 where toPSInteger w = PSInteger (i<0) i where i = fromIntegral w
-- instance ToPSInteger Word   where toPSInteger w = PSInteger (i<0) i where i = fromIntegral w

-- | Convert a 'PSInteger' value to something else if possible
--
-- The instances for 'FromPSInteger' are supposed to be consistent with the respective instances for 'ToPSInteger', e.g.
--
-- > fromPSInteger . toPSInteger == Just
--
type FromPSInteger :: Type -> Constraint
class FromPSInteger a where
  fromPSInteger :: PSInteger -> Maybe a

-- instance FromPSInteger Word where
--   fromPSInteger (PSInteger w)  = intCastMaybe (toW64 w)
--   fromPSInteger (PSInteger i) = intCastMaybe i

-- instance FromPSInteger Word64 where
--   fromPSInteger (PSInteger w)  = Just $! toW64 w
--   fromPSInteger (PSInteger i) = intCastMaybe i

instance FromPSInteger Word32 where
  fromPSInteger (PSInteger i) = intCastMaybe i

instance FromPSInteger Word16 where
  fromPSInteger (PSInteger i) = intCastMaybe i

instance FromPSInteger Word8 where
  fromPSInteger (PSInteger i) = intCastMaybe i

-----

instance FromPSInteger Int where
  fromPSInteger (PSInteger i) = intCastMaybe i

instance FromPSInteger Int64 where
  fromPSInteger (PSInteger i) = Just i

instance FromPSInteger Int32 where
  fromPSInteger (PSInteger i) = intCastMaybe i

instance FromPSInteger Int16 where
  fromPSInteger (PSInteger i) = intCastMaybe i

instance FromPSInteger Int8 where
  fromPSInteger (PSInteger i) = intCastMaybe i

----------------------------------------------------------------------------

instance Bounded PSInteger where
  minBound = PSInteger minBound
  maxBound = PSInteger  (-1) -- this is why we can't autoderive

instance Enum PSInteger where
  toEnum i = PSInteger (toEnum i)
  -- fromEnum (PSInteger i)  = fromEnum (toW64 i)
  fromEnum (PSInteger i) = fromEnum i

instance Show PSInteger where
  showsPrec p (PSInteger v) = showsPrec p v
  -- showsPrec p (PSInteger  v) = showsPrec p (toW64 v)

instance Read PSInteger where
  readsPrec p s = [ (i, rest) | (j, rest) <- readsPrec p s, Right i <- [fromIntegerTry j] ]

instance NFData PSInteger where
  rnf (PSInteger _) = ()

-- | Try to convert 'Integer' into 'PSInteger'
--
-- Will return @'Left' 'Underflow'@ or @'Left' 'Overflow'@ respectively if out of range
fromIntegerTry :: Integer -> Either ArithException PSInteger
fromIntegerTry i
  | i <  toInteger (minBound :: Int64)  = Left Underflow
  | i <= toInteger (maxBound :: Int64)  = Right $! PSInteger (fromInteger i)
  | i <= toInteger (maxBound :: Word64) = Right $! PSInteger (fromInteger i)
  | otherwise = Left Overflow

-- | This instance will throw the respective arithmetic 'Underflow' and 'Overflow' exception if the range of 'PSInteger' is exceeded.
instance Num PSInteger where
  fromInteger i = either throw id (fromIntegerTry i)

  negate (PSInteger v)
    | v == minBound = PSInteger v -- NB: for the usual twos complement integers, `negate minBound == minBound`
    | otherwise     = PSInteger (negate v)
  negate (PSInteger v)
    | v == minBound = PSInteger v
    | otherwise     = throw Underflow

  -- addition
  PSInteger 0 + x = x
  x + PSInteger 0 = x

  PSInteger _  + PSInteger  _ = throw Overflow

  x@(PSInteger _) + y@(PSInteger _) = y + x
  PSInteger y + PSInteger x
    | y > 0     = if z < 0 then PSInteger z else throw Overflow
    | otherwise = PSInteger z
    where
      z = x + y

  PSInteger y + PSInteger x
    | x > 0, y > 0, z < 0 = PSInteger z
    | x < 0, y < 0, z > 0 = throw Underflow
    | otherwise           = PSInteger z
    where
      z = x + y

  signum (PSInteger _)  = PSInteger 1
  signum (PSInteger v) = PSInteger (signum v)

  abs v@(PSInteger _) = v
  abs v0@(PSInteger v)
    | v >= 0 = v0
    | v == minBound = PSInteger  v
    | otherwise     = PSInteger (negate v)


  PSInteger _  * PSInteger _ = throw Overflow
  PSInteger 0  * PSInteger _     = PSInteger 0
  PSInteger 1  * x                 = x
  PSInteger _      * PSInteger 0 = PSInteger 0
  x                  * PSInteger 1 = x

  -- cheat
  x * y = fromInteger (toInteger x * toInteger y)

instance Real PSInteger where
  toRational (PSInteger i) = toRational i
  -- toRational (PSInteger u)  = toRational (toW64 u)

instance Integral PSInteger where
  toInteger (PSInteger i) = toInteger i
  -- toInteger (PSInteger u)  = toInteger (toW64 u)

  quotRem _ (PSInteger 0)    = throw DivideByZero
  quotRem x (PSInteger 1)    = (x, PSInteger 0)
  quotRem x (PSInteger (-1)) = (negate x, PSInteger 0)

  quotRem (PSInteger x) (PSInteger y)
    | (x',y') <- quotRem x y = (PSInteger x', PSInteger y')

  -- cheat
  quotRem x y
    | (x',y') <- quotRem (toInteger x) (toInteger y) = (fromInteger x', fromInteger y')

----------------------------------------------------------------------------

-- | This 'Persist' instance encodes\/decodes to\/from PackStream format
--
-- When serializing 'PSInteger's via 'get' the shortest encoding is
-- used. Moreoever, for non-negative integers the unsigned encoding is
-- always used.
--
-- Deserialization via 'get' will only fail if a non-integer PackStream tag is encountered.
--
instance Persist PSInteger where
  get = getPSInteger
  put = putPSInteger

-- | Serializes 'PSInteger' to PackStream
--
-- The shortest encoding is used to serialize
-- 'PSInteger's. Moreoever, for non-negative integers the unsigned
-- encoding is always used.
putPSInteger :: PSInteger -> Put
putPSInteger (PSInteger i)
  | i >= -16 && i <= 127              = putInt8 (fromIntegral i)
  | i >= -0x80 && i < 0x80           = putWord8 TAG_INT_8  >> putInt8     (fromIntegral i)
  | i >= -0x8000 && i < 0x8000       = putWord8 TAG_INT_16 >> putInt16be  (fromIntegral i)
  | i >= -0x80000000 && i < 0x80000000 = putWord8 TAG_INT_32 >> putInt32be  (fromIntegral i)
  | otherwise                          = putWord8 TAG_INT_64 >> putInt64be                i

-- | Deserializes 'PSInteger' from PackStream
--
-- This operation will only fail if a non-integer PackStream tag is encountered.
getPSInteger :: Get PSInteger
getPSInteger = do { tag <- getWord8; tryPSInteger tag id (fail "expected PackStream int") }

-- | Try to decode a 'PSInteger' given a tag byte, applying @f@ on success or falling through to the continuation.
{-# INLINE tryPSInteger #-}
tryPSInteger :: Word8 -> (PSInteger -> a) -> Get a -> Get a
tryPSInteger tag' f cont = case tag' of
  c | is_TAG_TINY_INT c -> pure $! f $! toPSInteger (intCastIso c :: Int8)
  TAG_INT_8           -> f . toPSInteger <$> getInt8
  TAG_INT_16          -> f . toPSInteger <$> getInt16be
  TAG_INT_32          -> f . toPSInteger <$> getInt32be
  TAG_INT_64          -> f . toPSInteger <$> getInt64be
  _                   -> cont