5

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
4

2 に答える 2

2

わかりましたので、最終的にこれを行う方法を見つけ出し、このパターンをpipes-parseライブラリに体系化しました。pipes-parseチュートリアルでは、具体的には「ネスティング」セクションでこれを行う方法について説明しています。

このチュートリアルでは、データ型にとらわれない解析 (つまり、要素の一般的なストリーム) についてのみ説明していますが、ByteStrings で動作するように拡張することもできます。

これを機能させる 2 つの重要なトリックは次のとおりです。

  • StatePグローバルに修正中 ( 内pipes-3.3.0)

  • サブパーサーを一時StatePレイヤーに埋め込み、新しい残り物コンテキストを使用するようにする

は、これらのトリックを独自のコードで使用できるようにpipes-attoparsec構築されたアップデートをまもなくリリースする予定です。pipes-parse

于 2013-06-03T19:56:46.823 に答える