packages feed

ascii-superset 1.0.1.15 → 1.0.2.0

raw patch · 8 files changed

+436/−154 lines, 8 filesdep +ascii-casedep +ascii-caselessdep +hspecdep −hedgehogPVP ok

version bump matches the API change (PVP)

Dependencies added: ascii-case, ascii-caseless, hspec

Dependencies removed: hedgehog

API changes (from Hackage documentation)

+ ASCII.CaseRefinement: asCaselessChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset => (CaselessChar -> CaselessChar) -> ASCII'case letterCase superset -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: asciiCaseUnsafe :: superset -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: data ASCII'case (letterCase :: Case) superset
+ ASCII.CaseRefinement: fromCaselessChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset => CaselessChar -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: fromCaselessCharList :: forall letterCase superset. KnownCase letterCase => StringSuperset superset => [CaselessChar] -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: instance (Data.Data.Data superset, Data.Typeable.Internal.Typeable letterCase) => Data.Data.Data (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance ASCII.CaseRefinement.KnownCase 'ASCII.Case.LowerCase
+ ASCII.CaseRefinement: instance ASCII.CaseRefinement.KnownCase 'ASCII.Case.UpperCase
+ ASCII.CaseRefinement: instance Data.Hashable.Class.Hashable superset => Data.Hashable.Class.Hashable (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance GHC.Base.Monoid superset => GHC.Base.Monoid (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance GHC.Base.Semigroup superset => GHC.Base.Semigroup (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance GHC.Classes.Eq superset => GHC.Classes.Eq (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance GHC.Classes.Ord superset => GHC.Classes.Ord (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance GHC.Generics.Generic (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: instance GHC.Show.Show superset => GHC.Show.Show (ASCII.CaseRefinement.ASCII'case letterCase superset)
+ ASCII.CaseRefinement: lift :: ASCII'case letterCase superset -> superset
+ ASCII.CaseRefinement: mapChars :: forall letterCase superset. KnownCase letterCase => StringSuperset superset => (CaselessChar -> CaselessChar) -> ASCII'case letterCase superset -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: substituteChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset => superset -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: substituteString :: forall letterCase superset. KnownCase letterCase => StringSuperset superset => superset -> ASCII'case letterCase superset
+ ASCII.CaseRefinement: toCaselessChar :: CharSuperset superset => ASCII'case letterCase superset -> CaselessChar
+ ASCII.CaseRefinement: toCaselessCharList :: forall letterCase superset. KnownCase letterCase => StringSuperset superset => ASCII'case letterCase superset -> [CaselessChar]
+ ASCII.CaseRefinement: type ASCII'lower superset = ASCII'case 'LowerCase superset
+ ASCII.CaseRefinement: type ASCII'upper superset = ASCII'case 'UpperCase superset
+ ASCII.CaseRefinement: validateChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset => superset -> Maybe (ASCII'case letterCase superset)
+ ASCII.CaseRefinement: validateString :: forall letterCase superset. KnownCase letterCase => StringSuperset superset => superset -> Maybe (ASCII'case letterCase superset)
+ ASCII.Lift: instance ASCII.Lift.Lift (ASCII.CaseRefinement.ASCII'case letterCase superset) superset

Files

ascii-superset.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0  name: ascii-superset-version: 1.0.1.15+version: 1.0.2.0 synopsis: Representing ASCII with refined supersets category: Data, Text @@ -34,10 +34,21 @@     ghc-options: -Wall      default-extensions:+        AllowAmbiguousTypes+        DataKinds+        FlexibleContexts+        FlexibleInstances+        KindSignatures+        MultiParamTypeClasses         NoImplicitPrelude+        RankNTypes+        ScopedTypeVariables+        TypeApplications      build-depends:-        ascii-char ^>= 1.0+        ascii-case ^>= 1.0.1+      , ascii-caseless ^>= 0.0.0+      , ascii-char ^>= 1.0       , base ^>= 4.14 || ^>= 4.15 || ^>= 4.16 || ^>= 4.17       , text ^>= 1.2.4 || ^>= 2.0 @@ -58,10 +69,11 @@       , hashable ^>= 1.3.5 || ^>= 1.4      exposed-modules:+        ASCII.CaseRefinement         ASCII.Superset-      , ASCII.Isomorphism-      , ASCII.Refinement-      , ASCII.Lift+        ASCII.Isomorphism+        ASCII.Refinement+        ASCII.Lift  test-suite test-ascii-superset     import: base@@ -76,4 +88,4 @@      build-depends:         ascii-superset-      , hedgehog ^>= 1.0.5 || ^>= 1.1 || ^>= 1.2+      , hspec ^>= 2.8.5 || ^>= 2.9 || ^>= 2.10
changelog.md view
@@ -1,3 +1,12 @@+### 1.0.2.0 (2023-01-02)++Add module `ASCII.CaseRefinement`++* This includes `ASCII'case`, a new refinement type for characters strings.+  It asserts that any character which is a letter must be of a particular case.++Switch test suite from `hedgehog` to `hspec`+ ### 1.0.1.15 (2022-01-02)  Minor Cabal correction (change `extra-doc-files` to `extra-source-files`)
+ library/ASCII/CaseRefinement.hs view
@@ -0,0 +1,207 @@+module ASCII.CaseRefinement+  (+    {- * ASCII'case type constructor -} ASCII'case, lift, asciiCaseUnsafe,+    {- ** Aliases -} {- $aliases -} ASCII'upper, ASCII'lower,+    {- * Character functions -} validateChar, fromCaselessChar,+          toCaselessChar, substituteChar, asCaselessChar,+    {- * String functions -} validateString, fromCaselessCharList,+          toCaselessCharList, substituteString, mapChars,+  )+  where++import ASCII.Case (Case (..))+import ASCII.Caseless (CaselessChar)+import ASCII.Superset (CharSuperset, StringSuperset)++import qualified ASCII.Case as Case+import qualified ASCII.Caseless as Caseless+import qualified ASCII.Char as ASCII+import qualified ASCII.Superset as Superset++import Control.Monad (guard)+import Data.Bool (Bool (..))+import Data.Data (Data, Typeable)+import Data.Eq (Eq)+import Data.Foldable (any)+import Data.Function (id, ($), (.))+import Data.Hashable (Hashable)+import Data.Maybe (Maybe (..))+import Data.Monoid (Monoid)+import Data.Ord (Ord, (>))+import Data.Semigroup (Semigroup)+import Data.Traversable (traverse)+import GHC.Generics (Generic)+import Prelude (succ)+import Text.Show (Show, showList, showParen, showString, showsPrec)++import qualified Data.Bool as Bool+import qualified Data.List as List++{- | This type constructor indicates that a value from some ASCII superset is+valid ASCII, and also that any letters belong to a particular 'Case' indicated+by the @letterCase@ type parameter.++The @superset@ 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'case' ''ASCII.Case.UpperCase'+'Data.Text.Text'@ may contain only uppercase ASCII letters and ASCII+non-letters. -}+newtype ASCII'case (letterCase :: Case) superset = ASCII'case_Unsafe+  { lift :: superset+      {- ^ Discard the evidence that the value is known to consist+           entirely of ASCII characters in a particular case -}+  }++deriving stock instance Eq superset =>+    Eq (ASCII'case letterCase superset)++deriving stock instance Ord superset =>+    Ord (ASCII'case letterCase superset)++deriving newtype instance Hashable superset =>+    Hashable (ASCII'case letterCase superset)++deriving newtype instance Semigroup superset =>+    Semigroup (ASCII'case letterCase superset)++deriving newtype instance Monoid superset =>+    Monoid (ASCII'case letterCase superset)++deriving stock instance (Data superset, Typeable letterCase) =>+    Data (ASCII'case letterCase superset)++deriving stock instance Generic (ASCII'case letterCase superset)++instance Show superset => Show (ASCII'case letterCase superset) where+    showsPrec d x = showParen (d > app_prec) $+        showString "asciiCaseUnsafe " . showsPrec (succ app_prec) (lift x)+      where app_prec = 10++    showList x = showString "asciiCaseUnsafe " . showList (List.map lift x)++{-| Change the type of an ASCII superset value that is known to be valid ASCII where+letters are restricted to the 'Case' designated by the @letterCase@ type variable.++This is "unsafe" because this assertion is unchecked, so this function is capable+of producing an invalid 'ASCII'case' value. -}+asciiCaseUnsafe :: superset -> ASCII'case letterCase superset+asciiCaseUnsafe = ASCII'case_Unsafe++---++{- $aliases++The 'ASCII'upper' and 'ASCII'lower' type aliases exist primarily so that you can+use 'ASCII'case' without the DataKinds language extension.++-}++type ASCII'upper superset = ASCII'case 'UpperCase superset++type ASCII'lower superset = ASCII'case 'LowerCase superset++---++class KnownCase (letterCase :: Case) where theCase :: Case+instance KnownCase 'UpperCase where theCase = UpperCase+instance KnownCase 'LowerCase where theCase = LowerCase++---++{-| Return 'Just' an 'ASCII'case' character if the input is an ASCII character+in the proper case, or 'Nothing' otherwise -}+validateChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset =>+    superset {- ^ Character which may or may not be in the ASCII character set;+                  if a letter, may be in any case -}+    -> Maybe (ASCII'case letterCase superset)+validateChar x = do+    c <- Superset.toCharMaybe x+    guard (Bool.not (Case.isCase (Case.opposite (theCase @letterCase)) c))+    Just (asciiCaseUnsafe x)++{- | Return an 'ASCII'case' character if the input is an ASCII character in the+proper case, or 'ASCII.Substitute' otherwise -}+substituteChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset =>+    superset+    -> ASCII'case letterCase superset+substituteChar x = case validateChar x of+    Nothing -> asciiCaseUnsafe (Superset.fromChar ASCII.Substitute)+    Just c -> c++{-| Lift a 'CaselessChar' into a superset type, wrapped in the 'ASCII'case'+refinement to save the evidence that it is ASCII in a particular case -}+fromCaselessChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset =>+    CaselessChar -- ^ Character which, if it is a letter, does not have a specified case+    -> ASCII'case letterCase superset+fromCaselessChar = asciiCaseUnsafe . Superset.fromChar . Caseless.toCase (theCase @letterCase)++{-| Given a character from some type that is known to represent an ASCII+character in a particular case, obtain the caseless ASCII character it+represents -}+toCaselessChar :: CharSuperset superset =>+    ASCII'case letterCase superset {- ^ Character that is known to be ASCII, and+                                        in the particular case if it is a letter -}+    -> CaselessChar+toCaselessChar =  Caseless.disregardCase . Superset.toCharUnsafe . lift++{-| Given a character from a larger set that is known to represent an ASCII+character, manipulate it as if it were an ASCII character -}+asCaselessChar :: forall letterCase superset. KnownCase letterCase => CharSuperset superset =>+    (CaselessChar -> CaselessChar) -- ^ Case-insensitive function over ASCII characters+    -> ASCII'case letterCase superset {- ^ Character that is known to be ASCII, and+                                           in the particular case if it is a letter -}+    -> ASCII'case letterCase superset+asCaselessChar f = asciiCaseUnsafe . Superset.asCharUnsafe g . lift+  where+    g = Caseless.toCase (theCase @letterCase) . f . Caseless.assumeCaseUnsafe (theCase @letterCase)++---++{-| Return 'Just' an 'ASCII'case' string if the input consists entirely of ASCII+characters in the proper case, or 'Nothing' otherwise -}+validateString :: forall letterCase superset. KnownCase letterCase => StringSuperset superset =>+    superset -- ^ String which may or may not be valid ASCII, where letters may be in any case+    -> Maybe (ASCII'case letterCase superset)+validateString x = do+    s <- Superset.toCharListMaybe x+    guard (Bool.not (any (Case.isCase (Case.opposite (theCase @letterCase))) s))+    Just (asciiCaseUnsafe x)++{-| Lift a list of 'CaselessChar' into a superset string type, wrapped in the+'ASCII'case' refinement to save the evidence that all of the characters in the+string are ASCII in a particular case. -}+fromCaselessCharList :: forall letterCase superset. KnownCase letterCase => StringSuperset superset =>+    [CaselessChar] -- ^ Case-insensitive ASCII string represented as a list of caseless characters+    -> ASCII'case letterCase superset+fromCaselessCharList = asciiCaseUnsafe . Superset.fromCharList . List.map (Caseless.toCase (theCase @letterCase))++{-| Given a string from some type that is known to represent only ASCII+characters in a particular case, obtain the caseless characters it represents -}+toCaselessCharList :: forall letterCase superset. KnownCase letterCase => StringSuperset superset =>+    ASCII'case letterCase superset -- ^ String that is known to be valid ASCII in a particular case+    -> [CaselessChar]+toCaselessCharList = List.map (Caseless.assumeCaseUnsafe (theCase @letterCase)) . Superset.toCharListUnsafe . lift++{-| Forces a string from a larger character set into cased ASCII by using the+'ASCII.Substitute' character in place of any unacceptable characters. -}+substituteString :: forall letterCase superset. KnownCase letterCase => StringSuperset superset =>+    superset -- ^ String which may or may not be valid ASCII, where letters may be in any case+    -> ASCII'case letterCase superset+substituteString = asciiCaseUnsafe . Superset.fromCharList . List.map f . Superset.toCharListSub+  where+    f x = if Case.isCase (Case.opposite (theCase @letterCase)) x+          then ASCII.Substitute+          else x++{-| Given a string from a larger set that is known to consist entirely of ASCII+characters in a particular case, map over the characters in the string as if+they were caseless ASCII characters -}+mapChars :: forall letterCase superset. KnownCase letterCase => StringSuperset superset =>+    (CaselessChar -> CaselessChar) -- ^ Case-insensitive function over ASCII characters+    -> ASCII'case letterCase superset -- ^ String that is known to be valid ASCII in a particular case+    -> ASCII'case letterCase superset+mapChars f = asciiCaseUnsafe . Superset.mapCharsUnsafe g . lift+  where+    g = Caseless.toCase (theCase @letterCase) . f . Caseless.assumeCaseUnsafe (theCase @letterCase)
library/ASCII/Isomorphism.hs view
@@ -1,28 +1,24 @@-module ASCII.Isomorphism ( CharIso (..), asChar, StringIso (..) ) where+module ASCII.Isomorphism (CharIso (..), asChar, StringIso (..)) where  import ASCII.Char (Char) import ASCII.Superset (CharSuperset (..), StringSuperset (..)) import Data.Function (id, (.)) import Data.List (map) -class CharSuperset char => CharIso char-  where+class CharSuperset char => CharIso char where     toChar :: char -> Char  asChar :: CharIso char => (Char -> Char) -> char -> char asChar f = fromChar . f . toChar -class StringSuperset string => StringIso string-  where+class StringSuperset string => StringIso string where     toCharList :: string -> [Char]     mapChars :: (Char -> Char) -> string -> string  -- | 'Char' is trivially isomorphic to itself. (This instance is uninteresting.)-instance CharIso Char-  where+instance CharIso Char where     toChar = id -instance CharIso char => StringIso [char]-  where+instance CharIso char => StringIso [char] where     toCharList = map toChar     mapChars f = map (asChar f)
library/ASCII/Lift.hs view
@@ -1,45 +1,51 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}--module ASCII.Lift ( Lift (..) ) where+module ASCII.Lift (Lift (..)) where +import ASCII.Case (Case (..))+import ASCII.CaseRefinement (ASCII'case) import ASCII.Char (Char) import ASCII.Refinement (ASCII) import ASCII.Superset (CharSuperset, StringSuperset) -import qualified ASCII.Refinement as R-import qualified ASCII.Superset as S+import qualified ASCII.Refinement as Refinement+import qualified ASCII.Superset as Superset+import qualified ASCII.CaseRefinement as CaseRefinement  import qualified Prelude+import Data.Function ((.)) --- | Embedding of one character set within another------ The @subset@ and @superset@ types may be characters or strings in ASCII, some subset of ASCII, or some superset of ASCII.----class Lift subset superset-  where+{-| Embedding of one character set within another -    {- | Converts from a smaller to a larger type.+The @subset@ and @superset@ types may be characters or strings in+ASCII, some subset of ASCII, or some superset of ASCII. -}+class Lift subset superset where +    {-| Converts from a smaller to a larger type.+     >>> lift CapitalLetterA :: Word8     65      >>> lift [CapitalLetterH,SmallLetterI,ExclamationMark] :: Text     "Hi!" -    Due to the highly polymorphic nature of the 'lift' function, often it must used with an explicit type signature or type application to avoid any type ambiguity.--    -}+    Due to the highly polymorphic nature of the 'lift' function,+    often it must used with an explicit type signature or type+    application to avoid any type ambiguity. -}      lift :: subset -> superset --- | A value from an ASCII superset that has been refined by the 'ASCII' type constructor may be lifted back into the superset by unwrapping it from the 'ASCII' type.--instance Lift (ASCII superset) superset where lift = R.lift---- | An ASCII 'Char' may be 'lift'ed into any larger character set (a 'CharSuperset'); for example, 'lift' can convert an ASCII character into a value of the standard 'Prelude.Char' type in "Prelude".+{-| A value from an ASCII superset that has been refined by the 'ASCII' type+constructor may be lifted back into the superset by unwrapping it from the+'ASCII' type. -}+instance Lift (ASCII superset) superset where lift = Refinement.lift -instance CharSuperset superset => Lift Char superset where lift = S.fromChar+instance Lift (ASCII'case letterCase superset) superset where lift = CaseRefinement.lift --- | An ASCII 'Char' list may be 'lift'ed into a string of any larger character set (a 'StringSuperset'); for example, 'lift' can convert a list of ASCII characters into a value of the standard 'Prelude.String' type in "Prelude".+{-| An ASCII 'Char' may be 'lift'ed into any larger character set (a+'CharSuperset'); for example, 'lift' can convert an ASCII character into a value+of the standard 'Prelude.Char' type in "Prelude". -}+instance CharSuperset superset => Lift Char superset where lift = Superset.fromChar -instance StringSuperset superset => Lift [Char] superset where lift = S.fromCharList+{-| An ASCII 'Char' list may be 'lift'ed into a string of any larger character+set (a 'StringSuperset'); for example, 'lift' can convert a list of ASCII+characters into a value of the standard 'Prelude.String' type in "Prelude". -}+instance StringSuperset superset => Lift [Char] superset where lift = Superset.fromCharList
library/ASCII/Refinement.hs view
@@ -25,12 +25,13 @@ import Prelude (succ) import Text.Show (Show, showList, showParen, showString, showsPrec) -{- | 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.---}+{-| 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)@@ -47,47 +48,43 @@  deriving stock instance Generic (ASCII superset) -instance Show superset => Show (ASCII superset)-  where+instance Show superset => Show (ASCII superset) where     showsPrec d x = showParen (d > app_prec) $         showString "asciiUnsafe " . showsPrec (succ app_prec) (lift x)       where app_prec = 10      showList x = showString "asciiUnsafe " . showList (map lift x) -instance CharSuperset char => CharSuperset (ASCII char)-  where+instance CharSuperset char => CharSuperset (ASCII char) where     isAsciiChar _ = True     fromChar = asciiUnsafe . S.fromChar     toCharUnsafe = S.toCharUnsafe . lift -instance CharSuperset char => I.CharIso (ASCII char)-  where+instance CharSuperset char => I.CharIso (ASCII char) where     toChar = S.toCharUnsafe -instance StringSuperset string => StringSuperset (ASCII string)-  where+instance StringSuperset string => StringSuperset (ASCII string) where     isAsciiString _ = True     fromCharList = asciiUnsafe . S.fromCharList     toCharListUnsafe = S.toCharListUnsafe . lift     toCharListSub = S.toCharListUnsafe . lift     substituteString = id -instance StringSuperset string => I.StringIso (ASCII string)-  where+instance 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 :: CharSuperset superset => superset -> Maybe (ASCII superset) validateChar x = if S.isAsciiChar x then Just (asciiUnsafe x) else Nothing @@ -100,46 +97,36 @@ toChar :: CharSuperset superset => ASCII superset -> ASCII.Char toChar = S.toCharUnsafe . lift -{- |-+{-| >>> fromCharList [CapitalLetterH,SmallLetterI,ExclamationMark] :: ASCII Text asciiUnsafe "Hi!"- -}- fromCharList :: StringSuperset superset => [ASCII.Char] -> ASCII superset fromCharList = asciiUnsafe . S.fromCharList -{- |-+{-| >>> toCharList (substituteString "Piñata" :: ASCII Text) [CapitalLetterP,SmallLetterI,Substitute,SmallLetterA,SmallLetterT,SmallLetterA]- -}- toCharList :: 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.+{-| 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 :: 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 :: StringSuperset superset => superset -> Maybe (ASCII superset) validateString x = if S.isAsciiString x then Just (asciiUnsafe x) else Nothing 
library/ASCII/Superset.hs view
@@ -1,14 +1,16 @@-module ASCII.Superset (-+module ASCII.Superset+  (     {- * Characters -}     {- ** Class -} CharSuperset (..),-    {- ** Functions -} asCharUnsafe, toCharMaybe, toCharOrFail, toCharSub, substituteChar, convertCharMaybe, convertCharOrFail,+    {- ** Functions -} asCharUnsafe, toCharMaybe, toCharOrFail,+        toCharSub, substituteChar, convertCharMaybe, convertCharOrFail,      {- * Strings -}     {- ** Class -} StringSuperset (..),-    {- ** Functions -} toCharListMaybe, toCharListOrFail, convertStringMaybe, convertStringOrFail--    ) where+    {- ** Functions -} toCharListMaybe, toCharListOrFail,+        convertStringMaybe, convertStringOrFail+  )+  where  import Control.Monad (return) import Control.Monad.Fail (MonadFail (fail))@@ -36,8 +38,7 @@  ---  Char  --- -class CharSuperset char-  where+class CharSuperset char where      isAsciiChar :: char -> Bool @@ -52,7 +53,8 @@ toCharMaybe = toCharOrFail  toCharOrFail :: (CharSuperset char, MonadFail context) => char -> context ASCII.Char-toCharOrFail x = if isAsciiChar x then return (toCharUnsafe x) else fail "Not an ASCII character"+toCharOrFail x = if isAsciiChar x then return (toCharUnsafe x)+    else fail "Not an ASCII character"  toCharSub :: CharSuperset char => char -> ASCII.Char toCharSub x = if isAsciiChar x then toCharUnsafe x else ASCII.Substitute@@ -60,19 +62,24 @@ substituteChar :: CharSuperset char => char -> char substituteChar x = if isAsciiChar x then x else fromChar ASCII.Substitute --- | Convert from one ASCII-superset character type to another via the ASCII 'ASCII.Char' type. Fails as 'Nothing' if the input is outside the ASCII character set.-convertCharMaybe :: (CharSuperset char1, CharSuperset char2) => char1 -> Maybe char2+{-| Convert from one ASCII-superset character type to another via the ASCII+'ASCII.Char' type. Fails as 'Nothing' if the input is outside the ASCII+character set. -}+convertCharMaybe :: (CharSuperset char1, CharSuperset char2) =>+    char1 -> Maybe char2 convertCharMaybe = convertCharOrFail --- | Convert from one ASCII-superset character type to another via the ASCII 'ASCII.Char' type. Fails with 'fail' if the input is outside the ASCII character set.-convertCharOrFail :: (CharSuperset char1, CharSuperset char2, MonadFail context) => char1 -> context char2+{-| Convert from one ASCII-superset character type to another via the ASCII+'ASCII.Char' type. Fails with 'fail' if the input is outside the ASCII character+set. -}+convertCharOrFail :: (CharSuperset char1, CharSuperset char2, MonadFail context) =>+    char1 -> context char2 convertCharOrFail = fmap fromChar . toCharOrFail   ---  String  --- -class StringSuperset string-  where+class StringSuperset string where      isAsciiString :: string -> Bool @@ -90,62 +97,64 @@ toCharListMaybe :: StringSuperset string => string -> Maybe [ASCII.Char] toCharListMaybe = toCharListOrFail -toCharListOrFail :: (StringSuperset string, MonadFail context) => string -> context [ASCII.Char]-toCharListOrFail x = if isAsciiString x then return (toCharListUnsafe x) else fail "String contains non-ASCII characters"+toCharListOrFail :: (StringSuperset string, MonadFail context) =>+    string -> context [ASCII.Char]+toCharListOrFail x = if isAsciiString x then return (toCharListUnsafe x)+    else fail "String contains non-ASCII characters" --- | Convert from one ASCII-superset string type to another by converting each character of the input string to an ASCII 'ASCII.Char', and then converting the ASCII character list to the desired output type. Fails as 'Nothing' if the input contains any character that is outside the ASCII character set.-convertStringMaybe :: (StringSuperset string1, StringSuperset string2) => string1 -> Maybe string2+{-| Convert from one ASCII-superset string type to another by converting each+character of the input string to an ASCII 'ASCII.Char', and then converting the+ASCII character list to the desired output type. Fails as 'Nothing' if the input+contains any character that is outside the ASCII character set. -}+convertStringMaybe :: (StringSuperset string1, StringSuperset string2) =>+    string1 -> Maybe string2 convertStringMaybe = convertStringOrFail --- | Convert from one ASCII-superset string type to another by converting each character of the input string to an ASCII 'ASCII.Char', and then converting the ASCII character list to the desired output type. Fails with 'fail' if the input contains any character that is outside the ASCII character set.-convertStringOrFail :: (StringSuperset string1, StringSuperset string2, MonadFail context) => string1 -> context string2+{-| Convert from one ASCII-superset string type to another by converting each+character of the input string to an ASCII 'ASCII.Char', and then converting the+ASCII character list to the desired output type. Fails with 'fail' if the input+contains any character that is outside the ASCII character set. -}+convertStringOrFail :: (StringSuperset string1, StringSuperset string2, MonadFail context) =>+    string1 -> context string2 convertStringOrFail = fmap fromCharList . toCharListOrFail   ---  Instances  ---  -- | 'ASCII.Char' is trivially a superset of itself. (This instance is uninteresting.)--instance CharSuperset ASCII.Char-  where+instance CharSuperset ASCII.Char where     isAsciiChar _ = Bool.True     fromChar = id     toCharUnsafe = id -instance CharSuperset Unicode.Char-  where+instance CharSuperset Unicode.Char where     isAsciiChar = (<= '\DEL')     fromChar = Unicode.chr . ASCII.toInt     toCharUnsafe = ASCII.fromIntUnsafe . Unicode.ord -instance CharSuperset Nat.Natural-  where+instance CharSuperset Nat.Natural where     isAsciiChar = (<= 127)     fromChar = Prelude.fromIntegral . ASCII.toInt     toCharUnsafe = ASCII.fromIntUnsafe . Prelude.fromIntegral -instance CharSuperset Int.Int-  where+instance CharSuperset Int.Int where     isAsciiChar x = (x >= 0) && (x <= 127)     fromChar = ASCII.toInt     toCharUnsafe = ASCII.fromIntUnsafe -instance CharSuperset Word.Word8-  where+instance CharSuperset Word.Word8 where     isAsciiChar = (<= 127)     fromChar = Prelude.fromIntegral . ASCII.toInt     toCharUnsafe = ASCII.fromIntUnsafe . Prelude.fromIntegral -instance CharSuperset char => StringSuperset [char]-  where+instance CharSuperset char => StringSuperset [char] where     isAsciiString = List.all isAsciiChar     fromCharList = List.map fromChar     toCharListUnsafe = List.map toCharUnsafe     toCharListSub = List.map toCharSub     substituteString = List.map substituteChar -instance StringSuperset T.Text-  where+instance StringSuperset T.Text where     isAsciiString = T.all isAsciiChar     fromCharList = T.pack . fromCharList     toCharListUnsafe = toCharListUnsafe . T.unpack@@ -153,8 +162,7 @@     substituteString = T.map substituteChar     mapCharsUnsafe f = T.map (asCharUnsafe f) -instance StringSuperset LT.Text-  where+instance StringSuperset LT.Text where     isAsciiString = LT.all isAsciiChar     fromCharList = LT.pack . fromCharList     toCharListUnsafe = toCharListUnsafe . LT.unpack@@ -162,8 +170,7 @@     substituteString = LT.map substituteChar     mapCharsUnsafe f = LT.map (asCharUnsafe f) -instance StringSuperset TB.Builder-  where+instance StringSuperset TB.Builder where     isAsciiString = isAsciiString . TB.toLazyText     fromCharList = TB.fromString . fromCharList     toCharListUnsafe = toCharListUnsafe . TB.toLazyText@@ -171,8 +178,7 @@     substituteString = TB.fromLazyText . substituteString . TB.toLazyText     mapCharsUnsafe f = TB.fromLazyText . mapCharsUnsafe f . TB.toLazyText -instance StringSuperset BS.ByteString-  where+instance StringSuperset BS.ByteString where     isAsciiString = BS.all isAsciiChar     fromCharList = BS.pack . fromCharList     toCharListUnsafe = toCharListUnsafe . BS.unpack@@ -180,8 +186,7 @@     substituteString = BS.map substituteChar     mapCharsUnsafe f = BS.map (asCharUnsafe f) -instance StringSuperset LBS.ByteString-  where+instance StringSuperset LBS.ByteString where     isAsciiString = LBS.all isAsciiChar     fromCharList = LBS.pack . fromCharList     toCharListUnsafe = toCharListUnsafe . LBS.unpack@@ -189,8 +194,7 @@     substituteString = LBS.map substituteChar     mapCharsUnsafe f = LBS.map (asCharUnsafe f) -instance StringSuperset BSB.Builder-  where+instance StringSuperset BSB.Builder where     isAsciiString = isAsciiString . BSB.toLazyByteString     fromCharList = BSB.lazyByteString . fromCharList     toCharListUnsafe = toCharListUnsafe . BSB.toLazyByteString
test/Main.hs view
@@ -1,57 +1,118 @@ module Main (main) where +import Test.Hspec+ import ASCII.Refinement (ASCII, asciiUnsafe)+import ASCII.CaseRefinement (ASCII'lower, ASCII'upper, asciiCaseUnsafe) +import qualified ASCII.CaseRefinement as CaseRefinement import qualified ASCII.Lift as Lift import qualified ASCII.Refinement as Refinement  import ASCII.Char (Char (..))+import qualified ASCII.Caseless as CC -import Control.Monad (Monad (..), when)-import Data.Bool (not)-import Data.Function (($))-import Data.Int (Int)-import Data.List (map)-import Data.Maybe (Maybe (..))-import Data.String (String) import Data.Text (Text) import Data.Word (Word8)-import System.Exit (exitFailure)-import System.IO (IO)--import Hedgehog (Property, checkParallel, discover, property, withTests, (===))+import Prelude  main :: IO ()-main = checkParallel $$(discover) >>= \ok -> when (not ok) exitFailure+main = hspec $ do -prop_lift_letter :: Property-prop_lift_letter = withTests 1 $ property $-    (Lift.lift CapitalLetterA :: Word8) === 65+    describe "lift" $ do -prop_lift_list :: Property-prop_lift_list = withTests 1 $ property $-    (Lift.lift [CapitalLetterH,SmallLetterI,ExclamationMark] :: Text) === "Hi!"+        it "letter" $ do+            let f x = Lift.lift x :: Word8+            f CapitalLetterA `shouldBe` 65 -prop_validate_char :: Property-prop_validate_char = withTests 1 $ property $-    (map Refinement.validateChar [-1, 65, 97, 128] :: [Maybe (ASCII Int)]) === [Nothing, Just (asciiUnsafe 65), Just (asciiUnsafe 97), Nothing]+        it "list" $ do+            let f x = Lift.lift x :: Text+            f [CapitalLetterH, SmallLetterI, ExclamationMark] `shouldBe` "Hi!" -prop_from_char_list :: Property-prop_from_char_list = withTests 1 $ property $-    (Refinement.fromCharList [CapitalLetterH,SmallLetterI,ExclamationMark] :: ASCII Text) === asciiUnsafe "Hi!"+    describe "refinement" $ do -prop_to_char_list :: Property-prop_to_char_list = withTests 1 $ property $-    Refinement.toCharList (Refinement.substituteString "Piñata" :: ASCII Text) === [CapitalLetterP, SmallLetterI, Substitute, SmallLetterA, SmallLetterT, SmallLetterA]+        it "validateChar" $ do+            let f x = Refinement.validateChar x :: Maybe (ASCII Int)+            f (-1) `shouldBe` Nothing+            f 65 `shouldBe` Just (asciiUnsafe 65)+            f 97 `shouldBe` Just (asciiUnsafe 97)+            f 128 `shouldBe` Nothing -prop_substitute_string :: Property-prop_substitute_string = withTests 1 $ property $-    (Refinement.substituteString "Cristóbal" :: ASCII Text) === asciiUnsafe "Crist\SUBbal"+        it "fromCharList" $ do+            let f x = Refinement.fromCharList x :: ASCII Text+            f [CapitalLetterH, SmallLetterI, ExclamationMark] `shouldBe` asciiUnsafe "Hi!" -prop_validate_text :: Property-prop_validate_text = withTests 1 $ property $-    (map Refinement.validateString ["Hello", "Cristóbal"] :: [Maybe (ASCII Text)]) === [Just (asciiUnsafe "Hello"), Nothing]+        it "toCharList" $ do+            let f x = Refinement.toCharList+                          (Refinement.substituteString x :: ASCII Text)+            f "Piñata" `shouldBe` [CapitalLetterP, SmallLetterI, Substitute,+                                   SmallLetterA, SmallLetterT, SmallLetterA] -prop_validate_string :: Property-prop_validate_string = withTests 1 $ property $-    (map Refinement.validateString ["Hello", "Cristóbal"] :: [Maybe (ASCII String)]) === [Just (asciiUnsafe "Hello"), Nothing]+        it "substituteString" $ do+            let f x = Refinement.substituteString x :: ASCII Text+            f "Cristóbal" `shouldBe` asciiUnsafe "Crist\SUBbal"++        it "validateString" $ do+            let f x = Refinement.validateString x :: Maybe (ASCII Text)+            f "Hello" `shouldBe` Just (asciiUnsafe "Hello")+            f "Cristóbal" `shouldBe` Nothing++    describe "case refinement" $ do++        describe "validateChar" $ do++            it "lower" $ do+                let f x = CaseRefinement.validateChar x :: Maybe (ASCII'lower Int)+                f (-1) `shouldBe` Nothing+                f 65 `shouldBe` Nothing+                f 97 `shouldBe` Just (asciiCaseUnsafe 97)+                f 128 `shouldBe` Nothing++            it "upper" $ do+                let f x = CaseRefinement.validateChar x :: Maybe (ASCII'upper Int)+                f (-1) `shouldBe` Nothing+                f 65 `shouldBe` Just (asciiCaseUnsafe 65)+                f 97 `shouldBe` Nothing+                f 128 `shouldBe` Nothing++        describe "fromCaselessCharList" $ do++            it "lower" $ do+                let f x = CaseRefinement.fromCaselessCharList x :: ASCII'lower Text+                f [CC.LetterH, CC.LetterI, CC.ExclamationMark] `shouldBe` asciiCaseUnsafe "hi!"++            it "upper" $ do+                let f x = CaseRefinement.fromCaselessCharList x :: ASCII'upper Text+                f [CC.LetterH, CC.LetterI, CC.ExclamationMark] `shouldBe` asciiCaseUnsafe "HI!"++        describe "toCaselessCharList" $ do++            it "lower" $ do+                let f x = CaseRefinement.toCaselessCharList (x :: ASCII'lower Text)+                f (asciiCaseUnsafe "hi!") `shouldBe` [CC.LetterH, CC.LetterI, CC.ExclamationMark]++            it "upper" $ do+                let f x = CaseRefinement.toCaselessCharList (x :: ASCII'upper Text)+                f (asciiCaseUnsafe "HI!") `shouldBe` [CC.LetterH, CC.LetterI, CC.ExclamationMark]++        describe "substituteString" $ do++            it "lower" $ do+                let f x = CaseRefinement.substituteString x :: ASCII'lower Text+                f "Cób" `shouldBe` asciiCaseUnsafe "\SUB\SUBb"++            it "upper" $ do+                let f x = CaseRefinement.substituteString x :: ASCII'upper Text+                f "Cób" `shouldBe` asciiCaseUnsafe "C\SUB\SUB"++        describe "validateString" $ do++            it "lower" $ do+                let f x = CaseRefinement.validateString x :: Maybe (ASCII'lower Text)+                f "hello" `shouldBe` Just (asciiCaseUnsafe "hello")+                f "Hello" `shouldBe` Nothing++            it "upper" $ do+                let f x = CaseRefinement.validateString x :: Maybe (ASCII'upper Text)+                f "HELLO" `shouldBe` Just (asciiCaseUnsafe "HELLO")+                f "Hello" `shouldBe` Nothing