Attoparsec Iteratee

Я хотел, чтобы немного узнать об Iteratees, переопределить простой парсер, который я сделал, используя Data.Iteratee и Data.Attoparsec.Iteratee. Хотя я очень сильно тупой. Ниже у меня есть простой пример, который способен анализировать одну строку из файла. Мой синтаксический анализатор читает по одной строке за раз, поэтому мне нужен способ подачи строк до итерации до тех пор, пока это не произойдет. Я прочитал все, что нашел в Google, но многие материалы по iteratee/enumerators довольно продвинуты. Это часть кода, которая имеет значение:

-- There are more imports above.
import Data.Attoparsec.Iteratee
import Data.Iteratee (joinI, run)
import Data.Iteratee.IO (defaultBufSize, enumFile)

line :: Parser ByteString -- left the implementation out (it doesn't check for 
                             new line)

iter = parserToIteratee line

main = do
    p <- liftM head getArgs
    i <- enumFile defaultBufSize p $ iter
    i' <- run i
    print i'

Этот пример будет анализировать и печатать одну строку из файла с несколькими строками. Оригинальный script сопоставил парсер над списком ByteStrings. Поэтому я хотел бы сделать то же самое здесь. Я нашел enumLines в Итерате, но я не могу на всю жизнь понять, как его использовать. Может быть, я неправильно понимаю его цель?

Ответ 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 или обрабатывать их другими способами.