8

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

getConsumed :: Get a -> Get (ByteString, a)

它应该像传入的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)

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

*** Exception: GetException "Failed reading: getBytes: negative length requested\nEmpty call stack\n"

所以我一定是在某个地方误解了一些关于谷物的事情。

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

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

编辑2:在考虑了更多之后,如果我在循环中手动输入单个块()并跟踪它所吃的东西,那么remaining给我当前块的长度似乎对我有利我做。我还没有设法使这种方法起作用,但它似乎比原来的方法更有希望。Getremaining >>= 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)

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

*** Exception: GetException "Failed reading: too few bytes\nFrom:\tdemandInput\n\n\nEmpty call stack\n"
4

2 回答 2

4

Cereal 包没有存储足够的信息来简单地实现您想要的。我希望您使用块的想法可能会奏效,或者可能是一个特殊的runGet. 分叉谷物和使用内部可能是您最简单的方法。

自己编写可以工作,这就是我在制作协议缓冲区库时所做的。我的自定义Text.ProtocolBuffers.Get库确实实现了足够的机制来做你想做的事:

import Text.ProtocolBuffers.Get
import Control.Applicative
import qualified Data.ByteString as B

getConsumed :: Get a -> Get (B.ByteString, a)
getConsumed thing = do
  start <- bytesRead
  (a,stop) <- lookAhead ((,) <$> thing <*> bytesRead)
  bs <- getByteString (fromIntegral (stop-start))
  return (bs,a)

这很清楚,因为我的库跟踪 byteRead 的数量。否则,API 与 Cereal 非常相似。

于 2012-07-11T08:22:52.947 回答
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)

一种解决方案是调用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)
于 2012-07-11T11:40:33.377 回答