Attoparsec Iteratee - PullRequest
       11

Attoparsec Iteratee

10 голосов
/ 15 июня 2011

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

-- 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'

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

1 Ответ

15 голосов
/ 15 июня 2011

Поскольку ваш синтаксический анализатор работает со строкой за раз, вам даже не нужно использовать 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", который является просто термином итерируемого для преобразователя потока.Он принимает потоковый процессор (iteratee) одного типа потока и преобразует его для работы с другим потоком.И enumLinesBS, и mapStream являются перечислителями.

Чтобы отобразить ваш синтаксический анализатор на несколько строк, достаточно mapStream:

i1 :: Iteratee [ByteString] IO (Iteratee [POut] IO [POut]
i1 = mapStream (A.parseOnly parser) stream2list

Вложенные итерации просто означают, что это преобразует поток[ByteString] для потока [POut], и когда запускается последний итератор (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 встроенным.

Естьвсе еще важный элемент, оставшийся все же.Эта функция просто возвращает результаты разбора в виде списка.Обычно вы хотите сделать что-то еще, например, объединить результаты со сгибом.

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

...