Iteratee I / O:需要事先知道文件大小

时间:2011-06-29 14:27:53

标签: haskell

假设我需要解析一个二进制文件,该文件以三个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应该返回错误。但是怎么样?是作为参数传递实际长度的唯一方法吗?这是迭代的方式吗?对我来说不是很增量:)

3 个答案:

答案 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作者提交了一份错误报告。