2011-06-29 7 views
5

Supongamos que necesito analizar un archivo binario, que comienza con tres números mágicos de 4 bytes. Dos de ellos son cadenas fijas. El otro, sin embargo, es la longitud del archivo.E/S Iteratee: necesita saber el tamaño del archivo de antemano

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import Data.Attoparsec 
import Data.Attoparsec.Enumerator 
import Data.Enumerator hiding (foldl, foldl', map, head) 
import Data.Enumerator.Binary hiding (map) 
import qualified Data.ByteString as S 
import System 

main = do 
    f:_ <- getArgs 
    eitherStat <- run (enumFile f $$ iterMagics) 
    case eitherStat of 
     Left _err -> putStrLn $ "Not a beam file: " ++ f 
     Right _ -> return() 

iterMagics :: Monad m => Iteratee S.ByteString m() 
iterMagics = iterParser parseMagics 

parseMagics :: Parser() 
parseMagics = do 
    _ <- string "FOR1" 
    len <- big_endians 4 -- need to compare with actual file length 
    _ <- string "BEAM" 
    return() 

big_endians :: Int -> Parser Int 
big_endians n = do 
    ws <- count n anyWord8 
    return $ foldl1 (\a b -> a * 256 + b) $ map fromIntegral ws 

Si la longitud indicada no coincide con la longitud real, idealmente iterMagics debe devolver un error. ¿Pero cómo? ¿Es la única manera de pasar la longitud real como argumento? ¿Es esta la forma iterativa de hacerlo? No muy incremental para mí :)

+0

¿Cuál es el programa que pasa la longitud real del archivo como argumento al generar inicialmente un iterador? Tal vez cambie su función 'iterMagics' para tomar la longitud del archivo como argumento. Si programa de manera inteligente, su código necesita pasar la longitud solo una vez. – fuz

Respuesta

5

Esto se puede hacer fácilmente con enumeratees. Primero lee los tres números mágicos de 4 bytes, luego ejecuta una iteración interna sobre el resto. Si está utilizando iteratee, que se vería más o menos así:

parseMagics :: Parser() 
parseMagics = do 
    _ <- string "FOR1" 
    len <- big_endians 4 -- need to compare with actual file length 
    _ <- string "BEAM" 
    return len 

iterMagics :: Monad m => Iteratee S.ByteString m (Either String SomeResult) 
iterMagics = do 
    len <- iterParser parseMagics 
    (result, bytesConsumed) <- joinI $ takeUpTo len (enumWith iterData I.length) 
    if len == bytesConsumed 
    then return $ Right result 
    else return $ Left "Data too short" 

En este caso no generará un error si el archivo es demasiado largo, pero va a dejar de leer. Puede modificarlo para verificar esa condición con bastante facilidad. No creo que el Enumerator tenga una función analógica a enumWith, por lo que probablemente necesite contar los bytes manualmente, pero se aplicaría el mismo principio.

Posiblemente un enfoque más pragmático es verificar el tamaño del archivo antes de ejecutar el enumerador, y luego simplemente compararlo con el valor en el encabezado. Tendrá que pasar el tamaño del archivo, o el recorrido de archivo, como un argumento para el iterado (pero no para el analizador).

import System.Posix 

iterMagics2 filepath = do 
    fsize <- liftIO . liftM fileSize $ getFileStatus filepath 
    len <- iterParser parseMagics 
+0

agradable. Esto es lo que estaba buscando. – edwardw

0

Una solución que puede preferir es simplemente el uso de dos pasos de análisis. Aquí enumeramos un archivo con un analizador que toma la longitud de la sección mágica del archivo y devuelve una longitud de bytes de longitud 'len'. De lo contrario, falla. Después de que estamos usando un analizador attoparsec regular a lo largo cadena de bytes que:

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import Data.Attoparsec 
import Data.Attoparsec.Enumerator 
import Data.Enumerator hiding (foldl, foldl', map, head) 
import Data.Enumerator.Binary hiding (map) 
import qualified Data.ByteString as S 
import System 

main = do 
    f:_ <- getArgs 
    eitherStat <- run (enumFile f $$ iterParser parseMagics) 
    case eitherStat of 
     Left _err -> putStrLn $ "Not a beam file: " ++ f 
     Right bs -> parse parseContents bs 

parseContents :: Parser() 
parseContents = do 
    ... 


parseMagics :: Parser ByteString 
parseMagics = do 
    _ <- string "FOR1" 
    len <- big_endians 4 
    _ <- string "BEAM" 
    rest <- take len 
    return rest 

big_endians :: Int -> Parser Int 
big_endians n = do 
    ws <- count n anyWord8 
    return $ foldl1 (\a b -> a * 256 + b) $ map fromIntegral ws 
+0

buen enfoque! ¿cuál es la implicación de rendimiento de 'take len' (puede ser bastante larga) y alimentarlo a la próxima iteratee es, sin embargo? – edwardw

+0

Encontré otra solución yo mismo, attoparsec 0.8.x tiene 'asegurar' que se ajusta a la ley. Pero desafortunadamente 0.9.x elimina esa función. Preguntándome por qué. – edwardw

+0

@edwardw: hacer 'tomar len' de esta manera es exactamente equivalente a hacer' Data.ByteString.readFile'. Realmente no tiene sentido usar un iteratee nunca más. –

0

encontrado una solución a mí mismo:

parseMagics :: Parser() 
parseMagics = do 
    _ <- string "FOR1" 
    len <- big_endians 4 
    _ <- string "BEAM" 
    return $ ensure len 

Pero attoparsec eliminado ensure recientemente. He archivado un informe de error para el autor de attoparsec en bitbucket.

+0

Creo que se eliminó 'asegurar 'porque este es exactamente el tipo de situación en la que no le gustaría usarlo. 'ensure' obliga a los siguientes 'n' bytes de datos, lo que significa que si intentas asegurar valores grandes, anulas por completo los beneficios del análisis incremental. –

+0

@John L, sospecho lo mismo. Gracias por confirmarlo – edwardw

Cuestiones relacionadas