ascii-th 1.1.0.0 → 1.1.1.0
raw patch · 5 files changed
+291/−69 lines, 5 filesdep +ascii-casedep +ascii-caselessdep ~ascii-supersetPVP ok
version bump matches the API change (PVP)
Dependencies added: ascii-case, ascii-caseless
Dependency ranges changed: ascii-superset
API changes (from Hackage documentation)
+ ASCII.QuasiQuoters: caseless :: QuasiQuoter
+ ASCII.QuasiQuoters: lower :: QuasiQuoter
+ ASCII.QuasiQuoters: upper :: QuasiQuoter
+ ASCII.TemplateHaskell: caselessIsStringPat :: [CaselessChar] -> Q Pat
+ ASCII.TemplateHaskell: caselessListExp :: [CaselessChar] -> Q Exp
+ ASCII.TemplateHaskell: caselessListPat :: [CaselessChar] -> Q Pat
Files
- ascii-th.cabal +4/−2
- changelog.md +14/−0
- library/ASCII/QuasiQuoters.hs +140/−8
- library/ASCII/TemplateHaskell.hs +21/−4
- test/Main.hs +112/−55
ascii-th.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: ascii-th-version: 1.1.0.0+version: 1.1.1.0 synopsis: Template Haskell support for ASCII category: Data, Text @@ -28,7 +28,9 @@ default-language: Haskell2010 ghc-options: -Wall build-depends:- ascii-char ^>= 1.0+ ascii-case ^>= 1.0+ , ascii-caseless ^>= 0.0+ , ascii-char ^>= 1.0 , ascii-superset ^>= 1.1.0 , base ^>= 4.14 || ^>= 4.15 || ^>= 4.16 || ^>= 4.17
changelog.md view
@@ -1,3 +1,17 @@+### 1.1.1.0 (2023-01-03)++New in `ASCII.QuasiQuoters`:++- `caseless`+- `upper`+- `lower`++New in `ASCII.TemplateHaskell`:++- `caselessListExp`+- `caselessListPat`+- `caselessIsStringPat`+ ### 1.1.0.0 (2023-01-03) Change `ascii-superset` version from 1.0 to 1.1
library/ASCII/QuasiQuoters.hs view
@@ -6,21 +6,29 @@ -} module ASCII.QuasiQuoters (- char,- string,+ {- * Character -} char,+ {- * String -} string,+ {- * Caseless string -} caseless,+ {- * Upper-case string -} upper,+ {- * Lower-case string -} lower, ) where +import ASCII.Case (Case (..))+import ASCII.Caseless (CaselessChar) import ASCII.Char (Char)-import ASCII.Superset (toCharListMaybe, toCharMaybe)-import ASCII.TemplateHaskell (isCharExp, isCharPat, isStringExp, isStringPat) import Control.Monad (return, (>=>)) import Control.Monad.Fail (MonadFail, fail)+import Data.Functor ((<$>)) import Data.Maybe (Maybe (..)) import Language.Haskell.TH.Quote (QuasiQuoter (..)) import Language.Haskell.TH.Syntax (Exp, Pat, Q) +import qualified ASCII.Case as Case+import qualified ASCII.Superset as S+import qualified ASCII.TemplateHaskell as TH import qualified Data.Char as Unicode+import qualified Data.List as List import qualified Data.String as Unicode {-| An expression pattern corresponding to an ASCII character@@ -58,7 +66,7 @@ -} char :: QuasiQuoter-char = expPatQQ requireOneAscii isCharExp isCharPat+char = expPatQQ requireOneAscii TH.isCharExp TH.isCharPat {- | An expression or pattern corresponding to an ASCII string @@ -100,22 +108,146 @@ -} string :: QuasiQuoter-string = expPatQQ requireAsciiList isStringExp isStringPat+string = expPatQQ requireAsciiList TH.isStringExp TH.isStringPat +{-| An expression or pattern corresponding to a case-insensitive ASCII string++=== In an expression context++A monomorphic expression of type @['CaselessChar']@.++@+[caseless|Hello!|] ==+ [LetterH, LetterE, LetterL, LetterL, LetterO, ExclamationMark]+@++=== In a pattern context++A case-insensitive match of any type belonging to the+'ASCII.Superset.ToCaselessString' class.++@+let+ x = case "Hello!" :: 'Text' of+ [caseless|Bye!|] -> 1+ [caseless|hEllo!|] -> 2+ _ -> 3+in+ x == 2+@++-}+caseless :: QuasiQuoter+caseless = expPatQQ requireAsciiListCI TH.caselessListExp TH.caselessIsStringPat++{-| An expression or pattern corresponding to an ASCII string where all the+letters are of lower case++The letters in the body of the quasi-quotation may be written in any case+you like; they will be converted to lower case automatically.++=== In an expression context++The expression can become any type belonging to the 'ASCII.Superset.FromString'+class. Any letters in the quoted content will be converted to lower case.++@+[lower|Hello!|] == ("hello!" :: 'Text')+@++=== In a pattern context++The pattern matches a value of a type satisfying the 'ASCII.Superset.ToString'+constraint. A value matches this pattern if:++* All of the letters in the tested value are in lower case+* The tested value satisfies a case-insensitive comparison+ with the quasi-quoted content++@+let+ x = case "hi!" :: 'Text' of+ [lower|wow|] -> 1+ [lower|Hi!|] -> 2+ _ -> 3+in+ x == 2+@++-}+lower :: QuasiQuoter+lower = ofCase LowerCase++{-| An expression or pattern corresponding to an ASCII string where all the+letters are of upper case++The letters in the body of the quasi-quotation may be written in any case+you like; they will be converted to upper case automatically.++=== In an expression context++The expression can become any type belonging to the 'ASCII.Superset.FromString'+class. Any letters in the quoted content will be converted to upper case.++@+[upper|Hello!|] == ("HELLO!" :: 'Text')+@++=== In a pattern context++The pattern matches a value of a type satisfying the 'ASCII.Superset.ToString'+constraint. A value matches this pattern if:++* All of the letters in the tested value are in upper case+* The tested value satisfies a case-insensitive comparison+ with the quasi-quoted content++@+let+ x = case "HI!" :: 'Text' of+ [QQ.upper|wow|] -> 1+ [QQ.upper|Hi!|] -> 2+ _ -> 3+in+ x == 2+@++-}+upper :: QuasiQuoter+upper = ofCase UpperCase++ofCase :: Case -> QuasiQuoter+ofCase c = expPatQQ (requireAsciiToCase c) TH.isStringExp TH.isStringPat++{-| Require the string to consist of exactly one ASCII character -} requireOneAscii :: Unicode.String -> Q Char requireOneAscii = requireOne >=> requireAscii +{-| Require the list to consist of exactly one element -} oneMaybe :: [a] -> Maybe a oneMaybe xs = case xs of [x] -> Just x; _ -> Nothing +{-| Require the string to consist of exactly one character -} requireOne :: Unicode.String -> Q Unicode.Char requireOne = oneMaybe || "Must be exactly one character." +{-| Require the character to be ASCII -} requireAscii :: Unicode.Char -> Q Char-requireAscii = toCharMaybe || "Must be an ASCII character."+requireAscii = S.toCharMaybe || "Must be an ASCII character." +{-| Require the string to consist of all ASCII characters -} requireAsciiList :: Unicode.String -> Q [Char]-requireAsciiList = toCharListMaybe || "Must be only ASCII characters."+requireAsciiList = S.toCharListMaybe || "Must be only ASCII characters."++{-| Require the string to consist of all ASCII characters,+and return them with letter case discarded -}+requireAsciiListCI :: Unicode.String -> Q [CaselessChar]+requireAsciiListCI = S.toCaselessCharListMaybe || "Must be only ASCII characters."++{-| Require the string to consist of all ASCII characters,+and return them converted to the given case -}+requireAsciiToCase :: Case -> Unicode.String -> Q [Char]+requireAsciiToCase c s = List.map (Case.toCase c) <$> requireAsciiList s (||) :: (a -> Maybe b) -> Unicode.String -> a -> Q b f || msg = \a -> case f a of Just b -> return b; Nothing -> fail msg
library/ASCII/TemplateHaskell.hs view
@@ -1,15 +1,21 @@ module ASCII.TemplateHaskell (- {- * Characters -} charExp, charPat,- {- * Character lists -} charListExp, charListPat,- {- * Character supersets -} isCharExp, isCharPat,- {- * String supersets -} isStringExp, isStringPat,+ {- * Monomorphic -}+ {- ** Character -} charExp, charPat,+ {- ** String -} charListExp, charListPat,+ {- ** Caseless string -} caselessListExp, caselessListPat,++ {- * Polymorphic -}+ {- ** Character -} isCharExp, isCharPat,+ {- ** String -} isStringExp, isStringPat,+ {- ** Caseless string -} caselessIsStringPat, ) where import qualified ASCII.Char as ASCII import qualified ASCII.Superset as S +import ASCII.Caseless (CaselessChar) import Data.Data (Data) import Data.Maybe (Maybe (..)) import Language.Haskell.TH.Syntax (Exp, Pat, Q, dataToExpQ, dataToPatQ)@@ -88,6 +94,12 @@ -} +caselessListExp :: [CaselessChar] -> Q Exp+caselessListExp = exp++caselessListPat :: [CaselessChar] -> Q Pat+caselessListPat = pat+ isCharExp :: ASCII.Char -> Q Exp isCharExp x = [| S.fromChar $(charExp x) |] @@ -115,3 +127,8 @@ {-| Pattern that matches a type with a 'S.ToString' constraint -} isStringPat :: [ASCII.Char] -> Q Pat isStringPat xs = [p| (S.toCharListMaybe -> Just $(charListPat xs)) |]++{-| Matches a value of any type belonging to the 'S.ToCaselessString'+class, as if it were a list of 'CaselessChar' -}+caselessIsStringPat :: [CaselessChar] -> Q Pat+caselessIsStringPat xs = [p| (S.toCaselessCharListMaybe -> Just $(caselessListPat xs)) |]
test/Main.hs view
@@ -1,8 +1,7 @@ module Main (main) where -import ASCII.QuasiQuoters (char, string)-import ASCII.TemplateHaskell (charExp, charListExp, charListPat, charPat,- isCharExp, isCharPat)+import qualified ASCII.QuasiQuoters as QQ+import qualified ASCII.TemplateHaskell as TH import ASCII.Char (Char (..)) import ASCII.Refinement (ASCII, asciiUnsafe)@@ -14,6 +13,7 @@ import Prelude (Integer) import System.IO (IO) +import qualified ASCII.Caseless as Caseless import qualified Data.ByteString.Builder as BS.Builder import qualified Data.ByteString.Lazy as LBS @@ -22,62 +22,119 @@ main :: IO () main = hspec $ do - describe "char quasi-quoter" $ do- it "can be ASCII.Char" $ shouldBe @Char [char|e|] SmallLetterE- it "can be Word8" $ shouldBe @Word8 [char|e|] 101- it "can be a pattern" $ do- let x = case Tilde of [char|@|] -> 1; [char|~|] -> 2; _ -> 3- shouldBe @Integer x 2+ describe "ASCII.QuasiQuoters" $ do - describe "string quasi-quoter" $ do- it "can be [ASCII.Char]" $ shouldBe @[Char] [string|Hello!|]- [CapitalLetterH, SmallLetterE, SmallLetterL,- SmallLetterL, SmallLetterO, ExclamationMark]- it "can be String" $ shouldBe @String [string|Hello!|] "Hello!"- it "can be Text" $ shouldBe @Text [string|Hello!|] "Hello!"- it "can be bytestring Builder" $ shouldBe @LBS.ByteString- (BS.Builder.toLazyByteString [string|Hello!|]) "Hello!"- it "can be a pattern" $ do- let x = case [CapitalLetterH, SmallLetterI] of- [string|Bye|] -> 1; [string|Hi|] -> 2; _ -> 3- shouldBe @Integer x 2+ describe "char" $ do+ it "can be ASCII.Char" $ shouldBe @Char [QQ.char|e|] SmallLetterE+ it "can be Word8" $ shouldBe @Word8 [QQ.char|e|] 101+ it "can be a pattern" $ do+ let x = case Tilde of [QQ.char|@|] -> 1; [QQ.char|~|] -> 2; _ -> 3+ shouldBe @Integer x 2 - describe "charExp" $- it "is a Char expression" $- shouldBe @Char $(charExp CapitalLetterF) CapitalLetterF+ describe "string" $ do+ it "can be [ASCII.Char]" $ shouldBe @[Char] [QQ.string|Hello!|]+ [CapitalLetterH, SmallLetterE, SmallLetterL,+ SmallLetterL, SmallLetterO, ExclamationMark]+ it "can be String" $ shouldBe @String [QQ.string|Hello!|] "Hello!"+ it "can be Text" $ shouldBe @Text [QQ.string|Hello!|] "Hello!"+ it "can be bytestring Builder" $ shouldBe @LBS.ByteString+ (BS.Builder.toLazyByteString [QQ.string|Hello!|]) "Hello!"+ it "can be a pattern" $ do+ let x = case [CapitalLetterH, SmallLetterI] of+ [QQ.string|Bye|] -> 1; [QQ.string|Hi|] -> 2; _ -> 3+ shouldBe @Integer x 2 - describe "charPat" $ do- it "is a Char pattern" $ do- let x = case SmallLetterS of $(charPat SmallLetterR) -> 1;- $(charPat SmallLetterS) -> 2;- _ -> 3- shouldBe @Integer x 2+ describe "caseless" $ do+ it "can be [CaselessChar]" $ shouldBe [QQ.caseless|Hello!|]+ [Caseless.LetterH, Caseless.LetterE, Caseless.LetterL,+ Caseless.LetterL, Caseless.LetterO, Caseless.ExclamationMark]+ it "can be a pattern over [CaselessChar]" $ do+ let x = case [Caseless.LetterH, Caseless.LetterI] of+ [QQ.caseless|Bye|] -> 1; [QQ.caseless|Hi|] -> 2; _ -> 3+ shouldBe @Integer x 2+ it "can be a pattern over Text" $ do+ let x = case "Hello!" :: Text of+ [QQ.caseless|Bye!|] -> 1; [QQ.caseless|Hello!|] -> 2; _ -> 3+ shouldBe @Integer x 2+ it "matches Text in a case-insensitive manner" $ do+ let x = case "Hello!" :: Text of+ [QQ.caseless|Bye!|] -> 1; [QQ.caseless|hEllo!|] -> 2; _ -> 3+ shouldBe @Integer x 2 - describe "charListExp" $ do- it "is a [Char] expression" $ shouldBe @[Char]- $(charListExp [CapitalLetterH, SmallLetterI])- [CapitalLetterH, SmallLetterI]+ describe "upper" $ do+ it "can be Text" $ shouldBe [QQ.upper|Hello!|] ("HELLO!" :: Text)+ it "can match Text" $ do+ let x = case "HI!" :: Text of+ [QQ.upper|wow|] -> 1+ [QQ.upper|Hi!|] -> 2+ _ -> 3+ shouldBe @Integer x 2+ it "only matches all upper-case Text" $ do+ let x = case "Hi!" :: Text of+ [QQ.upper|Hi!|] -> 1+ _ -> 2+ shouldBe @Integer x 2 - describe "charListPat" $ do- it "is a [Char] pattern" $ do- let x = case [CapitalLetterH, SmallLetterI] of- $(charListPat [CapitalLetterH, SmallLetterA]) -> 1- $(charListPat [CapitalLetterH, SmallLetterI]) -> 2- _ -> 3- shouldBe @Integer x 2+ describe "lower" $ do+ it "can be Text" $ shouldBe [QQ.lower|Hello!|] ("hello!" :: Text)+ it "can match Text" $ do+ let x = case "hi!" :: Text of+ [QQ.lower|wow|] -> 1+ [QQ.lower|Hi!|] -> 2+ _ -> 3+ shouldBe @Integer x 2+ it "only matches all lower-case Text" $ do+ let x = case "Hi!" :: Text of+ [QQ.lower|Hi!|] -> 1+ _ -> 2+ shouldBe @Integer x 2 - describe "isCharExp" $ do- it "can be ASCII.Char expression" $- shouldBe @Char $(isCharExp CapitalLetterA) CapitalLetterA- it "can be a Word8 expression" $- shouldBe @Word8 $(isCharExp CapitalLetterA) 65- it "can be an ASCII Word8 expression" $- shouldBe @(ASCII Word8)- $(isCharExp CapitalLetterA) (asciiUnsafe 65)+ describe "ASCII.TemplateHaskell" $ do - describe "isCharPat" $ do- it "can be a Word8 pattern" $ do- let x = case (66 :: Word8) of $(isCharPat CapitalLetterA) -> 1- $(isCharPat CapitalLetterB) -> 2- _ -> 3- shouldBe @Word8 x 2+ describe "charExp" $+ it "is a Char expression" $+ shouldBe @Char $(TH.charExp CapitalLetterF) CapitalLetterF++ describe "charPat" $ do+ it "is a Char pattern" $ do+ let x = case SmallLetterS of $(TH.charPat SmallLetterR) -> 1;+ $(TH.charPat SmallLetterS) -> 2;+ _ -> 3+ shouldBe @Integer x 2++ describe "charListExp" $ do+ it "is a [Char] expression" $ shouldBe @[Char]+ $(TH.charListExp [CapitalLetterH, SmallLetterI])+ [CapitalLetterH, SmallLetterI]++ describe "charListPat" $ do+ it "is a [Char] pattern" $ do+ let x = case [CapitalLetterH, SmallLetterI] of+ $(TH.charListPat [CapitalLetterH, SmallLetterA]) -> 1+ $(TH.charListPat [CapitalLetterH, SmallLetterI]) -> 2+ _ -> 3+ shouldBe @Integer x 2++ describe "isCharExp" $ do+ it "can be ASCII.Char expression" $+ shouldBe @Char $(TH.isCharExp CapitalLetterA) CapitalLetterA+ it "can be a Word8 expression" $+ shouldBe @Word8 $(TH.isCharExp CapitalLetterA) 65+ it "can be an ASCII Word8 expression" $+ shouldBe @(ASCII Word8)+ $(TH.isCharExp CapitalLetterA) (asciiUnsafe 65)++ describe "isCharPat" $ do+ it "can be a Word8 pattern" $ do+ let x = case (66 :: Word8) of $(TH.isCharPat CapitalLetterA) -> 1+ $(TH.isCharPat CapitalLetterB) -> 2+ _ -> 3+ shouldBe @Integer x 2++ describe "caselessIsStringPat" $ do+ it "matches a cased string regardless of its case" $ do+ let x = case [QQ.string|Hi!|] :: Text of+ $(TH.caselessIsStringPat [Caseless.LetterG, Caseless.LetterO, Caseless.FullStop]) -> 1+ $(TH.caselessIsStringPat [Caseless.LetterH, Caseless.LetterI, Caseless.ExclamationMark]) -> 2+ _ -> 3+ shouldBe @Integer x 2