{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} module Data.Locator.Common ( Locator (..), represent, value, toLocatorUnique, multiply, fromLocator, concatToInteger, digest, ) where import Prelude hiding (toInteger) import Crypto.Hash as Crypto import qualified Data.ByteArray as B import qualified Data.ByteString.Char8 as S import Data.List (mapAccumL) import Data.Set (Set) import qualified Data.Set as Set import Data.Word class (Ord α, Enum α, Bounded α) => Locator α where locatorToDigit :: α -> Char digitToLocator :: Char -> α represent :: Locator α => α -> Int -> Char represent (_ :: α) n = locatorToDigit $ (toEnum n :: α) {- value :: Locator α => α -> Char -> Int value c (_ :: α) = fromEnum $ (digitToLocator c :: α) -} value :: Locator α => α -> Char -> Int value (_ :: α) c = fromEnum $ (digitToLocator c :: α) -- {- | Represent a number in Locator16a format. This uses the Locator16 symbol set, and additionally specifies that no symbol can be repeated. The /a/ in Locator16a represents that this transformation is done on the cheap; when converting if we end up with \'9\' \'9\' we simply pick the subsequent digit in the enum, in this case getting you \'9\' \'K\'. Note that the transformation is /not/ reversible. A number like @4369@ (which is @0x1111@, incidentally) encodes as @12C4@. So do @4370@, @4371@, and @4372@. The point is not uniqueness, but readibility in adverse conditions. So while you can count locators, they don't map continuously to base10 integers. The first argument is the number of digits you'd like in the locator; if the number passed in is less than 16^limit, then the result will be padded. >>> toLocator16a 6 4369 12C40F -} toLocatorUnique :: Locator α => Int -> Int -> α -> String toLocatorUnique limit n (_ :: α) = let n' = abs n ls = convert n' (replicate limit (minBound @α)) (_, us) = mapAccumL uniq Set.empty ls in map locatorToDigit (take limit us) where convert :: Locator α => Int -> [α] -> [α] convert 0 xs = xs convert i xs = let (d, r) = divMod i 16 x = toEnum r in convert d (x : xs) uniq :: Locator α => Set α -> α -> (Set α, α) uniq s x = if Set.member x s then uniq s (subsequent x) else (Set.insert x s, x) subsequent :: Locator α => α -> α subsequent x = if x == maxBound then minBound else succ x multiply :: Locator α => α -> Int -> Char -> Int multiply (locator :: a) acc c = let base = fromEnum (maxBound @a) + 1 in (acc * base) + (value locator c) -- -- | Given a number encoded as a Locator, convert it back to an integer. fromLocator :: Locator α => α -> String -> Int fromLocator locator ss = foldl (multiply locator) 0 ss -- -- Given a string, convert it into a N character hash. -- concatToInteger :: [Word8] -> Int concatToInteger bytes = foldl fn 0 bytes where fn acc b = (acc * 256) + (fromIntegral b) digest :: String -> Int digest ws = i where i = concatToInteger h h = B.unpack h' h' = Crypto.hash x' :: Crypto.Digest Crypto.SHA1 x' = S.pack ws