enumerator 0.1.1 → 0.2
raw patch · 7 files changed
+348/−156 lines, 7 filesdep +extensible-exceptionsdep +textdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: extensible-exceptions, text
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Data.Enumerator.Text: ascii :: Codec
+ Data.Enumerator.Text: data Codec
+ Data.Enumerator.Text: decode :: (Monad m) => Codec -> Enumeratee SomeException ByteString Text m b
+ Data.Enumerator.Text: encode :: (Monad m) => Codec -> Enumeratee SomeException Text ByteString m b
+ Data.Enumerator.Text: enumFile :: FilePath -> Enumerator SomeException Text IO b
+ Data.Enumerator.Text: enumHandle :: Handle -> Enumerator SomeException Text IO b
+ Data.Enumerator.Text: instance Show Codec
+ Data.Enumerator.Text: iso8859_1 :: Codec
+ Data.Enumerator.Text: iterFile :: FilePath -> Iteratee SomeException Text IO ()
+ Data.Enumerator.Text: iterHandle :: Handle -> Iteratee SomeException Text IO ()
+ Data.Enumerator.Text: utf16_be :: Codec
+ Data.Enumerator.Text: utf16_le :: Codec
+ Data.Enumerator.Text: utf32_be :: Codec
+ Data.Enumerator.Text: utf32_le :: Codec
+ Data.Enumerator.Text: utf8 :: Codec
Files
- Examples/wc.hs +9/−104
- enumerator.cabal +16/−3
- hs/Data/Enumerator.hs +25/−19
- hs/Data/Enumerator/IO.hs +32/−30
- hs/Data/Enumerator/Text.hs +235/−0
- hs/Data/Enumerator/Util.hs +21/−0
- readme.txt +10/−0
Examples/wc.hs view
@@ -9,17 +9,15 @@ ----------------------------------------------------------------------------- module Main (main) where import Data.Enumerator-import Data.Enumerator.IO+import qualified Data.Enumerator.IO as EIO+import qualified Data.Enumerator.Text as ET import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.Text as T-import qualified Data.Text.Encoding as T -- support imports import Control.Exception as E import Data.List-import Data.Bits ((.&.))-import Data.Word (Word8) import Control.Monad (unless, forM_) import System.IO import System.Console.GetOpt@@ -53,24 +51,14 @@ -- -- Since it's possible for the input file's encoding to be non-UTF8, a bit -- of error handling is also required.+--+-- Note the use of joinI. 'ET.decode' is an enumeratee, which means it returns+-- an iteratee yielding an inner step. 'joinI' "collapses" an enumeratee's+-- return value, much as 'join' does to monadic values. iterChars :: Monad m => Iteratee E.SomeException B.ByteString m Integer-iterChars = continue (step (B.empty, 0)) where- step accT chunk = case chunk of- EOF -> let (extra, acc) = accT in yield acc (Chunks [extra])- (Chunks xs) -> case foldl' foldStep (Just accT) xs of- Just accT' -> continue $ step $! accT'- Nothing -> throwError (E.SomeException (E.ErrorCall "Invalid UTF-8"))- - -- The 'decodeUtf8' function is complicated, and defined later, but- -- all it does is decode as much input as possible, then return any- -- remaining bytes.- - foldStep Nothing _ = Nothing- foldStep (Just (extra, acc)) bytes = case decodeUtf8 (B.append extra bytes) of- Just (text, extra') -> Just (extra', acc + toInteger (T.length text))- Nothing -> Nothing-+iterChars = joinI (ET.decode ET.utf8 $$ count) where+ count = liftFoldL' (\acc t -> acc + toInteger (T.length t)) 0 main :: IO () main = do@@ -88,7 +76,7 @@ putStr $ filename ++ ": " -- see cat.hs for commented implementation of 'Data.Enumerator.IO.enumFile'- eitherStat <- run (enumFile filename $$ iter)+ eitherStat <- run (EIO.enumFile filename $$ iter) putStrLn $ case eitherStat of Left err -> "ERROR: " ++ show err Right stat -> show stat@@ -121,86 +109,3 @@ exitFailure return (Prelude.head options, files)---- Incremental UTF-8 validator / decoder--decodeUtf8 :: B.ByteString -> Maybe (T.Text, B.ByteString)-decodeUtf8 allBytes = loop B.empty allBytes where- - loop acc bytes | B.null bytes = Just (T.decodeUtf8 acc, bytes)- loop acc bytes = do- let (x0, x1, x2, x3) = indexes bytes- req <- required x0- if req > B.length bytes- then Just (T.decodeUtf8 acc, bytes)- else loop (B.append acc (B.take req bytes)) (B.drop req bytes)- - required x0- | x0 .&. 0x80 == 0x00 = Just 1- | x0 .&. 0xE0 == 0xC0 = Just 2- | x0 .&. 0xF0 == 0xE0 = Just 3- | x0 .&. 0xF8 == 0xF0 = Just 4- | otherwise = Nothing- - indexes bytes = (x0, x1, x2, x3) where- x0 = B.index bytes 0- x1 = B.index bytes 1- x2 = B.index bytes 2- x3 = B.index bytes 3---- UTF8 decoding gunk; mostly copied from Data.Text--between :: Word8 -- ^ byte to check- -> Word8 -- ^ lower bound- -> Word8 -- ^ upper bound- -> Bool-between x y z = x >= y && x <= z-{-# INLINE between #-}--validate1 :: Word8 -> Bool-validate1 x1 = between x1 0x00 0x7F-{-# INLINE validate1 #-}--validate2 :: Word8 -> Word8 -> Bool-validate2 x1 x2 = between x1 0xC2 0xDF && between x2 0x80 0xBF-{-# INLINE validate2 #-}--validate3 :: Word8 -> Word8 -> Word8 -> Bool-{-# INLINE validate3 #-}-validate3 x1 x2 x3 = validate3_1 ||- validate3_2 ||- validate3_3 ||- validate3_4- where- validate3_1 = (x1 == 0xE0) &&- between x2 0xA0 0xBF &&- between x3 0x80 0xBF- validate3_2 = between x1 0xE1 0xEC &&- between x2 0x80 0xBF &&- between x3 0x80 0xBF- validate3_3 = x1 == 0xED &&- between x2 0x80 0x9F &&- between x3 0x80 0xBF- validate3_4 = between x1 0xEE 0xEF &&- between x2 0x80 0xBF &&- between x3 0x80 0xBF--validate4 :: Word8 -> Word8 -> Word8 -> Word8 -> Bool-{-# INLINE validate4 #-}-validate4 x1 x2 x3 x4 = validate4_1 ||- validate4_2 ||- validate4_3- where - validate4_1 = x1 == 0xF0 &&- between x2 0x90 0xBF &&- between x3 0x80 0xBF &&- between x4 0x80 0xBF- validate4_2 = between x1 0xF1 0xF3 &&- between x2 0x80 0xBF &&- between x3 0x80 0xBF &&- between x4 0x80 0xBF- validate4_3 = x1 == 0xF4 &&- between x2 0x80 0x8F &&- between x3 0x80 0xBF &&- between x4 0x80 0xBF-
enumerator.cabal view
@@ -1,5 +1,5 @@ name: enumerator-version: 0.1.1+version: 0.2 synopsis: Implementation of Oleg Kiselyov's left-fold enumerators license: MIT license-file: license.txt@@ -19,6 +19,7 @@ extra-source-files: Examples/*.hs+ readme.txt source-repository head type: darcs@@ -29,10 +30,22 @@ hs-source-dirs: hs build-depends:- base >=3 && < 5- , transformers >= 0.2 && < 0.3+ transformers >= 0.2 && < 0.3 , bytestring >= 0.9 && < 0.10+ , text >= 0.7 && < 0.8 + if impl(ghc >= 6.10)+ build-depends:+ base >=4 && < 5+ else+ build-depends:+ base >=3 && < 4+ , extensible-exceptions+ exposed-modules: Data.Enumerator Data.Enumerator.IO+ Data.Enumerator.Text++ other-modules:+ Data.Enumerator.Util
hs/Data/Enumerator.hs view
@@ -33,12 +33,14 @@ , (>==>) , (<==<) -- ** Iteratees+ , run , consume , isEOF , liftTrans , liftFoldL , liftFoldL' , liftFoldM+ , printChunks -- ** Enumerators , enumEOF , enumList@@ -61,9 +63,6 @@ , Data.Enumerator.dropWhile , span , Data.Enumerator.break- -- * Utility functions- , run- , printChunks ) where import Data.List (genericDrop, genericLength, genericSplitAt) import Data.Monoid (Monoid, mempty, mappend, mconcat)@@ -215,6 +214,8 @@ case step of Error err -> runIteratee (h err) _ -> return step+infixl 1 >>==+ -- | Equivalent to (>>=), but allows 'Iteratee's with different input types -- to be composed. (>>==) :: Monad m =>@@ -223,6 +224,7 @@ Iteratee e a' m b' i >>== f = Iteratee $ runIteratee i >>= runIteratee . f {-# INLINE (>>==) #-}+infixr 1 ==<< -- | @(==\<\<) = flip (\>\>==)@ (==<<):: Monad m =>@@ -231,6 +233,7 @@ Iteratee e a' m b' (==<<) = flip (>>==) {-# INLINE (==<<) #-}+infixr 0 $$ -- | @($$) = (==\<\<)@ --@@ -242,6 +245,8 @@ Iteratee e a' m b' ($$) = (==<<) {-# INLINE ($$) #-}+infixr 1 >==>+ -- | @(>==>) e1 e2 s = e1 s >>== e2@ (>==>) :: Monad m => Enumerator e a m b ->@@ -250,6 +255,7 @@ Iteratee e a' m b' (>==>) e1 e2 s = e1 s >>== e2 {-# INLINE (>==>) #-}+infixr 1 <==< -- | @(\<==\<) = flip (>==>)@ (<==<) :: Monad m =>@@ -293,6 +299,22 @@ Chunks [] -> continue $ step acc Chunks xs -> Iteratee $ liftM (Continue . step) (foldM f acc xs) EOF -> yield acc EOF+-- | Run an iteratee until it finishes, and return either the final value+-- (if it succeeded) or the error (if it failed).+run :: Monad m => Iteratee e a m b -> m (Either e b)+run i = do+ mStep <- runIteratee $ enumEOF ==<< i+ case mStep of+ Error err -> return $ Left err+ Yield x _ -> return $ Right x+ Continue _ -> error "run: divergent iteratee"+-- | Print chunks as they're received from the enumerator, optionally+-- printing empty chunks.+printChunks :: (MIO.MonadIO m, Show a) => Bool -> Iteratee e a m ()+printChunks printEmpty = continue step where+ step (Chunks []) | not printEmpty = continue step+ step (Chunks xs) = MIO.liftIO (print xs) >> continue step+ step EOF = MIO.liftIO (putStrLn "EOF") >> yield () EOF -- | The most primitive enumerator; simply sends 'EOF'. The iteratee must -- either yield a value or throw an error continuing receiving 'EOF' will -- not terminate with any useful value.@@ -394,19 +416,3 @@ -- | @break p = 'span' (not . p)@ break :: Monad m => (a -> Bool) -> Iteratee e a m [a] break p = span $ not . p--- | Run an iteratee until it finishes, and return either the final value--- (if it succeeded) or the error (if it failed).-run :: Monad m => Iteratee e a m b -> m (Either e b)-run i = do- mStep <- runIteratee $ enumEOF ==<< i- case mStep of- Error err -> return $ Left err- Yield x _ -> return $ Right x- Continue _ -> error "run: divergent iteratee"--- | Print chunks as they're received from the enumerator, optionally--- printing empty chunks.-printChunks :: (MIO.MonadIO m, Show a) => Bool -> Iteratee e a m ()-printChunks printEmpty = continue step where- step (Chunks []) | not printEmpty = continue step- step (Chunks xs) = MIO.liftIO (print xs) >> continue step- step EOF = MIO.liftIO (putStrLn "EOF") >> yield () EOF
hs/Data/Enumerator/IO.hs view
@@ -17,7 +17,7 @@ , iterHandle ) where import Data.Enumerator-import Control.Monad.IO.Class (liftIO)+import Data.Enumerator.Util import qualified Control.Exception as E import qualified Data.ByteString as B import qualified System.IO as IO@@ -25,49 +25,51 @@ -- stream them to an 'Iteratee'. If an exception occurs during file IO, -- enumeration will stop and 'Error' will be returned. Exceptions from the -- iteratee are not caught.+--+-- This enumerator blocks until at least one byte is available from the+-- handle, and might read less than the maximum buffer size in some+-- cases. enumHandle :: Integer -- ^ Buffer size -> IO.Handle -> Enumerator E.SomeException B.ByteString IO b enumHandle bufferSize h = Iteratee . loop where- size' = fromInteger bufferSize- loop (Continue k) = read' k+ loop (Continue k) = withBytes $ \bytes -> if B.null bytes+ then return $ Continue k+ else runIteratee (k (Chunks [bytes])) >>= loop+ loop step = return step- read' k = do- eitherB <- E.try $ B.hGet h size'- case eitherB of- Left err -> return $ Error err- Right bytes | B.null bytes -> return $ Continue k- Right bytes -> do- step <- runIteratee (k (Chunks [bytes]))- loop step+ + intSize = fromInteger bufferSize+ withBytes = tryStep $ do+ hasInput <- E.catch+ (IO.hWaitForInput h (-1))+ (\(E.SomeException _) -> return False) + if hasInput+ then B.hGetNonBlocking h intSize+ else return B.empty -- | Opens a file path in binary mode, and passes the handle to 'enumHandle'. -- The file will be closed when the 'Iteratee' finishes. enumFile :: FilePath -> Enumerator E.SomeException B.ByteString IO b-enumFile path s = Iteratee $ do- eitherH <- E.try $ IO.openBinaryFile path IO.ReadMode- case eitherH of- Left err -> return $ Error err- Right h -> E.finally- (runIteratee (enumHandle 4096 h s))- (IO.hClose h)+enumFile path s = Iteratee io where+ withHandle = tryStep (IO.openBinaryFile path IO.ReadMode)+ io = withHandle $ \h -> E.finally+ (runIteratee (enumHandle 4096 h s))+ (IO.hClose h) -- | Read bytes from a stream and write them to a handle. If an exception -- occurs during file IO, enumeration will stop and 'Error' will be -- returned. iterHandle :: IO.Handle -> Iteratee E.SomeException B.ByteString IO () iterHandle h = continue step where step EOF = yield () EOF- step (Chunks bytes) = do- eitherErr <- liftIO . E.try $ mapM_ (B.hPut h) bytes- case eitherErr of- Left err -> throwError err- _ -> continue step+ step (Chunks []) = continue step+ step (Chunks bytes) = Iteratee io where+ put = mapM_ (B.hPut h) bytes+ io = tryStep put (\_ -> return $ Continue step) -- | Opens a file path in binary mode, and passes the handle to 'iterHandle'. -- The file will be closed when the 'Iteratee' finishes. iterFile :: FilePath -> Iteratee E.SomeException B.ByteString IO ()-iterFile path = Iteratee $ do- eitherH <- E.try $ IO.openBinaryFile path IO.WriteMode- case eitherH of- Left err -> return $ Error err- Right h -> E.finally- (runIteratee (iterHandle h))- (IO.hClose h)+iterFile path = Iteratee io where+ withHandle = tryStep (IO.openBinaryFile path IO.WriteMode)+ io = withHandle $ \h -> E.finally+ (runIteratee (iterHandle h))+ (IO.hClose h)
+ hs/Data/Enumerator/Text.hs view
@@ -0,0 +1,235 @@+-----------------------------------------------------------------------------+-- |+-- Module: Data.Enumerator.Text+-- Copyright: 2010 John Millikin+-- License: MIT+--+-- Maintainer: jmillikin@gmail.com+-- Portability: portable+--+-- Enumerator-based text IO+--+-----------------------------------------------------------------------------+module Data.Enumerator.Text (+ -- * Enumerators and iteratees+ enumHandle+ , enumFile+ , iterHandle+ , iterFile+ -- * Codecs+ , Codec+ , encode+ , decode+ , utf8+ , utf16_le+ , utf16_be+ , utf32_le+ , utf32_be+ , ascii+ , iso8859_1+ ) where+import qualified Control.Exception as E+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified System.IO as IO+import Control.Arrow (first)+import Data.Bits ((.&.))+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8+import qualified Data.Text.Encoding as TE+import Data.Bits ((.|.), shiftL)+import Data.Word (Word16)+import Prelude as Prelude+import Numeric (showIntAtBase)+import Data.Char (toUpper, intToDigit, ord)+import Data.Word (Word8)+import System.IO.Unsafe (unsafePerformIO)+import Data.Enumerator+import Data.Enumerator.Util+-- | Read lines of text from the handle, and stream them to an 'Iteratee'.+-- If an exception occurs during file IO, enumeration will stop and 'Error'+-- will be returned. Exceptions from the iteratee are not caught.+enumHandle :: IO.Handle -> Enumerator E.SomeException T.Text IO b+enumHandle h = Iteratee . loop where+ loop (Continue k) = withText $ \text -> if T.null text+ then return $ Continue k+ else runIteratee (k (Chunks [text])) >>= loop+ + loop step = return step+ withText = tryStep (T.hGetLine h)+-- | Opens a file path in text mode, and passes the handle to 'enumHandle'.+-- The file will be closed when the 'Iteratee' finishes.+enumFile :: FilePath -> Enumerator E.SomeException T.Text IO b+enumFile path s = Iteratee io where+ withHandle = tryStep (IO.openFile path IO.ReadMode)+ io = withHandle $ \h -> E.finally+ (runIteratee (enumHandle h s))+ (IO.hClose h)+-- | Read text from a stream and write it to a handle. If an exception+-- occurs during file IO, enumeration will stop and 'Error' will be+-- returned.+iterHandle :: IO.Handle -> Iteratee E.SomeException T.Text IO ()+iterHandle h = continue step where+ step EOF = yield () EOF+ step (Chunks []) = continue step+ step (Chunks chunks) = Iteratee io where+ put = mapM_ (T.hPutStr h) chunks+ io = tryStep put (\_ -> return $ Continue step)+-- | Opens a file path in text mode, and passes the handle to 'iterHandle'.+-- The file will be closed when the 'Iteratee' finishes.+iterFile :: FilePath -> Iteratee E.SomeException T.Text IO ()+iterFile path = Iteratee io where+ withHandle = tryStep (IO.openFile path IO.WriteMode)+ io = withHandle $ \h -> E.finally+ (runIteratee (iterHandle h))+ (IO.hClose h)+data Codec = Codec+ { codecName :: T.Text+ , codecEncode :: [T.Text] -> Either E.SomeException [B.ByteString]+ , codecDecode :: B.ByteString -> Either E.SomeException (T.Text, B.ByteString)+ }++instance Show Codec where+ showsPrec d c = showParen (d > 10) $+ showString "Codec " . shows (codecName c)+encode :: Monad m => Codec -> Enumeratee E.SomeException T.Text B.ByteString m b+encode codec = loop where+ loop = checkDone $ continue . step+ step k EOF = yield (Continue k) EOF+ step k (Chunks []) = continue $ step k+ step k (Chunks xs) = case codecEncode codec xs of+ Left err -> throwError err+ Right byteChunks -> k (Chunks byteChunks) >>== loop+decode :: Monad m => Codec -> Enumeratee E.SomeException B.ByteString T.Text m b+decode codec = loop B.empty where+ dec = codecDecode codec+ + loop acc = checkDone $ continue . step acc+ step acc k EOF = yield (Continue k) $ if B.null acc+ then EOF+ else Chunks [acc]+ step acc k (Chunks []) = continue $ step acc k+ step acc k (Chunks xs) = case dec (B.concat (acc:xs)) of+ Left err -> throwError err+ Right (text, extra) -> if T.null text+ then continue $ step extra k+ else k (Chunks [text]) >>== loop extra+utf8 :: Codec+utf8 = Codec name enc dec where+ name = T.pack "UTF-8"+ enc = Right . Prelude.map TE.encodeUtf8+ dec = unsafeTryDec . splitBytes+ splitBytes bytes = loop 0 where+ required x0+ | x0 .&. 0x80 == 0x00 = 1+ | x0 .&. 0xE0 == 0xC0 = 2+ | x0 .&. 0xF0 == 0xE0 = 3+ | x0 .&. 0xF8 == 0xF0 = 4+ + -- Invalid input; let Text figure it out+ | otherwise = 1+ maxN = B.length bytes+ + loop n | n == maxN = (TE.decodeUtf8 bytes, B.empty)+ loop n = let+ req = required $ B.index bytes n+ tooLong = first TE.decodeUtf8 $ B.splitAt n bytes+ decodeMore = loop $! n + req+ in if req > maxN then tooLong else decodeMore+utf16_le :: Codec+utf16_le = Codec name enc dec where+ name = T.pack "UTF-16-LE"+ enc = Right . Prelude.map TE.encodeUtf16LE+ dec = unsafeTryDec . splitBytes+ splitBytes bytes = loop 0 where+ maxN = B.length bytes+ + loop n | n == maxN = (TE.decodeUtf16LE bytes, B.empty)+ | (n + 1) == maxN = decodeTo n+ loop n = let+ req = utf16Required (B.index bytes 0) (B.index bytes 1)+ decodeMore = loop $! n + req+ in if req > maxN then decodeTo n else decodeMore+ + decodeTo n = first TE.decodeUtf16LE $ B.splitAt n bytes+utf16_be :: Codec+utf16_be = Codec name enc dec where+ name = T.pack "UTF-16-BE"+ enc = Right . Prelude.map TE.encodeUtf16BE+ dec = unsafeTryDec . splitBytes+ splitBytes bytes = loop 0 where+ maxN = B.length bytes+ + loop n | n == maxN = (TE.decodeUtf16BE bytes, B.empty)+ | (n + 1) == maxN = decodeTo n+ loop n = let+ req = utf16Required (B.index bytes 1) (B.index bytes 0)+ decodeMore = loop $! n + req+ in if req > maxN then decodeTo n else decodeMore+ + decodeTo n = first TE.decodeUtf16BE $ B.splitAt n bytes+utf16Required :: Word8 -> Word8 -> Int+utf16Required x0 x1 = required where+ required = if x >= 0xD800 && x <= 0xDBFF+ then 4+ else 2+ x :: Word16+ x = (fromIntegral x1 `shiftL` 8) .|. fromIntegral x0+utf32_le :: Codec+utf32_le = Codec name enc dec where+ name = T.pack "UTF-32-LE"+ enc = Right . Prelude.map TE.encodeUtf32LE+ dec = unsafeTryDec . utf32SplitBytes TE.decodeUtf32LE+ ++utf32_be :: Codec+utf32_be = Codec name enc dec where+ name = T.pack "UTF-32-BE"+ enc = Right . Prelude.map TE.encodeUtf32BE+ dec = unsafeTryDec . utf32SplitBytes TE.decodeUtf32BE+utf32SplitBytes :: (B.ByteString -> a) -> B.ByteString -> (a, B.ByteString)+utf32SplitBytes dec bytes = (dec toDecode, extra) where+ len = B.length bytes+ lenExtra = mod len 4+ lenToDecode = len - lenExtra+ (toDecode, extra) = if lenExtra == 0+ then (bytes, B.empty)+ else B.splitAt lenToDecode bytes+ascii :: Codec+ascii = Codec name (mapEither enc) dec where+ name = T.pack "ASCII"+ enc t = case T.findBy (\c -> ord c > 0x7F) t of+ Nothing -> Right . B8.pack . T.unpack $ t+ Just c -> illegalEnc name c+ dec bytes = case B.find (\w -> w > 0x7F) bytes of+ Nothing -> Right (T.pack (B8.unpack bytes), B.empty)+ Just w -> illegalDec name w+iso8859_1 :: Codec+iso8859_1 = Codec name (mapEither enc) dec where+ name = T.pack "ISO-8859-1"+ enc t = case T.findBy (\c -> ord c > 0xFF) t of+ Nothing -> Right . B8.pack . T.unpack $ t+ Just c -> illegalEnc name c+ dec bytes = Right (T.pack (B8.unpack bytes), B.empty)+illegalEnc :: T.Text -> Char -> Either E.SomeException a+illegalEnc name c = Left . E.SomeException . E.ErrorCall $ msg "" where+ len = Prelude.length+ pad str | len str < 4 = replicate (4 - len str) '0' ++ str+ | otherwise = str+ hex = "U+" ++ pad (showIntAtBase 16 (toUpper . intToDigit) (ord c) "")+ msg = (s "Codec " . shows name . s " can't encode character " . s hex)+ s = showString+illegalDec :: T.Text -> Word8 -> Either E.SomeException a+illegalDec name w = Left . E.SomeException . E.ErrorCall $ msg "" where+ len = Prelude.length+ pad str | len str < 2 = replicate (2 - len str) '0' ++ str+ | otherwise = str+ hex = "0x" ++ pad (showIntAtBase 16 (toUpper . intToDigit) w "")+ msg = (s "Codec " . shows name . s " can't decode byte " . s hex)+ s = showString+unsafeTryDec :: (a, b) -> Either E.SomeException (a, b)+unsafeTryDec (a, b) = unsafePerformIO $ do+ tried <- E.try $ E.evaluate a+ return $ case tried of+ Left err -> Left err+ Right _ -> Right (a, b)
+ hs/Data/Enumerator/Util.hs view
@@ -0,0 +1,21 @@+module Data.Enumerator.Util where+import Data.Enumerator+import qualified Control.Exception as E++tryStep :: IO t+ -> (t -> IO (Step E.SomeException a IO b))+ -> IO (Step E.SomeException a IO b)+tryStep get io = do+ tried <- E.try get+ case tried of+ Right t -> io t+ Left err -> return $ Error err+{-# INLINE tryStep #-}++mapEither :: (a -> Either e b) -> [a] -> Either e [b]+mapEither f = loop [] where+ loop acc [] = Right (reverse acc)+ loop acc (a:as) = case f a of+ Left err -> Left err+ Right b -> loop (b:acc) as+{-# INLINE mapEither #-}
+ readme.txt view
@@ -0,0 +1,10 @@+The source code for "enumerator" is literate. To build the library from scratch,+install the "anansi" application and then run:++ anansi -o hs/ enumerator.anansi++To generate the woven PDF, install NoWeb and then run:++ anansi -w -l latex-noweb -o enumerator.tex enumerator.anansi+ xelatex enumerator.tex+ xelatex enumerator.tex