packages feed

falsify-0.1.0: src/Test/Falsify/Internal/Driver/ReplaySeed.hs

{-# LANGUAGE CPP #-}

-- | Replay seeds
--
-- We need a seed/gamma pair to initialize a splitmix PRNG. This is however a
-- pretty low level implementation detail that I'd prefer not be be directly
-- visible. We therefore provide a thin layer on top, which provides an
-- "encoded" replay seed. This has the additional benefits that the length of
-- the replay seed is always the same (unlike just writing a 'Word64'), and we
-- could in principle at some point support other kinds of PRNGs.
module Test.Falsify.Internal.Driver.ReplaySeed (
    ReplaySeed(..)
  , parseReplaySeed
  , safeReadReplaySeed
  , splitmixReplaySeed
  ) where

import Data.String
import Data.Word
import Data.Binary
import System.Random.SplitMix

import qualified Data.ByteString.Base16.Lazy as Lazy.Base16
import qualified Data.ByteString.Lazy.Char8  as Lazy.Char8

data ReplaySeed =
    ReplaySplitmix Word64 Word64

splitmixReplaySeed :: SMGen -> ReplaySeed
splitmixReplaySeed = uncurry ReplaySplitmix . unseedSMGen

instance Binary ReplaySeed where
  put (ReplaySplitmix seed gamma) = do
      putWord8 1
      put seed
      put gamma

  get = do
      tag <- getWord8
      case tag of
        1 -> do seed  <- get
                gamma <- get
                if odd gamma
                  then return $ ReplaySplitmix seed gamma
                  else fail $ "ReplaySeed: expected odd gamma for splitmix"
        n -> fail $ "ReplaySeed: invalid tag: " ++ show n

instance Show ReplaySeed where
  show = Lazy.Char8.unpack . Lazy.Base16.encode . encode

instance IsString ReplaySeed where
  fromString = aux . safeReadReplaySeed
    where
      aux :: Maybe ReplaySeed -> ReplaySeed
      aux Nothing  = error "ReplaySeed: invalid seed"
      aux (Just s) = s

safeReadReplaySeed :: String -> Maybe ReplaySeed
safeReadReplaySeed = parseReplaySeed

#if MIN_VERSION_base(4,13,0)
parseReplaySeed :: forall m. MonadFail m => String -> m ReplaySeed
#else
parseReplaySeed :: forall m. Monad m => String -> m ReplaySeed
#endif

parseReplaySeed str = do
    raw <- case Lazy.Base16.decode (Lazy.Char8.pack str) of
             Left err -> fail err
             Right x  -> return x
    case decodeOrFail raw of
      Left  (_, _, err) -> fail err
      Right (_, _, x)   -> return x