PageRenderTime 22ms CodeModel.GetById 16ms app.highlight 4ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Data/NetCDF/Serialize.hs

http://github.com/jystic/netcdf-hs
Haskell | 172 lines | 119 code | 40 blank | 13 comment | 3 complexity | 6c3bdd16685af33f2b4dcce89ca36212 MD5 | raw file
Possible License(s): BSD-3-Clause
  1{-# LANGUAGE OverloadedStrings #-}
  2
  3module Data.NetCDF.Serialize (getHeader) where
  4
  5import           Control.Applicative
  6import           Data.Bits ((.&.), complement)
  7import           Data.ByteString (ByteString)
  8import qualified Data.ByteString.Char8 as B
  9import           Data.Int (Int16, Int32)
 10import           Data.Serialize
 11import           Data.Word (Word8, Word32)
 12import           Prelude hiding (take, drop)
 13
 14import           Data.NetCDF.Types
 15
 16------------------------------------------------------------------------
 17
 18-- | Parse the header of a NetCDF file.
 19getHeader :: Get Header
 20getHeader = do
 21    string "CDF"
 22
 23    format <- pure FormatClassic <* word8 1
 24          <|> pure Format64Bit   <* word8 2
 25          <?> "file format"
 26
 27    numrecs <- pure Streaming <* word32be 0xFFFFFFFF
 28           <|> NumRecs <$> getWord32be
 29
 30    dims  <- getDimensionList
 31    gatts <- getAttributeList
 32    vars  <- getVariableList dims (getOffsetFor format)
 33
 34    return (Header format numrecs dims gatts vars)
 35
 36-- | Create a parser for file offsets depending on the file format.
 37getOffsetFor :: Format -> Get FileOffset
 38getOffsetFor FormatClassic = fromIntegral <$> getWord32be
 39getOffsetFor Format64Bit   = fromIntegral <$> getWord64be
 40
 41getDimensionList :: Get [Dim]
 42getDimensionList = headerList 0xA getDimension <?> "dimensions"
 43
 44getAttributeList :: Get [Attr]
 45getAttributeList = headerList 0xC getAttribute <?> "attributes"
 46
 47getVariableList  :: [Dim] -> Get FileOffset -> Get [Var]
 48getVariableList dims getOffset = headerList 0xB (getVariable dims getOffset) <?> "variables"
 49
 50headerList :: Word32 -> Get a -> Get [a]
 51headerList tag gelem = absent <|> word32be tag *> getList gelem
 52  where
 53    absent = zero *> zero *> pure []
 54    zero   = word32be 0
 55
 56getName :: Get Name
 57getName = getAlignedBytes <?> "name"
 58
 59
 60getDimension :: Get Dim
 61getDimension = Dim <$> getName <*> getDimensionLength
 62
 63getDimensionLength :: Get DimLength
 64getDimensionLength = pure Unlimited <* word32be 0
 65                 <|> Fixed <$> getWord32be
 66
 67
 68getAttribute :: Get Attr
 69getAttribute = Attr <$> getName <*> getAttributeValue <?> "attribute"
 70
 71getAttributeValue :: Get AttrValue
 72getAttributeValue = do
 73    typ <- getVariableType
 74    case typ of
 75      VarByte   -> AttrByte   <$> getAlignedBytes
 76      VarChar   -> AttrChar   <$> getAlignedBytes
 77      VarShort  -> AttrShort  <$> getAlignedShorts
 78      VarInt    -> AttrInt    <$> getList getInt32be
 79      VarFloat  -> AttrFloat  <$> getList getFloat32be
 80      VarDouble -> AttrDouble <$> getList getFloat64be
 81
 82
 83getVariable :: [Dim] -> Get FileOffset -> Get Var
 84getVariable dims getOffset =
 85    Var <$> getName
 86        <*> getList ((dims !!) <$> nonNeg)
 87        <*> getAttributeList
 88        <*> getVariableType
 89        <*> getWord32be
 90        <*> getOffset
 91        <?> "variable"
 92
 93getVariableType :: Get VarType
 94getVariableType = do
 95    typ <- getWord32be
 96    case typ of
 97      1 -> pure VarByte
 98      2 -> pure VarChar
 99      3 -> pure VarShort
100      4 -> pure VarInt
101      5 -> pure VarFloat
102      6 -> pure VarDouble
103      _ -> fail ("unsupported nc_type = " ++ show typ)
104    <?> "variableType"
105
106
107------------------------------------------------------------------------
108-- Utils
109
110-- | Label the parser, in case failure occurs.
111(<?>) :: Get a -> String -> Get a
112g <?> lbl = label lbl g
113infix 0 <?>
114
115-- | Apply the given action repeatedly, returning every result.
116count :: Monad m => Int -> m a -> m [a]
117count n p = sequence (replicate n p)
118
119-- | Expect a specific string.
120string :: ByteString -> Get ()
121string xs = expectS B.unpack (getByteString $ B.length xs) xs
122
123-- | Expect a specific 8-bit word.
124word8 :: Word8 -> Get ()
125word8 = expect getWord8
126
127-- | Expect a specific 32-bit big endian word.
128word32be :: Word32 -> Get ()
129word32be = expect getWord32be
130
131expect :: (Show a, Eq a) => Get a -> a -> Get ()
132expect = expectS show
133
134expectS :: Eq a => (a -> String) -> Get a -> a -> Get ()
135expectS show' get' x = get' >>= \y ->
136    if x == y
137       then return ()
138       else fail ("expected " ++ show' x ++ " (was " ++ show' y ++ ")")
139
140getAlignedBytes :: Get ByteString
141getAlignedBytes = do
142    n <- nonNeg
143    getBytes n <* align n
144    <?> "aligned bytes"
145
146getAlignedShorts :: Get [Int16]
147getAlignedShorts = do
148    n <- nonNeg
149    count n getInt16be <* align (n * 2)
150    <?> "aligned shorts"
151
152-- | Re-aligns the current offset to a 4-byte boundary given
153-- the number of bytes read since the last alignment.
154align :: Int -> Get ()
155align bytesRead = getBytes padding *> pure () <?> "align"
156  where
157    boundary = (bytesRead + 3) .&. complement 0x3
158    padding  = boundary - bytesRead
159
160getList :: Get a -> Get [a]
161getList g = do
162    n <- nonNeg
163    count n g
164
165nonNeg :: Get Int
166nonNeg = fromIntegral <$> getWord32be
167
168getInt16be :: Get Int16
169getInt16be = fromIntegral <$> getWord16be
170
171getInt32be :: Get Int32
172getInt32be = fromIntegral <$> getWord32be