-- | Folds for text streams module Control.Foldl.Text ( -- * Folding fold , foldM -- * Folds , head , last , null , length , any , all , maximum , minimum , elem , notElem , find , index , elemIndex , findIndex , count , lazy -- * Re-exports -- $reexports , module Control.Foldl , module Data.Text ) where import Control.Foldl (Fold, FoldM) import Control.Foldl.Internal (Maybe'(..), strict, Either'(..), hush) import Data.Text (Text) import Prelude hiding ( head, last, null, length, any, all, maximum, minimum, elem, notElem ) import qualified Control.Foldl import qualified Control.Foldl.Internal import qualified Data.Text import qualified Data.Text.Lazy -- | Apply a strict left 'Fold' to lazy text fold :: Fold Text a -> Data.Text.Lazy.Text -> a fold (Control.Foldl.Fold step begin done) as = done (Data.Text.Lazy.foldlChunks step begin as) {-# INLINABLE fold #-} -- | Apply a strict monadic left 'FoldM' to lazy text foldM :: Monad m => FoldM m Text a -> Data.Text.Lazy.Text -> m a foldM (Control.Foldl.FoldM step begin done) as = do x <- Data.Text.Lazy.foldlChunks step' begin as done x where step' mx bs = do x <- mx x `seq` step x bs {-# INLINABLE foldM #-} {-| Get the first character of a text stream or return 'Nothing' if the stream is empty -} head :: Fold Text (Maybe Char) head = Control.Foldl.Fold step Nothing' Control.Foldl.Internal.lazy where step mc txt = if Data.Text.null txt then mc else case mc of Just' _ -> mc Nothing' -> Just' (Data.Text.head txt) {-# INLINABLE head #-} {-| Get the last character of a text stream or return 'Nothing' if the text stream is empty -} last :: Fold Text (Maybe Char) last = Control.Foldl.Fold step Nothing' Control.Foldl.Internal.lazy where step mc txt = if Data.Text.null txt then mc else Just' (Data.Text.last txt) -- TODO: Use `unsafeLast` when Debian Stable Haskell Platform has it {-# INLINABLE last #-} -- | Returns 'True' if the text stream is empty, 'False' otherwise null :: Fold Text Bool null = Control.Foldl.Fold step True id where step isNull txt = isNull && Data.Text.null txt {-# INLINABLE null #-} -- | Return the length of the text stream in characters length :: Num n => Fold Text n length = Control.Foldl.Fold (\n txt -> n + fromIntegral (Data.Text.length txt)) 0 id {-# INLINABLE length #-} {-| @(all predicate)@ returns 'True' if all characters satisfy the predicate, 'False' otherwise -} all :: (Char -> Bool) -> Fold Text Bool all predicate = Control.Foldl.Fold (\b txt -> b && Data.Text.all predicate txt) True id {-# INLINABLE all #-} {-| @(any predicate)@ returns 'True' if any character satisfies the predicate, 'False' otherwise -} any :: (Char -> Bool) -> Fold Text Bool any predicate = Control.Foldl.Fold (\b txt -> b || Data.Text.any predicate txt) False id {-# INLINABLE any #-} -- | Computes the maximum character maximum :: Fold Text (Maybe Char) maximum = Control.Foldl.Fold step Nothing' Control.Foldl.Internal.lazy where step mc txt = if Data.Text.null txt then mc else Just' (case mc of Nothing' -> Data.Text.maximum txt Just' c -> max c (Data.Text.maximum txt) ) {-# INLINABLE maximum #-} -- | Computes the minimum character minimum :: Fold Text (Maybe Char) minimum = Control.Foldl.Fold step Nothing' Control.Foldl.Internal.lazy where step mc txt = if Data.Text.null txt then mc else Just' (case mc of Nothing' -> Data.Text.minimum txt Just' c -> min c (Data.Text.minimum txt) ) {-# INLINABLE minimum #-} {-| @(elem c)@ returns 'True' if the text stream has a character equal to @c@, 'False' otherwise -} elem :: Char -> Fold Text Bool elem c = any (c ==) {-# INLINABLE elem #-} {-| @(notElem c)@ returns 'False' if the text stream has a character equal to @c@, 'True' otherwise -} notElem :: Char -> Fold Text Bool notElem c = all (c /=) {-# INLINABLE notElem #-} {-| @(find predicate)@ returns the first character that satisfies the predicate or 'Nothing' if no character satisfies the predicate -} find :: (Char -> Bool) -> Fold Text (Maybe Char) find predicate = Control.Foldl.Fold step Nothing' Control.Foldl.Internal.lazy where step mc txt = case mc of Nothing' -> strict (Data.Text.find predicate txt) Just' _ -> mc {-# INLINABLE find #-} {-| @(index n)@ returns the @n@th character of the text stream, or 'Nothing' if the stream has an insufficient number of characters -} index :: Integral n => n -> Fold Text (Maybe Char) index i = Control.Foldl.Fold step (Left' (fromIntegral i)) hush where step x txt = case x of Left' remainder -> let len = Data.Text.length txt in if remainder < len then Right' (Data.Text.index txt remainder) else Left' (remainder - len) _ -> x {-# INLINABLE index #-} {-| @(elemIndex c)@ returns the index of the first character that equals @c@, or 'Nothing' if no character matches -} elemIndex :: Num n => Char -> Fold Text (Maybe n) elemIndex c = findIndex (c ==) {-# INLINABLE elemIndex #-} {-| @(findIndex predicate)@ returns the index of the first character that satisfies the predicate, or 'Nothing' if no character satisfies the predicate -} findIndex :: Num n => (Char -> Bool) -> Fold Text (Maybe n) findIndex predicate = Control.Foldl.Fold step (Left' 0) hush where step x txt = case x of Left' m -> case Data.Text.findIndex predicate txt of Nothing -> Left' (m + fromIntegral (Data.Text.length txt)) Just n -> Right' (m + fromIntegral n) _ -> x {-# INLINABLE findIndex #-} -- | @(count c)@ returns the number of times @c@ appears count :: Num n => Char -> Fold Text n count c = Control.Foldl.Fold step 0 id where step n txt = n + fromIntegral (Data.Text.count (Data.Text.singleton c) txt) {-# INLINABLE count #-} -- | Combine all the strict `Text` chunks to build a lazy `Text` lazy :: Fold Text Data.Text.Lazy.Text lazy = fmap Data.Text.Lazy.fromChunks Control.Foldl.list {-# INLINABLE lazy #-} {- $reexports "Control.Foldl" re-exports the 'Fold' type @Data.Text@ re-exports the 'Text' type -}