Attoparsec Iteratee

Я хотел, просто чтобы немного узнать об 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 ответ

Решение

Поскольку ваш синтаксический анализатор работает со строкой одновременно, вам даже не нужно использовать 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 встраиваемый.

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

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

Другие вопросы по тегам