2012-07-11 10 views
8

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" 
+0

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. –

Respuesta

4

EDIT: ¡Otra solución, que no requiere cálculos adicionales!

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) 

Una solución es llamar lookAhead dos veces. La primera vez se asegura de que se carguen todos los fragmentos necesarios, y el segundo realiza el cálculo de la longitud real (junto con la devolución de los datos deserializados).

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) 
+0

Tu segunda opción funcionaría (al menos un subconjunto de) el trabajo de 'g' dos veces, ¿verdad? El primero parece funcionar sin embargo. ¡Gracias! – copumpkin

4

el cereal El paquete no almacena suficiente información para simplemente implementar lo que desea. Espero que su idea de usar fragmentos pueda funcionar, o quizás un runGet especial. La bifurcación del cereal y el uso de las partes internas es probablemente el camino más fácil.

Escribir tu propio trabajo, esto es lo que hice al hacer el protocol-buffers library. Mi biblioteca personalizada Text.ProtocolBuffers.Get no implementar suficientes máquinas para hacer lo que desee:

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) 

Esto es claro porque mi biblioteca seguimiento del número de byteRead. De lo contrario, la API es bastante similar a Cereal.

+0

Eso es genial, pero la razón principal por la que estoy usando cereal es el soporte fácil en otras bibliotecas, como conduit:/ – copumpkin

Cuestiones relacionadas