packages feed

streamly-core-0.3.0: src/Streamly/Internal/Data/Array.hs

{-# LANGUAGE CPP #-}
-- |
-- Module      : Streamly.Internal.Data.Array
-- Copyright   : (c) 2019 Composewell Technologies
-- License     : BSD3
-- Maintainer  : streamly@composewell.com
-- Stability   : experimental
-- Portability : GHC
--
module Streamly.Internal.Data.Array
    (
    -- * Setup
    -- $setup

    -- * Design Notes
    -- $design

    -- * The Array Type
      module Streamly.Internal.Data.Array.Type

    -- * Construction
    -- Monadic Folds
    , createOfLast

    -- * Random Access
    -- , getIndicesFrom    -- read from a given position to the end of file
    -- , getIndicesUpto    -- read from beginning up to the given position
    -- , getIndicesFromTo
    -- , getIndicesFromRev  -- read from a given position to the beginning of file
    -- , getIndicesUptoRev  -- read from end to the given position in file
    , indexReader
    , indexReaderFromThenTo

    -- * Search
    , binarySearch
    , findIndicesOf
    -- getIndicesOf
    , indexFinder -- see splitOn
    -- , findIndexOf
    -- , find

    -- * Casting
    , cast
    , asBytes
    , unsafeCast
    , asCStringUnsafe -- XXX asCString
    , asCWString

    -- * Subarrays
    -- , sliceOffLen
    , indexerFromLen
    , splitterFromLen

    -- * Streaming Operations
    , streamTransform

    -- * Folding
    , streamFold
    , foldM
    , foldRev

    -- * Stream of Arrays
    , concatSepBy
    , concatEndBy
    , concatEndBySeq

    , compactMax
    , compactMax'
    , compactSepByByte_
    , compactEndByByte_
    , compactEndByLn_

    -- * Parsing Stream of Arrays
    , foldBreakChunks -- Uses Stream, bad perf on break
    , foldChunks
    , foldBreak
    -- , parseBreakChunksK -- XXX uses Parser. parseBreak is better?
    , toParserK
    , parseBreak
    , parseBreakPos
    , parse
    , parsePos

    -- * Serialization
    , encodeAs
    , serialize
    , serialize'
    , deserialize

    -- * Deprecated
    , slicerFromLen
    , sliceIndexerFromLen
    , castUnsafe
    , getSliceUnsafe
    , pinnedSerialize
    , genSlicesFromLen
    , getSlicesFromLen
    , getIndices
    , writeLastN
    , interpose
    , interposeSuffix
    , intercalateSuffix
    , compactLE
    , pinnedCompactLE
    , compactOnByte
    , compactOnByteSuffix
    , splitOn
    , fold
    , foldBreakChunksK
    )
where

#include "assert.hs"
#include "deprecation.h"
#include "inline.hs"
#include "ArrayMacros.h"

import Control.Monad.IO.Class (MonadIO(..))
-- import Data.Bifunctor (first)
-- import Data.Either (fromRight)
import Data.Functor.Identity (Identity(..))
import Data.Proxy (Proxy(..))
import Data.Word (Word8)
import Foreign.C.String (CString, CWString)
import GHC.Types (SPEC(..))
import Streamly.Internal.Data.Unbox (Unbox(..))
import Prelude hiding (length, null, last, map, (!!), read, concat)

import Streamly.Internal.Data.MutByteArray.Type (PinnedState(..), MutByteArray)
import Streamly.Internal.Data.Serialize.Type (Serialize)
import Streamly.Internal.Data.Fold.Type (Fold(..))
import Streamly.Internal.Data.Parser (ParseError(..), ParseErrorPos(..))
import Streamly.Internal.Data.ParserK.Type
    (ParserK, ParseResult(..), Input(..), Step(..))
import Streamly.Internal.Data.Stream (Stream(..))
import Streamly.Internal.Data.StreamK.Type (StreamK)
import Streamly.Internal.Data.SVar.Type (adaptState, defState)
import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
import Streamly.Internal.Data.Unfold.Type (Unfold(..))
import Streamly.Internal.System.IO (unsafeInlineIO)

import qualified Streamly.Internal.Data.Fold.Type as Fold
import qualified Streamly.Internal.Data.Serialize.Type as Serialize
import qualified Streamly.Internal.Data.MutByteArray.Type as MBA
import qualified Streamly.Internal.Data.MutArray as MA
import qualified Streamly.Internal.Data.RingArray as RB
import qualified Streamly.Internal.Data.ParserDrivers as Drivers
import qualified Streamly.Internal.Data.Parser.Type as ParserD
import qualified Streamly.Internal.Data.ParserK.Type as ParserK
import qualified Streamly.Internal.Data.Stream as D
import qualified Streamly.Internal.Data.Stream as Stream
import qualified Streamly.Internal.Data.StreamK.Type as StreamK
import qualified Streamly.Internal.Data.Unfold as Unfold

import Streamly.Internal.Data.Array.Type

#include "DocTestDataArray.hs"

-- $design
--
-- To summarize:
--
--  * Arrays are finite and fixed in size
--  * provide /O(1)/ access to elements
--  * store only data and not functions
--  * provide efficient IO interfacing
--
-- 'Foldable' instance is not provided because the implementation would be much
-- less efficient compared to folding via streams.  'Semigroup' and 'Monoid'
-- instances should be used with care; concatenating arrays using binary
-- operations can be highly inefficient.  Instead, use
-- 'Streamly.Internal.Data.Stream.Chunked.toArray' to concatenate N
-- arrays at once.
--
-- Each array is one pointer visible to the GC.  Too many small arrays (e.g.
-- single byte) are only as good as holding those elements in a Haskell list.
-- However, small arrays can be compacted into large ones to reduce the
-- overhead. To hold 32GB memory in 32k sized buffers we need 1 million arrays
-- if we use one array for each chunk. This is still significant to add
-- pressure to GC.

-------------------------------------------------------------------------------
-- Folds with Array as the container
-------------------------------------------------------------------------------

-- NOTE: We could possible write this in terms of "MutArray.createOfLast" but
-- this causes regression. This is probably because mapping inside "Fold.ifThen"
-- is more efficient than mapping over "Fold.ifTen".
--
-- | @createOfLast n@ folds a maximum of @n@ elements from the end of the input
-- stream to an 'Array'.
--
{-# INLINE createOfLast #-}
createOfLast ::
       (Unbox a, MonadIO m) => Int -> Fold m a (Array a)
createOfLast n = Fold.ifThen (pure (n <= 0)) (Fold.fromPure empty) lst

    where

    lst =
        let f = fmap unsafeFreeze . RB.toMutArray
         in Fold.rmapM f $ RB.createOfLast n

{-# DEPRECATED writeLastN "Please use createOfLast instead." #-}
{-# INLINE writeLastN #-}
writeLastN :: (Unbox a, MonadIO m) => Int -> Fold m a (Array a)
writeLastN = createOfLast

-------------------------------------------------------------------------------
-- Random Access
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- Searching
-------------------------------------------------------------------------------

-- | Given a sorted array, perform a binary search to find the given element.
-- Returns the index of the element if found.
--
-- /Unimplemented/
{-# INLINE binarySearch #-}
binarySearch :: a -> Array a -> Maybe Int
binarySearch = undefined

-- find/findIndex etc can potentially be implemented more efficiently on arrays
-- compared to streams by using SIMD instructions.
-- We can also return a bit array instead.

-- Can use SIMD.

-- | Perform a linear search to find all the indices where a given element is
-- present in an array.
--
-- /Unimplemented/
indexFinder :: (a -> Bool) -> Unfold Identity (Array a) Int
indexFinder = undefined

-- |
-- /Unimplemented/
findIndicesOf :: (a -> Bool) -> Array a -> Stream Identity Int
findIndicesOf p = Stream.unfold (indexFinder p)

{-
findIndexOf :: (a -> Bool) -> Array a -> Maybe Int
findIndexOf p = Unfold.fold Fold.one . Stream.unfold (indexFinder p)

find :: (a -> Bool) -> Array a -> Bool
find = Unfold.fold Fold.null . Stream.unfold (indexFinder p)
-}

-------------------------------------------------------------------------------
-- Folds
-------------------------------------------------------------------------------

-- XXX We can potentially use SIMD instructions on arrays to fold faster.

-------------------------------------------------------------------------------
-- Slice
-------------------------------------------------------------------------------

getSliceUnsafe ::
       forall a. Unbox a
    => Int -- ^ starting index
    -> Int -- ^ length of the slice
    -> Array a
    -> Array a
RENAME(getSliceUnsafe,unsafeSliceOffLen)

splitOn :: (Monad m, Unbox a) =>
    (a -> Bool) -> Array a -> Stream m (Array a)
RENAME(splitOn,splitEndBy_)

{-# INLINE indexerFromLen #-}
indexerFromLen, sliceIndexerFromLen :: forall m a. (Monad m, Unbox a)
    => Int -- ^ from index
    -> Int -- ^ length of the slice
    -> Unfold m (Array a) (Int, Int)
indexerFromLen from len =
    Unfold.lmap unsafeThaw (MA.indexerFromLen from len)
RENAME(sliceIndexerFromLen,indexerFromLen)

{-# DEPRECATED genSlicesFromLen "Please use indexerFromLen instead." #-}
{-# INLINE genSlicesFromLen #-}
genSlicesFromLen :: forall m a. (Monad m, Unbox a)
    => Int -- ^ from index
    -> Int -- ^ length of the slice
    -> Unfold m (Array a) (Int, Int)
genSlicesFromLen = indexerFromLen

-- | Generate a stream of slices of specified length from an array, starting
-- from the supplied array index. The last slice may be shorter than the
-- requested length.
--
-- /Pre-release//
{-# INLINE splitterFromLen #-}
splitterFromLen, slicerFromLen :: forall m a. (Monad m, Unbox a)
    => Int -- ^ from index
    -> Int -- ^ length of the slice
    -> Unfold m (Array a) (Array a)
splitterFromLen from len =
    fmap unsafeFreeze
        $ Unfold.lmap unsafeThaw (MA.splitterFromLen from len)
RENAME(slicerFromLen,splitterFromLen)

{-# DEPRECATED getSlicesFromLen "Please use splitterFromLen instead." #-}
{-# INLINE getSlicesFromLen #-}
getSlicesFromLen :: forall m a. (Monad m, Unbox a)
    => Int -- ^ from index
    -> Int -- ^ length of the slice
    -> Unfold m (Array a) (Array a)
getSlicesFromLen = splitterFromLen

-------------------------------------------------------------------------------
-- Random reads and writes
-------------------------------------------------------------------------------

-- | Given a stream of array indices, read the elements on those indices from
-- the supplied Array. An exception is thrown if an index is out of bounds.
--
-- This is the most general operation. We can implement other operations in
-- terms of this:
--
-- @
-- read =
--      let u = lmap (\arr -> (0, length arr - 1)) Unfold.enumerateFromTo
--       in Unfold.lmap f (indexReader arr)
--
-- readRev =
--      let i = length arr - 1
--       in Unfold.lmap f (indexReaderFromThenTo i (i - 1) 0)
-- @
--
-- /Pre-release/
{-# INLINE indexReader #-}
indexReader :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a
indexReader m =
    let unf = MA.indexReaderWith (return . unsafeInlineIO) m
     in Unfold.lmap unsafeThaw unf

-- XXX DO NOT REMOVE, change the signature to use Stream instead of unfold
{-# DEPRECATED getIndices "Please use getIndices instead." #-}
{-# INLINE getIndices #-}
getIndices :: (Monad m, Unbox a) => Stream m Int -> Unfold m (Array a) a
getIndices = indexReader

-- | Unfolds @(from, then, to, array)@ generating a finite stream whose first
-- element is the array value from the index @from@ and the successive elements
-- are from the indices in increments of @then@ up to @to@. Index enumeration
-- can occur downwards or upwards depending on whether @then@ comes before or
-- after @from@.
--
-- @
-- getIndicesFromThenTo =
--     let f (from, next, to, arr) =
--             (Stream.enumerateFromThenTo from next to, arr)
--      in Unfold.lmap f getIndices
-- @
--
-- /Unimplemented/
{-# INLINE indexReaderFromThenTo #-}
indexReaderFromThenTo :: Unfold m (Int, Int, Int, Array a) a
indexReaderFromThenTo = undefined

-------------------------------------------------------------------------------
-- Transform via stream operations
-------------------------------------------------------------------------------

-- for non-length changing operations we can use the original length for
-- allocation. If we can predict the length then we can use the prediction for
-- new allocation. Otherwise we can use a hint and adjust dynamically.

{-
-- | Transform an array into another array using a pipe transformation
-- operation.
--
{-# INLINE runPipe #-}
runPipe :: (MonadIO m, Unbox a, Unbox b)
    => Pipe m a b -> Array a -> m (Array b)
runPipe f arr = P.runPipe (toArrayMinChunk (length arr)) $ f (read arr)
-}

-- XXX For transformations that cannot change the number of elements e.g. "map"
-- we can use a predetermined array length.
--
-- | Transform an array into another array using a stream transformation
-- operation.
--
-- /Pre-release/
{-# INLINE streamTransform #-}
streamTransform :: forall m a b. (MonadIO m, Unbox a, Unbox b)
    => (Stream m a -> Stream m b) -> Array a -> m (Array b)
streamTransform f arr =
    Stream.fold (createWith (length arr)) $ f (read arr)

-------------------------------------------------------------------------------
-- Casts
-------------------------------------------------------------------------------

-- | Cast an array having elements of type @a@ into an array having elements of
-- type @b@. The array size must be a multiple of the size of type @b@
-- otherwise accessing the last element of the array may result into a crash or
-- a random value.
--
-- /Pre-release/
--
unsafeCast, castUnsafe ::
#ifdef DEVBUILD
    Unbox b =>
#endif
    Array a -> Array b
unsafeCast (Array contents start end) =
    Array contents start end
RENAME(castUnsafe,unsafeCast)

-- | Cast an @Array a@ into an @Array Word8@.
--
--
asBytes :: Array a -> Array Word8
asBytes = unsafeCast

-- | Cast an array having elements of type @a@ into an array having elements of
-- type @b@. The length of the array should be a multiple of the size of the
-- target element otherwise 'Nothing' is returned.
--
--
cast :: forall a b. (Unbox b) => Array a -> Maybe (Array b)
cast arr =
    let len = byteLength arr
        r = len `mod` SIZE_OF(b)
     in if r /= 0
        then Nothing
        else Just $ unsafeCast arr

-- | Convert an array of any element type into a null terminated CString Ptr.
-- The array is copied to pinned memory.
--
-- /Unsafe/
--
-- /O(n) Time: (creates a copy of the array)/
--
-- /Pre-release/
--
asCStringUnsafe :: Array a -> (CString -> IO b) -> IO b
asCStringUnsafe arr = MA.asCString (unsafeThaw arr)

-- | Convert an array of any element type into a null terminated CWString Ptr.
-- The array is copied to pinned memory.
--
-- /Unsafe/
--
-- /O(n) Time: (creates a copy of the array)/
--
-- /Pre-release/
--
asCWString :: Array a -> (CWString -> IO b) -> IO b
asCWString arr = MA.asCWString (unsafeThaw arr)

-------------------------------------------------------------------------------
-- Folds
-------------------------------------------------------------------------------

-- XXX Use runIdentity for pure fold
-- XXX Rename fold to foldM, we can then use "fold" for pure folds.
-- XXX We do not need an INLINE on fold?

-- | Fold an array using a 'Fold'.
--
-- /Pre-release/
{-# INLINE foldM #-}
fold, foldM :: (Monad m, Unbox a) => Fold m a b -> Array a -> m b
foldM f arr = Stream.fold f (read arr)
RENAME(fold,foldM)

foldRev :: Unbox a => Fold.Fold Identity a b -> Array a -> b
foldRev f arr = runIdentity $ Stream.fold f (readRev arr)

-- | Fold an array using a stream fold operation.
--
-- /Pre-release/
{-# INLINE streamFold #-}
streamFold :: (Monad m, Unbox a) => (Stream m a -> m b) -> Array a -> m b
streamFold f arr = f (read arr)

--------------------------------------------------------------------------------
-- Serialization
--------------------------------------------------------------------------------

{-# INLINE encodeAs #-}
encodeAs :: forall a. Serialize a => PinnedState -> a -> Array Word8
encodeAs ps a =
    unsafeInlineIO $ do
        let len = Serialize.addSizeTo 0 a
        mbarr <- MBA.newAs ps len
        off <- Serialize.serializeAt 0 mbarr a
        assertM(len == off)
        pure $ Array mbarr 0 off

-- |
-- Properties:
-- 1. Identity: @deserialize . serialize == id@
-- 2. Encoded equivalence: @serialize a == serialize a@
{-# INLINE serialize #-}
serialize :: Serialize a => a -> Array Word8
serialize = encodeAs Unpinned

-- | Serialize a Haskell type to a pinned byte array. The array is allocated
-- using pinned memory so that it can be used directly in OS APIs for writing
-- to file or sending over the network.
--
-- Properties:
--
-- 1. Identity: @deserialize . serialize' == id@
-- 2. Encoded equivalence: @serialize' a == serialize' a@
{-# INLINE serialize' #-}
pinnedSerialize, serialize' :: Serialize a => a -> Array Word8
serialize' = encodeAs Pinned
RENAME_PRIME(pinnedSerialize,serialize)

-- XXX We can deserialize it like MutArray, returning the remaining slice.

-- | Decode a Haskell type from a byte array containing its serialized
-- representation.
{-# INLINE deserialize #-}
deserialize :: Serialize a => Array Word8 -> (a, Array Word8)
deserialize arr =
    let (a, b) = unsafeInlineIO $ MA.deserialize (unsafeThaw arr)
     in (a, unsafeFreeze b)

-------------------------------------------------------------------------------
-- Streams of Arrays
-------------------------------------------------------------------------------

-- TODO: efficiently compare two streams of arrays. Two streams can have chunks
-- of different sizes, we can handle that in the stream comparison abstraction.
-- This could be useful e.g. to fast compare whether two files differ.

-- | Insert the given element between arrays and flatten.
--
-- >>> concatSepBy x = Stream.unfoldEachSepBy x Array.reader
--
{-# INLINE concatSepBy #-}
concatSepBy, interpose :: (Monad m, Unbox a) =>
    a -> Stream m (Array a) -> Stream m a
concatSepBy x = D.unfoldEachSepBy x reader

RENAME(interpose,concatSepBy)

data FlattenState s =
      OuterLoop s
    | InnerLoop s !MutByteArray !Int !Int

-- | Insert the given element after each array and flatten. This is similar to
-- unlines.
--
-- >>> concatEndBy x = Stream.unfoldEachEndBy x Array.reader
--
{-# INLINE_NORMAL concatEndBy #-}
concatEndBy, interposeSuffix :: forall m a. (Monad m, Unbox a)
    => a -> Stream m (Array a) -> Stream m a
-- concatEndBy x = D.unfoldEachEndBy x reader
concatEndBy sep (D.Stream step state) = D.Stream step' (OuterLoop state)

    where

    {-# INLINE_LATE step' #-}
    step' gst (OuterLoop st) = do
        r <- step (adaptState gst) st
        return $ case r of
            D.Yield Array{..} s ->
                D.Skip (InnerLoop s arrContents arrStart arrEnd)
            D.Skip s -> D.Skip (OuterLoop s)
            D.Stop -> D.Stop

    step' _ (InnerLoop st _ p end) | p == end =
        return $ D.Yield sep $ OuterLoop st

    step' _ (InnerLoop st contents p end) = do
        let !x = unsafeInlineIO $ peekAt p contents
        return $ D.Yield x (InnerLoop st contents (INDEX_NEXT(p,a)) end)

RENAME(interposeSuffix,concatEndBy)

-- | Insert the given array after each array and flatten.
--
-- >>> concatEndBySeq x = Stream.unfoldEachEndBySeq x Array.reader
--
{-# INLINE concatEndBySeq #-}
concatEndBySeq, intercalateSuffix :: (Monad m, Unbox a)
    => Array a -> Stream m (Array a) -> Stream m a
concatEndBySeq x = D.unfoldEachEndBySeq x reader

RENAME(intercalateSuffix,concatEndBySeq)

-- | @compactMax n@ coalesces adjacent arrays in the input stream
-- only if the combined size would be less than or equal to n.
--
-- Generates unpinned arrays irrespective of the pinning status of input
-- arrays.
{-# INLINE_NORMAL compactMax #-}
compactMax, compactLE :: (MonadIO m, Unbox a)
    => Int -> Stream m (Array a) -> Stream m (Array a)
compactMax n stream =
    D.map unsafeFreeze $ MA.compactMax n $ D.map unsafeThaw stream

RENAME(compactLE,compactMax)

-- | Like 'compactMax' but generates pinned arrays.
{-# INLINE_NORMAL compactMax' #-}
compactMax', pinnedCompactLE :: (MonadIO m, Unbox a)
    => Int -> Stream m (Array a) -> Stream m (Array a)
compactMax' n stream =
    D.map unsafeFreeze $ MA.compactMax' n $ D.map unsafeThaw stream

{-# DEPRECATED pinnedCompactLE "Please use compactMax' instead." #-}
{-# INLINE pinnedCompactLE #-}
pinnedCompactLE = compactMax'

-- | Split a stream of byte arrays on a given separator byte, dropping the
-- separator and coalescing all the arrays between two separators into a single
-- array.
--
{-# INLINE compactSepByByte_ #-}
compactSepByByte_, compactOnByte
    :: (MonadIO m)
    => Word8
    -> Stream m (Array Word8)
    -> Stream m (Array Word8)
compactSepByByte_ byte =
    fmap unsafeFreeze . MA.compactSepByByte_ byte . fmap unsafeThaw

RENAME(compactOnByte,compactSepByByte_)

-- | Like 'compactSepByByte_', but considers the separator in suffix position
-- instead of infix position.
{-# INLINE compactEndByByte_ #-}
compactEndByByte_, compactOnByteSuffix
    :: (MonadIO m)
    => Word8
    -> Stream m (Array Word8)
    -> Stream m (Array Word8)
compactEndByByte_ byte =
    fmap unsafeFreeze . MA.compactEndByByte_ byte . fmap unsafeThaw
-- compactEndByByte_ byte = chunksEndBy_ (== byte) . concat

RENAME(compactOnByteSuffix,compactEndByByte_)

-- XXX On windows we should compact on "\r\n". We can just compact on '\n' and
-- drop the last byte in each array if it is '\r'.

-- | Compact byte arrays on newline character, dropping the newline char.
{-# INLINE compactEndByLn_ #-}
compactEndByLn_ :: MonadIO m
    => Stream m (Array Word8)
    -> Stream m (Array Word8)
compactEndByLn_ = compactEndByByte_ 10

-------------------------------------------------------------------------------
-- Folding Streams of Arrays
-------------------------------------------------------------------------------

-- XXX This should not be used for breaking a stream as the D.cons used in
-- reconstructing the stream could be very bad for performance. This can only
-- be useful in folding without breaking.
{-# INLINE_NORMAL foldBreakChunks #-}
foldBreakChunks :: forall m a b. (MonadIO m, Unbox a) =>
    Fold m a b -> Stream m (Array a) -> m (b, Stream m (Array a))
foldBreakChunks (Fold fstep initial _ final) stream@(Stream step state) = do
    res <- initial
    case res of
        Fold.Partial fs -> go SPEC state fs
        Fold.Done fb -> return $! (fb, stream)

    where

    {-# INLINE go #-}
    go !_ st !fs = do
        r <- step defState st
        case r of
            Stream.Yield (Array contents start end) s ->
                let fp = Tuple' end contents
                 in goArray SPEC s fp start fs
            Stream.Skip s -> go SPEC s fs
            Stream.Stop -> do
                b <- final fs
                return (b, D.nil)

    goArray !_ s (Tuple' end _) !cur !fs
        | cur == end = do
            go SPEC s fs
    goArray !_ st fp@(Tuple' end contents) !cur !fs = do
        x <- liftIO $ peekAt cur contents
        res <- fstep fs x
        let next = INDEX_NEXT(cur,a)
        case res of
            Fold.Done b -> do
                let arr = Array contents next end
                return $! (b, D.cons arr (D.Stream step st))
            Fold.Partial fs1 -> goArray SPEC st fp next fs1

-- This may be more robust wrt fusion compared to unfoldMany?

-- | Fold a stream of arrays using a 'Fold'. This is equivalent to the
-- following:
--
-- >>> foldChunks f = Stream.fold f . Stream.unfoldEach Array.reader
--
foldChunks :: (MonadIO m, Unbox a) => Fold m a b -> Stream m (Array a) -> m b
foldChunks f s = fmap fst (foldBreakChunks f s)
-- foldStream f = Stream.fold f . Stream.unfoldEach reader

-- | Fold a stream of arrays using a 'Fold' and return the remaining stream.
--
-- The following alternative to this function allows composing the fold using
-- the parser Monad:
--
-- @
-- foldBreakStreamK f s =
--       fmap (first (fromRight undefined))
--     $ StreamK.parseBreakChunks (ParserK.adaptC (Parser.fromFold f)) s
-- @
--
-- We can compare perf and remove this one or define it in terms of that.
--
foldBreak, foldBreakChunksK :: forall m a b. (MonadIO m, Unbox a) =>
    Fold m a b -> StreamK m (Array a) -> m (b, StreamK m (Array a))
{-
foldBreakChunksK f s =
      fmap (first (fromRight undefined))
    $ StreamK.parseBreakChunks (ParserK.adaptC (Parser.fromFold f)) s
-}
foldBreak (Fold fstep initial _ final) stream = do
    res <- initial
    case res of
        Fold.Partial fs -> go fs stream
        Fold.Done fb -> return (fb, stream)

    where

    {-# INLINE go #-}
    go !fs st = do
        let stop = (, StreamK.nil) <$> final fs
            single a = yieldk a StreamK.nil
            yieldk (Array contents start end) r =
                let fp = Tuple' end contents
                 in goArray fs r fp start
         in StreamK.foldStream defState yieldk single stop st

    goArray !fs st (Tuple' end _) !cur
        | cur == end = do
            go fs st
    goArray !fs st fp@(Tuple' end contents) !cur = do
        x <- liftIO $ peekAt cur contents
        res <- fstep fs x
        let next = INDEX_NEXT(cur,a)
        case res of
            Fold.Done b -> do
                let arr = Array contents next end
                return $! (b, StreamK.cons arr st)
            Fold.Partial fs1 -> goArray fs1 st fp next

RENAME(foldBreakChunksK,foldBreak)

{-
-- This can be generalized to any type provided it can be unfolded to a stream
-- and it can be combined using a semigroup operation.
--
{-# INLINE_NORMAL parseBreakD #-}
parseBreakD ::
       forall m a b. (MonadIO m, MonadThrow m, Unbox a)
    => PRD.Parser a m b
    -> D.Stream m (Array.Array a)
    -> m (b, D.Stream m (Array.Array a))
parseBreakD
    (PRD.Parser pstep initial extract) stream@(D.Stream step state) = do

    res <- initial
    case res of
        PRD.IPartial s -> go SPEC state (List []) s
        PRD.IDone b -> return (b, stream)
        PRD.IError err -> throwM $ ParseError err

    where

    -- "backBuf" contains last few items in the stream that we may have to
    -- backtrack to.
    --
    -- XXX currently we are using a dumb list based approach for backtracking
    -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.
    -- That will allow us more efficient random back and forth movement.
    go !_ st backBuf !pst = do
        r <- step defState st
        case r of
            D.Yield (Array contents start end) s ->
                gobuf SPEC s backBuf
                    (Tuple' end contents) start pst
            D.Skip s -> go SPEC s backBuf pst
            D.Stop -> do
                b <- extract pst
                return (b, D.nil)

    -- Use strictness on "cur" to keep it unboxed
    gobuf !_ s backBuf (Tuple' end _) !cur !pst
        | cur == end = do
            go SPEC s backBuf pst
    gobuf !_ s backBuf fp@(Tuple' end contents) !cur !pst = do
        x <- liftIO $ peekByteIndex contents cur
        pRes <- pstep pst x
        let next = INDEX_NEXT(cur,a)
        case pRes of
            PR.Partial 0 pst1 ->
                 gobuf SPEC s (List []) fp next pst1
            PR.Partial n pst1 -> do
                assert (n <= Prelude.length (x:getList backBuf)) (return ())
                let src0 = Prelude.take n (x:getList backBuf)
                    arr0 = A.fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    src = arr0 <> arr1
                let !(Array cont1 start end1) = src
                    fp1 = Tuple' end1 cont1
                gobuf SPEC s (List []) fp1 start pst1
            PR.Continue 0 pst1 ->
                gobuf SPEC s (List (x:getList backBuf)) fp next pst1
            PR.Continue n pst1 -> do
                assert (n <= Prelude.length (x:getList backBuf)) (return ())
                let (src0, buf1) = splitAt n (x:getList backBuf)
                    arr0 = A.fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    src = arr0 <> arr1
                let !(Array cont1 start end1) = src
                    fp1 = Tuple' end1 cont1
                gobuf SPEC s (List buf1) fp1 start pst1
            PR.Done 0 b -> do
                let arr = Array contents next end
                return (b, D.cons arr (D.Stream step s))
            PR.Done n b -> do
                assert (n <= Prelude.length (x:getList backBuf)) (return ())
                let src0 = Prelude.take n (x:getList backBuf)
                    -- XXX create the array in reverse instead
                    arr0 = A.fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    -- XXX Use StreamK to avoid adding arbitrary layers of
                    -- constructors every time.
                    str = D.cons arr0 (D.cons arr1 (D.Stream step s))
                return (b, str)
            PR.SError err -> throwM $ ParseError err

-- | Parse an array stream using the supplied 'Parser'.  Returns the parse
-- result and the unconsumed stream. Throws 'ParseError' if the parse fails.
--
-- 'parseBreak' is an alternative to this function which allows composing the
-- parser using the parser Monad.
--
-- We can compare perf and remove this one or define it in terms of that.
--
-- /Internal/
--
{-# INLINE_NORMAL parseBreakChunksK #-}
parseBreakChunksK ::
       forall m a b. (MonadIO m, Unbox a)
    => Parser a m b
    -> StreamK m (Array a)
    -> m (Either ParseError b, StreamK m (Array a))
parseBreakChunksK (Parser pstep initial extract) stream = do
    res <- initial
    case res of
        IPartial s -> go s stream []
        IDone b -> return (Right b, stream)
        IError err -> return (Left (ParseError err), stream)

    where

    -- "backBuf" contains last few items in the stream that we may have to
    -- backtrack to.
    --
    -- XXX currently we are using a dumb list based approach for backtracking
    -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.
    -- That will allow us more efficient random back and forth movement.
    go !pst st backBuf = do
        let stop = goStop pst backBuf -- (, K.nil) <$> extract pst
            single a = yieldk a StreamK.nil
            yieldk arr r = goArray pst backBuf r arr
         in StreamK.foldStream defState yieldk single stop st

    -- Use strictness on "cur" to keep it unboxed
    goArray !pst backBuf st (Array _ cur end) | cur == end = go pst st backBuf
    goArray !pst backBuf st (Array contents cur end) = do
        x <- liftIO $ peekAt cur contents
        pRes <- pstep pst x
        let next = INDEX_NEXT(cur,a)
        case pRes of
            Parser.SPartial 1 s ->
                 goArray s [] st (Array contents next end)
            Parser.SPartial m s -> do
                assertM(m <= 1)
                let n = 1 - m
                assert (n <= Prelude.length (x:backBuf)) (return ())
                let src0 = Prelude.take n (x:backBuf)
                    arr0 = fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    src = arr0 <> arr1
                goArray s [] st src
            Parser.SContinue 1 s ->
                goArray s (x:backBuf) st (Array contents next end)
            Parser.SContinue m s -> do
                assertM(m <= 1)
                let n = 1 - m
                assert (n <= Prelude.length (x:backBuf)) (return ())
                let (src0, buf1) = Prelude.splitAt n (x:backBuf)
                    arr0 = fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    src = arr0 <> arr1
                goArray s buf1 st src
            Parser.SDone 1 b -> do
                let arr = Array contents next end
                return (Right b, StreamK.cons arr st)
            Parser.SDone m b -> do
                assertM(m <= 1)
                let n = 1 - m
                assert (n <= Prelude.length (x:backBuf)) (return ())
                let src0 = Prelude.take n (x:backBuf)
                    -- XXX Use fromListRevN once implemented
                    -- arr0 = A.fromListRevN n src0
                    arr0 = fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    str = StreamK.cons arr0 (StreamK.cons arr1 st)
                return (Right b, str)
            Parser.SError err -> do
                let n = Prelude.length backBuf
                    arr0 = fromListN n (Prelude.reverse backBuf)
                    arr1 = Array contents cur end
                    str = StreamK.cons arr0 (StreamK.cons arr1 st)
                return (Left (ParseError err), str)

    -- This is a simplified goArray
    goExtract !pst backBuf (Array _ cur end)
        | cur == end = goStop pst backBuf
    goExtract !pst backBuf (Array contents cur end) = do
        x <- liftIO $ peekAt cur contents
        pRes <- pstep pst x
        let next = INDEX_NEXT(cur,a)
        case pRes of
            Parser.SPartial 0 s ->
                 goExtract s [] (Array contents next end)
            Parser.SPartial m s -> do
                assertM(m <= 0)
                let n = negate m
                assert (n <= Prelude.length (x:backBuf)) (return ())
                let src0 = Prelude.take n (x:backBuf)
                    arr0 = fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    src = arr0 <> arr1
                goExtract s [] src
            Parser.SContinue 0 s ->
                goExtract s backBuf (Array contents next end)
            Parser.SContinue m s -> do
                assertM(m <= 0)
                let n = negate m
                assert (n <= Prelude.length (x:backBuf)) (return ())
                let (src0, buf1) = Prelude.splitAt n (x:backBuf)
                    arr0 = fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    src = arr0 <> arr1
                goExtract s buf1 src
            Parser.SDone 0 b -> do
                let arr = Array contents next end
                return (Right b, StreamK.fromPure arr)
            Parser.SDone m b -> do
                assertM(m <= 0)
                let n = negate m
                assert (n <= Prelude.length backBuf) (return ())
                let src0 = Prelude.take n (x:backBuf)
                    -- XXX Use fromListRevN once implemented
                    -- arr0 = A.fromListRevN n src0
                    arr0 = fromListN n (Prelude.reverse src0)
                    arr1 = Array contents next end
                    str = StreamK.cons arr0 (StreamK.fromPure arr1)
                return (Right b, str)
            Parser.SError err -> do
                let n = Prelude.length backBuf
                    arr0 = fromListN n (Prelude.reverse backBuf)
                    arr1 = Array contents cur end
                    str = StreamK.cons arr0 (StreamK.fromPure arr1)
                return (Left (ParseError err), str)

    -- This is a simplified goExtract
    {-# INLINE goStop #-}
    goStop !pst backBuf = do
        pRes <- extract pst
        case pRes of
            Parser.SPartial _ _ -> error "Bug: parseBreak: Partial in extract"
            Parser.SContinue 0 s ->
                goStop s backBuf
            Parser.SContinue m s -> do
                assertM(m <= 0)
                let n = negate m
                assert (n <= Prelude.length backBuf) (return ())
                let (src0, buf1) = Prelude.splitAt n backBuf
                    arr = fromListN n (Prelude.reverse src0)
                goExtract s buf1 arr
            Parser.SDone 0 b ->
                return (Right b, StreamK.nil)
            Parser.SDone m b -> do
                assertM(m <= 0)
                let n = negate m
                assert (n <= Prelude.length backBuf) (return ())
                let src0 = Prelude.take n backBuf
                    -- XXX Use fromListRevN once implemented
                    -- arr0 = A.fromListRevN n src0
                    arr0 = fromListN n (Prelude.reverse src0)
                return (Right b, StreamK.fromPure arr0)
            Parser.SError err -> do
                let n = Prelude.length backBuf
                    arr0 = fromListN n (Prelude.reverse backBuf)
                return (Left (ParseError err), StreamK.fromPure arr0)
-}

-- | Run a 'ParserK' over a 'StreamK' of Arrays and return the parse result and
-- the remaining Stream.
{-# INLINE parseBreak #-}
parseBreak
    :: (Monad m, Unbox a)
    => ParserK (Array a) m b
    -> StreamK m (Array a)
    -> m (Either ParseError b, StreamK m (Array a))
parseBreak = Drivers.parseBreakChunks

-- | Like 'parseBreak' but includes stream position information in the error
-- messages.
--
{-# INLINE parseBreakPos #-}
parseBreakPos
    :: (Monad m, Unbox a)
    => ParserK (Array a) m b
    -> StreamK m (Array a)
    -> m (Either ParseErrorPos b, StreamK m (Array a))
parseBreakPos = Drivers.parseBreakChunksPos

{-# INLINE parse #-}
parse :: (Monad m, Unbox a) =>
    ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseError b)
parse f = fmap fst . parseBreak f

-- | Like 'parse' but includes stream position information in the error
-- messages.
--
{-# INLINE parsePos #-}
parsePos :: (Monad m, Unbox a) =>
    ParserK (Array a) m b -> StreamK m (Array a) -> m (Either ParseErrorPos b)
parsePos f = fmap fst . parseBreakPos f

-------------------------------------------------------------------------------
-- Convert ParserD to ParserK
-------------------------------------------------------------------------------

{-# INLINE adaptCWith #-}
adaptCWith
    :: forall m a s b r. (Monad m, Unbox a)
    => (s -> a -> m (ParserD.Step s b))
    -> m (ParserD.Initial s b)
    -> (s -> m (ParserD.Final s b))
    -> (ParseResult b -> Int -> Input (Array a) -> m (Step (Array a) m r))
    -> Int
    -> Int
    -> Input (Array a)
    -> m (Step (Array a) m r)
adaptCWith pstep initial extract cont !offset0 !usedCount !input = do
    res <- initial
    case res of
        ParserD.IPartial pst -> do
            case input of
                Chunk arr -> parseContChunk usedCount offset0 pst arr
                None -> parseContNothing usedCount pst
        ParserD.IDone b -> cont (Success offset0 b) usedCount input
        ParserD.IError err -> cont (Failure offset0 err) usedCount input

    where

    -- XXX We can maintain an absolute position instead of relative that will
    -- help in reporting of error location in the stream.
    {-# NOINLINE parseContChunk #-}
    parseContChunk !count !offset !state arr@(Array contents start end) = do
         if offset >= 0
         then go SPEC (start + offset * SIZE_OF(a)) state
         else return $ Continue offset (parseCont count state)

        where

        {-# INLINE onDone #-}
        onDone n b =
            assert (n <= length arr)
                (cont (Success n b) (count + n - offset) (Chunk arr))

        {-# INLINE callParseCont #-}
        callParseCont constr n pst1 =
            assert (n < 0 || n >= length arr)
                (return $ constr n (parseCont (count + n - offset) pst1))

        {-# INLINE onPartial #-}
        onPartial = callParseCont Partial

        {-# INLINE onContinue #-}
        onContinue = callParseCont Continue

        {-# INLINE onError #-}
        onError n err =
            cont (Failure n err) (count + n - offset) (Chunk arr)

        {-# INLINE onBack #-}
        onBack offset1 elemSize constr pst = do
            let pos = offset1 - start
             in if pos >= 0
                then go SPEC offset1 pst
                else constr (pos `div` elemSize) pst

        -- Note: div may be expensive but the alternative is to maintain an element
        -- offset in addition to a byte offset or just the element offset and use
        -- multiplication to get the byte offset every time, both these options
        -- turned out to be more expensive than using div.
        go !_ !cur !pst | cur >= end =
            onContinue ((end - start) `div` SIZE_OF(a))  pst
        go !_ !cur !pst = do
            let !x = unsafeInlineIO $ peekAt cur contents
            pRes <- pstep pst x
            let elemSize = SIZE_OF(a)
                next = INDEX_NEXT(cur,a)
                move n = cur + n * elemSize
                curOff = (cur - start) `div` elemSize
                nextOff = (next - start) `div` elemSize
            case pRes of
                ParserD.SDone 1 b ->
                    onDone nextOff b
                ParserD.SDone 0 b ->
                    onDone curOff b
                ParserD.SDone n b ->
                    onDone ((move n - start) `div` elemSize) b
                ParserD.SPartial 1 pst1 ->
                    go SPEC next pst1
                ParserD.SPartial 0 pst1 ->
                    go SPEC cur pst1
                ParserD.SPartial n pst1 ->
                    onBack (move n) elemSize onPartial pst1
                ParserD.SContinue 1 pst1 ->
                    go SPEC next pst1
                ParserD.SContinue 0 pst1 ->
                    go SPEC cur pst1
                ParserD.SContinue n pst1 ->
                    onBack (move n) elemSize onContinue pst1
                ParserD.SError err ->
                    onError curOff err

    {-# NOINLINE parseContNothing #-}
    parseContNothing !count !pst = do
        r <- extract pst
        case r of
            ParserD.FDone n b ->
                assert (n <= 0) (cont (Success n b) (count + n) None)
            ParserD.FContinue n pst1 ->
                assert (n <= 0)
                    (return $ Continue n (parseCont (count + n) pst1))
            ParserD.FError err ->
                -- XXX It is called only when there is no input arr. So using 0
                -- as the position is correct?
                cont (Failure 0 err) count None

    -- XXX Maybe we can use two separate continuations instead of using
    -- Just/Nothing cases here. That may help in avoiding the parseContJust
    -- function call.
    {-# INLINE parseCont #-}
    parseCont !cnt !pst (Chunk arr) = parseContChunk cnt 0 pst arr
    parseCont !cnt !pst None = parseContNothing cnt pst

-- | Convert a 'Parser' to 'ParserK' working on an Array stream.
--
-- /Pre-release/
--
{-# INLINE_LATE toParserK #-}
toParserK :: (Monad m, Unbox a) => ParserD.Parser a m b -> ParserK (Array a) m b
toParserK (ParserD.Parser step initial extract) =
    ParserK.MkParser $ adaptCWith step initial extract