2

我有一个接收器,想在其中使用 attoparsec 进行一些解析。碰巧我得到了一个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
4

2 回答 2

2

“部分”的想法是它返回一个延续函数;也就是说,一旦您有更多输入,您就可以使用该输入调用延续。尝试将剩余的行推回输入流充其量是浪费,因为您重复解析输入的第一位。

您需要编写函数以将解析器函数作为参数。那么你的部分案例应该是

Partial c -> sink c

这将导致“接收器”等待更多输入,然后将其交给“c”函数,该函数将从中断处继续解析新输入。

于 2014-11-09T20:54:40.123 回答
1

请记住,Conduit 没有连接输出的概念。所以会发生什么:

  • 管道获得部分输入。
  • 解析是不够的。
  • 你把它作为剩菜放回去。
  • 导管再次读取您放回的内容。
  • 这将永远存在。

如果你真的要追求反复尝试解析器的方向,你需要确保每次放回一个剩余值都比上一次大。所以你会做这样的事情:如果解析器没有完成,读取额外的输入,将它与你已经拥有的输入连接起来,把它作为剩余的东西推回去,然后再试一次。

请注意,上述过程具有复杂性O(n^2),如果您的解析器在消耗大量数据后成功,这将特别成问题。如果您一次接收一个字符(这可能会发生)并且解析器需要消耗 1000 个字符,那么您将获得大约 500000 个处理步骤。所以我强烈建议在 Conduit 和 Attoparsec 之间使用提供的绑定,或者,如果你想自己做,请正确使用Partial.

于 2014-11-11T19:08:06.333 回答