packages feed

text-ascii 1.0.0 → 1.0.1

raw patch · 8 files changed

+2687/−1033 lines, 8 filesdep +case-insensitivedep +megaparsecdep +optics-extradep −parsecdep ~optics-corePVP ok

version bump matches the API change (PVP)

Dependencies added: case-insensitive, megaparsec, optics-extra

Dependencies removed: parsec

Dependency ranges changed: optics-core

API changes (from Hackage documentation)

+ Text.Ascii: breakOn :: AsciiText -> AsciiText -> (AsciiText, AsciiText)
+ Text.Ascii: breakOnAll :: AsciiText -> AsciiText -> [(AsciiText, AsciiText)]
+ Text.Ascii: breakOnEnd :: AsciiText -> AsciiText -> (AsciiText, AsciiText)
+ Text.Ascii: bytes :: IxFold Int64 AsciiText Word8
+ Text.Ascii: center :: Int -> AsciiChar -> AsciiText -> AsciiText
+ Text.Ascii: chars :: IxTraversal' Int64 AsciiText AsciiChar
+ Text.Ascii: chunksOf :: Int -> AsciiText -> [AsciiText]
+ Text.Ascii: commonPrefixes :: AsciiText -> AsciiText -> Maybe (AsciiText, AsciiText, AsciiText)
+ Text.Ascii: count :: AsciiText -> AsciiText -> Int
+ Text.Ascii: dropAround :: (AsciiChar -> Bool) -> AsciiText -> AsciiText
+ Text.Ascii: dropEnd :: Int -> AsciiText -> AsciiText
+ Text.Ascii: index :: AsciiText -> Int -> Maybe AsciiChar
+ Text.Ascii: justifyLeft :: Int -> AsciiChar -> AsciiText -> AsciiText
+ Text.Ascii: justifyRight :: Int -> AsciiChar -> AsciiText -> AsciiText
+ Text.Ascii: lines :: AsciiText -> [AsciiText]
+ Text.Ascii: packedBytes :: Prism' [Word8] AsciiText
+ Text.Ascii: packedChars :: Iso' [AsciiChar] AsciiText
+ Text.Ascii: replace :: AsciiText -> AsciiText -> AsciiText -> AsciiText
+ Text.Ascii: replicate :: Int -> AsciiText -> AsciiText
+ Text.Ascii: splitOn :: AsciiText -> AsciiText -> [AsciiText]
+ Text.Ascii: strip :: AsciiText -> AsciiText
+ Text.Ascii: stripEnd :: AsciiText -> AsciiText
+ Text.Ascii: stripInfix :: AsciiText -> AsciiText -> Maybe (AsciiText, AsciiText)
+ Text.Ascii: stripStart :: AsciiText -> AsciiText
+ Text.Ascii: takeEnd :: Int -> AsciiText -> AsciiText
+ Text.Ascii: unlines :: Foldable f => f AsciiText -> AsciiText
+ Text.Ascii: unwords :: [AsciiText] -> AsciiText
+ Text.Ascii: words :: AsciiText -> [AsciiText]
+ Text.Ascii: zipWith :: (AsciiChar -> AsciiChar -> AsciiChar) -> AsciiText -> AsciiText -> AsciiText
+ Text.Ascii.Internal: AsciiChar :: Word8 -> AsciiChar
+ Text.Ascii.Internal: AsciiText :: ByteString -> AsciiText
+ Text.Ascii.Internal: [toByte] :: AsciiChar -> Word8
+ Text.Ascii.Internal: instance Control.DeepSeq.NFData Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance Control.DeepSeq.NFData Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance Data.CaseInsensitive.Internal.FoldCase Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance Data.CaseInsensitive.Internal.FoldCase Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance Data.Hashable.Class.Hashable Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance GHC.Base.Monoid Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance GHC.Base.Semigroup Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance GHC.Classes.Eq Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance GHC.Classes.Eq Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance GHC.Classes.Ord Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance GHC.Classes.Ord Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance GHC.Enum.Bounded Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance GHC.Exts.IsList Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance GHC.Show.Show Text.Ascii.Internal.AsciiChar
+ Text.Ascii.Internal: instance GHC.Show.Show Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance Optics.At.Core.Ixed Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance Text.Megaparsec.Stream.Stream Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance Text.Megaparsec.Stream.TraversableStream Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: instance Text.Megaparsec.Stream.VisualStream Text.Ascii.Internal.AsciiText
+ Text.Ascii.Internal: isJustAscii :: Word8 -> Maybe Char
+ Text.Ascii.Internal: newtype AsciiChar
+ Text.Ascii.Internal: newtype AsciiText
+ Text.Ascii.Internal: pattern AsByte :: Word8 -> AsciiChar
+ Text.Ascii.Internal: pattern AsChar :: Char -> AsciiChar
+ Text.Ascii.QQ: ascii :: QuasiQuoter
+ Text.Ascii.QQ: asciiQQ :: String -> Q Exp
+ Text.Ascii.QQ: char :: QuasiQuoter
+ Text.Ascii.QQ: charQQ :: String -> Q Exp
+ Text.Ascii.QQ: errDec :: String -> String -> Q [Dec]
+ Text.Ascii.QQ: errPat :: String -> String -> Q Pat
+ Text.Ascii.QQ: errType :: String -> String -> Q Type
+ Text.Ascii.QQ: isValidLead :: Char -> Bool
+ Text.Ascii.Unsafe: Unsafe :: a -> Unsafe
+ Text.Ascii.Unsafe: [safe] :: Unsafe -> a
+ Text.Ascii.Unsafe: foldl1 :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: foldl1' :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: foldr1 :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: foldr1' :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: head :: Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: index :: Unsafe AsciiText -> Int -> AsciiChar
+ Text.Ascii.Unsafe: init :: Unsafe AsciiText -> Unsafe AsciiText
+ Text.Ascii.Unsafe: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance Data.CaseInsensitive.Internal.FoldCase a => Data.CaseInsensitive.Internal.FoldCase (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance Data.Hashable.Class.Hashable a => Data.Hashable.Class.Hashable (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Base.Functor Text.Ascii.Unsafe.Unsafe
+ Text.Ascii.Unsafe: instance GHC.Base.Monoid a => GHC.Base.Monoid (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Base.Semigroup a => GHC.Base.Semigroup (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Classes.Eq a => GHC.Classes.Eq (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Classes.Ord a => GHC.Classes.Ord (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Enum.Bounded a => GHC.Enum.Bounded (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Enum.Enum (Text.Ascii.Unsafe.Unsafe Text.Ascii.Internal.AsciiChar)
+ Text.Ascii.Unsafe: instance GHC.Exts.IsList a => GHC.Exts.IsList (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance GHC.Read.Read (Text.Ascii.Unsafe.Unsafe Text.Ascii.Internal.AsciiChar)
+ Text.Ascii.Unsafe: instance GHC.Read.Read (Text.Ascii.Unsafe.Unsafe Text.Ascii.Internal.AsciiText)
+ Text.Ascii.Unsafe: instance GHC.Show.Show a => GHC.Show.Show (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance Text.Megaparsec.Stream.Stream a => Text.Megaparsec.Stream.Stream (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance Text.Megaparsec.Stream.TraversableStream a => Text.Megaparsec.Stream.TraversableStream (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: instance Text.Megaparsec.Stream.VisualStream a => Text.Megaparsec.Stream.VisualStream (Text.Ascii.Unsafe.Unsafe a)
+ Text.Ascii.Unsafe: last :: Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: maximum :: Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: minimum :: Unsafe AsciiText -> AsciiChar
+ Text.Ascii.Unsafe: newtype Unsafe (a :: Type)
+ Text.Ascii.Unsafe: scanl1 :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> Unsafe AsciiText
+ Text.Ascii.Unsafe: scanr1 :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> Unsafe AsciiText
+ Text.Ascii.Unsafe: tail :: Unsafe AsciiText -> Unsafe AsciiText

Files

CHANGELOG.md view
@@ -1,5 +1,32 @@ # Revision history for text-ascii +## 1.0.1 -- 2021-03-02++* Support GHC 9.+* Replace 8.10.3 with 8.10.4 in CI.+* Expose `Text.Ascii.Internal` and `Text.Ascii.QQ`.+* Add `Ixed` instance (and supporting type instances) for `AsciiText`.+* Add `Stream`, `VisualStream` and `TraversableStream` instances (and supporting+  type instances) for `AsciiText`.+* Drop Parsec in favour of Megaparsec.+* Add `FoldCase` instances for `AsciiChar` and `AsciiText`.+* Implement `lines`, `unlines`, `words`, `unwords`, `replicate`, `chunksOf`, +  `index`, `zipWith`, `justifyLeft`, `justifyRight`, `center`, `takeEnd`, +  `dropEnd`, `dropAround`, `strip`, `stripStart`, `stripEnd`, `commonPrefixes` +  for `AsciiText`.+* Implement [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html), as +  well as the following functions that use it:+  * `count`+  * `replace`+  * `splitOn`+  * `stripInfix`+  * `breakOnAll`+  * `breakOn`+  * `breakOnEnd`+* Add `Unsafe` module containing an `Unsafe` wrapper, plus instances and+  functions.+* Add a range of optics for `AsciiText`.+ ## 1.0.0 -- 2021-02-07  * First version. Released on an unsuspecting world.
README.md view
@@ -12,15 +12,21 @@ everything is total. When we include anything unsafe, it will be explicitly firewalled into its own module, behind a newtype. +### No boolean blindness++[Boolean blindness](http://dev.stephendiehl.com/hask/#boolean-blindness) is not+a good thing, for all the reasons listed in the link. Whenever possible, we'll+try and give more useful information than a `Bool`.+ ### Compatibility with the [`text`](http://hackage.haskell.org/package/text) API -We have (without conflicting with totality) a goal to match the API of the-`text` package exactly. If you know how to use `text`, you know how to use this-package too.+We want match the API of the `text` package exactly. If you know how to use `text`, +you'll know how to use this package too. Exceptions are made for places where+`text` is either partial or boolean-blind.  ### Discoverability, documentation and user-friendliness -In addition to documenting everything with Haddocks, we have over 100 doctests,+In addition to documenting everything with Haddocks, we have over 250 doctests, which provide _executable_ examples of how the API can be used, and how it will behave. We aim to clarify _every_ corner case left by the documentation of `text`, and care strongly about making the API easy to follow, learn and@@ -48,7 +54,8 @@  * 8.6.5 * 8.8.4-* 8.10.3+* 8.10.4+* 9.0.1  We check on the following platforms: 
src/Text/Ascii.hs view
@@ -1,999 +1,2104 @@ {-# LANGUAGE DerivingVia #-}-{-# LANGUAGE Trustworthy #-}-{-# LANGUAGE NoImplicitPrelude #-}---- |--- Module: Text.Ascii--- 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 strings.------ This module is designed for qualified importing:------ > import qualified Text.Ascii as Ascii------ /See also:/ [Wikipedia entry for ASCII](https://en.wikipedia.org/wiki/ASCII)-module Text.Ascii-  ( -- * Type-    AsciiText,--    -- * Creation-    empty,-    singleton,-    ascii,--    -- * Basic interface-    cons,-    snoc,-    uncons,-    unsnoc,-    length,--    -- * Transformations-    map,-    intercalate,-    intersperse,-    transpose,-    reverse,--    -- * Folds-    foldl,-    foldl',-    foldr,-    foldr',--    -- ** Special folds-    concat,-    concatMap,--    -- * Construction--    -- ** Scans-    scanl,-    scanr,--    -- ** Accumulating maps-    mapAccumL,-    mapAccumR,--    -- ** Generation and unfolding-    unfoldr,-    unfoldrN,--    -- * Substrings--    -- ** Breaking strings-    take,-    drop,-    takeWhile,-    takeWhileEnd,-    dropWhile,-    dropWhileEnd,-    splitAt,-    break,-    span,-    group,-    groupBy,-    inits,-    tails,--    -- ** Breaking into many substrings-    split,--    -- ** Breaking into lines and words--    -- * View patterns-    stripPrefix,-    stripSuffix,--    -- * Searching-    filter,-    find,-    partition,--    -- * Indexing-    findIndex,--    -- * Zipping-    zip,--    -- * Conversions-    fromText,-    fromByteString,-    toText,-    toByteString,--    -- * Optics-    textWise,-    byteStringWise,-  )-where--import Control.Category ((.))-import Data.Bifunctor (first)-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.Char (isAscii)-import Data.Coerce (coerce)-import Data.Maybe (Maybe (Just, Nothing))-import Data.Text (Text)-import qualified Data.Text as T-import Data.Text.Encoding (decodeUtf8, encodeUtf8)-import Optics.Prism (Prism', prism')-import Text.Ascii.Internal (AsciiChar (AsciiChar), AsciiText (AsciiText))-import Text.Ascii.QQ (ascii)-import Prelude-  ( Bool,-    Int,-    not,-    pure,-    ($),-    (<$>),-    (>),-  )---- Note on pragmata------ This is cribbed directly from bytestring, as I figure they know what they're--- doing way better than we do. When we add our own functionality, this probably--- needs to be considered more carefully. - Koz---- Creation---- $setup--- >>> :set -XNoImplicitPrelude--- >>> :set -XQuasiQuotes--- >>> :set -XOverloadedStrings--- >>> import Text.Ascii--- >>> import Text.Ascii.Char (char, upcase, AsciiCase (Lower), caseOf)--- >>> import Prelude ((.), ($), replicate, (<>), (==), (<), (/=))--- >>> import Data.Maybe (Maybe (Just), fromMaybe)--- >>> import qualified Data.ByteString as BS---- | The empty text.------ >>> empty--- ""------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-empty :: AsciiText-empty = coerce BS.empty---- | A text consisting of a single ASCII character.------ >>> singleton [char| 'w' |]--- "w"------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE [1] singleton #-}-singleton :: AsciiChar -> AsciiText-singleton = coerce BS.singleton---- Basic interface---- | Adds a character to the front of a text. This requires copying, which gives--- its complexity.------ >>> cons [char| 'n' |] [ascii| "eko" |]--- "neko"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE cons #-}-cons :: AsciiChar -> AsciiText -> AsciiText-cons = coerce BS.cons---- | Adds a character to the back of a text. This requires copying, which gives--- its complexity.------ >>> snoc [ascii| "nek" |] [char| 'o' |]--- "neko"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE snoc #-}-snoc :: AsciiText -> AsciiChar -> AsciiText-snoc = coerce BS.snoc---- | If the argument is non-empty, gives 'Just' the first character and the--- rest, and 'Nothing' otherwise.------ >>> uncons empty--- Nothing--- >>> uncons . singleton $ [char| 'w' |]--- Just ('0x77',"")--- >>> uncons [ascii| "nekomimi" |]--- Just ('0x6e',"ekomimi")------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE uncons #-}-uncons :: AsciiText -> Maybe (AsciiChar, AsciiText)-uncons = coerce BS.uncons---- | If the argument is non-empty, gives 'Just' the initial segment and the last--- character, and 'Nothing' otherwise.------ >>> unsnoc empty--- Nothing--- >>> unsnoc . singleton $ [char| 'w' |]--- Just ("",'0x77')--- >>> unsnoc [ascii| "catboy" |]--- Just ("catbo",'0x79')------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE unsnoc #-}-unsnoc :: AsciiText -> Maybe (AsciiText, AsciiChar)-unsnoc = coerce BS.unsnoc---- | The number of characters (and, since this is ASCII, bytes) in the text.------ >>> length . singleton $ [char| 'w' |]--- 1--- >>> length [ascii| "nyan nyan" |]--- 9------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE length #-}-length :: AsciiText -> Int-length = coerce BS.length---- Transformations---- | Copy, and apply the function to each element of, the text.------ >>> map (\c -> fromMaybe c . upcase $ c) [ascii| "nyan!" |]--- "NYAN!"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE map #-}-map :: (AsciiChar -> AsciiChar) -> AsciiText -> AsciiText-map = coerce BS.map---- | Takes a text and a list of texts, and concatenates the list after--- interspersing the first argument between each element of the list.------ >>> intercalate [ascii| " ~ " |] []--- ""--- >>> intercalate [ascii| " ~ " |] [[ascii| "nyan" |]]--- "nyan"--- >>> intercalate [ascii| " ~ " |] . replicate 3 $ [ascii| "nyan" |]--- "nyan ~ nyan ~ nyan"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE [1] intercalate #-}-intercalate :: AsciiText -> [AsciiText] -> AsciiText-intercalate = coerce BS.intercalate---- | Takes a character, and places it between the characters of a text.------ >>> intersperse [char| '~' |] empty--- ""--- >>> intersperse [char| '~' |] . singleton $ [char| 'w' |]--- "w"--- >>> intersperse [char| '~' |] [ascii| "nyan" |]--- "n~y~a~n"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-intersperse :: AsciiChar -> AsciiText -> AsciiText-intersperse = coerce BS.intersperse---- | Transpose the rows and columns of the argument. This uses--- 'Data.List.transpose' internally, and thus, isn't very efficient.------ >>> transpose []--- []--- >>> transpose [[ascii| "w" |]]--- ["w"]--- >>> transpose [[ascii| "nyan" |]]--- ["n","y","a","n"]--- >>> transpose . replicate 3 $ [ascii| "nyan" |]--- ["nnn","yyy","aaa","nnn"]--- >>> transpose [[ascii| "cat" |], [ascii| "boy" |], [ascii| "nyan" |]]--- ["cbn","aoy","tya","n"]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-transpose :: [AsciiText] -> [AsciiText]-transpose = coerce BS.transpose---- | Reverse the text.------ >>> reverse empty--- ""--- >>> reverse . singleton $ [char| 'w' |]--- "w"--- >>> reverse [ascii| "catboy goes nyan" |]--- "nayn seog yobtac"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-reverse :: AsciiText -> AsciiText-reverse = coerce BS.reverse---- TODO: Replace, justifyLeft, justifyRight, center---- Folds---- | Left-associative fold of a text.------ >>> foldl (\acc c -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]--- "f(f(f(f(f(f(ac)a)t)b)o)y)"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE foldl #-}-foldl :: (a -> AsciiChar -> a) -> a -> AsciiText -> a-foldl f x (AsciiText bs) = BS.foldl (coerce f) x bs---- | Left-associative fold of a text, strict in the accumulator.------ >>> foldl' (\acc c -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]--- "f(f(f(f(f(f(ac)a)t)b)o)y)"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE foldl' #-}-foldl' :: (a -> AsciiChar -> a) -> a -> AsciiText -> a-foldl' f x (AsciiText bs) = BS.foldl' (coerce f) x bs---- | Right-associative fold of a text.------ >>> foldr (\c acc -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]--- "f(f(f(f(f(f(ay)o)b)t)a)c)"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE foldr #-}-foldr :: (AsciiChar -> a -> a) -> a -> AsciiText -> a-foldr f x (AsciiText bs) = BS.foldr (coerce f) x bs---- | Right-associative fold of a text, strict in the accumulator.------ >>> foldr' (\c acc -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]--- "f(f(f(f(f(f(ay)o)b)t)a)c)"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE foldr' #-}-foldr' :: (AsciiChar -> a -> a) -> a -> AsciiText -> a-foldr' f x (AsciiText bs) = BS.foldr' (coerce f) x bs---- Special folds---- | Concatenate a list of texts.------ >>> concat []--- ""--- >>> concat [[ascii| "catboy" |]]--- "catboy"--- >>> concat . replicate 4 $ [ascii| "nyan" |]--- "nyannyannyannyan"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-concat :: [AsciiText] -> AsciiText-concat = coerce BS.concat---- | Map a text-producing function over a text, then concatenate the results.------ >>> concatMap singleton empty--- ""--- >>> concatMap singleton [ascii| "nyan" |]--- "nyan"--- >>> concatMap (\c -> singleton c <> singleton c) [ascii| "nekomimi" |]--- "nneekkoommiimmii"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-concatMap :: (AsciiChar -> AsciiText) -> AsciiText -> AsciiText-concatMap = coerce BS.concatMap---- | 'scanl' is similar to 'foldl', but returns a list of successive values from--- the left.------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE scanl #-}-scanl ::-  -- | accumulator -> element -> new accumulator-  (AsciiChar -> AsciiChar -> AsciiChar) ->-  -- | Starting accumulator value-  AsciiChar ->-  -- | Input of length \(n\)-  AsciiText ->-  -- | Output of length \(n + 1\)-  AsciiText-scanl = coerce BS.scanl---- | 'scanr' is similar to 'foldr', but returns a list of successive values from--- the right.------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE scanr #-}-scanr ::-  -- | element -> accumulator -> new accumulator-  (AsciiChar -> AsciiChar -> AsciiChar) ->-  -- | Starting accumulator value-  AsciiChar ->-  -- | Input of length \(n\)-  AsciiText ->-  -- | Output of length \(n + 1\)-  AsciiText-scanr = coerce BS.scanr---- Accumulating maps---- | Like a combination of 'map' and 'foldl''. Applies a function to each--- element of an 'AsciiText', passing an accumulating parameter from left to--- right, and returns a final 'AsciiText' along with the accumulating--- parameter's final value.------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE mapAccumL #-}-mapAccumL :: (a -> AsciiChar -> (a, AsciiChar)) -> a -> AsciiText -> (a, AsciiText)-mapAccumL f x (AsciiText bs) = AsciiText <$> BS.mapAccumL (coerce f) x bs---- | Like a combination of 'map' and 'foldr'. Applies a function to each element--- of an 'AsciiText', passing an accumulating parameter from right to left, and--- returns a final 'AsciiText' along with the accumulating parameter's final--- value.------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE mapAccumR #-}-mapAccumR :: (a -> AsciiChar -> (a, AsciiChar)) -> a -> AsciiText -> (a, AsciiText)-mapAccumR f x (AsciiText bs) = AsciiText <$> BS.mapAccumL (coerce f) x bs---- Generation and unfolding---- TODO: replicate---- | Similar to 'Data.List.unfoldr'. The function parameter takes a seed value,--- and produces either 'Nothing' (indicating that we're done) or 'Just' an--- 'AsciiChar' and a new seed value. 'unfoldr' then, given a starting seed, will--- repeatedly call the function parameter on successive seed values, returning--- the resulting 'AsciiText', based on the 'AsciiChar's produced, in the same--- order.------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE unfoldr #-}-unfoldr :: (a -> Maybe (AsciiChar, a)) -> a -> AsciiText-unfoldr f = AsciiText . BS.unfoldr (coerce f)---- | Similar to 'unfoldr', but also takes a maximum length parameter. The second--- element of the result tuple will be 'Nothing' if we finished with the--- function argument returning 'Nothing', and 'Just' the final seed value if we--- reached the maximum length before that happened.------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE unfoldrN #-}-unfoldrN :: Int -> (a -> Maybe (AsciiChar, a)) -> a -> (AsciiText, Maybe a)-unfoldrN n f = first AsciiText . BS.unfoldrN n (coerce f)---- | @take n t@ returns the prefix of @t@ with length--- \(\min \{ \max \{ 0, {\tt n}\}, {\tt length} \; {\tt t} \}\)------ >>> take (-100) [ascii| "catboy" |]--- ""--- >>> take 0 [ascii| "catboy" |]--- ""--- >>> take 3 [ascii| "catboy" |]--- "cat"--- >>> take 1000 [ascii| "catboy" |]--- "catboy"------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE take #-}-take :: Int -> AsciiText -> AsciiText-take = coerce BS.take---- TODO: takeEnd---- | @drop n t@ returns the suffix of @t@ with length--- \(\max \{ 0, \min \{ {\tt length} \; {\tt t}, {\tt length} \; {\tt t} - {\tt n} \} \}\)------ >>> drop (-100) [ascii| "catboy" |]--- "catboy"--- >>> drop 0 [ascii| "catboy" |]--- "catboy"--- >>> drop 3 [ascii| "catboy" |]--- "boy"--- >>> drop 1000 [ascii| "catboy" |]--- ""------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE drop #-}-drop :: Int -> AsciiText -> AsciiText-drop = coerce BS.drop---- TODO: dropEnd---- | @takeWhile p t@ returns the longest prefix of @t@ of characters that--- satisfy @p@.------ >>> takeWhile ((Just Lower ==) . caseOf) empty--- ""--- >>> takeWhile ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]--- "catboy"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE [1] takeWhile #-}-takeWhile :: (AsciiChar -> Bool) -> AsciiText -> AsciiText-takeWhile f (AsciiText at) = AsciiText . BS.takeWhile (coerce f) $ at---- | @takeWhileEnd p t@ returns the longest suffix of @t@ of characters that--- satisfy @p@. Equivalent to @'reverse' . 'takeWhile' p . 'reverse'@.------ >>> takeWhileEnd ((Just Lower ==) . caseOf) empty--- ""--- >>> takeWhileEnd ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]--- "nyan"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE takeWhileEnd #-}-takeWhileEnd :: (AsciiChar -> Bool) -> AsciiText -> AsciiText-takeWhileEnd f = AsciiText . BS.takeWhileEnd (coerce f) . coerce---- | @dropWhile p t@ returns the suffix remaining after @'takeWhile' p t@.------ >>> dropWhile ((Just Lower ==) . caseOf) empty--- ""--- >>> dropWhile ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]--- " goes nyan"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE [1] dropWhile #-}-dropWhile :: (AsciiChar -> Bool) -> AsciiText -> AsciiText-dropWhile f (AsciiText at) = AsciiText . BS.dropWhile (coerce f) $ at---- | @dropWhileEnd p t@ returns the prefix remaining after @'takeWhileEnd' p t@.--- Equivalent to @'reverse' . 'dropWhile' p . 'reverse'@.------ >>> dropWhileEnd ((Just Lower ==) . caseOf) empty--- ""--- >>> dropWhileEnd ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]--- "catboy goes "------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE dropWhileEnd #-}-dropWhileEnd :: (AsciiChar -> Bool) -> AsciiText -> AsciiText-dropWhileEnd f = AsciiText . BS.dropWhileEnd (coerce f) . coerce---- TODO: dropAround, strip, stripStart, stripEnd---- | @splitAt n t@ is equivalent to @('take' n t, 'drop' n t)@.------ >>> splitAt (-3) [ascii| "catboy" |]--- ("","catboy")--- >>> splitAt 0 [ascii| "catboy" |]--- ("","catboy")--- >>> splitAt 3 [ascii| "catboy" |]--- ("cat","boy")--- >>> splitAt 1000 [ascii| "catboy" |]--- ("catboy","")------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-{-# INLINE splitAt #-}-splitAt :: Int -> AsciiText -> (AsciiText, AsciiText)-splitAt = coerce BS.splitAt---- TODO: breakOn, breakOnEnd---- | @break p t@ is equivalent to @('takeWhile' ('not' p) t, 'dropWhile' ('not'--- p) t)@.------ >>> break ([char| ' ' |] ==) [ascii| "catboy goes nyan" |]--- ("catboy"," goes nyan")------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-break :: (AsciiChar -> Bool) -> AsciiText -> (AsciiText, AsciiText)-break = coerce BS.break---- | @span p t@ is equivalent to @('takeWhile' p t, 'dropWhile' p t)@.------ >>> span ([char| 'c' |] ==) [ascii| "catboy goes nyan" |]--- ("c","atboy goes nyan")------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE [1] span #-}-span :: (AsciiChar -> Bool) -> AsciiText -> (AsciiText, AsciiText)-span = coerce BS.span---- | Separate a text into a list of texts such that:------ * Their concatenation is equal to the original argument; and--- * Equal adjacent characters in the original argument are in the same text in--- the result.------ This is a specialized form of 'groupBy', and is about 40% faster than--- @'groupBy' '=='@.------ >>> group empty--- []--- >>> group . singleton $ [char| 'w' |]--- ["w"]--- >>> group [ascii| "nyan" |]--- ["n","y","a","n"]--- >>> group [ascii| "nyaaaan" |]--- ["n","y","aaaa","n"]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-group :: AsciiText -> [AsciiText]-group = coerce BS.group---- | Separate a text into a list of texts such that:------ * Their concatenation is equal to the original argument; and--- * Adjacent characters for which the function argument returns @True@ are in--- the same text in the result.------ 'group' is a special case for the function argument '=='; it is also about--- 40% faster.------ >>> groupBy (<) empty--- []--- >>> groupBy (<) . singleton $ [char| 'w' |]--- ["w"]--- >>> groupBy (<) [ascii| "catboy goes nyan" |]--- ["c","atboy"," goes"," nyan"]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-groupBy :: (AsciiChar -> AsciiChar -> Bool) -> AsciiText -> [AsciiText]-groupBy = coerce BS.groupBy---- | All prefixes of the argument, from shortest to longest.------ >>> inits empty--- [""]--- >>> inits . singleton $ [char| 'w' |]--- ["","w"]--- >>> inits [ascii| "nyan" |]--- ["","n","ny","nya","nyan"]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-inits :: AsciiText -> [AsciiText]-inits = coerce BS.inits---- | All suffixes of the argument, from shortest to longest.------ >>> tails empty--- [""]--- >>> tails . singleton $ [char| 'w' |]--- ["w",""]--- >>> tails [ascii| "nyan" |]--- ["nyan","yan","an","n",""]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-tails :: AsciiText -> [AsciiText]-tails = coerce BS.tails---- Breaking into many substrings---- TODO: splitOn---- | @split p t@ separates @t@ into components delimited by separators, for--- which @p@ returns @True@. The results do not contain the separators.------ \(n\) adjacent separators result in \(n - 1\) empty components in the result.------ >>> split ([char| '~' |] ==) empty--- []--- >>> split ([char| '~' |] ==) . singleton $ [char| '~' |]--- ["",""]--- >>> split ([char| '~' |] ==) [ascii| "nyan" |]--- ["nyan"]--- >>> split ([char| '~' |] ==) [ascii| "~nyan" |]--- ["","nyan"]--- >>> split ([char| '~' |] ==) [ascii| "nyan~" |]--- ["nyan",""]--- >>> split ([char| '~' |] ==) [ascii| "nyan~nyan"|]--- ["nyan","nyan"]--- >>> split ([char| '~' |] ==) [ascii| "nyan~~nyan" |]--- ["nyan","","nyan"]--- >>> split ([char| '~' |] ==) [ascii| "nyan~~~nyan" |]--- ["nyan","","","nyan"]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE split #-}-split :: (AsciiChar -> Bool) -> AsciiText -> [AsciiText]-split = coerce BS.splitWith---- TODO: chunksOf--- Breaking into lines and words--- TODO: lines, words, unlines, unwords---- View patterns---- | Return 'Just' the suffix of the second text if it has the first text as--- a prefix, 'Nothing' otherwise.------ >>> stripPrefix [ascii| "catboy" |] empty--- Nothing--- >>> stripPrefix empty [ascii| "catboy" |]--- Just "catboy"--- >>> stripPrefix [ascii| "nyan" |] [ascii| "nyan" |]--- Just ""--- >>> stripPrefix [ascii| "nyan" |] [ascii| "catboy" |]--- Nothing--- >>> stripPrefix [ascii| "catboy" |] [ascii| "catboy goes nyan" |]--- Just " goes nyan"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-stripPrefix :: AsciiText -> AsciiText -> Maybe AsciiText-stripPrefix = coerce BS.stripPrefix---- | Return 'Just' the prefix of the second text if it has the first text as--- a suffix, 'Nothing' otherwise.------ >>> stripSuffix [ascii| "catboy" |] empty--- Nothing--- >>> stripSuffix empty [ascii| "catboy" |]--- Just "catboy"--- >>> stripSuffix [ascii| "nyan" |] [ascii| "nyan" |]--- Just ""--- >>> stripSuffix [ascii| "nyan" |] [ascii| "catboy" |]--- Nothing--- >>> stripSuffix [ascii| "nyan" |] [ascii| "catboy goes nyan" |]--- Just "catboy goes "------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-stripSuffix :: AsciiText -> AsciiText -> Maybe AsciiText-stripSuffix = coerce BS.stripSuffix---- TODO: stripInfix, commonPrefixes---- Searching---- | Return the text comprised of all the characters that satisfy the function--- argument (that is, for which it returns 'True'), in the same order as in the--- original.------ >>> filter ([char| 'n' |] ==) empty--- ""--- >>> filter ([char| 'n' |] ==) [ascii| "catboy" |]--- ""--- >>> filter ([char| 'n' |] ==) [ascii| "nyan" |]--- "nn"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE filter #-}-filter :: (AsciiChar -> Bool) -> AsciiText -> AsciiText-filter = coerce BS.filter---- TODO: breakOnAll---- | Returns 'Just' the first character in the text satisfying the predicate,--- 'Nothing' otherwise.------ >>> find ([char| 'n' |] ==) empty--- Nothing--- >>> find ([char| 'n' |] ==) [ascii| "catboy" |]--- Nothing--- >>> find ([char| 'n' |] ==) [ascii| "nyan" |]--- Just '0x6e'--- >>> find ([char| 'n' |] /=) [ascii| "nyan" |]--- Just '0x79'------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE find #-}-find :: (AsciiChar -> Bool) -> AsciiText -> Maybe AsciiChar-find = coerce BS.find---- | @partition p t@ is equivalent to @('filter' p t, 'filter' ('not' p) t)@.------ >>> partition ([char| 'n' |] ==) empty--- ("","")--- >>> partition ([char| 'n' |] ==) . singleton $ [char| 'n' |]--- ("n","")--- >>> partition ([char| 'n' |] ==) . singleton $ [char| 'w' |]--- ("","w")--- >>> partition ([char| 'n' |] ==) [ascii| "nyan!" |]--- ("nn","ya!")------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-partition :: (AsciiChar -> Bool) -> AsciiText -> (AsciiText, AsciiText)-partition = coerce BS.partition---- Indexing---- TODO: index, safe only---- | Returns 'Just' the first index in the text such that the character at that--- index satisfies the predicate, 'Nothing' otherwise.------ >>> findIndex ([char| 'n' |] ==) empty--- Nothing--- >>> findIndex ([char| 'n' |] ==) . singleton $ [char| 'n' |]--- Just 0--- >>> findIndex ([char| 'n' |] ==) . singleton $ [char| 'w' |]--- Nothing--- >>> findIndex ([char| 'n' |] ==) [ascii| "nyan" |]--- Just 0------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-{-# INLINE [1] findIndex #-}-findIndex :: (AsciiChar -> Bool) -> AsciiText -> Maybe Int-findIndex = coerce BS.findIndex---- TODO: count---- Zipping---- | \'Pair off\' characters in both texts at corresponding indices. The result--- will be limited to the shorter of the two arguments.------ >>> zip empty [ascii| "catboy" |]--- []--- >>> zip [ascii| "catboy" |] empty--- []--- >>> zip [ascii| "catboy" |] [ascii| "nyan" |]--- [('0x63','0x6e'),('0x61','0x79'),('0x74','0x61'),('0x62','0x6e')]------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-zip :: AsciiText -> AsciiText -> [(AsciiChar, AsciiChar)]-zip = coerce BS.zip---- TODO: zipWith---- Conversions---- | Try and convert a 'Text' into an 'AsciiText'. Gives 'Nothing' if the 'Text'--- contains any symbols which lack an ASCII equivalent.------ >>> fromText "catboy"--- Just "catboy"--- >>> fromText "😺😺😺😺😺"--- Nothing------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-fromText :: Text -> Maybe AsciiText-fromText t = case T.find (not . isAscii) t of-  Nothing -> pure . AsciiText . encodeUtf8 $ t-  Just _ -> Nothing---- | Try and convert a 'ByteString' into an 'AsciiText'. Gives 'Nothing' if the--- 'ByteString' contains any bytes outside the ASCII range (that is, from 0 to--- 127 inclusive).------ >>> fromByteString "catboy"--- Just "catboy"--- >>> fromByteString . BS.pack $ [128]--- Nothing------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-fromByteString :: ByteString -> Maybe AsciiText-fromByteString bs = case BS.find (> 127) bs of-  Nothing -> pure . AsciiText $ bs-  Just _ -> Nothing---- | Convert an 'AsciiText' into a 'Text' (by copying).------ >>> toText empty--- ""--- >>> toText . singleton $ [char| 'w' |]--- "w"--- >>> toText [ascii| "nyan" |]--- "nyan"------ /Complexity:/ \(\Theta(n)\)------ @since 1.0.0-toText :: AsciiText -> Text-toText (AsciiText bs) = decodeUtf8 bs---- | Reinterpret an 'AsciiText' as a 'ByteString' (without copying).------ >>> toByteString empty--- ""--- >>> toByteString . singleton $ [char| 'w' |]--- "w"--- >>> toByteString [ascii| "nyan" |]--- "nyan"------ /Complexity:/ \(\Theta(1)\)------ @since 1.0.0-toByteString :: AsciiText -> ByteString-toByteString = coerce---- Prisms---- | A convenient demonstration of the relationship between 'toText' and--- 'fromText'.------ @since 1.0.0-textWise :: Prism' Text AsciiText-textWise = prism' toText fromText---- | A convenient demonstration of the relationship between 'toByteString' and--- 'fromByteString'.------ @since 1.0.0-byteStringWise :: Prism' ByteString AsciiText-byteStringWise = prism' toByteString fromByteString+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE NoImplicitPrelude #-}++-- |+-- Module: Text.Ascii+-- 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 strings.+--+-- This module is designed for qualified importing:+--+-- > import qualified Text.Ascii as Ascii+--+-- /See also:/ [Wikipedia entry for ASCII](https://en.wikipedia.org/wiki/ASCII)+module Text.Ascii+  ( -- * Type+    AsciiText,++    -- * Creation+    empty,+    singleton,+    ascii,++    -- * Basic interface+    cons,+    snoc,+    uncons,+    unsnoc,+    length,++    -- * Transformations+    map,+    intercalate,+    intersperse,+    transpose,+    reverse,+    replace,++    -- ** Justification+    justifyLeft,+    justifyRight,+    center,++    -- * Folds+    foldl,+    foldl',+    foldr,+    foldr',++    -- ** Special folds+    concat,+    concatMap,++    -- * Construction++    -- ** Scans+    scanl,+    scanr,++    -- ** Accumulating maps+    mapAccumL,+    mapAccumR,++    -- ** Generation and unfolding+    replicate,+    unfoldr,+    unfoldrN,++    -- * Substrings++    -- ** Breaking strings+    take,+    takeEnd,+    drop,+    dropEnd,+    takeWhile,+    takeWhileEnd,+    dropWhile,+    dropWhileEnd,+    dropAround,+    strip,+    stripStart,+    stripEnd,+    splitAt,+    breakOn,+    breakOnEnd,+    break,+    span,+    group,+    groupBy,+    inits,+    tails,++    -- ** Breaking into many substrings+    splitOn,+    split,+    chunksOf,++    -- ** Breaking into lines and words+    lines,+    unlines,+    words,+    unwords,++    -- * View patterns+    stripPrefix,+    stripSuffix,+    stripInfix,+    commonPrefixes,++    -- * Searching+    filter,+    breakOnAll,+    find,+    partition,++    -- * Indexing+    index,+    findIndex,+    count,++    -- * Zipping+    zip,+    zipWith,++    -- * Conversions+    fromText,+    fromByteString,+    toText,+    toByteString,++    -- * Optics+    textWise,+    byteStringWise,+    packedChars,+    chars,+    packedBytes,+    bytes,+  )+where++import Control.Category ((.))+import Data.Bifunctor (first)+import Data.Bool (Bool (False, True), otherwise, (&&))+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Optics as BSO+import Data.Char (isAscii)+import Data.Coerce (coerce)+import Data.Foldable (Foldable (foldMap))+import qualified Data.Foldable as F+import Data.Int (Int64)+import qualified Data.List as L+import Data.Maybe (Maybe (Just, Nothing))+import Data.Text (Text)+import qualified Data.Text as T+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Data.Word (Word8)+import Optics.Coerce (coerceA, coerceB, coerceS, coerceT)+import Optics.Getter (Getter, view)+import Optics.Iso (Iso')+import Optics.IxFold (IxFold)+import Optics.IxTraversal (IxTraversal')+import Optics.Optic (castOptic)+import Optics.Prism (Prism', prism')+import Optics.Review (Review, review)+import Text.Ascii.Internal (AsciiChar (AsciiChar), AsciiText (AsciiText))+import Text.Ascii.QQ (ascii, char)+import Prelude+  ( Int,+    not,+    pure,+    ($),+    (+),+    (-),+    (/=),+    (<),+    (<$>),+    (<=),+    (<>),+    (==),+    (>),+    (>=),+    (||),+  )+import qualified Prelude as P++-- Note on pragmata+--+-- This is cribbed directly from bytestring, as I figure they know what they're+-- doing way better than we do. When we add our own functionality, this probably+-- needs to be considered more carefully. - Koz++-- Creation++-- $setup+-- >>> :set -XNoImplicitPrelude+-- >>> :set -XQuasiQuotes+-- >>> :set -XOverloadedStrings+-- >>> import Text.Ascii+-- >>> import Text.Ascii.Char (char, upcase, AsciiCase (Lower), caseOf)+-- >>> import Prelude ((.), ($), (<>), (==), (<), (/=), (-), max, even)+-- >>> import qualified Prelude as Prelude+-- >>> import Data.Maybe (Maybe (Just), fromMaybe)+-- >>> import qualified Data.ByteString as BS+-- >>> import Optics.AffineFold (preview)+-- >>> import Optics.Review (review)+-- >>> import Optics.Getter (view)+-- >>> import Optics.IxTraversal (elementOf)+-- >>> import Optics.IxSetter (iover)+-- >>> import Data.Bool (bool)+-- >>> import Optics.IxFold (itoListOf)++-- | The empty text.+--+-- >>> empty+-- ""+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+empty :: AsciiText+empty = coerce BS.empty++-- | A text consisting of a single ASCII character.+--+-- >>> singleton [char| 'w' |]+-- "w"+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE [1] singleton #-}+singleton :: AsciiChar -> AsciiText+singleton = coerce BS.singleton++-- Basic interface++-- | Adds a character to the front of a text. This requires copying, which gives+-- its complexity.+--+-- >>> cons [char| 'n' |] [ascii| "eko" |]+-- "neko"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE cons #-}+cons :: AsciiChar -> AsciiText -> AsciiText+cons = coerce BS.cons++-- | Adds a character to the back of a text. This requires copying, which gives+-- its complexity.+--+-- >>> snoc [ascii| "nek" |] [char| 'o' |]+-- "neko"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE snoc #-}+snoc :: AsciiText -> AsciiChar -> AsciiText+snoc = coerce BS.snoc++-- | If the argument is non-empty, gives 'Just' the first character and the+-- rest, and 'Nothing' otherwise.+--+-- >>> uncons empty+-- Nothing+-- >>> uncons . singleton $ [char| 'w' |]+-- Just ('0x77',"")+-- >>> uncons [ascii| "nekomimi" |]+-- Just ('0x6e',"ekomimi")+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE uncons #-}+uncons :: AsciiText -> Maybe (AsciiChar, AsciiText)+uncons = coerce BS.uncons++-- | If the argument is non-empty, gives 'Just' the initial segment and the last+-- character, and 'Nothing' otherwise.+--+-- >>> unsnoc empty+-- Nothing+-- >>> unsnoc . singleton $ [char| 'w' |]+-- Just ("",'0x77')+-- >>> unsnoc [ascii| "catboy" |]+-- Just ("catbo",'0x79')+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE unsnoc #-}+unsnoc :: AsciiText -> Maybe (AsciiText, AsciiChar)+unsnoc = coerce BS.unsnoc++-- | The number of characters (and, since this is ASCII, bytes) in the text.+--+-- >>> length . singleton $ [char| 'w' |]+-- 1+-- >>> length [ascii| "nyan nyan" |]+-- 9+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE length #-}+length :: AsciiText -> Int+length = coerce BS.length++-- Transformations++-- | Copy, and apply the function to each element of, the text.+--+-- >>> map (\c -> fromMaybe c . upcase $ c) [ascii| "nyan!" |]+-- "NYAN!"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE map #-}+map :: (AsciiChar -> AsciiChar) -> AsciiText -> AsciiText+map = coerce BS.map++-- | Takes a text and a list of texts, and concatenates the list after+-- interspersing the first argument between each element of the list.+--+-- >>> intercalate [ascii| " ~ " |] []+-- ""+-- >>> intercalate [ascii| " ~ " |] [[ascii| "nyan" |]]+-- "nyan"+-- >>> intercalate [ascii| " ~ " |] . Prelude.replicate 3 $ [ascii| "nyan" |]+-- "nyan ~ nyan ~ nyan"+-- >>> intercalate empty . Prelude.replicate 3 $ [ascii| "nyan" |]+-- "nyannyannyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE [1] intercalate #-}+intercalate :: AsciiText -> [AsciiText] -> AsciiText+intercalate = coerce BS.intercalate++-- | Takes a character, and places it between the characters of a text.+--+-- >>> intersperse [char| '~' |] empty+-- ""+-- >>> intersperse [char| '~' |] . singleton $ [char| 'w' |]+-- "w"+-- >>> intersperse [char| '~' |] [ascii| "nyan" |]+-- "n~y~a~n"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+intersperse :: AsciiChar -> AsciiText -> AsciiText+intersperse = coerce BS.intersperse++-- | Transpose the rows and columns of the argument. This uses+-- 'Data.List.transpose' internally, and thus, isn't very efficient.+--+-- >>> transpose []+-- []+-- >>> transpose [[ascii| "w" |]]+-- ["w"]+-- >>> transpose [[ascii| "nyan" |]]+-- ["n","y","a","n"]+-- >>> transpose . Prelude.replicate 3 $ [ascii| "nyan" |]+-- ["nnn","yyy","aaa","nnn"]+-- >>> transpose [[ascii| "cat" |], [ascii| "boy" |], [ascii| "nyan" |]]+-- ["cbn","aoy","tya","n"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+transpose :: [AsciiText] -> [AsciiText]+transpose = coerce BS.transpose++-- | Reverse the text.+--+-- >>> reverse empty+-- ""+-- >>> reverse . singleton $ [char| 'w' |]+-- "w"+-- >>> reverse [ascii| "catboy goes nyan" |]+-- "nayn seog yobtac"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+reverse :: AsciiText -> AsciiText+reverse = coerce BS.reverse++-- | @replace needle replacement haystack@, given a @needle@ of length \(n\) and+-- a haystack of length \(h\), replaces each non-overlapping occurrence of+-- @needle@ in @haystack@ with @replacement@. If the @needle@ is empty, no+-- replacement will be performed. Equivalent to @'intercalate' replacement '.'+-- 'splitOn' needle '$' haystack@.+--+-- >>> replace empty [ascii| "NYAN~" |] [ascii| "catboy goes nyan nyan" |]+-- "catboy goes nyan nyan"+-- >>> replace [ascii| "nyan" |] [ascii| "NYAN~" |] empty+-- ""+-- >>> replace [ascii| "nyan" |] [ascii| "NYAN~" |] [ascii| "catboy goes nyan nyan" |]+-- "catboy goes NYAN~ NYAN~"+-- >>> replace [ascii| "nyan" |] [ascii| "NYAN~" |] [ascii| "nyanyan" |]+-- "NYAN~yan"+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- The analysis below also doesn't factor in the cost of performing the+-- replacement, as this is (among other things) proportional to the number of+-- matches of the needle (and thus is hard to quantify).+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+replace ::+  -- | @needle@ to search for+  AsciiText ->+  -- | @replacement@ to replace @needle@ with+  AsciiText ->+  -- | @haystack@ in which to search+  AsciiText ->+  AsciiText+replace needle replacement haystack+  | length needle == 0 || length haystack == 0 = haystack+  | length needle > length haystack = haystack+  | otherwise = intercalate replacement . splitOn needle $ haystack++-- | @justifyLeft n c t@ produces a result of length \(\max \{ {\tt n }, {\tt length} \; {\tt t} \}\),+-- consisting of a copy of @t@ followed by (zero or more) copies+-- of @c@.+--+-- >>> justifyLeft (-100) [char| '~' |] [ascii| "nyan" |]+-- "nyan"+-- >>> justifyLeft 4 [char| '~' |] [ascii| "nyan" |]+-- "nyan"+-- >>> justifyLeft 10 [char| '~' |] [ascii| "nyan" |]+-- "nyan~~~~~~"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+justifyLeft :: Int -> AsciiChar -> AsciiText -> AsciiText+justifyLeft n c t = t <> replicate (n - length t) (singleton c)++-- | @justifyRight n c t@ produces a result of length \(\max \{ {\tt n }, {\tt length} \; {\tt t} \}\),+-- consisting of (zero or more) copies of @c@ followed by a copy of @t@.+--+-- >>> justifyRight (-100) [char| '~' |] [ascii| "nyan" |]+-- "nyan"+-- >>> justifyRight 4 [char| '~' |] [ascii| "nyan" |]+-- "nyan"+-- >>> justifyRight 10 [char| '~' |] [ascii| "nyan" |]+-- "~~~~~~nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+justifyRight :: Int -> AsciiChar -> AsciiText -> AsciiText+justifyRight n c t = replicate (n - length t) (singleton c) <> t++-- | @center n c t@ produces a result of length \({\tt k } = \max \{ {\tt n }, {\tt length} \; {\tt t} \}\),+-- consisting of:+--+-- * \(\lceil \frac{{\tt k} - {\tt length} \; {\tt t}}{2} \rceil\) copies of @c@;+-- followed by+-- * A copy of @t@; followed by+-- * Zero or more copies of @c@+--+-- This means that the centering is \'left-biased\'. This mimicks the behaviour+-- of the function of the same name in the [text+-- package](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:center),+-- although that function's documenation does not describe this behaviour.+--+-- >>> center (-100) [char| '~' |] [ascii| "nyan" |]+-- "nyan"+-- >>> center 4 [char| '~' |] [ascii| "nyan" |]+-- "nyan"+-- >>> center 5 [char| '~' |] [ascii| "nyan" |]+-- "~nyan"+-- >>> center 6 [char| '~' |] [ascii| "nyan" |]+-- "~nyan~"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+center :: Int -> AsciiChar -> AsciiText -> AsciiText+center n c t+  | n <= length t = t+  | P.even (n - length t) = copied <> t <> copied+  | otherwise = copied <> singleton c <> t <> copied+  where+    copied :: AsciiText+    copied = replicate ((n - length t) `P.div` 2) (singleton c)++-- Folds++-- | Left-associative fold of a text.+--+-- >>> foldl (\acc c -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]+-- "f(f(f(f(f(f(ac)a)t)b)o)y)"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE foldl #-}+foldl :: (a -> AsciiChar -> a) -> a -> AsciiText -> a+foldl f x (AsciiText bs) = BS.foldl (coerce f) x bs++-- | Left-associative fold of a text, strict in the accumulator.+--+-- >>> foldl' (\acc c -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]+-- "f(f(f(f(f(f(ac)a)t)b)o)y)"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE foldl' #-}+foldl' :: (a -> AsciiChar -> a) -> a -> AsciiText -> a+foldl' f x (AsciiText bs) = BS.foldl' (coerce f) x bs++-- | Right-associative fold of a text.+--+-- >>> foldr (\c acc -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]+-- "f(f(f(f(f(f(ay)o)b)t)a)c)"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE foldr #-}+foldr :: (AsciiChar -> a -> a) -> a -> AsciiText -> a+foldr f x (AsciiText bs) = BS.foldr (coerce f) x bs++-- | Right-associative fold of a text, strict in the accumulator.+--+-- >>> foldr' (\c acc -> [ascii| "f(" |] <> acc <> singleton c <> [ascii| ")" |]) [ascii| "a" |] [ascii| "catboy" |]+-- "f(f(f(f(f(f(ay)o)b)t)a)c)"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE foldr' #-}+foldr' :: (AsciiChar -> a -> a) -> a -> AsciiText -> a+foldr' f x (AsciiText bs) = BS.foldr' (coerce f) x bs++-- Special folds++-- | Concatenate a list of texts.+--+-- >>> concat []+-- ""+-- >>> concat [[ascii| "catboy" |]]+-- "catboy"+-- >>> concat . Prelude.replicate 4 $ [ascii| "nyan" |]+-- "nyannyannyannyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+concat :: [AsciiText] -> AsciiText+concat = coerce BS.concat++-- | Map a text-producing function over a text, then concatenate the results.+--+-- >>> concatMap singleton empty+-- ""+-- >>> concatMap singleton [ascii| "nyan" |]+-- "nyan"+-- >>> concatMap (\c -> singleton c <> singleton c) [ascii| "nekomimi" |]+-- "nneekkoommiimmii"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+concatMap :: (AsciiChar -> AsciiText) -> AsciiText -> AsciiText+concatMap = coerce BS.concatMap++-- | 'scanl' is similar to 'foldl', but returns a list of successive values from+-- the left.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE scanl #-}+scanl ::+  -- | accumulator -> element -> new accumulator+  (AsciiChar -> AsciiChar -> AsciiChar) ->+  -- | Starting accumulator value+  AsciiChar ->+  -- | Input of length \(n\)+  AsciiText ->+  -- | Output of length \(n + 1\)+  AsciiText+scanl = coerce BS.scanl++-- | 'scanr' is similar to 'foldr', but returns a list of successive values from+-- the right.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE scanr #-}+scanr ::+  -- | element -> accumulator -> new accumulator+  (AsciiChar -> AsciiChar -> AsciiChar) ->+  -- | Starting accumulator value+  AsciiChar ->+  -- | Input of length \(n\)+  AsciiText ->+  -- | Output of length \(n + 1\)+  AsciiText+scanr = coerce BS.scanr++-- Accumulating maps++-- | Like a combination of 'map' and 'foldl''. Applies a function to each+-- element of an 'AsciiText', passing an accumulating parameter from left to+-- right, and returns a final 'AsciiText' along with the accumulating+-- parameter's final value.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE mapAccumL #-}+mapAccumL :: (a -> AsciiChar -> (a, AsciiChar)) -> a -> AsciiText -> (a, AsciiText)+mapAccumL f x (AsciiText bs) = AsciiText <$> BS.mapAccumL (coerce f) x bs++-- | Like a combination of 'map' and 'foldr'. Applies a function to each element+-- of an 'AsciiText', passing an accumulating parameter from right to left, and+-- returns a final 'AsciiText' along with the accumulating parameter's final+-- value.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE mapAccumR #-}+mapAccumR :: (a -> AsciiChar -> (a, AsciiChar)) -> a -> AsciiText -> (a, AsciiText)+mapAccumR f x (AsciiText bs) = AsciiText <$> BS.mapAccumL (coerce f) x bs++-- Generation and unfolding++-- | @replicate n t@ consists of @t@ repeated \(\max \{ 0, {\tt n } \}\) times.+--+-- >>> replicate (-100) [ascii| "nyan" |]+-- ""+-- >>> replicate 0 [ascii| "nyan" |]+-- ""+-- >>> replicate 3 [ascii| "nyan" |]+-- "nyannyannyan"+--+-- /Complexity:/ \(\Theta(n \cdot m)\)+--+-- @since 1.0.1+replicate :: Int -> AsciiText -> AsciiText+replicate n t+  | n <= 0 = empty+  | otherwise = concat . P.replicate n $ t++-- | Similar to 'Data.List.unfoldr'. The function parameter takes a seed value,+-- and produces either 'Nothing' (indicating that we're done) or 'Just' an+-- 'AsciiChar' and a new seed value. 'unfoldr' then, given a starting seed, will+-- repeatedly call the function parameter on successive seed values, returning+-- the resulting 'AsciiText', based on the 'AsciiChar's produced, in the same+-- order.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE unfoldr #-}+unfoldr :: (a -> Maybe (AsciiChar, a)) -> a -> AsciiText+unfoldr f = AsciiText . BS.unfoldr (coerce f)++-- | Similar to 'unfoldr', but also takes a maximum length parameter. The second+-- element of the result tuple will be 'Nothing' if we finished with the+-- function argument returning 'Nothing', and 'Just' the final seed value if we+-- reached the maximum length before that happened.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE unfoldrN #-}+unfoldrN :: Int -> (a -> Maybe (AsciiChar, a)) -> a -> (AsciiText, Maybe a)+unfoldrN n f = first AsciiText . BS.unfoldrN n (coerce f)++-- | @take n t@ returns the prefix of @t@ with length+-- \(\min \{ \max \{ 0, {\tt n}\}, {\tt length} \; {\tt t} \}\).+--+-- >>> take (-100) [ascii| "catboy" |]+-- ""+-- >>> take 0 [ascii| "catboy" |]+-- ""+-- >>> take 4 [ascii| "catboy" |]+-- "catb"+-- >>> take 1000 [ascii| "catboy" |]+-- "catboy"+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE take #-}+take :: Int -> AsciiText -> AsciiText+take = coerce BS.take++-- | @takeEnd n t@ returns the suffix of @t@ with length+-- \(\min \{ \max \{0, {\tt n} \}, {\tt length} \; {\tt t} \}\).+--+-- >>> takeEnd (-100) [ascii| "catboy" |]+-- ""+-- >>> takeEnd 0 [ascii| "catboy" |]+-- ""+-- >>> takeEnd 4 [ascii| "catboy" |]+-- "tboy"+-- >>> takeEnd 1000 [ascii| "catboy" |]+-- "catboy"+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+takeEnd :: Int -> AsciiText -> AsciiText+takeEnd n t = drop (length t - n) t++-- | @drop n t@ returns the suffix of @t@ with length+-- \(\max \{ 0, \min \{ {\tt length} \; {\tt t}, {\tt length} \; {\tt t} - {\tt n} \} \}\).+--+-- >>> drop (-100) [ascii| "catboy" |]+-- "catboy"+-- >>> drop 0 [ascii| "catboy" |]+-- "catboy"+-- >>> drop 4 [ascii| "catboy" |]+-- "oy"+-- >>> drop 1000 [ascii| "catboy" |]+-- ""+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE drop #-}+drop :: Int -> AsciiText -> AsciiText+drop = coerce BS.drop++-- | @dropEnd n t@ returns the prefix of @t@ with length+-- \(\max \{ 0, \min \{ {\tt length} \; {\tt t}, {\tt length} \; {\tt t} - {\tt n} \} \}\).+--+-- >>> dropEnd (-100) [ascii| "catboy" |]+-- "catboy"+-- >>> dropEnd 0 [ascii| "catboy" |]+-- "catboy"+-- >>> dropEnd 4 [ascii| "catboy" |]+-- "ca"+-- >>> dropEnd 1000 [ascii| "catboy" |]+-- ""+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+dropEnd :: Int -> AsciiText -> AsciiText+dropEnd n t = take (length t - n) t++-- | @takeWhile p t@ returns the longest prefix of @t@ of characters that+-- satisfy @p@.+--+-- >>> takeWhile ((Just Lower ==) . caseOf) empty+-- ""+-- >>> takeWhile ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]+-- "catboy"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE [1] takeWhile #-}+takeWhile :: (AsciiChar -> Bool) -> AsciiText -> AsciiText+takeWhile f (AsciiText at) = AsciiText . BS.takeWhile (coerce f) $ at++-- | @takeWhileEnd p t@ returns the longest suffix of @t@ of characters that+-- satisfy @p@. Equivalent to @'reverse' . 'takeWhile' p . 'reverse'@.+--+-- >>> takeWhileEnd ((Just Lower ==) . caseOf) empty+-- ""+-- >>> takeWhileEnd ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]+-- "nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE takeWhileEnd #-}+takeWhileEnd :: (AsciiChar -> Bool) -> AsciiText -> AsciiText+takeWhileEnd f = AsciiText . BS.takeWhileEnd (coerce f) . coerce++-- | @dropWhile p t@ returns the suffix remaining after @'takeWhile' p t@.+--+-- >>> dropWhile ((Just Lower ==) . caseOf) empty+-- ""+-- >>> dropWhile ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]+-- " goes nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE [1] dropWhile #-}+dropWhile :: (AsciiChar -> Bool) -> AsciiText -> AsciiText+dropWhile f (AsciiText at) = AsciiText . BS.dropWhile (coerce f) $ at++-- | @dropWhileEnd p t@ returns the prefix remaining after @'takeWhileEnd' p t@.+-- Equivalent to @'reverse' . 'dropWhile' p . 'reverse'@.+--+-- >>> dropWhileEnd ((Just Lower ==) . caseOf) empty+-- ""+-- >>> dropWhileEnd ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]+-- "catboy goes "+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE dropWhileEnd #-}+dropWhileEnd :: (AsciiChar -> Bool) -> AsciiText -> AsciiText+dropWhileEnd f = AsciiText . BS.dropWhileEnd (coerce f) . coerce++-- | @dropAround p@ is equivalent to @'dropWhile' p '.' 'dropWhileEnd' p@.+--+-- >>> dropAround ((Just Lower ==) . caseOf) empty+-- ""+-- >>> dropAround ((Just Lower ==) . caseOf) [ascii| "catboy goes nyan" |]+-- " goes "+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+dropAround :: (AsciiChar -> Bool) -> AsciiText -> AsciiText+dropAround p = dropWhile p . dropWhileEnd p++-- | Remove the longest prefix /and/ suffix of the input comprised entirely of+-- whitespace characters. We define a \'whitespace character\' as any of the+-- following:+--+-- * TAB (0x09)+-- * LF (0x0a)+-- * VT (0x0b)+-- * FF (0x0c)+-- * CR (0x0d)+-- * Space (0x20)+--+-- >>> strip empty+-- ""+-- >>> strip [ascii| "catboy goes nyan" |]+-- "catboy goes nyan"+-- >>> strip [ascii| "\n\n    \tcatboy goes nyan" |]+-- "catboy goes nyan"+-- >>> strip [ascii| "catboy goes nyan   \t\t\n" |]+-- "catboy goes nyan"+-- >>> strip [ascii| "\n\n    \tcatboy goes nyan   \t\t\n" |]+-- "catboy goes nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+strip :: AsciiText -> AsciiText+strip = dropAround isSpace++-- | Remove the longest prefix of the input comprised entirely of whitespace+-- characters. We define a \'whitespace character\' as any of the following:+--+-- * TAB (0x09)+-- * LF (0x0a)+-- * VT (0x0b)+-- * FF (0x0c)+-- * CR (0x0d)+-- * Space (0x20)+--+-- >>> stripStart empty+-- ""+-- >>> stripStart [ascii| "catboy goes nyan" |]+-- "catboy goes nyan"+-- >>> stripStart [ascii| "\n\n    \tcatboy goes nyan" |]+-- "catboy goes nyan"+-- >>> stripStart [ascii| "catboy goes nyan   \t\t\n" |]+-- "catboy goes nyan   \t\t\n"+-- >>> stripStart [ascii| "\n\n    \tcatboy goes nyan   \t\t\n" |]+-- "catboy goes nyan   \t\t\n"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+stripStart :: AsciiText -> AsciiText+stripStart = dropWhile isSpace++-- | Remove the longest suffix of the input comprised entirely of whitespace+-- characters. We define a \'whitespace character\' as any of the following:+--+-- * TAB (0x09)+-- * LF (0x0a)+-- * VT (0x0b)+-- * FF (0x0c)+-- * CR (0x0d)+-- * Space (0x20)+--+-- >>> stripEnd empty+-- ""+-- >>> stripEnd [ascii| "catboy goes nyan" |]+-- "catboy goes nyan"+-- >>> stripEnd [ascii| "\n\n    \tcatboy goes nyan" |]+-- "\n\n    \tcatboy goes nyan"+-- >>> stripEnd [ascii| "catboy goes nyan   \t\t\n" |]+-- "catboy goes nyan"+-- >>> stripEnd [ascii| "\n\n    \tcatboy goes nyan   \t\t\n" |]+-- "\n\n    \tcatboy goes nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+stripEnd :: AsciiText -> AsciiText+stripEnd = dropWhileEnd isSpace++-- | @splitAt n t@ is equivalent to @('take' n t, 'drop' n t)@.+--+-- >>> splitAt (-3) [ascii| "catboy" |]+-- ("","catboy")+-- >>> splitAt 0 [ascii| "catboy" |]+-- ("","catboy")+-- >>> splitAt 3 [ascii| "catboy" |]+-- ("cat","boy")+-- >>> splitAt 1000 [ascii| "catboy" |]+-- ("catboy","")+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+{-# INLINE splitAt #-}+splitAt :: Int -> AsciiText -> (AsciiText, AsciiText)+splitAt = coerce BS.splitAt++-- | @breakOn needle haystack@, given a @needle@ of length \(n\) and a+-- @haystack@ of length \(h\), attempts to find the first instance of @needle@+-- in @haystack@. If successful, return a tuple consisting of:+--+-- * The prefix of @haystack@ before the match; and+-- * The rest of @haystack@, starting with the match.+--+-- If the needle is empty, this returns @('empty', haystack)@. If no match can+-- be found, this instead returns @(haystack, 'empty')@.+--+-- If you need to repeatedly split on the same needle, consider 'breakOnAll', as+-- this will be more efficient due to only having to run the matching algorithm+-- once.+--+-- >>> breakOn empty [ascii| "catboy goes nyan" |]+-- ("","catboy goes nyan")+-- >>> breakOn [ascii| "nyan" |] empty+-- ("","")+-- >>> breakOn [ascii| "goes" |] [ascii| "catboy goes nyan" |]+-- ("catboy ","goes nyan")+-- >>> breakOn [ascii| "catboy" |] [ascii| "nyan nyan nyan" |]+-- ("nyan nyan nyan","")+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+breakOn :: AsciiText -> AsciiText -> (AsciiText, AsciiText)+breakOn needle@(AsciiText n) haystack@(AsciiText h)+  | length needle == 0 = (empty, haystack)+  | otherwise = case indices n h of+    [] -> (haystack, empty)+    ix : _ -> splitAt ix haystack++-- | @breakOnEnd needle haystack@, given a @needle@ of length \(n\) and a+-- @haystack@ of length \(h\), attempts to find the last instance of @needle@ in+-- @haystack@. If successful, return a tuple consisting of:+--+-- * The prefix of @haystack@ up to, and including, the match; and+-- * The rest of @haystack@.+--+-- If the needle is empty, this returns @(haystack, 'empty')@. If no match can+-- be found, this instead returns @('empty', haystack)@.+--+-- This function is similar to 'breakOn'. If you need to repeatedly split on the+-- same needle, consider 'breakOnAll', as this will be more efficient due to+-- only having to run the matching algorithm once.+--+-- >>> breakOnEnd empty [ascii| "catboy goes nyan" |]+-- ("catboy goes nyan","")+-- >>> breakOnEnd [ascii| "nyan" |] empty+-- ("","")+-- >>> breakOnEnd [ascii| "goes" |] [ascii| "catboy goes nyan" |]+-- ("catboy goes"," nyan")+-- >>> breakOnEnd [ascii| "catboy" |] [ascii| "nyan nyan nyan" |]+-- ("","nyan nyan nyan")+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+breakOnEnd :: AsciiText -> AsciiText -> (AsciiText, AsciiText)+breakOnEnd needle@(AsciiText n) haystack@(AsciiText h)+  | length needle == 0 = (haystack, empty)+  | otherwise = case go . indices n $ h of+    Nothing -> (empty, haystack)+    Just ix -> splitAt (ix + length needle) haystack+  where+    go :: [Int] -> Maybe Int+    go = \case+      [] -> Nothing+      [i] -> Just i+      (_ : is) -> go is++-- | @break p t@ is equivalent to @('takeWhile' ('not' p) t, 'dropWhile' ('not'+-- p) t)@.+--+-- >>> break ([char| ' ' |] ==) [ascii| "catboy goes nyan" |]+-- ("catboy"," goes nyan")+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+break :: (AsciiChar -> Bool) -> AsciiText -> (AsciiText, AsciiText)+break = coerce BS.break++-- | @span p t@ is equivalent to @('takeWhile' p t, 'dropWhile' p t)@.+--+-- >>> span ([char| 'c' |] ==) [ascii| "catboy goes nyan" |]+-- ("c","atboy goes nyan")+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE [1] span #-}+span :: (AsciiChar -> Bool) -> AsciiText -> (AsciiText, AsciiText)+span = coerce BS.span++-- | Separate a text into a list of texts such that:+--+-- * Their concatenation is equal to the original argument; and+-- * Equal adjacent characters in the original argument are in the same text in+-- the result.+--+-- This is a specialized form of 'groupBy', and is about 40% faster than+-- @'groupBy' '=='@.+--+-- >>> group empty+-- []+-- >>> group . singleton $ [char| 'w' |]+-- ["w"]+-- >>> group [ascii| "nyan" |]+-- ["n","y","a","n"]+-- >>> group [ascii| "nyaaaan" |]+-- ["n","y","aaaa","n"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+group :: AsciiText -> [AsciiText]+group = coerce BS.group++-- | Separate a text into a list of texts such that:+--+-- * Their concatenation is equal to the original argument; and+-- * Adjacent characters for which the function argument returns @True@ are in+-- the same text in the result.+--+-- 'group' is a special case for the function argument '=='; it is also about+-- 40% faster.+--+-- >>> groupBy (<) empty+-- []+-- >>> groupBy (<) . singleton $ [char| 'w' |]+-- ["w"]+-- >>> groupBy (<) [ascii| "catboy goes nyan" |]+-- ["c","atboy"," goes"," nyan"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+groupBy :: (AsciiChar -> AsciiChar -> Bool) -> AsciiText -> [AsciiText]+groupBy = coerce BS.groupBy++-- | All prefixes of the argument, from shortest to longest.+--+-- >>> inits empty+-- [""]+-- >>> inits . singleton $ [char| 'w' |]+-- ["","w"]+-- >>> inits [ascii| "nyan" |]+-- ["","n","ny","nya","nyan"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+inits :: AsciiText -> [AsciiText]+inits = coerce BS.inits++-- | All suffixes of the argument, from shortest to longest.+--+-- >>> tails empty+-- [""]+-- >>> tails . singleton $ [char| 'w' |]+-- ["w",""]+-- >>> tails [ascii| "nyan" |]+-- ["nyan","yan","an","n",""]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+tails :: AsciiText -> [AsciiText]+tails = coerce BS.tails++-- Breaking into many substrings++-- | @splitOn needle haystack@, given a @needle@ of length \(n\) and a haystack+-- of length \(h\), breaks @haystack@ into pieces, separated by @needle@. Any+-- occurrences of @needle@ in @haystack@ are consumed.+--+-- >>> splitOn empty [ascii| "catboy goes nyan and goes nyan" |]+-- ["catboy goes nyan and goes nyan"]+-- >>> splitOn [ascii| "nyan" |] empty+-- [""]+-- >>> splitOn [ascii| "nyan" |] [ascii| "catboy goes nyan and goes nyan" |]+-- ["catboy goes "," and goes ",""]+-- >>> splitOn [ascii| "nyan" |] [ascii| "nyan" |]+-- ["",""]+-- >>> splitOn [ascii| "nyan" |] [ascii| "catboy" |]+-- ["catboy"]+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+splitOn :: AsciiText -> AsciiText -> [AsciiText]+splitOn needle@(AsciiText n) haystack@(AsciiText h)+  | needleLen == 0 = [haystack]+  | length haystack == 0 = [empty]+  | needleLen == 1 = split (== (AsciiChar . BS.head $ n)) haystack+  | otherwise = go 0 (indices n h)+  where+    needleLen :: Int+    needleLen = length needle+    go :: Int -> [Int] -> [AsciiText]+    go pos = \case+      [] -> [drop pos haystack]+      (ix : ixes) ->+        let chunkLen = ix - pos+            segment = take chunkLen . drop pos $ haystack+         in segment : go (pos + chunkLen + needleLen) ixes++-- | @split p t@ separates @t@ into components delimited by separators, for+-- which @p@ returns @True@. The results do not contain the separators.+--+-- \(n\) adjacent separators result in \(n - 1\) empty components in the result.+--+-- >>> split ([char| '~' |] ==) empty+-- []+-- >>> split ([char| '~' |] ==) . singleton $ [char| '~' |]+-- ["",""]+-- >>> split ([char| '~' |] ==) [ascii| "nyan" |]+-- ["nyan"]+-- >>> split ([char| '~' |] ==) [ascii| "~nyan" |]+-- ["","nyan"]+-- >>> split ([char| '~' |] ==) [ascii| "nyan~" |]+-- ["nyan",""]+-- >>> split ([char| '~' |] ==) [ascii| "nyan~nyan"|]+-- ["nyan","nyan"]+-- >>> split ([char| '~' |] ==) [ascii| "nyan~~nyan" |]+-- ["nyan","","nyan"]+-- >>> split ([char| '~' |] ==) [ascii| "nyan~~~nyan" |]+-- ["nyan","","","nyan"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE split #-}+split :: (AsciiChar -> Bool) -> AsciiText -> [AsciiText]+split = coerce BS.splitWith++-- | Splits a text into chunks of the specified length. Equivalent to repeatedly+-- 'take'ing the specified length until exhaustion. The last item in the result+-- may thus be shorter than requested.+--+-- For any @n <= 0@ and any @t@, @chunksOf n t@ yields the empty list. This is+-- identical to the behaviour of the function of the same name in the [text+-- package](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:chunksOf),+-- although it doesn't document this fact.+--+-- >>> chunksOf (-100) [ascii| "I am a catboy" |]+-- []+-- >>> chunksOf (-100) empty+-- []+-- >>> chunksOf 0 [ascii| "I am a catboy" |]+-- []+-- >>> chunksOf 0 empty+-- []+-- >>> chunksOf 1 [ascii| "I am a catboy" |]+-- ["I"," ","a","m"," ","a"," ","c","a","t","b","o","y"]+-- >>> chunksOf 1 empty+-- []+-- >>> chunksOf 2 [ascii| "I am a catboy" |]+-- ["I ","am"," a"," c","at","bo","y"]+-- >>> chunksOf 300 [ascii| "I am a catboy" |]+-- ["I am a catboy"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+chunksOf :: Int -> AsciiText -> [AsciiText]+chunksOf n t+  | n <= 0 = []+  | t == empty = []+  | otherwise = case splitAt n t of+    (h, t') -> h : chunksOf n t'++-- Breaking into lines and words++-- | Identical to the functions of the same name in the [text+-- package](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:lines),+-- and [the+-- Prelude](https://hackage.haskell.org/package/base-4.14.1.0/docs/Prelude.html#v:lines).+-- Specifically, separates the argument into pieces, with LF characters (0x0a) as+-- separators. A single trailing LF is ignored. None of the final results+-- contain LF.+--+-- We chose to follow the same semantics for this function as the text package+-- and the Prelude. This has some consequences,+-- which the documentation of both the text package and the Prelude does not+-- properly explain. We list them here - bear these in mind when using this+-- function, as well as 'unlines':+--+-- * No platform-specific concept of a \'newline\' is ever used by this+-- function. Separation is done on LF, and /only/ LF, regardless of platform.+-- The documentation in both the text package and the Prelude confusingly refers+-- to \'newline characters\', which is a category error. We thus specify that LF+-- is the character being split on, rather than mentioning \'newlines\' in any+-- way, shape or form.+-- * @'unlines' '.' 'lines'@ is /not/ the same as @'Prelude.id'@. This is+-- misleadingly described in the Prelude, which claims that (its version of)+-- @unlines@ is \'an inverse operation\' to (its version of) @lines@. For a+-- precise explanation of why this is the case, please see the documentation for+-- 'unlines'.+-- * @'lines'@ is not the same as @'split' (['char'| \'\n\' |] '==')@. See the+-- doctests below for a demonstration of how they differ.+--+-- >>> lines empty+-- []+-- >>> split ([char| '\n' |] ==) empty+-- []+-- >>> lines [ascii| "catboy goes nyan" |]+-- ["catboy goes nyan"]+-- >>> split ([char| '\n' |] ==) [ascii| "catboy goes nyan" |]+-- ["catboy goes nyan"]+-- >>> lines [ascii| "catboy goes nyan\n" |]+-- ["catboy goes nyan"]+-- >>> split ([char| '\n' |] ==) [ascii| "catboy goes nyan\n" |]+-- ["catboy goes nyan",""]+-- >>> lines [ascii| "\ncatboy\n\n\ngoes\n\nnyan\n\n" |]+-- ["","catboy","","","goes","","nyan",""]+-- >>> split ([char| '\n' |] ==) [ascii| "\ncatboy\n\n\ngoes\n\nnyan\n\n" |]+-- ["","catboy","","","goes","","nyan","",""]+-- >>> lines [ascii| "\r\ncatboy\r\ngoes\r\nnyan\r\n" |]+-- ["\r","catboy\r","goes\r","nyan\r"]+-- >>> split ([char| '\n' |] ==) [ascii| "\r\ncatboy\r\ngoes\r\nnyan\r\n" |]+-- ["\r","catboy\r","goes\r","nyan\r",""]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- /See also:/ [Wikipedia on newlines](https://en.wikipedia.org/wiki/Newline)+--+-- @since 1.0.1+lines :: AsciiText -> [AsciiText]+lines (AsciiText bs) = coerce . go $ bs+  where+    go :: ByteString -> [ByteString]+    go rest = case BS.uncons rest of+      Nothing -> []+      Just _ -> case BS.break (0x0a ==) rest of+        (h, t) ->+          h : case BS.uncons t of+            Nothing -> []+            Just (_, t') -> go t'++-- | Identical to the functions of the same name in the [text+-- package](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:words)+-- and [the+-- Prelude](https://hackage.haskell.org/package/base-4.14.1.0/docs/Prelude.html#v:words).+-- Specifically, separates the argument into pieces, with (non-empty sequences+-- of) word separator characters as separators. A \'word separator character\'+-- is any of the following:+--+-- * TAB (0x09)+-- * LF (0x0a)+-- * VT (0x0b)+-- * FF (0x0c)+-- * CR (0x0d)+-- * Space (0x20)+--+-- None of the final results contain any word separator characters. Any sequence+-- of leading, or trailing, word separator characters will be ignored.+--+-- We chose to follow the same semantics for this function as the text package+-- and the Prelude. This has the consequence that @'unwords' '.' 'words'@ is+-- /not/ the same as 'Prelude.id', although the documentation for the Prelude+-- confusingly describes (its version of) @unwords@ as an \'inverse operation\'+-- to (its version of) @words@. See the documentation for 'unwords' for an+-- explanation of why this is the case.+--+-- >>> words empty+-- []+-- >>> words [ascii| "catboy" |]+-- ["catboy"]+-- >>> words [ascii| "  \r\r\r\rcatboy   \n\rgoes\t\t\t\t\tnyan\n  " |]+-- ["catboy","goes","nyan"]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+words :: AsciiText -> [AsciiText]+words (AsciiText bs) = coerce . go $ bs+  where+    go :: ByteString -> [ByteString]+    go rest =+      let rest' = BS.dropWhile isSep rest+       in case BS.length rest' of+            0 -> []+            _ -> case BS.break isSep rest' of+              (h, t) -> h : go t+    isSep :: Word8 -> Bool+    isSep w8+      | w8 == 32 = True+      | 9 <= w8 && w8 <= 13 = True+      | otherwise = False++-- | Identical to the functions of the same name in the [text+-- package](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:unlines)+-- and [the+-- Prelude](https://hackage.haskell.org/package/base-4.14.1.0/docs/Prelude.html#v:unlines).+-- Specifically, appends an LF character to each of the texts, then concatenates. Equivalent+-- to @'foldMap' (`'snoc'` [char| '\n' |])@.+--+-- We chose to follow the same semantics for this function as the text package+-- and the Prelude. This has some consequences, which the documentation of both+-- the text package and the Prelude does not properly explain. We list them here+-- - bear these in mind when using this function, as well as 'lines':+--+-- * No platform-specific concept of a \'newline\' is ever used by this+-- function. The documentation in both the text package and the Prelude+-- confusing refer to appending a \'terminating newline\', which is only a+-- correct statement on platforms where a newline is LF. We thus specify that we+-- append LF, rather than mentioning \'newlines\' in any way, shape or form.+-- * @'unlines' '.' 'lines'@ is /not/ the same as @'Prelude.id'@. This is+-- misleadingly described in the Prelude, which claims that (its version of)+-- @unlines@ is \'an inverse operation\' to (its version of) @lines@. See the+-- doctests below for a demonstration of this.+--+-- >>> unlines []+-- ""+-- >>> unlines [[ascii| "nyan" |]]+-- "nyan\n"+-- >>> unlines . Prelude.replicate 3 $ [ascii| "nyan" |]+-- "nyan\nnyan\nnyan\n"+-- >>> unlines . lines $ [ascii| "catboy goes nyan" |]+-- "catboy goes nyan\n"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- /See also:/ [Wikipedia on newlines](https://en.wikipedia.org/wiki/Newline)+--+-- @since 1.0.1+unlines :: (Foldable f) => f AsciiText -> AsciiText+unlines = foldMap (`snoc` [char| '\n' |])++-- | Identical to the functions of the same name in the [text+-- package](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text.html#v:unwords)+-- and [the+-- Prelude](https://hackage.haskell.org/package/base-4.14.1.0/docs/Prelude.html#v:unwords).+-- Specifically, links together adjacent texts with a Space character. Equivalent to+-- @'intercalate' [ascii| " " |]@.+--+-- We chose to follow the same semantics for this function as the text package+-- and the Prelude. This has the consequence that @'unwords' '.' 'words'@ is+-- /not/ the same as 'Prelude.id', although the documentation for the Prelude+-- confusingly describes (its version of) @unwords@ as an \'inverse operation\'+-- to (its version of) @words@. See the doctests below for a demonstration of+-- this.+--+-- >>> unwords []+-- ""+-- >>> unwords [[ascii| "nyan" |]]+-- "nyan"+-- >>> unwords . Prelude.replicate 3 $ [ascii| "nyan" |]+-- "nyan nyan nyan"+-- >>> unwords . words $ [ascii| "nyan\nnyan\nnyan" |]+-- "nyan nyan nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+unwords :: [AsciiText] -> AsciiText+unwords = intercalate [ascii| " " |]++-- View patterns++-- | Return 'Just' the suffix of the second text if it has the first text as+-- a prefix, 'Nothing' otherwise.+--+-- >>> stripPrefix [ascii| "catboy" |] empty+-- Nothing+-- >>> stripPrefix empty [ascii| "catboy" |]+-- Just "catboy"+-- >>> stripPrefix [ascii| "nyan" |] [ascii| "nyan" |]+-- Just ""+-- >>> stripPrefix [ascii| "nyan" |] [ascii| "catboy" |]+-- Nothing+-- >>> stripPrefix [ascii| "catboy" |] [ascii| "catboy goes nyan" |]+-- Just " goes nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+stripPrefix :: AsciiText -> AsciiText -> Maybe AsciiText+stripPrefix = coerce BS.stripPrefix++-- | Return 'Just' the prefix of the second text if it has the first text as+-- a suffix, 'Nothing' otherwise.+--+-- >>> stripSuffix [ascii| "catboy" |] empty+-- Nothing+-- >>> stripSuffix empty [ascii| "catboy" |]+-- Just "catboy"+-- >>> stripSuffix [ascii| "nyan" |] [ascii| "nyan" |]+-- Just ""+-- >>> stripSuffix [ascii| "nyan" |] [ascii| "catboy" |]+-- Nothing+-- >>> stripSuffix [ascii| "nyan" |] [ascii| "catboy goes nyan" |]+-- Just "catboy goes "+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+stripSuffix :: AsciiText -> AsciiText -> Maybe AsciiText+stripSuffix = coerce BS.stripSuffix++-- | @stripInfix needle haystack@, given a needle of length \(n\) and a haystack+-- of length \(h\), attempts to find the first instance of @needle@ in+-- @haystack@. If successful, it returns 'Just' the pair consisting of:+--+-- * All the text in @haystack@ before the first instance of @needle@; and+-- * All the text in @haystack@ after, but not including, the first instance of+-- @needle@.+--+-- If there is no instance of @needle@ in @haystack@, this returns 'Nothing'.+--+-- >>> stripInfix [ascii| "catboy" |] empty+-- Nothing+-- >>> stripInfix empty [ascii| "nyan catboy nyan nyan" |]+-- Nothing+-- >>> stripInfix [ascii| "catboy" |] [ascii| "catboy" |]+-- Just ("","")+-- >>> stripInfix [ascii| "catboy" |] [ascii| "nyan catboy" |]+-- Just ("nyan ","")+-- >>> stripInfix [ascii| "catboy" |] [ascii| "catboy nyan" |]+-- Just (""," nyan")+-- >>> stripInfix [ascii| "catboy" |] [ascii| "nyan catboy nyan nyan" |]+-- Just ("nyan "," nyan nyan")+-- >>> stripInfix [ascii| "nyan" |] [ascii| "nyanyanyan" |]+-- Just ("","yanyan")+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+stripInfix :: AsciiText -> AsciiText -> Maybe (AsciiText, AsciiText)+stripInfix needle@(AsciiText n) haystack@(AsciiText h)+  | P.min (length needle) (length haystack) == 0 = Nothing+  | otherwise = case indices n h of+    [] -> Nothing+    (ix : _) -> Just (take ix haystack, drop (ix + length needle) haystack)++-- | Find the longest non-empty common prefix of the arguments and return it,+-- along with the remaining suffixes of both arguments. If the arguments lack a+-- common, non-empty prefix, returns 'Nothing'.+--+-- >>> commonPrefixes empty [ascii| "catboy" |]+-- Nothing+-- >>> commonPrefixes [ascii| "catboy" |] empty+-- Nothing+-- >>> commonPrefixes [ascii| "catboy" |] [ascii| "nyan" |]+-- Nothing+-- >>> commonPrefixes [ascii| "catboy" |] [ascii| "catboy" |]+-- Just ("catboy","","")+-- >>> commonPrefixes [ascii| "nyan" |] [ascii| "nyan nyan" |]+-- Just ("nyan",""," nyan")+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+commonPrefixes :: AsciiText -> AsciiText -> Maybe (AsciiText, AsciiText, AsciiText)+commonPrefixes (AsciiText t1) (AsciiText t2) =+  go2 <$> F.foldl' go Nothing [0 .. P.min (BS.length t1) (BS.length t2) - 1]+  where+    go :: Maybe Int -> Int -> Maybe Int+    go acc i+      | BS.index t1 i == BS.index t2 i = Just i+      | otherwise = acc+    go2 :: Int -> (AsciiText, AsciiText, AsciiText)+    go2 i = case BS.splitAt (i + 1) t1 of+      (h, t) -> coerce (h, t, BS.drop (i + 1) t2)++-- Searching++-- | Return the text comprised of all the characters that satisfy the function+-- argument (that is, for which it returns 'True'), in the same order as in the+-- original.+--+-- >>> filter ([char| 'n' |] ==) empty+-- ""+-- >>> filter ([char| 'n' |] ==) [ascii| "catboy" |]+-- ""+-- >>> filter ([char| 'n' |] ==) [ascii| "nyan" |]+-- "nn"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE filter #-}+filter :: (AsciiChar -> Bool) -> AsciiText -> AsciiText+filter = coerce BS.filter++-- | @breakOnAll needle haystack@, given a @needle@ of length \(n\) and a+-- @haystack@ of length \(h\), finds all non-overlapping instances of @needle@+-- in @haystack@. Each result consists of the following elements:+--+-- * The prefix prior to the match; and+-- * The match, followed by the rest of the string.+--+-- If given an empty needle, the result is a singleton list containing a pair of+-- the entire haystack and the empty text. If given an empty haystack, the+-- result is an empty list.+--+-- >>> breakOnAll empty [ascii| "nyan nyan nyan" |]+-- [("nyan nyan nyan","")]+-- >>> breakOnAll [ascii| "nyan" |] empty+-- []+-- >>> breakOnAll [ascii| "nyan" |] [ascii| "nyan" |]+-- [("","nyan")]+-- >>> breakOnAll [ascii| "nyan" |] [ascii| "nyan nyan nyan" |]+-- [("","nyan nyan nyan"),("nyan ","nyan nyan"),("nyan nyan ","nyan")]+-- >>> breakOnAll [ascii| "nyan" |] [ascii| "nyanyanyan" |]+-- [("","nyanyanyan"),("nyanya","nyan")]+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+breakOnAll :: AsciiText -> AsciiText -> [(AsciiText, AsciiText)]+breakOnAll needle@(AsciiText n) haystack@(AsciiText h)+  | length needle == 0 = [(haystack, empty)]+  | length haystack == 0 = []+  | otherwise = (`splitAt` haystack) <$> indices n h++-- | Returns 'Just' the first character in the text satisfying the predicate,+-- 'Nothing' otherwise.+--+-- >>> find ([char| 'n' |] ==) empty+-- Nothing+-- >>> find ([char| 'n' |] ==) [ascii| "catboy" |]+-- Nothing+-- >>> find ([char| 'n' |] ==) [ascii| "nyan" |]+-- Just '0x6e'+-- >>> find ([char| 'n' |] /=) [ascii| "nyan" |]+-- Just '0x79'+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE find #-}+find :: (AsciiChar -> Bool) -> AsciiText -> Maybe AsciiChar+find = coerce BS.find++-- | @partition p t@ is equivalent to @('filter' p t, 'filter' ('not' p) t)@.+--+-- >>> partition ([char| 'n' |] ==) empty+-- ("","")+-- >>> partition ([char| 'n' |] ==) . singleton $ [char| 'n' |]+-- ("n","")+-- >>> partition ([char| 'n' |] ==) . singleton $ [char| 'w' |]+-- ("","w")+-- >>> partition ([char| 'n' |] ==) [ascii| "nyan!" |]+-- ("nn","ya!")+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+partition :: (AsciiChar -> Bool) -> AsciiText -> (AsciiText, AsciiText)+partition = coerce BS.partition++-- Indexing++-- | Retrieve the ASCII character at the given position in the text. Indexes+-- begin from 0. If the index provided is invalid (that is, less than 0, equal+-- to the length of the text, or greater), return 'Nothing'; otherwise, return+-- 'Just' the character at that position.+--+-- >>> index [ascii| "nyan nyan nyan" |] (-100)+-- Nothing+-- >>> index [ascii| "nyan nyan nyan" |] 0+-- Just '0x6e'+-- >>> index [ascii| "nyan nyan nyan" |] 5+-- Just '0x6e'+-- >>> index [ascii| "nyan nyan nyan" |] 2000+-- Nothing+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+index :: AsciiText -> Int -> Maybe AsciiChar+index at i+  | i < 0 || i >= length at = Nothing+  | otherwise = Just . coerce BS.index at $ i++-- | Returns 'Just' the first index in the text such that the character at that+-- index satisfies the predicate, 'Nothing' otherwise.+--+-- >>> findIndex ([char| 'n' |] ==) empty+-- Nothing+-- >>> findIndex ([char| 'n' |] ==) . singleton $ [char| 'n' |]+-- Just 0+-- >>> findIndex ([char| 'n' |] ==) . singleton $ [char| 'w' |]+-- Nothing+-- >>> findIndex ([char| 'n' |] ==) [ascii| "nyan" |]+-- Just 0+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+{-# INLINE [1] findIndex #-}+findIndex :: (AsciiChar -> Bool) -> AsciiText -> Maybe Int+findIndex = coerce BS.findIndex++-- | @count needle haystack@, given a @needle@ of length \(n\) and a haystack of+-- length \(h\), counts the number of non-overlapping occurrences of @needle@ in+-- @haystack@. If @needle@ is empty, the count will be 0.+--+-- >>> count empty [ascii| "nyan nyan nyan" |]+-- 0+-- >>> count [ascii| "nyan" |] empty+-- 0+-- >>> count [ascii| "nyan" |] [ascii| "nyan" |]+-- 1+-- >>> count [ascii| "nyan" |] [ascii| "nyan nyan nyan" |]+-- 3+-- >>> count [ascii| "nyan" |] [ascii| "nyanyanyan" |]+-- 2+--+-- = On complexity+--+-- This function is based on a variant of the+-- [NSN](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html) algorithm,+-- except it does not detect overlapping needles. Its average-case analysis is+-- based on the assumption that:+--+-- * All ASCII symbols are equally likely to occur in both the needle and the+-- haystack; and+-- * The needle has length at least two; and+-- * Both the needle and the haystack contain at least four unique symbols.+--+-- We fall back to 'split' for singleton needles, and there is no work to be+-- done on empty needles, which means the second assumption always holds.+--+-- Worst-case behaviour becomes more likely the more your input satisfies the+-- following conditions:+--+-- * The needle and/or haystack use few unique symbols (less than four is the+-- worst); or+-- * The haystack contains many instances of the second symbol of the needle+-- which don't lead to full matches.+--+-- /Complexity:/ \(\Theta(h)\) average case, \(\Theta(h \cdot n\)\) worst-case.+--+-- /See also:/ Note that all the below are references for the original+-- algorithm, which includes searching for overlapping needles. Thus, our+-- implementation will perform better than the analysis suggests.+--+-- * [Description and pseudocode](https://www-igm.univ-mlv.fr/~lecroq/string/node13.html)+-- * ["Algorithms on Strings"](https://www.cambridge.org/core/books/algorithms-on-strings/19049704C876795D95D8882C73257C70) by Crochemore, Hancart and Lecroq. PDF available [here](https://www.researchgate.net/publication/220693689_Algorithms_on_Strings).+--+-- @since 1.0.1+count :: AsciiText -> AsciiText -> Int+count needle@(AsciiText n) haystack@(AsciiText h)+  | P.min (length needle) (length haystack) == 0 = 0+  | length needle == 1 = BS.count (BS.head n) h+  | otherwise = P.length . indices n $ h++-- Zipping++-- | \'Pair off\' characters in both texts at corresponding indices. The result+-- will be limited to the shorter of the two arguments.+--+-- >>> zip empty [ascii| "catboy" |]+-- []+-- >>> zip [ascii| "catboy" |] empty+-- []+-- >>> zip [ascii| "catboy" |] [ascii| "nyan" |]+-- [('0x63','0x6e'),('0x61','0x79'),('0x74','0x61'),('0x62','0x6e')]+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+zip :: AsciiText -> AsciiText -> [(AsciiChar, AsciiChar)]+zip = coerce BS.zip++-- | Combine two texts together in lockstep to produce a new text, using the+-- provided function to combine ASCII characters at each step. The length of the+-- result will be the minimum of the lengths of the two text arguments.+--+-- >>> zipWith max [ascii| "I am a catboy" |] empty+-- ""+-- >>> zipWith max empty [ascii| "I am a catboy" |]+-- ""+-- >>> zipWith max [ascii| "I am a catboy" |] [ascii| "Nyan nyan nyan nyan nyan" |]+-- "Nyan nycntnyy"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+zipWith ::+  (AsciiChar -> AsciiChar -> AsciiChar) -> AsciiText -> AsciiText -> AsciiText+zipWith f t1 t2 = unfoldr go (t1, t2)+  where+    go :: (AsciiText, AsciiText) -> Maybe (AsciiChar, (AsciiText, AsciiText))+    go (acc1, acc2) = do+      (h1, t1') <- uncons acc1+      (h2, t2') <- uncons acc2+      pure (f h1 h2, (t1', t2'))++-- Conversions++-- | Try and convert a 'Text' into an 'AsciiText'. Gives 'Nothing' if the 'Text'+-- contains any symbols which lack an ASCII equivalent.+--+-- >>> fromText "catboy"+-- Just "catboy"+-- >>> fromText "😺😺😺😺😺"+-- Nothing+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+fromText :: Text -> Maybe AsciiText+fromText t = case T.find (not . isAscii) t of+  Nothing -> pure . AsciiText . encodeUtf8 $ t+  Just _ -> Nothing++-- | Try and convert a 'ByteString' into an 'AsciiText'. Gives 'Nothing' if the+-- 'ByteString' contains any bytes outside the ASCII range (that is, from 0 to+-- 127 inclusive).+--+-- >>> fromByteString "catboy"+-- Just "catboy"+-- >>> fromByteString . BS.pack $ [128]+-- Nothing+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+fromByteString :: ByteString -> Maybe AsciiText+fromByteString bs = case BS.find (> 127) bs of+  Nothing -> pure . AsciiText $ bs+  Just _ -> Nothing++-- | Convert an 'AsciiText' into a 'Text' (by copying).+--+-- >>> toText empty+-- ""+-- >>> toText . singleton $ [char| 'w' |]+-- "w"+-- >>> toText [ascii| "nyan" |]+-- "nyan"+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.0+toText :: AsciiText -> Text+toText (AsciiText bs) = decodeUtf8 bs++-- | Reinterpret an 'AsciiText' as a 'ByteString' (without copying).+--+-- >>> toByteString empty+-- ""+-- >>> toByteString . singleton $ [char| 'w' |]+-- "w"+-- >>> toByteString [ascii| "nyan" |]+-- "nyan"+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.0+toByteString :: AsciiText -> ByteString+toByteString = coerce++-- Optics++-- | A convenient demonstration of the relationship between 'toText' and+-- 'fromText'.+--+-- >>> preview textWise "catboy goes nyan"+-- Just "catboy goes nyan"+-- >>> preview textWise "😺😺😺😺😺"+-- Nothing+-- >>> review textWise [ascii| "catboys are amazing" |]+-- "catboys are amazing"+--+-- @since 1.0.0+textWise :: Prism' Text AsciiText+textWise = prism' toText fromText++-- | A convenient demonstration of the relationship between 'toByteString' and+-- 'fromByteString'.+--+-- >>> preview byteStringWise "catboy goes nyan"+-- Just "catboy goes nyan"+-- >>> preview byteStringWise . BS.pack $ [0xff, 0xff]+-- Nothing+-- >>> review byteStringWise [ascii| "I love catboys" |]+-- "I love catboys"+--+-- @since 1.0.0+byteStringWise :: Prism' ByteString AsciiText+byteStringWise = prism' toByteString fromByteString++-- | Pack (or unpack) a list of ASCII characters into a text.+--+-- >>> view packedChars [[char| 'n' |], [char| 'y' |], [char| 'a' |], [char| 'n' |]]+-- "nyan"+-- >>> review packedChars [ascii| "nyan" |]+-- ['0x6e','0x79','0x61','0x6e']+--+-- @since 1.0.1+packedChars :: Iso' [AsciiChar] AsciiText+packedChars =+  coerceS . coerceT . coerceA . coerceB $ BSO.packedBytes @ByteString++-- | Traverse the individual ASCII characters in a text.+--+-- >>> preview (elementOf chars 0) [ascii| "I am a catboy" |]+-- Just '0x49'+-- >>> preview (elementOf chars 100) [ascii| "I am a catboy" |]+-- Nothing+-- >>> iover chars (\i x -> bool x [char| 'w' |] . even $ i) [ascii| "I am a catboy" |]+-- "w wmwawcwtwow"+--+-- @since 1.0.1+chars :: IxTraversal' Int64 AsciiText AsciiChar+chars = coerceS . coerceT . coerceA . coerceB $ BSO.bytes @ByteString++-- | Pack (or unpack) a list of bytes into a text. This isn't as capable as+-- 'packedChars', as that would allow construction of invalid texts.+--+-- >>> preview packedBytes [0x6e, 0x79, 0x61, 0x6e]+-- Just "nyan"+-- >>> preview packedBytes [0xff, 0xfe]+-- Nothing+-- >>> review packedBytes [ascii| "nyan" |]+-- [110,121,97,110]+--+-- @since 1.0.1+packedBytes :: Prism' [Word8] AsciiText+packedBytes = prism' (review go) (P.fmap (view go2) . P.traverse asciify)+  where+    go :: Review [Word8] AsciiText+    go = castOptic . coerceA . coerceB $ BSO.packedBytes @ByteString+    go2 :: Getter [Word8] AsciiText+    go2 = castOptic . coerceA . coerceB $ BSO.packedBytes @ByteString++-- | Access the individual bytes in a text. This isn't as capable as 'chars', as+-- that would allow modifications of the bytes in ways that aren't valid as+-- ASCII.+--+-- >>> itoListOf bytes [ascii| "I am a catboy" |]+-- [(0,73),(1,32),(2,97),(3,109),(4,32),(5,97),(6,32),(7,99),(8,97),(9,116),(10,98),(11,111),(12,121)]+--+-- @since 1.0.1+bytes :: IxFold Int64 AsciiText Word8+bytes = castOptic . coerceS . coerceT $ BSO.bytes @ByteString++-- Helpers++isSpace :: AsciiChar -> Bool+isSpace (AsciiChar w8)+  | w8 == 32 = True+  | 9 <= w8 && w8 <= 13 = True+  | otherwise = False++asciify :: Word8 -> Maybe Word8+asciify w8+  | w8 <= 127 = Just w8+  | otherwise = Nothing++indices :: ByteString -> ByteString -> [Int]+indices needle haystack+  | P.min needleLen haystackLen == 0 = []+  | needleLen == 1 = BS.elemIndices (BS.head needle) haystack+  | otherwise = L.unfoldr go 0+  where+    go :: Int -> Maybe (Int, Int)+    go j+      | j > (haystackLen - needleLen) = Nothing+      | BS.index needle 1 /= BS.index haystack (j + 1) = go (j + kay)+      | otherwise = do+        let fragment = BS.take needleLen . BS.drop j $ haystack+        if fragment == needle+          then pure (j, j + needleLen)+          else go (j + ell)+    kay :: Int+    kay+      | BS.head needle == BS.index needle 1 = 2+      | otherwise = 1+    ell :: Int+    ell+      | BS.head needle == BS.index needle 1 = 1+      | otherwise = 2+    needleLen :: Int+    needleLen = BS.length needle+    haystackLen :: Int+    haystackLen = BS.length haystack
src/Text/Ascii/Char.hs view
@@ -58,6 +58,8 @@ -- $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.@@ -357,11 +359,25 @@  -- | 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' (chr . fromIntegral . toByte) 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
src/Text/Ascii/Internal.hs view
@@ -6,16 +6,47 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} +-- |+-- Module: Text.Ascii.Internal+-- Copyright: (C) 2021 Koz Ross+-- License: Apache 2.0+-- Maintainer: Koz Ross <koz.ross@retro-freedom.nz>+-- Stability: unstable, not subject to PVP+-- Portability: GHC only+--+-- This is an internal module, and is /not/ subject to the PVP. It can change+-- in any way, at any time, and should not be depended on unless you know+-- /exactly/ what you are doing. You have been warned. module Text.Ascii.Internal where  import Control.DeepSeq (NFData) import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.CaseInsensitive (FoldCase (foldCase)) import Data.Char (chr, isAscii) import Data.Coerce (coerce) import Data.Hashable (Hashable)+import qualified Data.List.NonEmpty as NE import Data.Word (Word8) import GHC.Exts (IsList (Item, fromList, fromListN, toList)) import Numeric (showHex)+import Optics.AffineTraversal (An_AffineTraversal, atraversal)+import Optics.At.Core (Index, IxValue, Ixed (IxKind, ix))+import Text.Megaparsec.Stream+  ( Stream+      ( Token,+        Tokens,+        chunkLength,+        chunkToTokens,+        take1_,+        takeN_,+        takeWhile_,+        tokenToChunk,+        tokensToChunk+      ),+    TraversableStream (reachOffset),+    VisualStream (showTokens),+  ) import Type.Reflection (Typeable)  -- | Represents valid ASCII characters, which are bytes from @0x00@ to @0x7f@.@@ -45,9 +76,18 @@  -- | @since 1.0.0 instance Bounded AsciiChar where+  {-# INLINEABLE minBound #-}   minBound = AsciiChar 0+  {-# INLINEABLE maxBound #-}   maxBound = AsciiChar 127 +-- | @since 1.0.1+instance FoldCase AsciiChar where+  {-# INLINEABLE foldCase #-}+  foldCase ac@(AsciiChar w8)+    | 65 <= w8 && w8 <= 90 = AsciiChar (w8 + 32)+    | otherwise = ac+ -- | View an 'AsciiChar' as its underlying byte. You can pattern match on this, -- but since there are more bytes than valid ASCII characters, you cannot use -- this to construct.@@ -103,6 +143,70 @@       . coerce @[AsciiChar] @[Word8]   {-# INLINEABLE toList #-}   toList = coerce . toList . coerce @AsciiText @ByteString++-- | @since 1.0.1+type instance Index AsciiText = Int++-- | @since 1.0.1+type instance IxValue AsciiText = AsciiChar++-- | @since 1.0.1+instance Ixed AsciiText where+  type IxKind AsciiText = An_AffineTraversal+  {-# INLINEABLE ix #-}+  ix i = atraversal get put+    where+      get :: AsciiText -> Either AsciiText AsciiChar+      get (AsciiText at) = case at BS.!? i of+        Nothing -> Left . AsciiText $ at+        Just w8 -> Right . AsciiChar $ w8+      put :: AsciiText -> AsciiChar -> AsciiText+      put (AsciiText at) (AsciiChar ac) = case BS.splitAt i at of+        (lead, end) -> case BS.uncons end of+          Nothing -> AsciiText at+          Just (_, end') -> AsciiText (lead <> BS.singleton ac <> end')++-- | @since 1.0.1+instance FoldCase AsciiText where+  {-# INLINEABLE foldCase #-}+  foldCase (AsciiText bs) = AsciiText . BS.map go $ bs+    where+      go :: Word8 -> Word8+      go w8+        | 65 <= w8 && w8 <= 90 = w8 + 32+        | otherwise = w8++-- | @since 1.0.1+instance Stream AsciiText where+  type Token AsciiText = AsciiChar+  type Tokens AsciiText = AsciiText+  {-# INLINEABLE tokenToChunk #-}+  tokenToChunk _ = coerce BS.singleton+  {-# INLINEABLE tokensToChunk #-}+  tokensToChunk _ = fromList+  {-# INLINEABLE chunkToTokens #-}+  chunkToTokens _ = toList+  {-# INLINEABLE chunkLength #-}+  chunkLength _ = coerce BS.length+  {-# INLINEABLE take1_ #-}+  take1_ = coerce BS.uncons+  {-# INLINEABLE takeN_ #-}+  takeN_ n at@(AsciiText bs)+    | n <= 0 = Just (coerce BS.empty, at)+    | BS.length bs == 0 = Nothing+    | otherwise = Just . coerce . BS.splitAt n $ bs+  {-# INLINEABLE takeWhile_ #-}+  takeWhile_ = coerce BS.span++-- | @since 1.0.1+instance VisualStream AsciiText where+  {-# INLINEABLE showTokens #-}+  showTokens _ = fmap (chr . fromIntegral) . coerce @_ @[Word8] . NE.toList++-- | @since 1.0.1+instance TraversableStream AsciiText where+  {-# INLINEABLE reachOffset #-}+  reachOffset o ps = coerce (reachOffset o ps)  -- Helpers 
src/Text/Ascii/QQ.hs view
@@ -1,6 +1,17 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE Trustworthy #-} +-- |+-- Module: Text.Ascii.QQ+-- Copyright: (C) 2021 Koz Ross+-- License: Apache 2.0+-- Maintainer: Koz Ross <koz.ross@retro-freedom.nz>+-- Stability: unstable, not subject to PVP+-- Portability: GHC only+--+-- This is an internal module, and is /not/ subject to the PVP. It can change+-- in any way, at any time, and should not be depended on unless you know+-- /exactly/ what you are doing. You have been warned. module Text.Ascii.QQ where  import Data.ByteString (ByteString)@@ -13,6 +24,7 @@     ord,   ) import Data.Functor (void)+import Data.Void (Void) import GHC.Exts (IsList (fromList)) import Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter)) import Language.Haskell.TH.Syntax@@ -24,7 +36,7 @@     Type,   ) import Text.Ascii.Internal (AsciiChar (AsciiChar), AsciiText (AsciiText))-import Text.Parsec+import Text.Megaparsec   ( Parsec,     between,     eof,@@ -33,10 +45,11 @@     oneOf,     parse,     satisfy,-    spaces,+    single,     try,   )-import qualified Text.Parsec as Parsec+import Text.Megaparsec.Char (space)+import Text.Megaparsec.Error (errorBundlePretty)  -- $setup -- >>> :set -XQuasiQuotes@@ -74,7 +87,7 @@  asciiQQ :: String -> Q Exp asciiQQ input = case parse (between open close go) "" input of-  Left err -> fail . show $ err+  Left err -> fail . errorBundlePretty $ err   Right result ->     pure       . AppE (ConE 'AsciiText)@@ -84,12 +97,12 @@       . BS.unpack       $ result   where-    open :: Parsec String () ()-    open = spaces *> (void . Parsec.char $ '"')-    close :: Parsec String () ()-    close = Parsec.char '"' *> spaces *> eof-    go :: Parsec String () ByteString-    go = BS.pack <$> manyTill asciiByte (lookAhead . try . Parsec.char $ '"')+    open :: Parsec Void String ()+    open = space *> (void . single $ '"')+    close :: Parsec Void String ()+    close = single '"' *> space *> eof+    go :: Parsec Void String ByteString+    go = BS.pack <$> manyTill asciiByte (lookAhead . try . single $ '"')     asciiByte = do       c <- satisfy isAscii       case c of@@ -110,15 +123,15 @@  charQQ :: String -> Q Exp charQQ input = case parse (between open close go) "" input of-  Left err -> fail . show $ err+  Left err -> fail . errorBundlePretty $ err   Right result ->     pure . AppE (ConE 'AsciiChar) . LitE . IntegerL . fromIntegral $ result   where-    open :: Parsec String () ()-    open = spaces *> (void . Parsec.char $ '\'')-    close :: Parsec String () ()-    close = Parsec.char '\'' *> spaces *> eof-    go :: Parsec String () Int+    open :: Parsec Void String ()+    open = space *> (void . single $ '\'')+    close :: Parsec Void String ()+    close = single '\'' *> space *> eof+    go :: Parsec Void String Int     go = do       c1 <- satisfy isValidLead       case c1 of
+ src/Text/Ascii/Unsafe.hs view
@@ -0,0 +1,389 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++-- |+-- Module: Text.Ascii.Unsafe+-- Copyright: (C) 2021 Koz Ross+-- License: Apache 2.0+-- Maintainer: Koz Ross <koz.ross@retro-freedom.nz>+-- Stability: stable+-- Portability: GHC only+--+-- A wrapper for partial type class instances and functions.+--+-- This module is designed for qualified importing:+--+-- > import qualified Text.Ascii.Unsafe as Unsafe+module Text.Ascii.Unsafe+  ( -- * Types+    Unsafe (..),++    -- * Text functions+    head,+    last,+    tail,+    init,+    foldl1,+    foldl1',+    foldr1,+    foldr1',+    maximum,+    minimum,+    scanl1,+    scanr1,+    index,+  )+where++import Control.DeepSeq (NFData)+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.CaseInsensitive (FoldCase)+import Data.Coerce (coerce)+import Data.Hashable (Hashable)+import Data.Kind (Type)+import Data.Word (Word8)+import GHC.Exts (IsList)+import GHC.Read (expectP, lexP, parens, readPrec)+import Text.Ascii.Internal (AsciiChar (AsciiChar), AsciiText (AsciiText))+import Text.Megaparsec.Stream (Stream, TraversableStream, VisualStream)+import Text.ParserCombinators.ReadPrec (ReadPrec)+import Text.Read (Lexeme (Char))+import Type.Reflection (Typeable)+import Prelude hiding+  ( foldl1,+    foldr1,+    head,+    init,+    last,+    maximum,+    minimum,+    scanl1,+    scanr1,+    tail,+  )++-- | A wrapper for a type, designating that partial type class methods or other+-- functions are available for it.+--+-- The role of 'Unsafe''s type argument is set to nominal. Among other things,+-- it means that this type can't be coerced or derived through. This ensures+-- clear indication when (and to what extent) non-total operations occur in any+-- code using them.+--+-- @since 1.0.1+newtype Unsafe (a :: Type) = Unsafe {safe :: a}+  deriving+    ( -- | @since 1.0.1+      Eq,+      -- | @since 1.0.1+      Ord,+      -- | @since 1.0.1+      Bounded,+      -- | @since 1.0.1+      Hashable,+      -- | @since 1.0.1+      NFData,+      -- | @since 1.0.1+      FoldCase,+      -- | @since 1.0.1+      Semigroup,+      -- | @since 1.0.1+      Monoid,+      -- | @since 1.0.1+      IsList,+      -- | @since 1.0.1+      Stream,+      -- | @since 1.0.1+      VisualStream,+      -- | @since 1.0.1+      TraversableStream,+      -- | @since 1.0.1+      Show+    )+    via a+  deriving stock+    ( -- | @since 1.0.1+      Typeable,+      -- | @since 1.0.1+      Functor+    )++type role Unsafe nominal++-- | @since 1.0.1+instance Read (Unsafe AsciiChar) where+  {-# INLINEABLE readPrec #-}+  readPrec = parens go+    where+      go :: ReadPrec (Unsafe AsciiChar)+      go =+        Unsafe . AsciiChar <$> do+          expectP (Char '\'')+          expectP (Char '0')+          expectP (Char 'x')+          Char d1 <- lexP+          Char d2 <- lexP+          expectP (Char '\'')+          case d1 of+            '0' -> fromSecondDigit d2+            '1' -> (16 +) <$> fromSecondDigit d2+            '2' -> (32 +) <$> fromSecondDigit d2+            '3' -> (48 +) <$> fromSecondDigit d2+            '4' -> (64 +) <$> fromSecondDigit d2+            '5' -> (80 +) <$> fromSecondDigit d2+            '6' -> (96 +) <$> fromSecondDigit d2+            '7' -> (112 +) <$> fromSecondDigit d2+            _ -> fail $ "Expected digit from 0 to 7, instead got '" <> [d1] <> "'"++-- | @since 1.0.1+instance Enum (Unsafe AsciiChar) where+  {-# INLINEABLE succ #-}+  succ (Unsafe (AsciiChar w8))+    | w8 < 127 = Unsafe . AsciiChar $ w8 + 1+    | otherwise = error "Out of range for ASCII character"+  {-# INLINEABLE pred #-}+  pred (Unsafe (AsciiChar w8))+    | w8 > 0 = Unsafe . AsciiChar $ w8 - 1+    | otherwise = error "Out of range for ASCII character"+  {-# INLINEABLE toEnum #-}+  toEnum n+    | 0 <= n && n <= 127 = Unsafe . AsciiChar . fromIntegral $ n+    | otherwise = error "Out of range for ASCII character"+  {-# INLINEABLE fromEnum #-}+  fromEnum (Unsafe (AsciiChar w8)) = fromIntegral w8+  {-# INLINEABLE enumFrom #-}+  enumFrom (Unsafe (AsciiChar w8)) = coerce [w | w <- [w8 ..], w <= 127]+  {-# INLINEABLE enumFromThen #-}+  enumFromThen (Unsafe (AsciiChar start)) (Unsafe (AsciiChar step)) =+    coerce [w | w <- [start, step ..], w <= 127]+  {-# INLINEABLE enumFromTo #-}+  enumFromTo (Unsafe (AsciiChar start)) (Unsafe (AsciiChar end)) =+    coerce [w | w <- [start .. end], w <= 127]+  {-# INLINEABLE enumFromThenTo #-}+  enumFromThenTo (Unsafe (AsciiChar start)) (Unsafe (AsciiChar step)) (Unsafe (AsciiChar end)) =+    coerce [w | w <- [start, step .. end], w <= 127]++-- | @since 1.0.1+instance Read (Unsafe AsciiText) where+  {-# INLINEABLE readPrec #-}+  readPrec = Unsafe . AsciiText <$> go+    where+      go :: ReadPrec ByteString+      go = do+        bs :: ByteString <- readPrec+        case BS.findIndex (>= 128) bs of+          Nothing -> pure bs+          Just i -> error $ "Non-ASCII byte at index " <> show i++-- Functions++-- $setup+-- >>> :set -XNoImplicitPrelude+-- >>> :set -XQuasiQuotes+-- >>> import Text.Ascii.Unsafe+-- >>> import Text.Ascii (ascii)+-- >>> import Prelude ((.), ($))++-- | Yield the first character of the text.+--+-- /Requirements:/ Text is not empty.+--+-- >>> head . Unsafe $ [ascii| "catboy" |]+-- '0x63'+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+head :: Unsafe AsciiText -> AsciiChar+head = coerce BS.head++-- | Yield the last character of the text.+--+-- /Requirements:/ Text is not empty.+--+-- >>> last . Unsafe $ [ascii| "catboy" |]+-- '0x79'+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+last :: Unsafe AsciiText -> AsciiChar+last = coerce BS.last++-- | Yield the text without its first character.+--+-- /Requirements:/ Text is not empty.+--+-- >>> tail . Unsafe $ [ascii| "catboy" |]+-- "atboy"+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+tail :: Unsafe AsciiText -> Unsafe AsciiText+tail = coerce BS.tail++-- | Yield the text without its last character.+--+-- /Requirements:/ Text is not empty.+--+-- >>> init . Unsafe $ [ascii| "catboy" |]+-- "catbo"+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+init :: Unsafe AsciiText -> Unsafe AsciiText+init = coerce BS.init++-- | Left-associative fold of a text without a base case.+--+-- /Requirements:/ Text is not empty.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+foldl1 :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar+foldl1 = coerce BS.foldl1++-- | Left-associative fold of a text without a base case, strict in the+-- accumulator.+--+-- /Requirements:/ Text is not empty.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+foldl1' :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar+foldl1' = coerce BS.foldl1'++-- | Right-associative fold of a text without a base case.+--+-- /Requirements:/ Text is not empty.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+foldr1 :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar+foldr1 = coerce BS.foldr1++-- | Right-associative fold of a text without a base case, strict in the+-- accumulator.+--+-- /Requirements:/ Text is not empty.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+foldr1' :: (AsciiChar -> AsciiChar -> AsciiChar) -> Unsafe AsciiText -> AsciiChar+foldr1' = coerce BS.foldr1'++-- | Yield the character in the text whose byte representation is numerically+-- the largest.+--+-- /Requirements:/ Text is not empty.+--+-- >>> maximum . Unsafe $ [ascii| "catboy" |]+-- '0x79'+-- >>> maximum . Unsafe $ [ascii| "nyan~" |]+-- '0x7e'+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+maximum :: Unsafe AsciiText -> AsciiChar+maximum = coerce BS.maximum++-- | Yield the character in the text whose byte representation is numerically+-- the smallest.+--+-- /Requirements:/ Text is not empty.+--+-- >>> minimum . Unsafe $ [ascii| "catboy" |]+-- '0x61'+-- >>> minimum . Unsafe $ [ascii| " nyan" |]+-- '0x20'+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+minimum :: Unsafe AsciiText -> AsciiChar+minimum = coerce BS.minimum++-- | 'scanl1' is similar to 'foldl1', but returns a list of successive values+-- from the left.+--+-- /Requirements:/ Text is not empty.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+scanl1 ::+  -- | accumulator -> element -> new accumulator+  (AsciiChar -> AsciiChar -> AsciiChar) ->+  -- | Input of length \(n\)+  Unsafe AsciiText ->+  -- | Output of length \(n - 1\)+  Unsafe AsciiText+scanl1 = coerce BS.scanl1++-- | 'scanr1' is similar to 'foldr1', but returns a list of successive values+-- from the right.+--+-- /Requirements:/ Text is not empty.+--+-- /Complexity:/ \(\Theta(n)\)+--+-- @since 1.0.1+scanr1 ::+  -- | element -> accumulator -> new accumulator+  (AsciiChar -> AsciiChar -> AsciiChar) ->+  -- | Input of length \(n\)+  Unsafe AsciiText ->+  -- | Output of length \(n - 1\)+  Unsafe AsciiText+scanr1 = coerce BS.scanr1++-- | Yield the character at the given position.+--+-- /Requirements:/ The position must be at least 0, and at most the length of+-- the text - 1.+--+-- >>> index (Unsafe [ascii| "catboy" |]) 0+-- '0x63'+-- >>> index (Unsafe $ [ascii| "catboy" |]) 4+-- '0x6f'+--+-- /Complexity:/ \(\Theta(1)\)+--+-- @since 1.0.1+index :: Unsafe AsciiText -> Int -> AsciiChar+index = coerce BS.index++-- Helpers++fromSecondDigit :: Char -> ReadPrec Word8+fromSecondDigit = \case+  '0' -> pure 0+  '1' -> pure 1+  '2' -> pure 2+  '3' -> pure 3+  '4' -> pure 4+  '5' -> pure 5+  '6' -> pure 6+  '7' -> pure 7+  '8' -> pure 8+  '9' -> pure 9+  'a' -> pure 10+  'b' -> pure 11+  'c' -> pure 12+  'd' -> pure 13+  'e' -> pure 14+  'f' -> pure 15+  d -> fail $ "Expected hex digit, instead got '" <> [d] <> "'"
text-ascii.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               text-ascii-version:            1.0.0+version:            1.0.1 synopsis:           ASCII string and character processing. description:   A total-by-default, tested and documented library for@@ -14,7 +14,7 @@ bug-reports:        https://github.org/kozross/text-ascii/issues copyright:          (C) Koz Ross 2021 category:           Text-tested-with:        GHC ==8.6.5 || ==8.8.4 || ==8.10.3+tested-with:        GHC ==8.6.5 || ==8.8.4 || ==8.10.3 || ==9.0.1 build-type:         Simple extra-source-files:   CHANGELOG.md@@ -24,18 +24,19 @@   exposed-modules:     Text.Ascii     Text.Ascii.Char--  other-modules:     Text.Ascii.Internal     Text.Ascii.QQ+    Text.Ascii.Unsafe    build-depends:     , base              >=4.12     && <5     , bytestring        ^>=0.11.0.0+    , case-insensitive  ^>=1.2.1.0     , deepseq           ^>=1.4.0.0     , hashable          ^>=1.3.0.0-    , optics-core       ^>=0.3.0.0-    , parsec            ^>=3.1.14.0+    , megaparsec        ^>=9.0.1+    , optics-core       ^>=0.4+    , optics-extra      ^>=0.4     , template-haskell  >=2.14.0.0 && <3.0.0.0     , text              ^>=1.2.4.1 @@ -43,13 +44,5 @@     -Wall -Wcompat -Wincomplete-record-updates     -Wincomplete-uni-patterns -Wredundant-constraints -  if flag(dev)-    ghc-options: -Werror-   hs-source-dirs:   src   default-language: Haskell2010--flag dev-  description: Always use this during development!-  manual:      True-  default:     False