{-| Module : Irc.Identifier Description : Type and operations for nicknames and channel names Copyright : (c) Eric Mertens, 2016 License : ISC Maintainer : emertens@gmail.com This module defines support for working with IRC's numeric reply codes. Pattern synonyms are provided for each of the possible IRC reply codes. Reply code information was extracted from https://www.alien.net.au/irc/irc2numerics.html -} module Irc.Identifier ( Identifier , idDenote , mkId , idText , idTextNorm , idPrefix ) where import Control.Monad.ST import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Char import Data.Foldable import Data.Function import Data.Hashable import Data.Monoid import Data.Primitive.ByteArray import Data.String import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.Vector.Primitive as PV import qualified Data.Primitive.ByteArray as BA import Data.Primitive.ByteArray (ByteArray) import Data.Word -- | Identifier representing channels and nicknames data Identifier = Identifier {-# UNPACK #-} !Text {-# UNPACK #-} !ByteArray -- | This indexing function exists to specialize the type -- of 'BA.indexByteArray'. indexWord8 :: ByteArray -> Int -> Word8 indexWord8 = BA.indexByteArray -- | Equality on normalized identifier instance Eq Identifier where Identifier _ x == Identifier _ y = BA.sizeofByteArray x == BA.sizeofByteArray y && all (\i -> indexWord8 x i == indexWord8 y i) [0 .. BA.sizeofByteArray x - 1] -- | Show as string literal instance Show Identifier where show = show . idText -- | Read as string literal instance Read Identifier where readsPrec p x = [ (mkId t, rest) | (t,rest) <- readsPrec p x] -- | Comparison on normalized identifier instance Ord Identifier where compare (Identifier _ x) (Identifier _ y) = mconcat [ indexWord8 x i `compare` indexWord8 y i | i <- [0..n-1]] <> (BA.sizeofByteArray x `compare` BA.sizeofByteArray y) where n = min (BA.sizeofByteArray x) (BA.sizeofByteArray y) -- | Hash on normalized identifier instance Hashable Identifier where hashWithSalt salt (Identifier _ b@(ByteArray arr)) = hashByteArrayWithSalt arr 0 (BA.sizeofByteArray b) salt -- | @'fromString' = 'mkId' . 'fromString' instance IsString Identifier where fromString = mkId . fromString -- | Construct an 'Identifier' from a 'ByteString' mkId :: Text -> Identifier mkId x = Identifier x (ircFoldCase (Text.encodeUtf8 x)) -- | Returns the original 'Text' of an 'Identifier' idText :: Identifier -> Text idText (Identifier x _) = x -- | Returns a 'ByteArray' of an 'Identifier' -- which is suitable for comparison or hashing -- which has been normalized for case. idDenote :: Identifier -> ByteArray idDenote (Identifier _ x) = x -- | Returns the case-normalized 'Text' for an identifier. idTextNorm :: Identifier -> Text idTextNorm (Identifier _ x) = Text.decodeUtf8 (B.pack [ indexWord8 x i | i <- [0 .. BA.sizeofByteArray x - 1]]) -- | Returns 'True' when the first argument is a prefix of the second. idPrefix :: Identifier -> Identifier -> Bool idPrefix (Identifier _ x) (Identifier _ y) = BA.sizeofByteArray x <= BA.sizeofByteArray y && all (\i -> indexWord8 x i == indexWord8 y i) [0 .. BA.sizeofByteArray x - 1] -- | Capitalize a string according to RFC 2812 -- Latin letters are capitalized and {|}~ are mapped to [\]^ ircFoldCase :: ByteString -> ByteArray ircFoldCase bs = runST $ do let n = B.length bs a <- BA.newByteArray n for_ [0..n-1] $ \i -> BA.writeByteArray a i (casemap PV.! fromIntegral (B.index bs i)) BA.unsafeFreezeByteArray a casemap :: PV.Vector Word8 casemap = PV.fromList $ map (fromIntegral . ord) $ ['\x00'..'`'] ++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^" ++ ['\x7f'..'\xff']