2011-06-29 7 views
5

4バイトの3つのマジックナンバーで始まるバイナリファイルを解析する必要があるとします。 2つは固定ストリングです。ただし、もう1つはファイルの長さです。Iteratee I/O:ファイルサイズを事前に知る必要があります

上記の長さが実際の長さと一致しない場合は、理想的にはiterMagicsがエラーを返す必要があります。しかしどうですか?実際の長さを引数として渡す唯一の方法はありますか?これはiteratee-ishのやり方ですか?私にとってはそれほど増分ではありません:)

+0

最初にイテレータを生成するときに実際のファイル長を引数として渡すプログラムとは何ですか?関数の 'iterMagics'を変更してファイルの長さを引数として取るかもしれません。スマートをプログラミングする場合、コードは長さを1回だけ渡す必要があります。 – fuz

答えて

5

これは簡単に列挙型で行うことができます。最初に、3つの4バイトのマジックナンバーを読み込み、残りのものに対して内部イテレートを実行します。ファイルが長すぎる

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" 

は、この場合、それはエラーをスローしませんが、読み取りを停止します:あなたはiterateeを使用している場合、それは多かれ少なかれ、このようなようになります。あなたはそれをかなり簡単にその状態をチェックするために変更することができます。私はEnumeratorがenumWithのアナログ機能を持っているとは思わないので、手動でバイト数をカウントする必要があるかもしれませんが、同じ原則が適用されます。

もっと実用的なアプローチは、列挙子を実行する前にファイルサイズを確認し、それをヘッダーの値と比較するだけです。 iterateeの引数としてfilesizeまたはfilepathを渡す必要があります(ただし、パーサーは必要ありません)。

import System.Posix 

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

これは私が探していたものです。 – edwardw

0

あなたが好むかもしれない1つの解決策は、2段階の解析だけです。ここでは、ファイルのマジックセクションから長さをとり、長さ 'len'のバイトストリングを返すパーサーでファイルを列挙します。それ以外の場合は失敗します。私自身は

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

すてきなアプローチ! 'take len'(かなり長いかもしれない)を実行し、それを次のiterateeに与えるというパフォーマンスの意味は何ですか? – edwardw

+0

自分自身で別の解決策を見つけたattoparsec 0.8.xには、請求書に適合する「保証」があります。しかし残念なことに0.9.xはそのような機能を削除します。なぜかと思う。 – edwardw

+0

@edwardw:このように 'take len'を実行することは、' Data.ByteString.readFile'を実行することとまったく同じです。もうiterateeを使うのは本当に意味がありません。 –

0

見つかり一つの解::その後、私たちはそのバイト文字列を超える定期attoparsecパーサを使用している

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

しかしattoparsecは最近ensureを削除しました。私はbitbucketでattoparsecの著者にバグレポートを提出しました。

+0

私は '確実に'が削除されたと思うのですが、これはまさにあなたがそれを使いたくない状況だからです。 'ensure'はデータの次のnバイトを強制します。つまり、大きな値を確保しようとすると、インクリメンタル解析の利点を完全に無効にします。 –

+0

@ジョンL、私は同じと思う。それを確認していただきありがとうございます。 – edwardw

関連する問題