4バイトの3つのマジックナンバーで始まるバイナリファイルを解析する必要があるとします。 2つは固定ストリングです。ただし、もう1つはファイルの長さです。Iteratee I/O:ファイルサイズを事前に知る必要があります
上記の長さが実際の長さと一致しない場合は、理想的にはiterMagics
がエラーを返す必要があります。しかしどうですか?実際の長さを引数として渡す唯一の方法はありますか?これはiteratee-ishのやり方ですか?私にとってはそれほど増分ではありません:)
4バイトの3つのマジックナンバーで始まるバイナリファイルを解析する必要があるとします。 2つは固定ストリングです。ただし、もう1つはファイルの長さです。Iteratee I/O:ファイルサイズを事前に知る必要があります
上記の長さが実際の長さと一致しない場合は、理想的にはiterMagics
がエラーを返す必要があります。しかしどうですか?実際の長さを引数として渡す唯一の方法はありますか?これはiteratee-ishのやり方ですか?私にとってはそれほど増分ではありません:)
これは簡単に列挙型で行うことができます。最初に、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
これは私が探していたものです。 – edwardw
あなたが好むかもしれない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
すてきなアプローチ! 'take len'(かなり長いかもしれない)を実行し、それを次のiterateeに与えるというパフォーマンスの意味は何ですか? – edwardw
自分自身で別の解決策を見つけたattoparsec 0.8.xには、請求書に適合する「保証」があります。しかし残念なことに0.9.xはそのような機能を削除します。なぜかと思う。 – edwardw
@edwardw:このように 'take len'を実行することは、' Data.ByteString.readFile'を実行することとまったく同じです。もうiterateeを使うのは本当に意味がありません。 –
見つかり一つの解::その後、私たちはそのバイト文字列を超える定期attoparsecパーサを使用している
parseMagics :: Parser()
parseMagics = do
_ <- string "FOR1"
len <- big_endians 4
_ <- string "BEAM"
return $ ensure len
しかしattoparsecは最近ensure
を削除しました。私はbitbucketでattoparsecの著者にバグレポートを提出しました。
私は '確実に'が削除されたと思うのですが、これはまさにあなたがそれを使いたくない状況だからです。 'ensure'はデータの次のnバイトを強制します。つまり、大きな値を確保しようとすると、インクリメンタル解析の利点を完全に無効にします。 –
@ジョンL、私は同じと思う。それを確認していただきありがとうございます。 – edwardw
最初にイテレータを生成するときに実際のファイル長を引数として渡すプログラムとは何ですか?関数の 'iterMagics'を変更してファイルの長さを引数として取るかもしれません。スマートをプログラミングする場合、コードは長さを1回だけ渡す必要があります。 – fuz