2011-06-29 2 views
5

मान लीजिए कि मुझे एक बाइनरी फ़ाइल को पार्स करने की आवश्यकता है, जो तीन 4-बाइट जादू संख्याओं से शुरू होता है। उनमें से दो निश्चित तार हैं। दूसरा, हालांकि, फ़ाइल की लंबाई है।Iteratee I/O: फ़ाइल आकार को पहले से जानने की आवश्यकता है

{-# 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 एक त्रुटि लौटना चाहिए। पर कैसे? एक तर्क के रूप में वास्तविक लंबाई पारित करने का एकमात्र तरीका है? क्या यह ऐसा करने के लिए iteratee-ish तरीका है? मेरे लिए बहुत वृद्धि नहीं है :)

+0

एक तर्क के रूप वास्तविक फ़ाइल लंबाई गुजर जब शुरू में एक इटरेटर पैदा करने के साथ कार्यक्रम क्या है? फ़ाइल लंबाई को तर्क के रूप में लेने के लिए शायद अपने फ़ंक्शन 'iterMagics' को बदलें। यदि आप स्मार्ट प्रोग्राम करते हैं, तो आपके कोड को केवल एक बार लंबाई पारित करने की आवश्यकता होती है। – fuz

उत्तर

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" 

यदि फ़ाइल बहुत बड़ी है इस मामले में यह एक त्रुटि फेंक नहीं होगा, लेकिन इसे पढ़ने के बंद हो जाएगा। आप उस स्थिति को काफी आसानी से जांचने के लिए इसे संशोधित कर सकते हैं। मुझे नहीं लगता कि एन्युमरेटर के पास enumWith पर एनालॉग फ़ंक्शन है, इसलिए आपको शायद बाइट्स को मैन्युअल रूप से गिनने की आवश्यकता होगी, लेकिन वही सिद्धांत लागू होगा।

संभावित रूप से अधिक व्यावहारिक दृष्टिकोण गणक को चलाने से पहले फाइलसाइज़ की जांच करना है, और उसके बाद हेडर में मान की तुलना करें। आपको इरेटेट (लेकिन पार्सर नहीं) के लिए तर्क के रूप में, फ़ाइलसाइज, या फ़ाइलपैथ को पास करने की आवश्यकता होगी।

import System.Posix 

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

अच्छा। यह वही है जिसे मैं देख रहा था। – edwardw

0

एक समाधान जिसे आप पसंद कर सकते हैं केवल दो चरण पार्सिंग का उपयोग कर रहा है। यहां हम एक पार्सर के साथ एक फाइल का आकलन करते हैं जो फ़ाइल के जादू खंड से लंबाई लेता है और लम्बाई 'लेंस' का एक बाइट्रिंग देता है। यह अन्यथा विफल रहता है। अपने आप को

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

अच्छा दृष्टिकोण! 'ले लेन' करने का प्रदर्शन निहितार्थ क्या हो सकता है (काफी लंबा हो सकता है) और इसे अगले इटरेटे को खिलाया जाना है, यद्यपि? – edwardw

+0

खुद को एक और समाधान मिला, attoparsec 0.8.x में 'सुनिश्चित' है जो बिल को फिट करता है। लेकिन दुर्भाग्य से 0.9.x इस तरह के समारोह को हटा देता है। सोच रहा हूँ क्यों। – edwardw

+0

@edwardw: इस तरीके से 'ले लेन' करना 'Data.ByteString.readFile' करने के बराबर है। अब इटारेट का उपयोग करने के लिए वास्तव में कोई बात नहीं है। –

0

मिले एक समाधान: उसके बाद हम उस bytestring पर एक नियमित attoparsec पार्सर का उपयोग कर रहे

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

लेकिन attoparsec ensure हाल ही में हटा दिया। मैंने बिटबकेट पर attoparsec लेखक को एक बग रिपोर्ट दायर की है।

+0

मुझे लगता है कि 'सुनिश्चित करें' हटा दिया गया था क्योंकि यह वास्तव में ऐसी स्थिति है जिसमें आप इसका उपयोग नहीं करना चाहते हैं। 'सुनिश्चित करें 'डेटा के अगले' एन 'बाइट्स को बल देता है, जिसका अर्थ है कि यदि आप बड़े मूल्यों को सुनिश्चित करने की कोशिश कर रहे हैं तो आप वृद्धिशील पार्सिंग के किसी भी लाभ को पूरी तरह से अस्वीकार कर देते हैं। –

+0

@ जॉन एल, मुझे संदेह है। इसकी पुष्टि करने के लिए धन्यवाद। – edwardw

संबंधित मुद्दे