Skip to content

Commit 239c508

Browse files
committed
Implement hGetLine.
--HG-- extra : convert_revision : e165c4f2de68d78cb36024906ef9eccf052ad5de
1 parent 033b9d4 commit 239c508

2 files changed

Lines changed: 160 additions & 7 deletions

File tree

Data/Text/IO.hs

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{-# LANGUAGE BangPatterns, CPP #-}
1+
{-# LANGUAGE BangPatterns, CPP, RecordWildCards #-}
22
-- |
33
-- Module : Data.Text.IO
44
-- Copyright : (c) Bryan O'Sullivan 2009,
@@ -31,6 +31,7 @@ module Data.Text.IO
3131
, putStrLn
3232
) where
3333

34+
import Debug.Trace
3435
import Data.Text (Text)
3536
import Prelude hiding (appendFile, getContents, getLine, interact, putStr,
3637
putStrLn, readFile, writeFile)
@@ -40,15 +41,22 @@ import qualified Data.ByteString as B
4041
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
4142
#else
4243
import Data.IORef (readIORef, writeIORef)
43-
import Data.Text.Fusion (stream)
44+
import qualified Data.Text as T
45+
import Data.Text.Fusion (stream, unstream)
4446
import Data.Text.Fusion.Internal (Step(..), Stream(..))
47+
import Data.Text.Fusion.Size (exactSize, maxSize)
48+
import Data.Text.Unsafe (inlinePerformIO)
49+
import Foreign.Storable (peekElemOff)
4550
import GHC.IO.Buffer (Buffer(..), BufferState(..), CharBufElem, CharBuffer,
46-
RawCharBuffer, emptyBuffer, newCharBuffer, writeCharBuf)
47-
import GHC.IO.Handle.Internals (wantWritableHandle)
51+
RawCharBuffer, bufferAdjustL, bufferElems, charSize, emptyBuffer, isEmptyBuffer, newCharBuffer, readCharBuf, withRawBuffer,
52+
writeCharBuf)
53+
import GHC.IO.Handle.Internals (ioe_EOF, readTextDevice, wantReadableHandle_,
54+
wantWritableHandle)
4855
import GHC.IO.Handle.Text (commitBuffer')
4956
import GHC.IO.Handle.Types (BufferList(..), BufferMode(..), Handle__(..),
5057
Newline(..))
5158
import System.IO (IOMode(..), openFile, withFile)
59+
import System.IO.Error (isEOFError)
5260
#endif
5361

5462
-- | The 'readFile' function reads a file and returns the contents of
@@ -79,14 +87,127 @@ hGetLine :: Handle -> IO Text
7987
#if __GLASGOW_HASKELL__ <= 610
8088
hGetLine = fmap decodeUtf8 . B.hGetLine
8189
#else
82-
hGetLine = undefined
90+
hGetLine h = wantReadableHandle_ "hGetLine" h go
91+
where go hh@Handle__{..} = readIORef haCharBuffer >>= hGetLineLoop hh []
92+
93+
hGetLineLoop :: Handle__ -> [Text] -> CharBuffer -> IO Text
94+
hGetLineLoop hh@Handle__{..} ts buf@Buffer{ bufL=r0, bufR=w, bufRaw=raw0 } = do
95+
let findEOL raw r
96+
| r == w = return (False, w)
97+
| otherwise = do
98+
(c,r') <- readCharBuf raw r
99+
if c == '\n'
100+
then return (True, r)
101+
else findEOL raw r'
102+
(eol, off) <- findEOL raw0 r0
103+
(t,r') <- if haInputNL == CRLF
104+
then unpack_nl raw0 r0 off
105+
else do t <- unpack raw0 r0 off
106+
return (t,off)
107+
if eol
108+
then do writeIORef haCharBuffer (bufferAdjustL (off+1) buf)
109+
return $! T.concat (reverse (t:ts))
110+
else do
111+
let buf1 = bufferAdjustL r' buf
112+
maybe_buf <- maybeFillReadBuffer hh buf1
113+
case maybe_buf of
114+
-- Nothing indicates we caught an EOF, and we may have a
115+
-- partial line to return.
116+
Nothing -> do
117+
-- we reached EOF. There might be a lone \r left
118+
-- in the buffer, so check for that and
119+
-- append it to the line if necessary.
120+
--
121+
let pre | isEmptyBuffer buf1 = T.empty
122+
| otherwise = T.singleton '\r'
123+
writeIORef haCharBuffer buf1{ bufL=0, bufR=0 }
124+
let str = T.concat . reverse $ pre:t:ts
125+
if T.null str
126+
then ioe_EOF
127+
else return str
128+
Just new_buf ->
129+
hGetLineLoop hh (t:ts) new_buf
130+
131+
-- This function is lifted almost verbatim from GHC.IO.Handle.Text.
132+
maybeFillReadBuffer :: Handle__ -> CharBuffer -> IO (Maybe CharBuffer)
133+
maybeFillReadBuffer handle_ buf
134+
= catch (Just `fmap` getSomeCharacters handle_ buf) $ \e ->
135+
if isEOFError e
136+
then return Nothing
137+
else ioError e
138+
139+
unpack :: RawCharBuffer -> Int -> Int -> IO Text
140+
unpack !buf !r !w
141+
| charSize /= 4 = sizeError "unpack"
142+
| r == w = return T.empty
143+
| otherwise = withRawBuffer buf go
144+
where
145+
go pbuf = return $! unstream (Stream next r (exactSize (w-r)))
146+
where
147+
next !i | i >= w = Done
148+
| otherwise = Yield (let c = ix i in trace (show (w,i,c)) c) (i+1)
149+
ix i = inlinePerformIO $ peekElemOff pbuf i
150+
151+
unpack_nl :: RawCharBuffer -> Int -> Int -> IO (Text, Int)
152+
unpack_nl !buf !r !w
153+
| charSize /= 4 = sizeError "unpack_nl"
154+
| r == w = return (T.empty, 0)
155+
| otherwise = withRawBuffer buf $ go
156+
where
157+
go pbuf = do
158+
let t = unstream (Stream next r (maxSize (w-r)))
159+
w' = w - 1
160+
return $ if ix w' == '\r'
161+
then (t,w')
162+
else (t,w)
163+
where
164+
next !i | i >= w = Done
165+
| c == '\r' = let i' = i + 1
166+
in if i' < w
167+
then if ix i' == '\n'
168+
then Yield '\n' (i+2)
169+
else Yield '\n' i'
170+
else Done
171+
| otherwise = Yield c (i+1)
172+
where c = ix i
173+
ix i = inlinePerformIO $ peekElemOff pbuf i
174+
175+
sizeError :: String -> a
176+
sizeError loc = error $ "Data.Text.IO." ++ loc ++ ": bad internal buffer size"
177+
178+
-- This function is completely lifted from GHC.IO.Handle.Text.
179+
getSomeCharacters :: Handle__ -> CharBuffer -> IO CharBuffer
180+
getSomeCharacters handle_@Handle__{..} buf@Buffer{..} =
181+
case bufferElems buf of
182+
-- buffer empty: read some more
183+
0 -> readTextDevice handle_ buf
184+
185+
-- if the buffer has a single '\r' in it and we're doing newline
186+
-- translation: read some more
187+
1 | haInputNL == CRLF -> do
188+
(c,_) <- readCharBuf bufRaw bufL
189+
if c == '\r'
190+
then do -- shuffle the '\r' to the beginning. This is only safe
191+
-- if we're about to call readTextDevice, otherwise it
192+
-- would mess up flushCharBuffer.
193+
-- See [note Buffer Flushing], GHC.IO.Handle.Types
194+
_ <- writeCharBuf bufRaw 0 '\r'
195+
let buf' = buf{ bufL=0, bufR=1 }
196+
readTextDevice handle_ buf'
197+
else do
198+
return buf
199+
200+
-- buffer has some chars in it already: just return it
201+
_otherwise ->
202+
return buf
83203
#endif
84204

85205
-- | Write a string to a handle.
86206
hPutStr :: Handle -> Text -> IO ()
87207
#if __GLASGOW_HASKELL__ <= 610
88208
hPutStr h = B.hPutStr h . encodeUtf8
89209
#else
210+
-- This function is lifted almost verbatim from GHC.IO.Handle.Text.
90211
hPutStr h t = do
91212
(buffer_mode, nl) <-
92213
wantWritableHandle "hPutStr" h $ \h_ -> do
@@ -193,7 +314,16 @@ putStrLn = hPutStrLn stdout
193314
-- Under GHC 6.10 and earlier, the system I\/O libraries do not
194315
-- support locale-sensitive I\/O. All data read by functions in this
195316
-- module is decoded as UTF-8, and before data is written, it is first
196-
-- encoded as UTF-8.
317+
-- encoded as UTF-8. If you must use a non-UTF-8 locale on an older
318+
-- version of GHC, you will have to perform the transcoding yourself,
319+
-- e.g. as follows:
320+
--
321+
-- > import qualified Data.ByteString as B
322+
-- > import Data.Text (Text)
323+
-- > import Data.Text.Encoding (encodeUtf16)
324+
-- >
325+
-- > putStr_Utf16LE :: Text -> IO ()
326+
-- > putStr_Utf16LE t = B.putStr (encodeUtf16LE t)
197327
--
198328
-- Beginning with GHC 6.12, text I\/O is performed using the system or
199329
-- handle's current locale.

Data/Text/Unsafe.hs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-}
12
-- |
23
-- Module : Data.Text.Unsafe
34
-- Copyright : (c) Bryan O'Sullivan 2009
@@ -11,7 +12,8 @@
1112
-- use in heavily tested code.
1213
module Data.Text.Unsafe
1314
(
14-
iter
15+
inlinePerformIO
16+
, iter
1517
, iter_
1618
, reverseIter
1719
, unsafeHead
@@ -23,6 +25,14 @@ import Data.Text.Internal (Text(..))
2325
import Data.Text.UnsafeChar (unsafeChr)
2426
import Data.Text.Encoding.Utf16 (chr2)
2527
import qualified Data.Text.Array as A
28+
#if defined(__GLASGOW_HASKELL__)
29+
# if __GLASGOW_HASKELL__ >= 611
30+
import GHC.IO (IO(IO))
31+
# else
32+
import GHC.IOBase (IO(IO))
33+
# endif
34+
import GHC.Base (realWorld#)
35+
#endif
2636

2737
-- | /O(1)/ A variant of 'head' for non-empty 'Text'. 'unsafeHead'
2838
-- omits the check for the empty case, so there is an obligation on
@@ -77,3 +87,16 @@ reverseIter (Text arr off len) i
7787
j = assert (i >= 0) $ off + i
7888
k = j - 1
7989
{-# INLINE reverseIter #-}
90+
91+
-- | Just like unsafePerformIO, but we inline it. Big performance gains as
92+
-- it exposes lots of things to further inlining. /Very unsafe/. In
93+
-- particular, you should do no memory allocation inside an
94+
-- 'inlinePerformIO' block. On Hugs this is just @unsafePerformIO@.
95+
--
96+
{-# INLINE inlinePerformIO #-}
97+
inlinePerformIO :: IO a -> a
98+
#if defined(__GLASGOW_HASKELL__)
99+
inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
100+
#else
101+
inlinePerformIO = unsafePerformIO
102+
#endif

0 commit comments

Comments
 (0)