我正在使用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
给我当前块的长度似乎对我有利我做。我还没有设法使这种方法起作用,但它似乎比原来的方法更有希望。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)
不幸的是,一段时间后我的示例输入似乎仍然失败:
*** Exception: GetException "Failed reading: too few bytes\nFrom:\tdemandInput\n\n\nEmpty call stack\n"