Канализационная раковина с остатком
У меня есть раковина и я хочу сделать разбор с аттопарсек в нем. Бывает, что я получаю Partial
результат. Поэтому я подумал, что я мог бы просто использовать leftover
вернуть недостаточное количество контента обратно, чтобы потом было добавлено больше. Но никакой новый контент не добавляется, как я надеялся. Я был бы очень признателен за любые предложения о том, как решить эту проблему. Спасибо!
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad.IO.Class (liftIO)
import Data.Conduit
import qualified Data.Conduit.List as CL
import qualified Data.ByteString.Char8 as BS
import Data.Attoparsec.Char8
main = (CL.sourceList [BS.pack "foo", BS.pack "bar"]) $$ sink -- endless loop
-- this works:
-- main = (CL.sourceList [BS.pack "foobar"]) $$ sink
sink :: Sink BS.ByteString IO ()
sink = awaitForever $ \str -> do
liftIO $ putStrLn $ BS.unpack str -- debug, will print foo forever.
case (parse (string "foobar") str) of
Fail _ _ _ -> do
liftIO $ putStr $ "f: " ++ BS.unpack str
sink
Partial _ -> do
leftover str
sink
Done rest final -> do
liftIO $ putStr $ "d: " ++ show final ++ " // " ++ show rest
sink
2 ответа
Имейте в виду, что в Conduit отсутствует концепция объединения выходных данных. Итак, что происходит:
- Канал получает частичный ввод.
- Недостаточно разобрать.
- Вы положили его обратно как остаток.
- Канал снова читает то же, что вы положили обратно.
- И это продолжается вечно.
Если вы действительно хотите придерживаться направления повторных попыток парсера, вам нужно убедиться, что каждый раз, когда вы возвращаете оставшееся значение, оно больше, чем в предыдущий раз. Таким образом, вы должны сделать что-то вроде этого: если парсер не завершит работу, прочитайте дополнительный ввод, объедините его с уже имеющимся вводом, отодвиньте его как остаток и попробуйте снова.
Обратите внимание, что описанная выше процедура имеет сложность O (n ^ 2), что будет особенно проблематично, если ваш анализатор преуспеет после использования большого блока данных. Если вы будете получать по одному символу за раз (что может случиться), и парсер должен будет потреблять 1000 символов, вы получите что-то вроде 500000 шагов обработки. Поэтому я настоятельно рекомендую использовать либо предоставленную привязку между Conduit и Attoparsec, либо, если вы хотите сделать это самостоятельно, правильно использовать продолжение, предоставленное Partial
,
Идея "Частичного" состоит в том, что он возвращает вам функцию продолжения; то есть, когда у вас есть больше входных данных, вы вызываете продолжение с этим входным сигналом. Попытка вставить оставшиеся строки обратно во входной поток в лучшем случае расточительна, потому что вы многократно анализируете первый бит ввода.
Вам нужно написать свою функцию, чтобы взять функцию парсера в качестве параметра. Тогда ваш частичный случай должен читать
Partial c -> sink c
Это заставит "сток" ждать больше ввода, а затем передать его функции "с", которая продолжит анализ нового ввода с того места, где он остановился.