[英]“Sub-parsers” in pipes-attoparsec
我正在嘗試使用Haskell中的pipes-attoparsec解析二進制數據。 管道(代理)涉及的原因是將讀取與解析交錯以避免大文件的高內存使用。 許多二進制格式基於塊(或塊),它們的大小通常由文件中的字段描述。 我不確定這個塊的解析器是什么被調用,但這就是我所說的標題中的“sub-parser”。 我遇到的問題是以簡潔的方式實現它們而沒有可能很大的內存占用。 我想出了兩個在某些方面都失敗的替代方案。
備選方案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
我喜歡稱之為“固定輸入”解析器。
我可以告訴你pipes-parse
將如何做到這一點。 您可以在庫的parseN
和parseWhile
函數中看到我將要在pipes-parse
的內容的parseN
。 這些實際上是用於通用輸入,但我在這里和這里也寫了類似的String
解析器。
訣竅很簡單,你在輸入標記的末端插入一個你希望解析器停止的地方,運行解析器(如果遇到輸入標記的假端則會失敗),然后刪除輸入標記的結尾。
顯然,這並不像我說的那么容易,但這是一般原則。 棘手的部分是:
這樣做仍然會流動。 我鏈接的那個並沒有這樣做,但是,你以流式方式執行此操作的方法是在上游插入一個管道,計算流經它的字節,然后將輸入結束標記插入正確的位置。
不干擾輸入標記的現有結束
這個技巧可以適用於pipes-attoparsec
,但我認為最好的解決辦法是讓attoparsec
直接包含這個功能。 但是,如果該解決方案不可用,那么我們可以限制輸入到attoparsec
解析器的輸入。
好的,所以我終於想出了如何做到這一點,並且我已經在pipes-parse
庫中編寫了這個模式。 pipes-parse
教程解釋了如何執行此操作,特別是在“嵌套”部分中。
本教程僅解釋了這種與數據類型無關的解析(即通用的元素流),但您也可以將其擴展為與ByteString
一起使用。
使這項工作的兩個關鍵技巧是:
將StateP
修復為全局(在pipes-3.3.0
)
將子解析器嵌入到瞬態StateP
層中,以便它使用新的剩余上下文
pipes-attoparsec
將很快發布一個基於pipes-parse
的更新,以便您可以在自己的代碼中使用這些技巧。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.