-- | This module provides support for parsing values from 'InputStream's using -- @attoparsec@. {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} module System.IO.Streams.Internal.Attoparsec ( -- * Parsing parseFromStreamInternal , ParseData(..) -- * Parse Exceptions , ParseException(..) , eitherResult ) where ------------------------------------------------------------------------------ import Control.Exception (Exception, throwIO) import Control.Monad (unless) import qualified Data.Attoparsec.ByteString.Char8 as S import qualified Data.Attoparsec.Text as T import Data.Attoparsec.Types (IResult (..), Parser) import qualified Data.ByteString as S import Data.List (intercalate) import Data.String (IsString) import qualified Data.Text as T import Data.Typeable (Typeable) import Prelude hiding (null, read) ------------------------------------------------------------------------------ import System.IO.Streams.Internal (InputStream) import qualified System.IO.Streams.Internal as Streams ------------------------------------------------------------------------------ -- | An exception raised when parsing fails. data ParseException = ParseException String deriving (Typeable) instance Show ParseException where show (ParseException s) = "Parse exception: " ++ s instance Exception ParseException ------------------------------------------------------------------------------ class (IsString i) => ParseData i where parse :: Parser i a -> i -> IResult i a feed :: IResult i r -> i -> IResult i r null :: i -> Bool ------------------------------------------------------------------------------ instance ParseData S.ByteString where parse = S.parse feed = S.feed null = S.null ------------------------------------------------------------------------------ instance ParseData T.Text where parse = T.parse feed = T.feed null = T.null ------------------------------------------------------------------------------ -- | Internal version of parseFromStream allowing dependency injection of the -- parse functions for testing. parseFromStreamInternal :: ParseData i => (Parser i r -> i -> IResult i r) -> (IResult i r -> i -> IResult i r) -> Parser i r -> InputStream i -> IO r parseFromStreamInternal parseFunc feedFunc parser is = Streams.read is >>= maybe (finish $ parseFunc parser "") (\s -> if null s then parseFromStreamInternal parseFunc feedFunc parser is else go $! parseFunc parser s) where leftover x = unless (null x) $ Streams.unRead x is finish k = let k' = feedFunc (feedFunc k "") "" in case k' of Fail x _ _ -> leftover x >> err k' Partial _ -> err k' -- should be impossible Done x r -> leftover x >> return r err r = let (Left (!_,c,m)) = eitherResult r in throwIO $ ParseException (ctxMsg c ++ m) ctxMsg [] = "" ctxMsg xs = "[parsing " ++ intercalate "/" xs ++ "] " go r@(Fail x _ _) = leftover x >> err r go (Done x r) = leftover x >> return r go r = Streams.read is >>= maybe (finish r) (\s -> if null s then go r else go $! feedFunc r s) ------------------------------------------------------------------------------ -- A replacement for attoparsec's 'eitherResult', which discards information -- about the context of the failed parse. eitherResult :: IsString i => IResult i r -> Either (i, [String], String) r eitherResult (Done _ r) = Right r eitherResult (Fail residual ctx msg) = Left (residual, ctx, msg) eitherResult _ = Left ("", [], "Result: incomplete input")