stable-tree-0.5.0: src/Data/StableTree/Key.hs
-- |
-- Module : Data.StableTree.Types.Key
-- Copyright : Jeremy Groven
-- License : BSD3
--
-- Tools for working with StableTree keys.
module Data.StableTree.Key
( Key(fromKey)
, SomeKey(..)
, Terminal
, Nonterminal
, wrap
, unwrap
) where
import qualified Data.ByteString as BS
import Data.Serialize ( Serialize, encode )
import Data.Bits ( (.&.), shiftR, xor )
import Data.ByteString ( ByteString )
import Data.Word ( Word8, Word64 )
-- |Used to indicate that a 'Key' is terminal
data Terminal
-- |Used to indicate that a 'Key' is not terminal
data Nonterminal
-- |A wrapper for keys; this has an ephemeral 't' that will be either
-- 'Terminal' or 'Nonterminal' depending on the result of @byte k@.
newtype Key t k = Key { fromKey :: k } deriving ( Eq, Ord, Show )
-- |A sum type to contain either a 'Terminal' or a 'Nonterminal' 'Key'
data SomeKey k = SomeKey_T (Key Terminal k)
| SomeKey_N (Key Nonterminal k)
deriving ( Eq, Ord, Show )
-- |Do the magic of wrapping up a key into a 'SomeKey'
wrap :: Serialize k => k -> SomeKey k
wrap k =
let w8 = byte k
x = w8 `xor` (w8 `shiftR` 4)
w4 = x .&. 0xf
in if w4 == 0xf
then SomeKey_T $ Key k
else SomeKey_N $ Key k
-- |Extract the original key from a wrapped one
unwrap :: SomeKey k -> k
unwrap (SomeKey_T (Key k)) = k
unwrap (SomeKey_N (Key k)) = k
-- |Calculate a single-byte hash for a 'Serialize'
byte :: Serialize t => t -> Word8
byte val =
let bs = encode val
fnv = fnv1a bs
w32 = fnv `xor` (fnv `shiftR` 32)
w16 = w32 `xor` (w32 `shiftR` 16)
w8 = w16 `xor` (w16 `shiftR` 8)
in toEnum $ fromEnum $ 0xff .&. w8
fnv1a :: ByteString -> Word64
fnv1a = BS.foldl upd basis
where
upd hsh oct = prime * (hsh `xor` (toEnum $ fromEnum oct))
prime = 1099511628211
basis = 14695981039346656037