packages feed

ascii-superset-1.2.2.0: library/ASCII/Refinement.hs

module ASCII.Refinement
  (
    {- * ASCII type constructor -} ASCII, lift, asciiUnsafe,
    {- * Character functions -} validateChar, fromChar, toChar, substituteChar, asChar,
    {- * String functions -} validateString, fromCharList, toCharList, substituteString, mapChars
  )
  where

import qualified ASCII.Char as ASCII
import qualified ASCII.Isomorphism as I
import qualified ASCII.Superset as S

import ASCII.CaseRefinement (KnownCase (..), ASCII'case, ASCII'upper, ASCII'lower)
import Data.Data (Data)
import Data.Eq (Eq)
import Data.Function (id, ($), (.))
import Data.Hashable (Hashable)
import Data.List (map)
import Data.Maybe (Maybe (..))
import Data.Monoid (Monoid)
import Data.Ord (Ord, (>))
import Data.Semigroup (Semigroup)
import GHC.Generics (Generic)
import Prelude (succ)

import qualified Data.Bool as Bool
import qualified Text.Show as Show

{-| This type constructor indicates that a value from some ASCII superset
    is valid ASCII

The type parameter is the ASCII superset, which should be a type with an
instance of either 'CharSuperset' or 'StringSuperset'.

For example, whereas a 'Data.Text.Text' value may contain a combination of ASCII
and non-ASCII characters, a value of type @'ASCII' 'Data.Text.Text'@ may contain
only ASCII characters. -}
newtype ASCII superset = ASCII_Unsafe { lift :: superset }

deriving stock instance Eq superset => Eq (ASCII superset)

deriving stock instance Ord superset => Ord (ASCII superset)

deriving newtype instance Hashable superset => Hashable (ASCII superset)

deriving newtype instance Semigroup superset => Semigroup (ASCII superset)

deriving newtype instance Monoid superset => Monoid (ASCII superset)

deriving stock instance Data superset => Data (ASCII superset)

deriving stock instance Generic (ASCII superset)

instance Show.Show superset => Show.Show (ASCII superset) where
    showsPrec d x = Show.showParen (d > app_prec) $
        Show.showString "asciiUnsafe " . Show.showsPrec (succ app_prec) (lift x)
      where app_prec = 10

    showList x = Show.showString "asciiUnsafe " . Show.showList (map lift x)

instance S.ToCaselessChar char => S.ToCaselessChar (ASCII char) where
    isAsciiCaselessChar _ = Bool.True
    toCaselessCharUnsafe = S.toCaselessCharUnsafe . lift

instance S.CharSuperset char => S.ToChar (ASCII char) where
    isAsciiChar _ = Bool.True
    toCharUnsafe = S.toCharUnsafe . lift

instance S.CharSuperset char => S.FromChar (ASCII char) where
    fromChar = asciiUnsafe . S.fromChar

instance S.CharSuperset char => S.CharSuperset (ASCII char) where
    toCaseChar c = asciiUnsafe . S.toCaseChar c . lift

instance S.CharSuperset char => I.CharIso (ASCII char) where
    toChar = S.toCharUnsafe

instance S.ToCaselessString string => S.ToCaselessString (ASCII string) where
    isAsciiCaselessString _ = Bool.True
    toCaselessCharListUnsafe = S.toCaselessCharListUnsafe . lift
    toCaselessCharListSub = S.toCaselessCharListSub . lift

instance S.ToString string => S.ToString (ASCII string) where
    isAsciiString _ = Bool.True
    toCharListUnsafe = S.toCharListUnsafe . lift
    toCharListSub = S.toCharListUnsafe . lift

instance S.FromString string => S.FromString (ASCII string) where
    fromCharList = asciiUnsafe . S.fromCharList

instance S.StringSuperset string => S.StringSuperset (ASCII string) where
    substituteString = id
    toCaseString c = asciiUnsafe . S.toCaseString c . lift

instance S.StringSuperset string => I.StringIso (ASCII string) where
    toCharList = S.toCharListUnsafe
    mapChars = S.mapCharsUnsafe

{-| Change the type of an ASCII superset value that is known to be valid ASCII

This is "unsafe" because this assertion is unchecked, so this function is capable
of producing an invalid 'ASCII' value. -}
asciiUnsafe :: superset -> ASCII superset
asciiUnsafe = ASCII_Unsafe

{-|

@
(map validateChar [-1, 65, 97, 128] :: [Maybe (ASCII Int)])
    == [Nothing, Just (asciiUnsafe 65), Just (asciiUnsafe 97), Nothing]
@ -}
validateChar :: S.CharSuperset superset => superset -> Maybe (ASCII superset)
validateChar x = if S.isAsciiChar x then Just (asciiUnsafe x) else Nothing

substituteChar :: S.CharSuperset superset => superset -> ASCII superset
substituteChar x = if S.isAsciiChar x then asciiUnsafe x else fromChar ASCII.Substitute

fromChar :: S.CharSuperset superset => ASCII.Char -> ASCII superset
fromChar = asciiUnsafe . S.fromChar

toChar :: S.CharSuperset superset => ASCII superset -> ASCII.Char
toChar = S.toCharUnsafe . lift

{-|

@
fromCharList [CapitalLetterH, SmallLetterI, ExclamationMark]
    == (asciiUnsafe "Hi!" :: ASCII Text)
@ -}
fromCharList :: S.StringSuperset superset => [ASCII.Char] -> ASCII superset
fromCharList = asciiUnsafe . S.fromCharList

{-|

@
toCharList (substituteString \"Piñata" :: ASCII Text) ==
    [CapitalLetterP, SmallLetterI, Substitute, SmallLetterA, SmallLetterT, SmallLetterA]
@ -}
toCharList :: S.StringSuperset superset => ASCII superset -> [ASCII.Char]
toCharList = S.toCharListUnsafe . lift

{-| Forces a string from a larger character set into ASCII by using the
    'ASCII.Substitute' character in place of any non-ASCII characters

@
(substituteString \"Cristóbal" :: ASCII Text) == asciiUnsafe "Crist\SUBbal"
@ -}
substituteString :: S.StringSuperset superset => superset -> ASCII superset
substituteString = asciiUnsafe . S.substituteString

{-|

@
(map validateString [\"Hello", \"Cristóbal"] :: [Maybe (ASCII Text)])
    == [Just (asciiUnsafe \"Hello"), Nothing]

(map validateString [\"Hello", \"Cristóbal"] :: [Maybe (ASCII String)])
    == [Just (asciiUnsafe \"Hello"), Nothing]
@ -}
validateString :: S.StringSuperset superset => superset -> Maybe (ASCII superset)
validateString x = if S.isAsciiString x then Just (asciiUnsafe x) else Nothing

asChar :: S.CharSuperset superset => (ASCII.Char -> ASCII.Char) -> ASCII superset -> ASCII superset
asChar f = asciiUnsafe . S.asCharUnsafe f . lift

mapChars :: S.StringSuperset superset => (ASCII.Char -> ASCII.Char) -> ASCII superset -> ASCII superset
mapChars f = asciiUnsafe . S.mapCharsUnsafe f . lift