Канализационная раковина с остатком

У меня есть раковина и я хочу сделать разбор с аттопарсек в нем. Бывает, что я получаю 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

Это заставит "сток" ждать больше ввода, а затем передать его функции "с", которая продолжит анализ нового ввода с того места, где он остановился.

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