2007-12-19 [長年日記]

[Haskell] BitSyntaxを使ってtzfileをパースする

Haskellにはバイナリを読み書きする時に使えるBitSyntaxというライブラリがあります。rubyで言うところのpackとかunpackに近いでしょうか。パースする時には返り値の型が引数に依存するので、その部分のコードはTemplate Haskellになっています。例えば、tzfile形式のファイルをパースするならこんな感じになります。

{-# LANGUAGE TemplateHaskell #-}

import Control.Exception
import Control.Monad
import Data.BitSyntax
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.Maybe
import System.IO

parseFile :: FilePath -> IO ()
parseFile path = do
    bracket (openFile path ReadMode)
            hClose
            (parse path)

parse :: String -> Handle -> IO ()
parse name h = do
    header <- BS.hGet h 44
    let Just (magic, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt) =
            $(bitSyn ([Fixed 4, Skip 16] ++ replicate 6 (Unsigned 4))) header
    when (magic /= BS8.pack "TZif") $
        fail "Invalid magic."
    transitionTimes <- forM [1..timecnt] $ \_ -> do
        liftM decodeU32 $ BS.hGet h 4
    localTimeTypes <- forM [1..timecnt] $ \_ -> do
        liftM decodeU8 $ BS.hGet h 1
    types <- forM [1..typecnt] $ \_ -> do
        b <- BS.hGet h 6
        return $ fromJust $ $(bitSyn [Unsigned 4, Unsigned 1, Unsigned 1]) b
    abbrevs <- BS.hGet h (fromIntegral charcnt)
    leaps <- forM [1..leapcnt] $ \_ -> do
        b <- BS.hGet h 8
        return $ fromJust $ $(bitSyn [Unsigned 4, Unsigned 4]) b
    isstds <- forM [1..ttisstdcnt] $ \_ -> do
        liftM decodeU8 $ BS.hGet h 1
    isgmts <- forM [1..ttisgmtcnt] $ \_ -> do
        liftM decodeU8 $ BS.hGet h 1
    return ()
[]