2011-06-29 19 views
5

Giả sử tôi cần phải phân tích cú pháp một tệp nhị phân, bắt đầu bằng ba số ma thuật 4 byte. Hai trong số đó là các chuỗi cố định. Các khác, tuy nhiên, là chiều dài của tập tin.Iteratee I/O: cần phải biết kích thước tập tin trước

{-# 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 

Nếu độ dài được nêu không khớp với độ dài thực tế, lý tưởng là iterMagics sẽ trả về lỗi. Nhưng bằng cách nào? Là cách duy nhất để vượt qua chiều dài thực tế như là một đối số? Đây có phải là cách lặp lại để làm như vậy không? Không tăng thêm cho tôi :)

+0

Chương trình có truyền độ dài tệp thực tế làm đối số khi ban đầu tạo trình lặp không? Có thể thay đổi hàm của bạn 'iterMagics' để lấy độ dài tệp làm đối số. Nếu bạn lập trình thông minh, mã của bạn cần phải vượt qua độ dài chỉ một lần. – fuz

Trả lời

5

Điều này có thể dễ dàng thực hiện với các liệt kê. Trước tiên, bạn đọc ba số ma thuật 4 byte, sau đó chạy một iteratee bên trong qua phần còn lại. Nếu bạn đang sử dụng iteratee, nó sẽ trông giống như hơn hoặc ít hơn như thế này:

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" 

Trong trường hợp này nó sẽ không ném ra một lỗi nếu file quá dài, nhưng nó sẽ ngừng đọc. Bạn có thể sửa đổi nó để kiểm tra tình trạng đó khá dễ dàng. Tôi không nghĩ rằng Enumerator có một chức năng tương tự để enumWith, vì vậy bạn có thể cần phải đếm byte bằng tay, nhưng nguyên tắc tương tự sẽ được áp dụng.

Có thể cách tiếp cận thực dụng hơn là kiểm tra các tệp trước khi chạy điều tra, và sau đó chỉ so sánh với giá trị trong tiêu đề. Bạn sẽ cần phải vượt qua các filesize, hoặc filepath, như một đối số cho iteratee (nhưng không phải là trình phân tích cú pháp).

import System.Posix 

iterMagics2 filepath = do 
    fsize <- liftIO . liftM fileSize $ getFileStatus filepath 
    len <- iterParser parseMagics 
+0

đẹp. Đây là những gì tôi đang tìm kiếm. – edwardw

0

Một giải pháp bạn có thể đặt trước chỉ là sử dụng phân tích cú pháp hai bước. Ở đây chúng tôi liệt kê một tập tin với một phân tích cú pháp mà mất chiều dài từ phần ma thuật của tập tin và trả về một bytestring chiều dài 'len'. Nó không thành công. Sau đó chúng tôi đang sử dụng một cú pháp attoparsec thường xuyên hơn bytestring rằng:

{-# 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 
+0

cách tiếp cận tốt đẹp! ý nghĩa của việc thực hiện 'lấy len' (có thể là khá dài) và cho ăn nó với iteratee tiếp theo là gì? – edwardw

+0

Tìm thấy một giải pháp khác bản thân mình, attoparsec 0.8.x có 'đảm bảo' phù hợp với hóa đơn. Nhưng tiếc là 0.9.x loại bỏ chức năng như vậy. Tự hỏi tại sao. – edwardw

+0

@edwardw: làm 'lấy len' theo cách này là chính xác tương đương với việc' Data.ByteString.readFile'. Có thực sự không có điểm để sử dụng một iteratee ở tất cả nữa. –

0

Tìm thấy một giải pháp bản thân mình:

parseMagics :: Parser() 
parseMagics = do 
    _ <- string "FOR1" 
    len <- big_endians 4 
    _ <- string "BEAM" 
    return $ ensure len 

Nhưng attoparsec loại bỏ ensure thời gian gần đây. Tôi đã gửi một báo cáo lỗi cho tác giả attoparsec trên bitbucket.

+0

Tôi nghĩ rằng 'đảm bảo 'đã bị xóa vì đây chính là loại tình huống mà bạn không muốn sử dụng. 'đảm bảo' buộc byte 'n' tiếp theo của dữ liệu, có nghĩa là nếu bạn đang cố gắng đảm bảo các giá trị lớn, bạn hoàn toàn phủ nhận bất kỳ lợi ích nào của phân tích gia tăng. –

+0

@ John L, tôi nghi ngờ như vậy. Cảm ơn bạn đã xác nhận. – edwardw

Các vấn đề liên quan