{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

-- | ASCII utility functions.
module Text.Ascii
  (
  -- * ASCII checks
    IsAscii(..)
  , isAscii
  , Ascii
  , maybeAscii
  , ascii
  -- * Character properties
  , isControl
  , isPrintable
  , isWhiteSpace
  , isSpaceOrTab
  , isLower
  , isUpper
  , toLower
  , toUpper
  , isAlpha
  , isAlphaNum
  , isDecDigit
  , isNzDecDigit
  , fromDecDigit
  , fromNzDecDigit
  , unsafeFromDecDigit
  , isBinDigit
  , isNzBinDigit
  , fromBinDigit
  , fromNzBinDigit
  , unsafeFromBinDigit
  , isOctDigit
  , isNzOctDigit
  , fromOctDigit
  , fromNzOctDigit
  , unsafeFromOctDigit
  , isUpHexDigit
  , isNzUpHexDigit
  , fromUpHexDigit
  , fromNzUpHexDigit
  , unsafeFromUpHexDigit
  , isLowHexDigit
  , isNzLowHexDigit
  , fromLowHexDigit
  , fromNzLowHexDigit
  , unsafeFromLowHexDigit
  , isHexDigit
  , isNzHexDigit
  , fromHexDigit
  , fromNzHexDigit
  , unsafeFromHexDigit
  -- * Byte properties
  , isControl8
  , isPrintable8
  , isWhiteSpace8
  , isSpaceOrTab8
  , isLower8
  , isUpper8
  , toLower8
  , toUpper8
  , isAlpha8
  , isAlphaNum8
  , isDecDigit8
  , isNzDecDigit8
  , fromDecDigit8
  , fromNzDecDigit8
  , unsafeFromDecDigit8
  , isBinDigit8
  , isNzBinDigit8
  , fromBinDigit8
  , fromNzBinDigit8
  , unsafeFromBinDigit8
  , isOctDigit8
  , isNzOctDigit8
  , fromOctDigit8
  , fromNzOctDigit8
  , unsafeFromOctDigit8
  , isUpHexDigit8
  , isNzUpHexDigit8
  , fromUpHexDigit8
  , fromNzUpHexDigit8
  , unsafeFromUpHexDigit8
  , isLowHexDigit8
  , isNzLowHexDigit8
  , fromLowHexDigit8
  , fromNzLowHexDigit8
  , unsafeFromLowHexDigit8
  , isHexDigit8
  , isNzHexDigit8
  , fromHexDigit8
  , fromNzHexDigit8
  , unsafeFromHexDigit8
  ) where

import Data.Checked
import Data.Function (on)
import Data.Char (ord, chr)
import Data.String (IsString(..))
import Data.Word (Word8)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as TS
import qualified Data.Text.Lazy as TL
import Data.Monoid (Monoid(..))
import Data.CaseInsensitive (FoldCase(..))
import Data.Hashable (Hashable(..))

data IsAscii = IsAscii

instance Property IsAscii Word8 where
  holds _ = (< 128)
  {-# INLINE holds #-}

instance Property IsAscii BS.ByteString where
  holds _ = BS.all isAscii
  {-# INLINE holds #-}

instance Property IsAscii BL.ByteString where
  holds _ = BL.all isAscii
  {-# INLINE holds #-}

instance Property IsAscii Char where
  holds _ = (< 128) . ord
  {-# INLINE holds #-}

instance Property IsAscii α  Property IsAscii [α] where
  holds _ = all isAscii
  {-# INLINE holds #-}

instance Property IsAscii TS.Text where
  holds _ = TS.all isAscii
  {-# INLINE holds #-}

instance Property IsAscii TL.Text where
  holds _ = TL.all isAscii
  {-# INLINE holds #-}

isAscii  Property IsAscii v  v  Bool 
isAscii = holds IsAscii
{-# INLINE isAscii #-}

type Ascii α = Checked IsAscii α

instance Eq α  Eq (Ascii α) where
  (==) = (==) `on` checked
  {-# INLINE (==) #-}

instance Ord α  Ord (Ascii α) where
  compare = compare `on` checked
  {-# INLINE compare #-}

instance Show α  Show (Ascii α) where
  showsPrec p = showsPrec p . checked

instance Monoid α  Monoid (Ascii α) where
  mempty = trustMe mempty
  {-# INLINE mempty #-}
  mappend x y = trustMe $ mappend (checked x) (checked y)
  {-# INLINE mappend #-}

instance IsString α  IsString (Ascii α) where
  fromString s | isAscii s = trustMe $ fromString s
               | otherwise = error $ "Not an ASCII string: " ++ show s
  {-# INLINE fromString #-}

instance Hashable α  Hashable (Ascii α) where
#if MIN_VERSION_hashable(1,2,0)
  hashWithSalt s = hashWithSalt s . checked
  {-# INLINE hashWithSalt #-}
#else
  hash = hash . checked
  {-# INLINE hash #-}
#endif

instance FoldCase (Ascii Char) where
  foldCase = trustMap toLower
  {-# INLINE foldCase #-}

instance FoldCase (Ascii α)  FoldCase (Ascii [α]) where
  foldCase = trustMap $ map $ checked . foldCase . trustThat IsAscii
  {-# INLINE foldCase #-}

instance FoldCase (Ascii BS.ByteString) where
  foldCase = trustMap $ BS.map toLower8
  {-# INLINE foldCase #-}

instance FoldCase (Ascii BL.ByteString) where
  foldCase = trustMap $ BL.map toLower8
  {-# INLINE foldCase #-}

instance FoldCase (Ascii TS.Text) where
  foldCase = trustMap $ TS.map toLower
  {-# INLINE foldCase #-}

instance FoldCase (Ascii TL.Text) where
  foldCase = trustMap $ TL.map toLower
  {-# INLINE foldCase #-}

-- | Map a character to its ASCII encoding if possible, otherwise
--   return 'Nothing'.
maybeAscii  Char  Maybe Word8
maybeAscii c | isAscii c = Just $ ascii c
             | otherwise = Nothing
{-# INLINABLE maybeAscii #-}

-- | Encode an ASCII character. No checks are performed.
ascii  Char  Word8
ascii = fromIntegral . ord
{-# INLINE ascii #-}

-- | Test if a character is an ASCII control character.
isControl  Char  Bool
isControl c = w < 32 || w == 127
  where w = ord c
{-# INLINE isControl #-}

-- | Test if a character is an ASCII printable character.
isPrintable  Char  Bool
isPrintable c = w >= 32 && w < 127
  where w = ord c
{-# INLINE isPrintable #-}

-- | Test if a character is an ASCII whitespace character.
isWhiteSpace  Char  Bool
isWhiteSpace c = c == ' ' || (w >= 9 && w <= 13)
  where w = ord c
{-# INLINE isWhiteSpace #-}

-- | Test if a character is the SPACE or the TAB character.
isSpaceOrTab  Char  Bool
isSpaceOrTab c = c == ' ' || c == '\t'
{-# INLINE isSpaceOrTab #-}

-- | Test if a character is an ASCII lower-case letter.
isLower  Char  Bool
isLower c = c >= 'a' && c <= 'z'
{-# INLINE isLower #-}

-- | Test if a character is an ASCII upper-case letter.
isUpper  Char  Bool
isUpper c = c >= 'A' && c <= 'Z'
{-# INLINE isUpper #-}

-- | Map lower-case ASCII letters to the corresponding upper-case letters,
--   leaving other characters as is.
toLower  Char  Char
toLower c | isUpper c = chr (ord c + 32)
          | otherwise = c
{-# INLINABLE toLower #-}

-- | Map upper-case ASCII letters to the corresponding lower-case letters,
--   leaving other characters as is.
toUpper  Char  Char
toUpper c | isLower c = chr (ord c - 32)
          | otherwise = c
{-# INLINABLE toUpper #-}

-- | Test if a character is an ASCII letter.
isAlpha  Char  Bool
isAlpha c = isUpper c || isLower c
{-# INLINABLE isAlpha #-}

-- | Test if a character is either an ASCII letter or a decimal digit.
isAlphaNum  Char  Bool
isAlphaNum c = isDecDigit c || isAlpha c
{-# INLINABLE isAlphaNum #-}

-- | Test if a character is a decimal digit (/'0' ... '9'/).
isDecDigit  Char  Bool
isDecDigit c = c >= '0' && c <= '9'
{-# INLINE isDecDigit #-}

-- | Test if a character is a non-zero decimal digit (/'1' ... '9'/).
isNzDecDigit  Char  Bool
isNzDecDigit c = c >= '1' && c <= '9'
{-# INLINE isNzDecDigit #-}

-- | Map a decimal digit to the corresponding number. Return 'Nothing' on
--   other inputs.
fromDecDigit  Num a  Char  Maybe a
fromDecDigit c | isDecDigit c = Just $ unsafeFromDecDigit c
               | otherwise    = Nothing
{-# INLINABLE fromDecDigit #-}

-- | Map non-zero decimal digits to the corresponding numbers. Return
--   'Nothing' on other inputs.
fromNzDecDigit  Num a  Char  Maybe a
fromNzDecDigit c | isNzDecDigit c = Just $ unsafeFromDecDigit c
                 | otherwise      = Nothing
{-# INLINABLE fromNzDecDigit #-}

-- | Map decimal digits to the corresponding numbers. No checks are performed.
unsafeFromDecDigit  Num a  Char  a
unsafeFromDecDigit c = fromIntegral (ord c - ord '0')
{-# INLINE unsafeFromDecDigit #-}

-- | Test if a character is a binary digit (/'0'/ or /'1'/).
isBinDigit  Char  Bool
isBinDigit c = c == '0' || c == '1'
{-# INLINE isBinDigit #-}

-- | Test if a character is the non-zero binary digit (/'1'/).
isNzBinDigit  Char  Bool
isNzBinDigit c = c == '1'
{-# INLINE isNzBinDigit #-}

-- | Map binary digits to the corresponding numbers. Return 'Nothing' on
--   other inputs.
fromBinDigit  Num a  Char  Maybe a
fromBinDigit c | isBinDigit c = Just $ unsafeFromBinDigit c
               | otherwise    = Nothing
{-# INLINABLE fromBinDigit #-}

-- | Map the digit /'1'/ to the number /1/. Return 'Nothing' on other inputs.
fromNzBinDigit  Num a  Char  Maybe a
fromNzBinDigit c | isNzBinDigit c = Just 1
                 | otherwise      = Nothing
{-# INLINABLE fromNzBinDigit #-}

-- | Map binary digits to the corresponding numbers. No checks are performed.
unsafeFromBinDigit  Num a  Char  a
unsafeFromBinDigit = unsafeFromDecDigit
{-# INLINE unsafeFromBinDigit #-}

-- | Test if a character is an octal digit (/'0' ... '7'/).
isOctDigit  Char  Bool
isOctDigit c = c >= '0' && c <= '7'
{-# INLINE isOctDigit #-}

-- | Test if a character is a non-zero octal digit (/'1' ... '7'/).
isNzOctDigit  Char  Bool
isNzOctDigit c = c >= '1' && c <= '7'
{-# INLINE isNzOctDigit #-}

-- | Map octal digits to the corresponding numbers. Return 'Nothing' on
--   other inputs.
fromOctDigit  Num a  Char  Maybe a
fromOctDigit c | isOctDigit c = Just $ unsafeFromOctDigit c
               | otherwise    = Nothing
{-# INLINABLE fromOctDigit #-}

-- | Map non-zero octal digits to the corresponding numbers. Return
--   'Nothing' on other inputs.
fromNzOctDigit  Num a  Char  Maybe a
fromNzOctDigit c | isNzOctDigit c = Just $ unsafeFromOctDigit c
                 | otherwise      = Nothing
{-# INLINABLE fromNzOctDigit #-}

-- | Map octal digits to the corresponding numbers. No checks are performed.
unsafeFromOctDigit  Num a  Char  a
unsafeFromOctDigit = unsafeFromDecDigit
{-# INLINE unsafeFromOctDigit #-}

isLowAF  Char  Bool
isLowAF c = c >= 'a' && c <= 'f'
{-# INLINE isLowAF #-}

fromLowAF  Num a  Char  a
fromLowAF c = fromIntegral (ord c - ord 'a' + 10)
{-# INLINE fromLowAF #-}

-- | Test if a character is a lower-case hexadecimal digit
--   (/'0' ... '9'/ or /'a' ... 'f'/).
isLowHexDigit  Char  Bool
isLowHexDigit c = isDecDigit c || isLowAF c
{-# INLINABLE isLowHexDigit #-}

-- | Test if a character is a non-zero lower-case hexadecimal digit
--   (/'1' ... '9'/ or /'a' ... 'f'/).
isNzLowHexDigit  Char  Bool
isNzLowHexDigit c = isNzDecDigit c || isLowAF c
{-# INLINABLE isNzLowHexDigit #-}

-- | Map lower-case hexadecimal digits to the corresponding numbers.
--   Return 'Nothing' on other inputs.
fromLowHexDigit  Num a  Char  Maybe a
fromLowHexDigit c | isDecDigit c = Just $ unsafeFromDecDigit c
                  | isLowAF c    = Just $ fromLowAF c
                  | otherwise    = Nothing
{-# INLINABLE fromLowHexDigit #-}

-- | Map non-zero lower-case hexadecimal digits to the corresponding numbers.
--   Return 'Nothing' on other inputs.
fromNzLowHexDigit  Num a  Char  Maybe a
fromNzLowHexDigit c | isNzDecDigit c = Just $ unsafeFromDecDigit c
                    | isLowAF c      = Just $ fromLowAF c
                    | otherwise      = Nothing
{-# INLINABLE fromNzLowHexDigit #-}

-- | Map lower-case hexadecimal digits to the corresponding numbers.
--   No checks are performed.
unsafeFromLowHexDigit  Num a  Char  a
unsafeFromLowHexDigit c | c < 'a'   = unsafeFromDecDigit c
                        | otherwise = fromLowAF c
{-# INLINE unsafeFromLowHexDigit #-}

isUpAF  Char  Bool
isUpAF c = c >= 'A' && c <= 'F'
{-# INLINE isUpAF #-}

fromUpAF  Num a  Char  a
fromUpAF c = fromIntegral (ord c - ord 'A' + 10)
{-# INLINE fromUpAF #-}

-- | Test if a character is an upper-case hexadecimal digit
--   (/'0' ... '9'/ or /'A' ... 'F'/).
isUpHexDigit  Char  Bool
isUpHexDigit c = isDecDigit c || isUpAF c
{-# INLINABLE isUpHexDigit #-}

-- | Test if a character is a non-zero upper-case hexadecimal digit
--   (/'1' ... '9'/ or /'A' ... 'F'/).
isNzUpHexDigit  Char  Bool
isNzUpHexDigit c = isNzDecDigit c || isUpAF c
{-# INLINABLE isNzUpHexDigit #-}

-- | Map upper-case hexadecimal digits to the corresponding numbers.
--   Return 'Nothing' on other inputs.
fromUpHexDigit  Num a  Char  Maybe a
fromUpHexDigit c | isDecDigit c = Just $ unsafeFromDecDigit c
                 | isUpAF c     = Just $ fromUpAF c
                 | otherwise    = Nothing
{-# INLINABLE fromUpHexDigit #-}

-- | Map non-zero upper-case hexadecimal digits to the corresponding numbers.
--   Return 'Nothing' on other inputs.
fromNzUpHexDigit  Num a  Char  Maybe a
fromNzUpHexDigit c | isNzDecDigit c = Just $ unsafeFromDecDigit c
                   | isUpAF c       = Just $ fromUpAF c
                   | otherwise      = Nothing
{-# INLINABLE fromNzUpHexDigit #-}

-- | Map upper-case hexadecimal digits to the corresponding numbers.
--   No checks are performed.
unsafeFromUpHexDigit  Num a  Char  a
unsafeFromUpHexDigit c | c < 'A'   = unsafeFromDecDigit c
                       | otherwise = fromUpAF c
{-# INLINE unsafeFromUpHexDigit #-}

-- | Test if a character is a hexadecimal digit
--   (/'0' ... '9'/ or /'a' ... 'f'/ or /'A' ... 'F'/).
isHexDigit  Char  Bool
isHexDigit c = isDecDigit c || isUpAF c || isLowAF c
{-# INLINABLE isHexDigit #-}

-- | Test if a character is a non-zero hexadecimal digit
--   (/'1' ... '9'/ or /'a' ... 'f'/ or /'A' ... 'F'/).
isNzHexDigit  Char  Bool
isNzHexDigit c = isNzDecDigit c || isUpAF c || isLowAF c
{-# INLINABLE isNzHexDigit #-}

-- | Map hexadecimal digits to the corresponding numbers.
--   Return 'Nothing' on other inputs.
fromHexDigit  Num a  Char  Maybe a
fromHexDigit c | isDecDigit c = Just $ unsafeFromDecDigit c
               | isUpAF c     = Just $ fromUpAF c
               | isLowAF c    = Just $ fromLowAF c
               | otherwise    = Nothing
{-# INLINABLE fromHexDigit #-}

-- | Map non-zero hexadecimal digits to the corresponding numbers.
--   Return 'Nothing' on other inputs.
fromNzHexDigit  Num a  Char  Maybe a
fromNzHexDigit c | isNzDecDigit c = Just $ unsafeFromDecDigit c
                 | isUpAF c       = Just $ fromUpAF c
                 | isLowAF c      = Just $ fromLowAF c
                 | otherwise      = Nothing
{-# INLINABLE fromNzHexDigit #-}

-- | Map hexadecimal digits to the corresponding numbers. No checks are
--   performed.
unsafeFromHexDigit  Num a  Char  a
unsafeFromHexDigit c | c < 'A'   = unsafeFromDecDigit c
                     | c < 'a'   = fromUpAF c
                     | otherwise = fromLowAF c
{-# INLINE unsafeFromHexDigit #-}

-- | Test if a byte is the encoding of an ASCII control character.
isControl8  Word8  Bool
isControl8 w = w < 32 || w == 127
{-# INLINE isControl8 #-}

-- | Test if a byte is the encoding of an ASCII printable character.
isPrintable8  Word8  Bool
isPrintable8 w = w >= 32 && w < 127
{-# INLINE isPrintable8 #-}

-- | Test if a byte is the encoding of an ASCII whitespace character.
isWhiteSpace8  Word8  Bool
isWhiteSpace8 w = w == ascii ' ' || w >= 9 && w <= 13
{-# INLINE isWhiteSpace8 #-}

-- | Test if a byte is the encoding of the SPACE or the TAB character.
isSpaceOrTab8  Word8  Bool
isSpaceOrTab8 w = w == ascii ' ' || w == ascii '\t'
{-# INLINE isSpaceOrTab8 #-}

-- | Test if a byte is the encoding of an ASCII lower-case letter.
isLower8  Word8  Bool
isLower8 w = w >= ascii 'a' && w <= ascii 'z'
{-# INLINE isLower8 #-}

-- | Test if a byte is the encoding of an ASCII upper-case letter.
isUpper8  Word8  Bool
isUpper8 w = w >= ascii 'A' && w <= ascii 'Z'
{-# INLINE isUpper8 #-}

-- | Map the encodings of lower-case ASCII letters to the encodings of
--   the corresponding upper-case letters, leaving other bytes as is.
toLower8  Word8  Word8
toLower8 w | isUpper8 w = w + 32
           | otherwise  = w
{-# INLINABLE toLower8 #-}

-- | Map the encodings of upper-case ASCII letters to the encodings of
--   the corresponding lower-case letters, leaving other bytes as is.
toUpper8  Word8  Word8
toUpper8 w | isLower8 w = w - 32
           | otherwise  = w
{-# INLINABLE toUpper8 #-}

-- | Test if a byte is the encoding of an ASCII letter.
isAlpha8  Word8  Bool
isAlpha8 w = isUpper8 w || isLower8 w
{-# INLINABLE isAlpha8 #-}

-- | Test if a byte is the encoding of either an ASCII letter
--   or a decimal digit.
isAlphaNum8  Word8  Bool
isAlphaNum8 w = isDecDigit8 w || isAlpha8 w
{-# INLINABLE isAlphaNum8 #-}

-- | Test if a byte is the encoding of a decimal digit (/'0' ... '9'/).
isDecDigit8  Word8  Bool
isDecDigit8 w = w >= ascii '0' && w <= ascii '9'
{-# INLINE isDecDigit8 #-}

-- | Test if a byte is the encoding of a non-zero decimal digit
--   (/'1' ... '9'/).
isNzDecDigit8  Word8  Bool
isNzDecDigit8 w = w >= ascii '1' && w <= ascii '9'
{-# INLINE isNzDecDigit8 #-}

-- | Map the encoding of a decimal digit to the corresponding number.
--   Return 'Nothing' on other inputs.
fromDecDigit8  Num a  Word8  Maybe a
fromDecDigit8 w | isDecDigit8 w = Just $ unsafeFromDecDigit8 w
                | otherwise  = Nothing
{-# INLINABLE fromDecDigit8 #-}

-- | Map the encoding of a non-zero decimal digit to the corresponding number.
--   Return 'Nothing' on other inputs.
fromNzDecDigit8  Num a  Word8  Maybe a
fromNzDecDigit8 w | isNzDecDigit8 w = Just $ unsafeFromDecDigit8 w
                  | otherwise       = Nothing
{-# INLINABLE fromNzDecDigit8 #-}

-- | Map the encoding of a decimal digit to the corresponding number.
--   No checks are performed.
unsafeFromDecDigit8  Num a  Word8  a
unsafeFromDecDigit8 w = fromIntegral (w - ascii '0')
{-# INLINE unsafeFromDecDigit8 #-}

-- | Test if a byte is the encoding of a binary digit (/'0'/ or /'1'/).
isBinDigit8  Word8  Bool
isBinDigit8 w = w == ascii '0' || w == ascii '1'
{-# INLINE isBinDigit8 #-}

-- | Test if a byte is the encoding of the non-zero binary digit (/'1'/).
isNzBinDigit8  Word8  Bool
isNzBinDigit8 w = w == ascii '1'
{-# INLINE isNzBinDigit8 #-}

-- | Map the encoding of a binary digit to the corresponding number.
--   Return 'Nothing' on other inputs.
fromBinDigit8  Num a  Word8  Maybe a
fromBinDigit8 w | isBinDigit8 w = Just $ unsafeFromBinDigit8 w
                | otherwise     = Nothing
{-# INLINABLE fromBinDigit8 #-}

-- | Map the encoding of the digit /'1'/ to the number /1/.
--   Return 'Nothing' on other inputs.
fromNzBinDigit8  Num a  Word8  Maybe a
fromNzBinDigit8 w | isNzBinDigit8 w = Just 1
                  | otherwise       = Nothing
{-# INLINABLE fromNzBinDigit8 #-}

-- | Map the encoding of a binary digit to the corresponding number.
--   No checks are performed.
unsafeFromBinDigit8  Num a  Word8  a
unsafeFromBinDigit8 = unsafeFromDecDigit8
{-# INLINE unsafeFromBinDigit8 #-}

-- | Test if a byte is the encoding of an octal digit (/'0' ... '7'/).
isOctDigit8  Word8  Bool
isOctDigit8 w = w >= ascii '0' && w <= ascii '7'
{-# INLINE isOctDigit8 #-}

-- | Test if a byte is the encoding of a non-zero octal digit
--   (/'1' ... '7'/).
isNzOctDigit8  Word8  Bool
isNzOctDigit8 w = w >= ascii '1' && w <= ascii '7'
{-# INLINE isNzOctDigit8 #-}

-- | Map the encoding of an octal digit to the corresponding number.
--   Return 'Nothing' on other inputs.
fromOctDigit8  Num a  Word8  Maybe a
fromOctDigit8 w | isOctDigit8 w = Just $ unsafeFromOctDigit8 w
                | otherwise     = Nothing
{-# INLINABLE fromOctDigit8 #-}

-- | Map the encoding of a non-zero octal digit to the corresponding number.
--   Return 'Nothing' on other inputs.
fromNzOctDigit8  Num a  Word8  Maybe a
fromNzOctDigit8 w | isNzOctDigit8 w = Just $ unsafeFromOctDigit8 w
                  | otherwise       = Nothing
{-# INLINABLE fromNzOctDigit8 #-}

-- | Map the encoding of an octal digit to the corresponding number.
--   No checks are performed.
unsafeFromOctDigit8  Num a  Word8  a
unsafeFromOctDigit8 = unsafeFromDecDigit8
{-# INLINE unsafeFromOctDigit8 #-}

isLowAF8  Word8  Bool
isLowAF8 w = w >= ascii 'a' && w <= ascii 'f'
{-# INLINE isLowAF8 #-}

fromLowAF8  Num a  Word8  a
fromLowAF8 w = fromIntegral (w - ascii 'a' + 10)
{-# INLINE fromLowAF8 #-}

-- | Test if a byte is the encoding of a lower-case hexadecimal digit
--   (/'0' ... '9'/ or /'a' ... 'f'/).
isLowHexDigit8  Word8  Bool
isLowHexDigit8 w = isDecDigit8 w || isLowAF8 w
{-# INLINABLE isLowHexDigit8 #-}

-- | Test if a byte is the encoding of a non-zero lower-case hexadecimal digit
--   (/'1' ... '9'/ or /'a' ... 'f'/).
isNzLowHexDigit8  Word8  Bool
isNzLowHexDigit8 w = isNzDecDigit8 w || isLowAF8 w
{-# INLINABLE isNzLowHexDigit8 #-}

-- | Map the encoding of a lower-case hexadecimal digit to the corresponding
--   number. Return 'Nothing' on other inputs.
fromLowHexDigit8  Num a  Word8  Maybe a
fromLowHexDigit8 w | isDecDigit8 w = Just $ unsafeFromDecDigit8 w
                   | isLowAF8 w    = Just $ fromLowAF8 w
                   | otherwise     = Nothing
{-# INLINABLE fromLowHexDigit8 #-}

-- | Map the encoding of a non-zero lower-case hexadecimal digit to
--   the corresponding number. Return 'Nothing' on other inputs.
fromNzLowHexDigit8  Num a  Word8  Maybe a
fromNzLowHexDigit8 w | isNzDecDigit8 w = Just $ unsafeFromDecDigit8 w
                     | isLowAF8 w      = Just $ fromLowAF8 w
                     | otherwise       = Nothing
{-# INLINABLE fromNzLowHexDigit8 #-}

-- | Map the encoding of a lower-case hexadecimal digit to the corresponding
--   number. No checks are performed.
unsafeFromLowHexDigit8  Num a  Word8  a
unsafeFromLowHexDigit8 w | w < ascii 'a' = unsafeFromDecDigit8 w
                         | otherwise     = fromLowAF8 w
{-# INLINE unsafeFromLowHexDigit8 #-}

isUpAF8  Word8  Bool
isUpAF8 w = w >= ascii 'A' && w <= ascii 'F'
{-# INLINE isUpAF8 #-}

fromUpAF8  Num a  Word8  a
fromUpAF8 w = fromIntegral (w - ascii 'A' + 10)
{-# INLINE fromUpAF8 #-}

-- | Test if a byte is the encoding of an upper-case hexadecimal digit
--   (/'0' ... '9'/ or /'A' ... 'F'/).
isUpHexDigit8  Word8  Bool
isUpHexDigit8 w = isDecDigit8 w || isUpAF8 w
{-# INLINABLE isUpHexDigit8 #-}

-- | Test if a byte is the encoding of a non-zero upper-case hexadecimal digit
--   (/'1' ... '9'/ or /'A' ... 'F'/).
isNzUpHexDigit8  Word8  Bool
isNzUpHexDigit8 w = isNzDecDigit8 w || isUpAF8 w
{-# INLINABLE isNzUpHexDigit8 #-}

-- | Map the encoding of an upper-case hexadecimal digit to the corresponding
--   number. Return 'Nothing' on other inputs.
fromUpHexDigit8  Num a  Word8  Maybe a
fromUpHexDigit8 w | isDecDigit8 w = Just $ unsafeFromDecDigit8 w
                  | isUpAF8 w     = Just $ fromUpAF8 w
                  | otherwise     = Nothing
{-# INLINABLE fromUpHexDigit8 #-}

-- | Map the encoding of a non-zero upper-case hexadecimal digit to
--   the corresponding number. Return 'Nothing' on other inputs.
fromNzUpHexDigit8  Num a  Word8  Maybe a
fromNzUpHexDigit8 w | isNzDecDigit8 w = Just $ unsafeFromDecDigit8 w
                    | isUpAF8 w       = Just $ fromUpAF8 w
                    | otherwise       = Nothing
{-# INLINABLE fromNzUpHexDigit8 #-}

-- | Map the encoding of an upper-case hexadecimal digit to the corresponding
--   number. No checks are performed.
unsafeFromUpHexDigit8  Num a  Word8  a
unsafeFromUpHexDigit8 w | w < ascii 'A' = unsafeFromDecDigit8 w
                        | otherwise     = fromUpAF8 w
{-# INLINE unsafeFromUpHexDigit8 #-}

-- | Test if a byte is the encoding of a hexadecimal digit
--   (/'0' ... '9'/ or /'a' ... 'f'/ or /'A' ... 'F'/).
isHexDigit8  Word8  Bool
isHexDigit8 w = isDecDigit8 w || isUpAF8 w || isLowAF8 w
{-# INLINABLE isHexDigit8 #-}

-- | Test if a byte is the encoding of a non-zero hexadecimal digit
--   (/'1' ... '9'/ or /'a' ... 'f'/ or /'A' ... 'F'/).
isNzHexDigit8  Word8  Bool
isNzHexDigit8 w = isNzDecDigit8 w || isUpAF8 w || isLowAF8 w
{-# INLINABLE isNzHexDigit8 #-}

-- | Map the encoding of a hexadecimal digit to the corresponding
--   number. Return 'Nothing' on other inputs.
fromHexDigit8  Num a  Word8  Maybe a
fromHexDigit8 w | isDecDigit8 w = Just $ unsafeFromDecDigit8 w
                | isUpAF8 w     = Just $ fromUpAF8 w
                | isLowAF8 w    = Just $ fromLowAF8 w
                | otherwise     = Nothing
{-# INLINABLE fromHexDigit8 #-}

-- | Map the encoding of a non-zero hexadecimal digit to the corresponding
--   number. Return 'Nothing' on other inputs.
fromNzHexDigit8  Num a  Word8  Maybe a
fromNzHexDigit8 w | isNzDecDigit8 w = Just $ unsafeFromDecDigit8 w
                  | isUpAF8 w       = Just $ fromUpAF8 w
                  | isLowAF8 w      = Just $ fromLowAF8 w
                  | otherwise       = Nothing
{-# INLINABLE fromNzHexDigit8 #-}

-- | Map the encoding of a hexadecimal digit to the corresponding
--   number. No checks are performed.
unsafeFromHexDigit8  Num a  Word8  a
unsafeFromHexDigit8 w | w < ascii 'A' = unsafeFromDecDigit8 w
                      | w < ascii 'a' = fromUpAF8 w
                      | otherwise     = fromLowAF8 w
{-# INLINE unsafeFromHexDigit8 #-}