Ответ 1
Поскольку ваш парсер работает по строке за раз, вам даже не нужно использовать attoparsec-iteratee. Я бы написал это как:
import Data.Iteratee as I
import Data.Iteratee.Char
import Data.Attoparsec as A
parser :: Parser ParseOutput
type POut = Either String ParseOutput
processLines :: Iteratee ByteString IO [POut]
processLines = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) stream2list
Ключом к пониманию этого является "enumeratee", который является просто итерационным термином для конвертера потока. Он требует потокового процессора (итерации) одного типа потока и преобразует его для работы с другим потоком. Оба enumLinesBS
и mapStream
являются перечисляемыми.
Чтобы отобразить ваш парсер на несколько строк, mapStream
достаточно:
i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list
Вложенные итерации просто означают, что это преобразует поток [ByteString]
в поток [POut]
, а когда выполняется окончательный iteratee (stream2list), он возвращает этот поток как [POut]
. Итак, теперь вам нужен итерационный эквивалент lines
для создания этого потока [ByteString]
, что и делает enumLinesBS
:
i2 :: Iteratee ByteString IO (Iteratee [ByteString] IO (Iteratee [POut] m [POut])))
i2 = enumLinesBS $ mapStream f stream2list
Но эта функция довольно громоздка для использования из-за всей вложенности. То, что мы действительно хотим, - это способ прямого вывода данных между потоковыми преобразователями и, в конце концов, упростить все до одного итерации. Для этого мы используем joinI
, (><>)
и (><>)
:
e1 :: Iteratee [POut] IO a -> Iteratee ByteString IO (Iteratee [POut] IO a)
e1 = enumLinesBS ><> mapStream (A.parseOnly parser)
i' :: Iteratee ByteString IO [POut]
i' = joinI $ e1 stream2list
что эквивалентно тому, как я написал его выше, с e1
inlined.
Остается еще важный элемент. Эта функция просто возвращает результаты анализа в списке. Обычно вы хотели бы сделать что-то еще, например, объединить результаты со сгибом.
edit: Data.Iteratee.ListLike.mapM_
часто полезно для создания потребителей. В этот момент каждый элемент потока является результатом синтаксического анализа, поэтому, если вы хотите их распечатать, вы можете использовать
consumeParse :: Iteratee [POut] IO ()
consumeParse = I.mapM_ (either (\e -> return ()) print)
processLines2 :: Iteratee ByteString IO ()
processLines2 = joinI $ (enumLinesBS ><> I.mapStream (A.parseOnly parser)) consumeParse
Это будет печатать только успешные анализы. Вы можете легко сообщать об ошибках в STDERR или обрабатывать их другими способами.