2011-06-29 11 views
5

Üç 4 baytlık sihirli sayı ile başlayan bir ikili dosyayı ayrıştırmam gerektiğini varsayalım. Bunlardan ikisi sabit iplerdir. Diğer, ancak, dosyanın uzunluğu. Belirtilen uzunluk gerçek uzunluğunu uyuşmuyorsaIteratee I/O: dosya boyutunu önceden bilmem gerekiyor

{-# 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 

, ideal olarak iterMagics bir hata dönmelidir. Ama nasıl? Gerçek uzunluğu bir argüman olarak iletmenin tek yolu var mı? Bunu yapmak için iteratee-ish yolu bu mu? Benim için çok fazla değil :)

+0

Başlangıçta bir yineleyici oluştururken gerçek dosya uzunluğunu argüman olarak geçiren program nedir? Belki de, dosya uzunluğunu argüman olarak almak için 'iterMagics' işlevini değiştirin. Eğer akıllı program yaparsanız, kodunuzun uzunluğu sadece bir kez geçmesi gerekir. – fuz

cevap

5

Bu, numaralandırıcılar ile kolayca yapılabilir. Öncelikle üç 4 baytlık sihirli sayıları okursunuz, ardından geri kalan kısımda bir iç yinelemeyi çalıştırırsınız. dosya çok uzunsa

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" 

Bu durumda bir hata atmaz, ama okumayı keser: Eğer iteratee kullanıyorsanız, daha çok veya daha az böyle gibi görünecektir. Bu durumu oldukça kolay kontrol etmek için değiştirebilirsiniz. Enumerator'un enumWith analog işlevine sahip olduğunu sanmıyorum, bu nedenle muhtemelen baytları elle saymanız gerekir, ancak aynı ilke geçerli olur.

Muhtemelen daha pragmatik bir yaklaşım numaralandırıcıyı çalıştırmadan önce dosya boyutu kontrol etmektir ve ardından başlığın hemen değere karşılaştırmak. Dosya boyutunu veya dosya yolunu yinelemeye (ancak ayrıştırıcıya değil) bir argüman olarak geçirmeniz gerekir.

import System.Posix 

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

güzel. Aradığım şey bu. – edwardw

0

Tercih edebileceğiniz bir çözüm sadece iki adım ayrıştırma kullanmaktır. Burada, dosyanın sihir bölümünün uzunluğunu alan ve 'len' uzunluğunun bir testini döndüren bir çözümleyici ile bir dosya numaralandırıyoruz. Aksi halde başarısız olur.

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

Ama attoparsec geçenlerde ensure kaldırıldı: kendim

{-# 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

güzel yaklaşım! 'yapmak' yapmak ('uzun sürebilirim') yapmak ve onu bir sonraki iterasyona beslemek performansın ne anlama geldiğidir? – edwardw

+0

Kendime başka bir çözüm buldum, attoparsec 0.8.x'in faturasına uygun olmasını sağla. Fakat maalesef 0.9.x bu işlevi kaldırır. Nedenini merak etmek. – edwardw

+0

@edwardw: 'bu şekilde al' yapmak, DataByteString.readFile' işlevini yapmaya tam olarak eşdeğerdir. Artık yinelemeyi kullanmanın hiçbir anlamı yok. –

0

Bulunan tek çözüm: Bundan sonra biz bu bytestring üzerinde düzenli attoparsec ayrıştırıcı kullanıyoruz. Bitbucket'te attoparsec yazarına bir hata raporu gönderdim.

+0

Sanırım 'emin' kaldırıldı çünkü bu tam olarak kullanmak istemeyeceğiniz bir durum. '' emin '' bir sonraki 'n' veri baytını zorlar, yani büyük değerleri sağlamaya çalışıyorsanız, artan ayrıştırma avantajlarını tamamen ortadan kaldırırsınız demektir. –

+0

@John L, Aynısından şüpheleniyorum. Doğruladığın için teşekkürler. – edwardw

İlgili konular