lattest-lib-0.1.1: src/System/IO/Streams/Synchronized/Attoparsec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module System.IO.Streams.Synchronized.Attoparsec (
parseFromStream,
parserToInputStream
)
where
import Control.Exception(Exception)
import Control.Monad(unless)
import Data.String(IsString)
import Control.Monad.Extra((||^), (&&^))
import qualified Data.Attoparsec.ByteString as C8(parse,feed, Parser)
import Data.Attoparsec.Types(Parser,IResult(..))
import Data.ByteString(ByteString)
import qualified Data.ByteString as C8(null)
import Data.List(intercalate)
import Lattest.Streams.Synchronized (TInputStream,makeTInputStream,hasInput)
import qualified Lattest.Streams.Synchronized as Streams (read, unRead)
import Control.Concurrent.STM.TVar(TVar, newTVarIO, writeTVar, readTVar)
import Control.Concurrent.STM(STM, throwSTM)
parseFromStream :: TVar (Bool, IResult ByteString (Maybe r)) -> Parser ByteString (Maybe r) -> TInputStream ByteString -> STM (Maybe r)
parseFromStream stateVar parser is = do
parseFromStream' stateVar True is
(closed, state) <- readTVar stateVar
writeTVar stateVar (closed, C8.parse parser "")
if closed
then return Nothing
else case state of
(Done "" r) -> return r
other ->
let (residual,ctx,message) = errorContext other
in throwSTM $ ParseException (toMsg ctx ++ message ++ ", residual [" ++ show residual ++ "]")
where
toMsg [] = ""
toMsg xs = "[parsing " ++ intercalate "/" xs ++ "] "
parseFromStream' :: TVar (Bool, IResult ByteString (Maybe r)) -> Bool -> TInputStream ByteString -> STM ()
parseFromStream' stateVar blockUntilFinished is = do
(closed, r) <- readTVar stateVar
if closed
then return ()
else doParse r >>= writeTVar stateVar
where
doParse :: IResult ByteString (Maybe r) -> STM (Bool, IResult ByteString (Maybe r))
doParse r = do
stop <- return (isFinished r) ||^ (return (not blockUntilFinished) &&^ (not <$> hasInput is))
if stop
then do
r' <- unread' r
return (False, r')
else do
m <- Streams.read is
case m of
Nothing -> do
r' <- unread' r
return (True, r')
Just s -> if C8.null s
then doParse r
else doParse $! C8.feed r s
unread rest = unless (C8.null rest) $ Streams.unRead rest is
unread' (Fail rest ctx e) = unread rest >> return (Fail "" ctx e)
unread' (Done rest result) = unread rest >> return (Done "" result)
unread' partial = return partial
errorContext :: IsString a => IResult a r -> (a, [String], String)
errorContext (Fail residual ctx msg) = (residual, ctx, msg)
errorContext (Partial _) = ("", [], "")
errorContext (Done residual _) = (residual, [],"")
isFinished :: IResult a b -> Bool
isFinished (Partial _) = False
isFinished _ = True
newtype ParseException = ParseException String
instance Exception ParseException
instance Show ParseException where
show (ParseException message) = "Parse error: " ++ message
-- Convert a stream of bytes to a stream of actions parsed from those bytes, using the provided parser. The stream ends if either the parser yields
-- a Nothing or if the underlying stream yields a Nothing
-- If the parser yields a Nothing, then all bytes up to that Nothing are consumed from the underlying stream, but if the underyling stream yields a
-- Nothing, then all bytes since the last Just result are unread.
parserToInputStream :: C8.Parser (Maybe r)
-> TInputStream ByteString
-> IO (TInputStream r)
parserToInputStream parser is = do
stateVar <- newTVarIO (False, C8.parse parser "")
makeTInputStream (parseFromStream stateVar parser is) (parserHasInput stateVar)
where
parserHasInput stateVar = do
parseFromStream' stateVar False is
(closed, state) <- readTVar stateVar
return $ closed || isFinished state