estoy usando Data.Serialize.Get
y estoy tratando de definir lo siguiente combinador:Tener mi cereal y analizarlo demasiado
getConsumed :: Get a -> Get (ByteString, a)
que debe actuar como el pasado en Get
acción, sino también devolver el ByteString
que el Get
consumado. El caso de uso es que tengo una estructura binaria que necesito para analizar y hash, y no sé la longitud antes de analizarla.
Este combinador, a pesar de su semántica simple, está demostrando ser sorprendentemente difícil de implementar.
Sin entrar en los detalles internos de Get
, mi instinto era utilizar esta monstruosidad:
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)
que utilizará búsqueda hacia delante, echar un vistazo al resto de bytes antes y después de ejecutar la acción, devolver el resultado de la acción , y luego consume la longitud. Esto no debería duplicar ningún trabajo, pero ocasionalmente falla con:
*** Exception: GetException "Failed reading: getBytes: negative length requested\nEmpty call stack\n"
así que debo estar malentendiendo algo acerca de los cereales en alguna parte.
¿Alguien ve lo que está mal con mi definición de getconsumed
o tiene una mejor idea de cómo implementarlo?
Editar: Dan Doel señala que remaining
puede simplemente devolver la longitud restante de un trozo dado, lo cual no es muy útil si cruza un límite de trozo. No estoy seguro de cuál es el objetivo de la acción, en ese caso, ¡pero eso explica por qué mi código no funcionaba! Ahora solo necesito encontrar una alternativa viable.
Edición 2: después de pensarlo un poco más, parece que el hecho de que remaining
me da la longitud del trozo actual puede ser una ventaja para mí si me alimento del Get
manualmente con trozos individuales (remaining >>= getBytes
) en un bucle y hacer un seguimiento de lo que está comiendo mientras lo hago. Todavía no he logrado que este enfoque funcione, pero parece más prometedor que el original.
Datos 3: si alguien tiene curiosidad, aquí está el código de edición 2 anterior:
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)
Por desgracia, todavía parece fallar después de un tiempo en mi entrada de la muestra con:
*** Exception: GetException "Failed reading: too few bytes\nFrom:\tdemandInput\n\n\nEmpty call stack\n"
FWIW con el paquete iteratee esto es 'enumWith parser stream2stream', que básicamente hace exactamente lo que sugiere en su segunda edición. Puede encontrar útil esa definición, o posiblemente 'countConsumed', que hace algo ligeramente diferente pero es más simple de asimilar. –