假设我需要解析一个二进制文件,该文件以三个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
Run Code Online (Sandbox Code Playgroud)
如果规定的长度与实际长度不匹配,理想情况下iterMagics应返回错误.但是怎么样?是作为参数传递实际长度的唯一方法吗?这是迭代的方式吗?对我来说不是很增量:)
这可以通过枚举来轻松完成.首先,你读取三个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"
Run Code Online (Sandbox Code Playgroud)
在这种情况下,如果文件太长,它将不会抛出错误,但它将停止读取.您可以修改它以相当容易地检查该条件.我不认为Enumerator具有模拟功能enumWith,因此您可能需要手动计算字节数,但同样的原则也适用.
可能更实用的方法是在运行枚举器之前检查文件大小,然后将其与标头中的值进行比较.您需要将filesize或filepath作为参数传递给iteratee(而不是解析器).
import System.Posix
iterMagics2 filepath = do
fsize <- liftIO . liftM fileSize $ getFileStatus filepath
len <- iterParser parseMagics
Run Code Online (Sandbox Code Playgroud)