Haskell で pipes-attoparsec を使用してバイナリ データを解析しようとしています。パイプ (プロキシ) が関与する理由は、読み取りと解析をインターリーブして、大きなファイルで大量のメモリを使用しないようにするためです。多くのバイナリ形式はブロック (またはチャンク) に基づいており、そのサイズは多くの場合、ファイル内のフィールドによって記述されます。このようなブロックのパーサーが何と呼ばれているかはわかりませんが、タイトルの「サブパーサー」とはそういう意味です。私が抱えている問題は、潜在的に大きなメモリフットプリントなしで簡潔な方法でそれらを実装することです. それぞれが何らかの点で失敗する 2 つの代替案を思いつきました。
代替案 1 は、ブロックを別のバイト文字列に読み込み、それに対して別のパーサーを開始することです。簡潔ではありますが、ブロックが大きいとメモリ使用量が高くなります。
代替案 2 は、同じコンテキストで解析を続け、消費されたバイト数を追跡することです。この追跡はエラーが発生しやすく、最終的な blockParser を構成するすべてのパーサーに感染しているようです。不正な形式の入力ファイルの場合、追跡されたサイズを比較する前に、サイズ フィールドで示されるよりもさらに解析することで時間を浪費することもあります。
import Control.Proxy.Attoparsec
import Control.Proxy.Trans.Either
import Data.Attoparsec as P
import Data.Attoparsec.Binary
import qualified Data.ByteString as BS
parser = do
size <- fromIntegral <$> anyWord32le
-- alternative 1 (ignore the Either for simplicity):
Right result <- parseOnly blockParser <$> P.take size
return result
-- alternative 2
(result, trackedSize) <- blockparser
when (size /= trackedSize) $ fail "size mismatch"
return result
blockParser = undefined
main = withBinaryFile "bin" ReadMode go where
go h = fmap print . runProxy . runEitherK $ session h
session h = printD <-< parserD parser <-< throwParsingErrors <-< parserInputD <-< readChunk h 128
readChunk h n () = runIdentityP go where
go = do
c <- lift $ BS.hGet h n
unless (BS.null c) $ respond c *> go