{-# LANGUAGE BangPatterns, RecordWildCards #-}
module Data.Text.Internal.IO
(
hGetLineWith
, readChunk
) where
import qualified Control.Exception as E
import Data.IORef (readIORef, writeIORef)
import Data.Text (Text)
import Data.Text.Internal.Fusion (unstream)
import Data.Text.Internal.Fusion.Types (Step(..), Stream(..))
import Data.Text.Internal.Fusion.Size (exactSize, maxSize)
import Data.Text.Unsafe (inlinePerformIO)
import Foreign.Storable (peekElemOff)
import GHC.IO.Buffer (Buffer(..), CharBuffer, RawCharBuffer, bufferAdjustL,
bufferElems, charSize, isEmptyBuffer, readCharBuf,
withRawBuffer, writeCharBuf)
import GHC.IO.Handle.Internals (ioe_EOF, readTextDevice, wantReadableHandle_)
import GHC.IO.Handle.Types (Handle__(..), Newline(..))
import System.IO (Handle)
import System.IO.Error (isEOFError)
import qualified Data.Text as T
hGetLineWith :: ([Text] -> t) -> Handle -> IO t
hGetLineWith f h = wantReadableHandle_ "hGetLine" h go
where
go hh@Handle__{..} = readIORef haCharBuffer >>= fmap f . hGetLineLoop hh []
hGetLineLoop :: Handle__ -> [Text] -> CharBuffer -> IO [Text]
hGetLineLoop hh@Handle__{..} = go where
go ts buf@Buffer{ bufL=r0, bufR=w, bufRaw=raw0 } = do
let findEOL raw r | r == w = return (False, w)
| otherwise = do
(c,r') <- readCharBuf raw r
if c == '\n'
then return (True, r)
else findEOL raw r'
(eol, off) <- findEOL raw0 r0
(t,r') <- if haInputNL == CRLF
then unpack_nl raw0 r0 off
else do t <- unpack raw0 r0 off
return (t,off)
if eol
then do writeIORef haCharBuffer (bufferAdjustL (off+1) buf)
return $ reverse (t:ts)
else do
let buf1 = bufferAdjustL r' buf
maybe_buf <- maybeFillReadBuffer hh buf1
case maybe_buf of
Nothing -> do
let pre | isEmptyBuffer buf1 = T.empty
| otherwise = T.singleton '\r'
writeIORef haCharBuffer buf1{ bufL=0, bufR=0 }
let str = reverse . filter (not . T.null) $ pre:t:ts
if null str
then ioe_EOF
else return str
Just new_buf -> go (t:ts) new_buf
maybeFillReadBuffer :: Handle__ -> CharBuffer -> IO (Maybe CharBuffer)
maybeFillReadBuffer handle_ buf
= E.catch (Just `fmap` getSomeCharacters handle_ buf) $ \e ->
if isEOFError e
then return Nothing
else ioError e
unpack :: RawCharBuffer -> Int -> Int -> IO Text
unpack !buf !r !w
| charSize /= 4 = sizeError "unpack"
| r >= w = return T.empty
| otherwise = withRawBuffer buf go
where
go pbuf = return $! unstream (Stream next r (exactSize (w-r)))
where
next !i | i >= w = Done
| otherwise = Yield (ix i) (i+1)
ix i = inlinePerformIO $ peekElemOff pbuf i
unpack_nl :: RawCharBuffer -> Int -> Int -> IO (Text, Int)
unpack_nl !buf !r !w
| charSize /= 4 = sizeError "unpack_nl"
| r >= w = return (T.empty, 0)
| otherwise = withRawBuffer buf $ go
where
go pbuf = do
let !t = unstream (Stream next r (maxSize (w-r)))
w' = w - 1
return $ if ix w' == '\r'
then (t,w')
else (t,w)
where
next !i | i >= w = Done
| c == '\r' = let i' = i + 1
in if i' < w
then if ix i' == '\n'
then Yield '\n' (i+2)
else Yield '\n' i'
else Done
| otherwise = Yield c (i+1)
where c = ix i
ix i = inlinePerformIO $ peekElemOff pbuf i
getSomeCharacters :: Handle__ -> CharBuffer -> IO CharBuffer
getSomeCharacters handle_@Handle__{..} buf@Buffer{..} =
case bufferElems buf of
0 -> {-# SCC "readTextDevice" #-} readTextDevice handle_ buf
1 | haInputNL == CRLF -> do
(c,_) <- readCharBuf bufRaw bufL
if c == '\r'
then do
_ <- writeCharBuf bufRaw 0 '\r'
let buf' = buf{ bufL=0, bufR=1 }
readTextDevice handle_ buf'
else do
return buf
_otherwise -> {-# SCC "otherwise" #-} return buf
readChunk :: Handle__ -> CharBuffer -> IO Text
readChunk hh@Handle__{..} buf = do
buf'@Buffer{..} <- getSomeCharacters hh buf
(t,r) <- if haInputNL == CRLF
then unpack_nl bufRaw bufL bufR
else do t <- unpack bufRaw bufL bufR
return (t,bufR)
writeIORef haCharBuffer (bufferAdjustL r buf')
return t
sizeError :: String -> a
sizeError loc = error $ "Data.Text.IO." ++ loc ++ ": bad internal buffer size"