吃谷物并解析它

cop*_*kin 8 haskell

我正在使用Data.Serialize.Get并且正在尝试定义以下组合器:

getConsumed :: Get a -> Get (ByteString, a)
Run Code Online (Sandbox Code Playgroud)

它应该像传入的Get动作一样,但也返回消耗的ByteString那个Get.用例是我有一个二进制结构,我需要解析和散列,并且在解析它之前我不知道它的长度.

这个组合器尽管语义简单,但实现起来却非常棘手.

没有深入研究内部Get,我的直觉是使用这种怪异:

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
  (len, r) <- lookAhead $ do
                before <- remaining
                res <- g
                after <- remaining
                return (before - after, res)
  bs <- getBytes len
  return (bs, r)
Run Code Online (Sandbox Code Playgroud)

哪个将使用前瞻,查看运行操作之前和之后的剩余字节,返回操作的结果,然后消耗长度.这不应该复制任何工作,但偶尔会失败:

*** Exception: GetException "Failed reading: getBytes: negative length requested\nEmpty call stack\n"
Run Code Online (Sandbox Code Playgroud)

所以我一定是在某个地方误解谷物.

有没有人看到我的定义getconsumed有什么问题,或者对如何实现它有更好的想法?

编辑:Dan Doel指出,remaining只能返回给定块的剩余长度,如果跨越块边界,这不是非常有用.在这种情况下,我不确定操作的重点是什么,但这解释了为什么我的代码无效!现在我只需找到一个可行的替代方案.

编辑2:在考虑了它之后,似乎事实remaining是,如果我在循环中Get手动提供单个块(remaining >>= getBytes)并跟踪它吃的是什么,那么给我当前块的长度可能对我有利.我做到了 我还没有设法让这种方法工作,但它似乎比原来的更有希望.

编辑3:如果有人好奇,这里的代码来自上面的编辑2:

getChunk :: Get B.ByteString
getChunk = remaining >>= getBytes

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
    (len, res) <- lookAhead $ measure g
    bs <- getBytes len
    return (bs, res)
  where
  measure :: Get a -> Get (Int ,a)
  measure g = do
    chunk <- getChunk
    measure' (B.length chunk) (runGetPartial g chunk)

  measure' :: Int -> Result a -> Get (Int, a)
  measure' !n (Fail e) = fail e
  measure' !n (Done r bs) = return (n - B.length bs, r)
  measure' !n (Partial f) = do
    chunk <- getChunk
    measure' (n + B.length chunk) (f chunk)
Run Code Online (Sandbox Code Playgroud)

不幸的是,在我的示例输入上一段时间后它似乎仍然失败:

*** Exception: GetException "Failed reading: too few bytes\nFrom:\tdemandInput\n\n\nEmpty call stack\n"
Run Code Online (Sandbox Code Playgroud)

moo*_*moo 4

编辑:另一种解决方案,不进行额外的计算!

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
  (len, r) <- lookAhead $ do
                (res,after) <- lookAhead $ liftM2 (,) g remaining
                total <- remaining
                return (total-after, res)
  bs <- getBytes len
  return (bs, r)
Run Code Online (Sandbox Code Playgroud)

一种解决方案是调用lookAhead两次。第一次确保加载所有必需的块,第二次执行实际长度计算(以及返回反序列化数据)。

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed g = do
  _ <- lookAhead g -- Make sure all necessary chunks are preloaded
  (len, r) <- lookAhead $ do
                before <- remaining
                res <- g
                after <- remaining
                return (before - after, res)
  bs <- getBytes len
  return (bs, r)
Run Code Online (Sandbox Code Playgroud)