-- |
-- Module: M.IO.Internal.Read
-- Description: Stream parsing utilities
-- License: BSD-3-Clause
--
-- This module provides parsing utilities for reading structured data from streams.
module M.IO.Internal.Read
  ( -- * Basic parsing
    parseio,
    parseio0,

    -- * Lifted versions
    parseiolift,
    parseio0lift,
  )
where

import Control.Exception
import Control.Monad.IO.Class
import Data.ByteString (ByteString)
import Data.Functor
import FlatParse.Stateful
import System.IO.Streams
import Prelude hiding (read)

-- | parse from a stream (with no state and int value of 0). see also:
-- 'parseio'
parseio0 ::
  (Exception e) =>
  -- | input stream
  InputStream ByteString ->
  -- | parser
  ParserIO () e a ->
  -- | result
  IO a
parseio0 :: forall e a.
Exception e =>
InputStream ByteString -> ParserIO () e a -> IO a
parseio0 = () -> Int -> InputStream ByteString -> ParserIO () e a -> IO a
forall e r a.
Exception e =>
r -> Int -> InputStream ByteString -> ParserIO r e a -> IO a
parseio () Int
0
{-# INLINE parseio0 #-}

-- | lifted version of 'parseio0'
parseio0lift ::
  (MonadIO m, Exception e) =>
  -- | input stream
  InputStream ByteString ->
  -- | parser
  ParserIO () e a ->
  -- | result
  m a
parseio0lift :: forall (m :: * -> *) e a.
(MonadIO m, Exception e) =>
InputStream ByteString -> ParserIO () e a -> m a
parseio0lift = () -> Int -> InputStream ByteString -> ParserIO () e a -> m a
forall (m :: * -> *) e r a.
(MonadIO m, Exception e) =>
r -> Int -> InputStream ByteString -> ParserIO r e a -> m a
parseiolift () Int
0
{-# INLINE parseio0lift #-}

-- | Parse from a stream. Automatically handles chunked input by
-- concatenating chunks until a complete parse succeeds.
--
-- May throw:
-- * The parser's error type @e@
-- * IOError "parseio: unexpected end of input"
parseio ::
  (Exception e) =>
  -- | state
  r ->
  -- | int value
  Int ->
  -- | input stream
  InputStream ByteString ->
  -- | parser
  ParserIO r e a ->
  -- | result
  IO a
parseio :: forall e r a.
Exception e =>
r -> Int -> InputStream ByteString -> ParserIO r e a -> IO a
parseio r
v Int
i InputStream ByteString
s ParserIO r e a
f = ByteString -> IO a
parse ByteString
forall a. Monoid a => a
mempty
  where
    -- controlled by a two-state machine
    parse :: ByteString -> IO a
parse ByteString
b = ParserIO r e a -> r -> Int -> ByteString -> IO (Result e a)
forall r e a.
ParserIO r e a -> r -> Int -> ByteString -> IO (Result e a)
runParserIO ParserIO r e a
f r
v Int
i ByteString
b IO (Result e a) -> (Result e a -> IO a) -> IO a
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ByteString -> Result e a -> IO a
check ByteString
b
    check :: ByteString -> Result e a -> IO a
check ByteString
b = \case
      OK a
a Int
_ ByteString
r -> ByteString -> InputStream ByteString -> IO ()
forall a. a -> InputStream a -> IO ()
unRead ByteString
r InputStream ByteString
s IO () -> a -> IO a
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> a
a
      Result e a
Fail ->
        InputStream ByteString -> IO (Maybe ByteString)
forall a. InputStream a -> IO (Maybe a)
read InputStream ByteString
s IO (Maybe ByteString) -> (Maybe ByteString -> IO a) -> IO a
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
          -- note: FlatParse is not a resumable parser.
          -- so, we must start over from the beginning
          Just ByteString
t -> ByteString -> IO a
parse (ByteString
b ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
t) -- concatenate and start over
          Maybe ByteString
Nothing -> String -> IO a
forall a. String -> IO a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"parseio: unexpected end of input"
      Err e
e -> e -> IO a
forall e a. Exception e => e -> IO a
throwIO e
e

-- | lifted version of 'parseio'
parseiolift ::
  (MonadIO m, Exception e) =>
  -- | state
  r ->
  -- | int value
  Int ->
  -- | input stream
  InputStream ByteString ->
  -- | parser
  ParserIO r e a ->
  -- | result
  m a
parseiolift :: forall (m :: * -> *) e r a.
(MonadIO m, Exception e) =>
r -> Int -> InputStream ByteString -> ParserIO r e a -> m a
parseiolift = (((IO a -> m a
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO .) .) .) ((Int -> InputStream ByteString -> ParserIO r e a -> IO a)
 -> Int -> InputStream ByteString -> ParserIO r e a -> m a)
-> (r -> Int -> InputStream ByteString -> ParserIO r e a -> IO a)
-> r
-> Int
-> InputStream ByteString
-> ParserIO r e a
-> m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. r -> Int -> InputStream ByteString -> ParserIO r e a -> IO a
forall e r a.
Exception e =>
r -> Int -> InputStream ByteString -> ParserIO r e a -> IO a
parseio
{-# INLINE parseiolift #-}