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