假设我需要解析一个二进制文件,该文件以三个4字节幻数开头。其中两个是固定字符串。另一个是文件的长度。
{-# 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 $$ iterMagics)
case eitherStat of
Left _err -> putStrLn $ "Not a beam file: " ++ f
Right _ -> return ()
iterMagics :: Monad m => Iteratee S.ByteString m ()
iterMagics = iterParser parseMagics
parseMagics :: Parser ()
parseMagics = do
_ <- string "FOR1"
len <- big_endians 4 -- need to compare with actual file length
_ <- string "BEAM"
return ()
big_endians :: Int -> Parser Int
big_endians n = do
ws <- count n anyWord8
return $ foldl1 (\a b -> a * 256 + b) $ map fromIntegral ws
如果规定的长度与实际长度不匹配,理想情况下iterMagics
应该返回错误。但是怎么样?是作为参数传递实际长度的唯一方法吗?这是迭代的方式吗?对我来说不是很增量:)
答案 0 :(得分:5)
这可以通过枚举来轻松完成。首先,你读取三个4字节的幻数,然后在余数上运行内部迭代。如果你使用iteratee,它看起来会像这样:或多或少:
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"
在这种情况下,如果文件太长,它将不会抛出错误,但它将停止读取。您可以修改它以相当容易地检查该条件。我不认为Enumerator具有enumWith
的模拟函数,因此您可能需要手动计算字节数,但同样的原则也适用。
可能更实用的方法是在运行枚举器之前检查文件大小,然后将其与标头中的值进行比较。您需要将filesize或filepath作为参数传递给iteratee(而不是解析器)。
import System.Posix
iterMagics2 filepath = do
fsize <- liftIO . liftM fileSize $ getFileStatus filepath
len <- iterParser parseMagics
答案 1 :(得分:0)
您可能喜欢的一种解决方案就是使用两步解析。这里我们使用解析器枚举一个文件,该解析器从文件的magic部分获取长度并返回长度为'len'的字节串。否则就失败了。之后我们在该字节串上使用常规的attoparsec解析器:
{-# 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
答案 2 :(得分:0)
自己找到一个解决方案:
parseMagics :: Parser ()
parseMagics = do
_ <- string "FOR1"
len <- big_endians 4
_ <- string "BEAM"
return $ ensure len
但最近attoparsec删除了ensure
。我已经在bitbucket上向attoparsec作者提交了一份错误报告。