-- | Conversions from 'Char'.
module Unwitch.Convert.Char
  ( -- * Conversions
    toInt
  , toWord
  , fromInt
  , fromWord
#ifdef __GLASGOW_HASKELL__
  -- * Unboxed conversions
  -- $unboxed
  , fromInt#
  , fromWord#
#endif
  )
where

import Data.Char (ord, chr)
#ifdef __GLASGOW_HASKELL__
import GHC.Exts (Int(..), Word(..), Char(..), chr#,
                 word2Int#, (>=#), (<=#), leWord#, gtWord#)
#endif

#ifdef __GLASGOW_HASKELL__
-- $unboxed
-- These use GHC unboxed types and unboxed sums for zero-allocation
-- failure handling. Requires the @MagicHash@, @UnboxedSums@ and
-- @UnboxedTuples@ language extensions.
-- See the <https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/primitives.html GHC manual on unboxed types>.
#endif

-- | Converts a Char to its Unicode codepoint as Int. Infallible.
toInt :: Char -> Int
toInt :: Char -> Int
toInt = Char -> Int
ord

-- | Converts a Char to its Unicode codepoint as Word. Infallible.
toWord :: Char -> Word
toWord :: Char -> Word
toWord = Int -> Word
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Word) -> (Char -> Int) -> Char -> Word
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Int
ord

-- | Converts an Int to a Char if it is a valid Unicode codepoint.
-- Valid range: 0..0xD7FF and 0xE000..0x10FFFF (excludes surrogates).
fromInt :: Int -> Maybe Char
fromInt :: Int -> Maybe Char
fromInt Int
i = if Integer -> Bool
isValidCodepoint (Int -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
i)
  then Char -> Maybe Char
forall a. a -> Maybe a
Just (Char -> Maybe Char) -> Char -> Maybe Char
forall a b. (a -> b) -> a -> b
$ Int -> Char
chr Int
i
  else Maybe Char
forall a. Maybe a
Nothing

-- | Converts a Word to a Char if it is a valid Unicode codepoint.
-- Valid range: 0..0xD7FF and 0xE000..0x10FFFF (excludes surrogates).
fromWord :: Word -> Maybe Char
fromWord :: Word -> Maybe Char
fromWord Word
w = if Integer -> Bool
isValidCodepoint (Word -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word
w)
  then Char -> Maybe Char
forall a. a -> Maybe a
Just (Char -> Maybe Char) -> Char -> Maybe Char
forall a b. (a -> b) -> a -> b
$ Int -> Char
chr (Word -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word
w)
  else Maybe Char
forall a. Maybe a
Nothing

#ifdef __GLASGOW_HASKELL__
-- | Unboxed variant of 'fromInt'. Checks valid Unicode codepoint range.
fromInt# :: Int -> (# Char | (# #) #)
fromInt# :: Int -> (# Char | (# #) #)
fromInt# (I# Int#
i#) = case Int#
i# Int# -> Int# -> Int#
>=# Int#
0# of
  Int#
1# -> case Int#
i# Int# -> Int# -> Int#
<=# Int#
0xD7FF# of
    Int#
1# -> (# Char# -> Char
C# (Int# -> Char#
chr# Int#
i#) | #)
    Int#
_  -> case Int#
i# Int# -> Int# -> Int#
>=# Int#
0xE000# of
      Int#
1# -> case Int#
i# Int# -> Int# -> Int#
<=# Int#
0x10FFFF# of
        Int#
1# -> (# Char# -> Char
C# (Int# -> Char#
chr# Int#
i#) | #)
        Int#
_  -> (# | (# #) #)
      Int#
_  -> (# | (# #) #)
  Int#
_  -> (# | (# #) #)

-- | Unboxed variant of 'fromWord'. Checks valid Unicode codepoint range.
fromWord# :: Word -> (# Char | (# #) #)
fromWord# :: Word -> (# Char | (# #) #)
fromWord# (W# Word#
w#) = case Word# -> Word# -> Int#
leWord# Word#
w# Word#
0xD7FF## of
  Int#
1# -> (# Char# -> Char
C# (Int# -> Char#
chr# (Word# -> Int#
word2Int# Word#
w#)) | #)
  Int#
_  -> case Word# -> Word# -> Int#
gtWord# Word#
w# Word#
0xDFFF## of
    Int#
1# -> case Word# -> Word# -> Int#
leWord# Word#
w# Word#
0x10FFFF## of
      Int#
1# -> (# Char# -> Char
C# (Int# -> Char#
chr# (Word# -> Int#
word2Int# Word#
w#)) | #)
      Int#
_  -> (# | (# #) #)
    Int#
_  -> (# | (# #) #)
#endif

isValidCodepoint :: Integer -> Bool
isValidCodepoint :: Integer -> Bool
isValidCodepoint Integer
cp =
  (Integer
cp Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0 Bool -> Bool -> Bool
&& Integer
cp Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
0xD7FF) Bool -> Bool -> Bool
|| (Integer
cp Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0xE000 Bool -> Bool -> Bool
&& Integer
cp Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
0x10FFFF)