{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE Trustworthy #-}

-- |
-- Module: Text.Ascii.Char
-- Copyright: (C) 2021 Koz Ross
-- License: Apache 2.0
-- Maintainer: Koz Ross <koz.ross@retro-freedom.nz>
-- Stability: stable
-- Portability: GHC only
--
-- An implementation of ASCII characters, as bytes restricted to the range 0 -
-- 127 inclusive.
--
-- /See also:/ [Wikipedia entry for ASCII](https://en.wikipedia.org/wiki/ASCII)
module Text.Ascii.Char
  ( -- * ASCII characters

    -- ** Type
    AsciiChar (AsByte, AsChar),

    -- ** Construction
    char,
    fromChar,
    fromByte,

    -- ** Transformation
    upcase,
    downcase,

    -- * Categorization
    AsciiType (Control, Printable),
    charType,
    AsciiCategory (Other, Punctuation, Letter, Number, Symbol),
    categorize,
    categorizeGeneral,
    AsciiCase (Upper, Lower),
    caseOf,

    -- * Optics
    charWise,
    byteWise,
  )
where

import Control.DeepSeq (NFData)
import Control.Monad (guard)
import Data.Char (GeneralCategory, chr, generalCategory, isAscii, ord)
import Data.Functor (($>))
import Data.Hashable (Hashable)
import Data.Word (Word8)
import Optics.Prism (Prism', prism')
import Text.Ascii.Internal (AsciiChar (AsciiChar), toByte, pattern AsByte, pattern AsChar)
import Text.Ascii.QQ (char)

-- $setup
-- >>> :set -XQuasiQuotes
-- >>> import Text.Ascii.Char
-- >>> import Optics.AffineFold (preview)
-- >>> import Optics.Review (review)

-- | Try and turn a 'Char' into the equivalent 'AsciiChar'. Will return
-- 'Nothing' if given a 'Char' that has no ASCII equivalent.
--
-- >>> fromChar '0'
-- Just '0x30'
-- >>> fromChar '😺'
-- Nothing
--
-- @since 1.0.0
fromChar :: Char -> Maybe AsciiChar
fromChar :: Char -> Maybe AsciiChar
fromChar Char
c =
  if Char -> Bool
isAscii Char
c
    then AsciiChar -> Maybe AsciiChar
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AsciiChar -> Maybe AsciiChar)
-> (Char -> AsciiChar) -> Char -> Maybe AsciiChar
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> AsciiChar
AsciiChar (Word8 -> AsciiChar) -> (Char -> Word8) -> Char -> AsciiChar
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Word8) -> (Char -> Int) -> Char -> Word8
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Int
ord (Char -> Maybe AsciiChar) -> Char -> Maybe AsciiChar
forall a b. (a -> b) -> a -> b
$ Char
c
    else Maybe AsciiChar
forall a. Maybe a
Nothing

-- | Try to give the 'AsciiChar' corresponding to the given byte. Will return
-- 'Nothing' if given a byte that doesn't correspond to an ASCII character.
--
-- >>> fromByte 50
-- Just '0x32'
-- >>> fromByte 128
-- Nothing
--
-- @since 1.0.0
fromByte :: Word8 -> Maybe AsciiChar
fromByte :: Word8 -> Maybe AsciiChar
fromByte Word8
w8 =
  if Char -> Bool
isAscii (Char -> Bool) -> (Word8 -> Char) -> Word8 -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
chr (Int -> Char) -> (Word8 -> Int) -> Word8 -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word8 -> Bool) -> Word8 -> Bool
forall a b. (a -> b) -> a -> b
$ Word8
w8
    then AsciiChar -> Maybe AsciiChar
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AsciiChar -> Maybe AsciiChar)
-> (Word8 -> AsciiChar) -> Word8 -> Maybe AsciiChar
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> AsciiChar
AsciiChar (Word8 -> Maybe AsciiChar) -> Word8 -> Maybe AsciiChar
forall a b. (a -> b) -> a -> b
$ Word8
w8
    else Maybe AsciiChar
forall a. Maybe a
Nothing

-- | Give the 'AsciiChar' corresponding to the uppercase version of the
-- argument. Will give 'Nothing' if given an 'AsciiChar' which has no uppercase
-- version, or is uppercase already.
--
-- >>> upcase [char| 'a' |]
-- Just '0x41'
-- >>> upcase [char| '0' |]
-- Nothing
--
-- @since 1.0.0
upcase :: AsciiChar -> Maybe AsciiChar
upcase :: AsciiChar -> Maybe AsciiChar
upcase c :: AsciiChar
c@(AsciiChar Word8
w8) =
  AsciiChar -> Maybe AsciiCase
caseOf AsciiChar
c Maybe AsciiCase
-> (AsciiCase -> Maybe AsciiChar) -> Maybe AsciiChar
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\AsciiCase
cs -> Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (AsciiCase
cs AsciiCase -> AsciiCase -> Bool
forall a. Eq a => a -> a -> Bool
== AsciiCase
Lower) Maybe () -> AsciiChar -> Maybe AsciiChar
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> Word8 -> AsciiChar
AsciiChar (Word8
w8 Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
- Word8
32))

-- | Give the 'AsciiChar' corresponding to the lowercase version of the
-- argument. Will give 'Nothing' if given an 'AsciiChar' which has no lowercase
-- version, or is lowercase already.
--
-- >>> downcase [char| 'C' |]
-- Just '0x63'
-- >>> downcase [char| '\\' |]
-- Nothing
--
-- @since 1.0.0
downcase :: AsciiChar -> Maybe AsciiChar
downcase :: AsciiChar -> Maybe AsciiChar
downcase c :: AsciiChar
c@(AsciiChar Word8
w8) =
  AsciiChar -> Maybe AsciiCase
caseOf AsciiChar
c Maybe AsciiCase
-> (AsciiCase -> Maybe AsciiChar) -> Maybe AsciiChar
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\AsciiCase
cs -> Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (AsciiCase
cs AsciiCase -> AsciiCase -> Bool
forall a. Eq a => a -> a -> Bool
== AsciiCase
Upper) Maybe () -> AsciiChar -> Maybe AsciiChar
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> Word8 -> AsciiChar
AsciiChar (Word8
w8 Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Word8
32))

-- Categorization

-- | A categorization of ASCII characters based on whether they're meant to be
-- displayed ('Printable') or for control ('Control').
--
-- @since 1.0.0
newtype AsciiType = AsciiType Word8
  deriving (AsciiType -> AsciiType -> Bool
(AsciiType -> AsciiType -> Bool)
-> (AsciiType -> AsciiType -> Bool) -> Eq AsciiType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AsciiType -> AsciiType -> Bool
== :: AsciiType -> AsciiType -> Bool
$c/= :: AsciiType -> AsciiType -> Bool
/= :: AsciiType -> AsciiType -> Bool
Eq, Eq AsciiType
Eq AsciiType =>
(AsciiType -> AsciiType -> Ordering)
-> (AsciiType -> AsciiType -> Bool)
-> (AsciiType -> AsciiType -> Bool)
-> (AsciiType -> AsciiType -> Bool)
-> (AsciiType -> AsciiType -> Bool)
-> (AsciiType -> AsciiType -> AsciiType)
-> (AsciiType -> AsciiType -> AsciiType)
-> Ord AsciiType
AsciiType -> AsciiType -> Bool
AsciiType -> AsciiType -> Ordering
AsciiType -> AsciiType -> AsciiType
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: AsciiType -> AsciiType -> Ordering
compare :: AsciiType -> AsciiType -> Ordering
$c< :: AsciiType -> AsciiType -> Bool
< :: AsciiType -> AsciiType -> Bool
$c<= :: AsciiType -> AsciiType -> Bool
<= :: AsciiType -> AsciiType -> Bool
$c> :: AsciiType -> AsciiType -> Bool
> :: AsciiType -> AsciiType -> Bool
$c>= :: AsciiType -> AsciiType -> Bool
>= :: AsciiType -> AsciiType -> Bool
$cmax :: AsciiType -> AsciiType -> AsciiType
max :: AsciiType -> AsciiType -> AsciiType
$cmin :: AsciiType -> AsciiType -> AsciiType
min :: AsciiType -> AsciiType -> AsciiType
Ord, Eq AsciiType
Eq AsciiType =>
(Int -> AsciiType -> Int)
-> (AsciiType -> Int) -> Hashable AsciiType
Int -> AsciiType -> Int
AsciiType -> Int
forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
$chashWithSalt :: Int -> AsciiType -> Int
hashWithSalt :: Int -> AsciiType -> Int
$chash :: AsciiType -> Int
hash :: AsciiType -> Int
Hashable, AsciiType -> ()
(AsciiType -> ()) -> NFData AsciiType
forall a. (a -> ()) -> NFData a
$crnf :: AsciiType -> ()
rnf :: AsciiType -> ()
NFData) via Word8

-- | @since 1.0.0
instance Show AsciiType where
  {-# INLINEABLE show #-}
  show :: AsciiType -> String
show = \case
    AsciiType
Control -> String
"Control"
    AsciiType
Printable -> String
"Printable"

-- | @since 1.0.0
instance Bounded AsciiType where
  minBound :: AsciiType
minBound = AsciiType
Control
  maxBound :: AsciiType
maxBound = AsciiType
Printable

-- | A control character is any of the first 32 bytes (0-31), plus @DEL@ (127).
--
-- @since 1.0.0
pattern Control :: AsciiType
pattern $mControl :: forall {r}. AsciiType -> ((# #) -> r) -> ((# #) -> r) -> r
$bControl :: AsciiType
Control <-
  AsciiType 0
  where
    Control = Word8 -> AsciiType
AsciiType Word8
0

-- | All ASCII characters whose byte is above 31 (and not 127) are printable
-- characters.
--
-- @since 1.0.0
pattern Printable :: AsciiType
pattern $mPrintable :: forall {r}. AsciiType -> ((# #) -> r) -> ((# #) -> r) -> r
$bPrintable :: AsciiType
Printable <-
  AsciiType 1
  where
    Printable = Word8 -> AsciiType
AsciiType Word8
1

{-# COMPLETE Control, Printable #-}

-- | Classify an 'AsciiChar' according to whether it's a control character or a
-- printable character.
--
-- >>> charType [char| '\0' |]
-- Control
-- >>> charType [char| 'w' |]
-- Printable
--
-- @since 1.0.0
charType :: AsciiChar -> AsciiType
charType :: AsciiChar -> AsciiType
charType (AsciiChar Word8
w8)
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
127 = AsciiType
Control
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
< Word8
32 = AsciiType
Control
  | Bool
otherwise = AsciiType
Printable

-- | A categorization of ASCII characters based on their usage. Based (loosely)
-- on Unicode categories.
--
-- @since 1.0.0
newtype AsciiCategory = AsciiCategory Word8
  deriving (AsciiCategory -> AsciiCategory -> Bool
(AsciiCategory -> AsciiCategory -> Bool)
-> (AsciiCategory -> AsciiCategory -> Bool) -> Eq AsciiCategory
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AsciiCategory -> AsciiCategory -> Bool
== :: AsciiCategory -> AsciiCategory -> Bool
$c/= :: AsciiCategory -> AsciiCategory -> Bool
/= :: AsciiCategory -> AsciiCategory -> Bool
Eq, Eq AsciiCategory
Eq AsciiCategory =>
(AsciiCategory -> AsciiCategory -> Ordering)
-> (AsciiCategory -> AsciiCategory -> Bool)
-> (AsciiCategory -> AsciiCategory -> Bool)
-> (AsciiCategory -> AsciiCategory -> Bool)
-> (AsciiCategory -> AsciiCategory -> Bool)
-> (AsciiCategory -> AsciiCategory -> AsciiCategory)
-> (AsciiCategory -> AsciiCategory -> AsciiCategory)
-> Ord AsciiCategory
AsciiCategory -> AsciiCategory -> Bool
AsciiCategory -> AsciiCategory -> Ordering
AsciiCategory -> AsciiCategory -> AsciiCategory
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: AsciiCategory -> AsciiCategory -> Ordering
compare :: AsciiCategory -> AsciiCategory -> Ordering
$c< :: AsciiCategory -> AsciiCategory -> Bool
< :: AsciiCategory -> AsciiCategory -> Bool
$c<= :: AsciiCategory -> AsciiCategory -> Bool
<= :: AsciiCategory -> AsciiCategory -> Bool
$c> :: AsciiCategory -> AsciiCategory -> Bool
> :: AsciiCategory -> AsciiCategory -> Bool
$c>= :: AsciiCategory -> AsciiCategory -> Bool
>= :: AsciiCategory -> AsciiCategory -> Bool
$cmax :: AsciiCategory -> AsciiCategory -> AsciiCategory
max :: AsciiCategory -> AsciiCategory -> AsciiCategory
$cmin :: AsciiCategory -> AsciiCategory -> AsciiCategory
min :: AsciiCategory -> AsciiCategory -> AsciiCategory
Ord, Eq AsciiCategory
Eq AsciiCategory =>
(Int -> AsciiCategory -> Int)
-> (AsciiCategory -> Int) -> Hashable AsciiCategory
Int -> AsciiCategory -> Int
AsciiCategory -> Int
forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
$chashWithSalt :: Int -> AsciiCategory -> Int
hashWithSalt :: Int -> AsciiCategory -> Int
$chash :: AsciiCategory -> Int
hash :: AsciiCategory -> Int
Hashable, AsciiCategory -> ()
(AsciiCategory -> ()) -> NFData AsciiCategory
forall a. (a -> ()) -> NFData a
$crnf :: AsciiCategory -> ()
rnf :: AsciiCategory -> ()
NFData) via Word8

-- | @since 1.0.0
instance Show AsciiCategory where
  {-# INLINEABLE show #-}
  show :: AsciiCategory -> String
show = \case
    AsciiCategory
Other -> String
"Other"
    AsciiCategory
Symbol -> String
"Symbol"
    AsciiCategory
Number -> String
"Number"
    AsciiCategory
Letter -> String
"Letter"
    AsciiCategory
Punctuation -> String
"Punctuation"

-- | @since 1.0.0
instance Bounded AsciiCategory where
  minBound :: AsciiCategory
minBound = AsciiCategory
Other
  maxBound :: AsciiCategory
maxBound = AsciiCategory
Symbol

-- | Something which doesn't fit into any of the other categories.
--
-- @since 1.0.0
pattern Other :: AsciiCategory
pattern $mOther :: forall {r}. AsciiCategory -> ((# #) -> r) -> ((# #) -> r) -> r
$bOther :: AsciiCategory
Other <-
  AsciiCategory 0
  where
    Other = Word8 -> AsciiCategory
AsciiCategory Word8
0

-- | A punctuation character.
--
-- @since 1.0.0
pattern Punctuation :: AsciiCategory
pattern $mPunctuation :: forall {r}. AsciiCategory -> ((# #) -> r) -> ((# #) -> r) -> r
$bPunctuation :: AsciiCategory
Punctuation <-
  AsciiCategory 1
  where
    Punctuation = Word8 -> AsciiCategory
AsciiCategory Word8
1

-- | A letter, either uppercase or lowercase.
--
-- @since 1.0.0
pattern Letter :: AsciiCategory
pattern $mLetter :: forall {r}. AsciiCategory -> ((# #) -> r) -> ((# #) -> r) -> r
$bLetter :: AsciiCategory
Letter <-
  AsciiCategory 2
  where
    Letter = Word8 -> AsciiCategory
AsciiCategory Word8
2

-- | A numerical digit.
--
-- @since 1.0.0
pattern Number :: AsciiCategory
pattern $mNumber :: forall {r}. AsciiCategory -> ((# #) -> r) -> ((# #) -> r) -> r
$bNumber :: AsciiCategory
Number <-
  AsciiCategory 3
  where
    Number = Word8 -> AsciiCategory
AsciiCategory Word8
3

-- | A symbol whose role isn't (normally) punctuation.
--
-- @since 1.0.0
pattern Symbol :: AsciiCategory
pattern $mSymbol :: forall {r}. AsciiCategory -> ((# #) -> r) -> ((# #) -> r) -> r
$bSymbol :: AsciiCategory
Symbol <-
  AsciiCategory 4
  where
    Symbol = Word8 -> AsciiCategory
AsciiCategory Word8
4

{-# COMPLETE Other, Punctuation, Letter, Number, Symbol #-}

-- | Classify an 'AsciiChar' based on its category.
--
-- >>> categorize [char| ',' |]
-- Punctuation
-- >>> categorize [char| '~' |]
-- Symbol
-- >>> categorize [char| 'w' |]
-- Letter
-- >>> categorize [char| '2' |]
-- Number
-- >>> categorize [char| '\0' |]
-- Other
--
-- @since 1.0.0
categorize :: AsciiChar -> AsciiCategory
categorize :: AsciiChar -> AsciiCategory
categorize c :: AsciiChar
c@(AsciiChar Word8
w8)
  | AsciiChar -> AsciiType
charType AsciiChar
c AsciiType -> AsciiType -> Bool
forall a. Eq a => a -> a -> Bool
== AsciiType
Control = AsciiCategory
Other
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x20 = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x21 Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x23 = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x24 = AsciiCategory
Symbol
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x25 Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x2a = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x2b = AsciiCategory
Symbol
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x2c Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x2f = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x30 Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x39 = AsciiCategory
Number
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x3a Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x3b = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x3c Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x3e = AsciiCategory
Symbol
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x3f Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x40 = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x41 Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x5a = AsciiCategory
Letter
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x5b Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x5d = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x5e = AsciiCategory
Symbol
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x5f = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x60 = AsciiCategory
Symbol
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x61 Bool -> Bool -> Bool
&& Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x7a = AsciiCategory
Letter
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x7b = AsciiCategory
Punctuation
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x7c = AsciiCategory
Symbol
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x7d = AsciiCategory
Punctuation
  | Bool
otherwise = AsciiCategory
Symbol -- This only leaves ~. - Koz

-- | Compatibility method for the 'GeneralCategory' provided by 'Data.Char'.
--
-- >>> categorizeGeneral [char| ',' |]
-- OtherPunctuation
-- >>> categorizeGeneral [char| '~' |]
-- MathSymbol
-- >>> categorizeGeneral [char| 'w' |]
-- LowercaseLetter
-- >>> categorizeGeneral [char| '2' |]
-- DecimalNumber
-- >>> categorizeGeneral [char| '\0' |]
-- Control
--
-- @since 1.0.0
categorizeGeneral :: AsciiChar -> GeneralCategory
categorizeGeneral :: AsciiChar -> GeneralCategory
categorizeGeneral (AsciiChar Word8
w8) = Char -> GeneralCategory
generalCategory (Char -> GeneralCategory)
-> (Word8 -> Char) -> Word8 -> GeneralCategory
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
chr (Int -> Char) -> (Word8 -> Int) -> Word8 -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word8 -> GeneralCategory) -> Word8 -> GeneralCategory
forall a b. (a -> b) -> a -> b
$ Word8
w8

-- | The case of an ASCII character (if it has one).
--
-- @since 1.0.0
newtype AsciiCase = AsciiCase Word8
  deriving (AsciiCase -> AsciiCase -> Bool
(AsciiCase -> AsciiCase -> Bool)
-> (AsciiCase -> AsciiCase -> Bool) -> Eq AsciiCase
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AsciiCase -> AsciiCase -> Bool
== :: AsciiCase -> AsciiCase -> Bool
$c/= :: AsciiCase -> AsciiCase -> Bool
/= :: AsciiCase -> AsciiCase -> Bool
Eq, Eq AsciiCase
Eq AsciiCase =>
(AsciiCase -> AsciiCase -> Ordering)
-> (AsciiCase -> AsciiCase -> Bool)
-> (AsciiCase -> AsciiCase -> Bool)
-> (AsciiCase -> AsciiCase -> Bool)
-> (AsciiCase -> AsciiCase -> Bool)
-> (AsciiCase -> AsciiCase -> AsciiCase)
-> (AsciiCase -> AsciiCase -> AsciiCase)
-> Ord AsciiCase
AsciiCase -> AsciiCase -> Bool
AsciiCase -> AsciiCase -> Ordering
AsciiCase -> AsciiCase -> AsciiCase
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: AsciiCase -> AsciiCase -> Ordering
compare :: AsciiCase -> AsciiCase -> Ordering
$c< :: AsciiCase -> AsciiCase -> Bool
< :: AsciiCase -> AsciiCase -> Bool
$c<= :: AsciiCase -> AsciiCase -> Bool
<= :: AsciiCase -> AsciiCase -> Bool
$c> :: AsciiCase -> AsciiCase -> Bool
> :: AsciiCase -> AsciiCase -> Bool
$c>= :: AsciiCase -> AsciiCase -> Bool
>= :: AsciiCase -> AsciiCase -> Bool
$cmax :: AsciiCase -> AsciiCase -> AsciiCase
max :: AsciiCase -> AsciiCase -> AsciiCase
$cmin :: AsciiCase -> AsciiCase -> AsciiCase
min :: AsciiCase -> AsciiCase -> AsciiCase
Ord, Eq AsciiCase
Eq AsciiCase =>
(Int -> AsciiCase -> Int)
-> (AsciiCase -> Int) -> Hashable AsciiCase
Int -> AsciiCase -> Int
AsciiCase -> Int
forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
$chashWithSalt :: Int -> AsciiCase -> Int
hashWithSalt :: Int -> AsciiCase -> Int
$chash :: AsciiCase -> Int
hash :: AsciiCase -> Int
Hashable, AsciiCase -> ()
(AsciiCase -> ()) -> NFData AsciiCase
forall a. (a -> ()) -> NFData a
$crnf :: AsciiCase -> ()
rnf :: AsciiCase -> ()
NFData) via Word8

-- | @since 1.0.0
instance Show AsciiCase where
  {-# INLINEABLE show #-}
  show :: AsciiCase -> String
show = \case
    AsciiCase
Upper -> String
"Upper"
    AsciiCase
Lower -> String
"Lower"

-- | @since 1.0.0
instance Bounded AsciiCase where
  minBound :: AsciiCase
minBound = AsciiCase
Upper
  maxBound :: AsciiCase
maxBound = AsciiCase
Lower

-- | Indicator of an uppercase character.
--
-- @since 1.0.0
pattern Upper :: AsciiCase
pattern $mUpper :: forall {r}. AsciiCase -> ((# #) -> r) -> ((# #) -> r) -> r
$bUpper :: AsciiCase
Upper <-
  AsciiCase 0
  where
    Upper = Word8 -> AsciiCase
AsciiCase Word8
0

-- | Indicator of a lowercase character.
--
-- @since 1.0.0
pattern Lower :: AsciiCase
pattern $mLower :: forall {r}. AsciiCase -> ((# #) -> r) -> ((# #) -> r) -> r
$bLower :: AsciiCase
Lower <-
  AsciiCase 1
  where
    Lower = Word8 -> AsciiCase
AsciiCase Word8
1

{-# COMPLETE Upper, Lower #-}

-- | Determine the case of an 'AsciiChar'. Returns 'Nothing' if the character
-- doesn't have a case.
--
-- >>> caseOf [char| 'w' |]
-- Just Lower
-- >>> caseOf [char| 'W' |]
-- Just Upper
-- >>> caseOf [char| '~' |]
-- Nothing
--
-- @since 1.0.0
caseOf :: AsciiChar -> Maybe AsciiCase
caseOf :: AsciiChar -> Maybe AsciiCase
caseOf c :: AsciiChar
c@(AsciiChar Word8
w8)
  | AsciiChar -> AsciiCategory
categorize AsciiChar
c AsciiCategory -> AsciiCategory -> Bool
forall a. Eq a => a -> a -> Bool
/= AsciiCategory
Letter = Maybe AsciiCase
forall a. Maybe a
Nothing
  | Word8
w8 Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x5a = AsciiCase -> Maybe AsciiCase
forall a. a -> Maybe a
Just AsciiCase
Upper
  | Bool
otherwise = AsciiCase -> Maybe AsciiCase
forall a. a -> Maybe a
Just AsciiCase
Lower

-- Optics

-- | A representation of the relationship between 'Char' and 'AsciiChar'.
--
-- >>> preview charWise 'w'
-- Just '0x77'
-- >>> preview charWise '😺'
-- Nothing
-- >>> review charWise [char| 'w' |]
-- 'w'
--
-- @since 1.0.0
charWise :: Prism' Char AsciiChar
charWise :: Prism' Char AsciiChar
charWise = (AsciiChar -> Char)
-> (Char -> Maybe AsciiChar) -> Prism' Char AsciiChar
forall b s a. (b -> s) -> (s -> Maybe a) -> Prism s s a b
prism' (Int -> Char
chr (Int -> Char) -> (AsciiChar -> Int) -> AsciiChar -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word8 -> Int) -> (AsciiChar -> Word8) -> AsciiChar -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AsciiChar -> Word8
toByte) Char -> Maybe AsciiChar
fromChar

-- | A representation of the relationship between ASCII characters and bytes.
--
-- >>> preview byteWise 0x20
-- Just '0x20'
-- >>> preview byteWise 0x81
-- Nothing
-- >>> review byteWise [char| 'w' |]
-- 119
--
-- @since 1.0.0
byteWise :: Prism' Word8 AsciiChar
byteWise :: Prism' Word8 AsciiChar
byteWise = (AsciiChar -> Word8)
-> (Word8 -> Maybe AsciiChar) -> Prism' Word8 AsciiChar
forall b s a. (b -> s) -> (s -> Maybe a) -> Prism s s a b
prism' AsciiChar -> Word8
toByte Word8 -> Maybe AsciiChar
fromByte