{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Fits.MegaParser where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS ( c2w )
import qualified Text.Megaparsec as M
import qualified Text.Megaparsec.Stream as M
import qualified Text.Megaparsec.Char as M
import qualified Text.Megaparsec.Char.Lexer as MCL
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Control.Applicative ( (<$>) )
import Control.Monad ( void )
import Numeric.Natural ( Natural )
import Data.ByteString ( ByteString )
import Data.Text ( Text )
import Data.Word ( Word8, Word16, Word32, Word64 )
import Data.Void ( Void )
import Data.Default ( def )
import Text.Ascii ( isAscii )
import Text.Megaparsec ( Parsec, ParseErrorBundle, (<|>), (<?>))
import Control.Applicative.Permutations
import Data.Proxy
import Data.Fits
type Parser = Parsec Void Text
type ParseErr = ParseErrorBundle Text Void
data DataUnitValues
= FITSUInt8 Word8
| FITSInt16 Word16
| FITSInt32 Word32
| FITSInt64 Word64
| FITSFloat32 Float
| FITSFloat64 Double
headerBlockParse :: Parser HeaderData
headerBlockParse = do
(simple, bitpix, axesDesc, bZero, bScale, ref, obs, instr, tele, object, pCreator, pDate, pEnd) <-
runPermutation $
(,,,,,,,,,,,,) <$> toPermutation (parseSimple <?> "simple")
<*> toPermutation (parseBitPix <?> "bitpix")
<*> toPermutation ((parseAxisCount >>= parseNaxes) <?> "axis parsing")
<*> toPermutationWithDefault 0 (parseBzero <?> "bzero")
<*> toPermutationWithDefault 0 (parseBscale <?> "bscale")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseReference <?> "reference")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseObserver <?> "observer")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseInstrument <?> "instrument")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseTelescope <?> "telescope")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseObject <?> "object")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseCreator <?> "creator")
<*> toPermutationWithDefault (StringValue NullString Nothing) (parseDate <?> "date")
<*> toPermutation (parseEnd <?> "end")
return defHeader { simpleFormat = simple
, bitPixFormat = bitpix
, axes = axesDesc
, referenceString = ref
, observerIdentifier = obs
, instrumentIdentifier = instr
, telescopeIdentifier = tele
, objectIdentifier = object
, observationDate = pDate
, authorIdentifier = pCreator }
where
defHeader = def :: HeaderData
parseSimple :: Parser SimpleFormat
parseSimple = M.string' "simple" >> parseEquals
>> ((conformParse >> consumeDead >> return Conformant)
<|> (nonConformParse >> consumeDead >> return NonConformant))
where
conformParse = M.char' 't'
nonConformParse = M.anySingle
parseEquals :: Parser ()
parseEquals = M.space >> M.char '=' >> M.space
parseBitPix :: Parser BitPixFormat
parseBitPix = M.string' "bitpix" >> parseEquals
>> ((M.chunk "8" >> consumeDead >> return EightBitInt)
<|> (M.chunk "16" >> consumeDead >> return SixteenBitInt)
<|> (M.chunk "32" >> consumeDead >> return ThirtyTwoBitInt)
<|> (M.chunk "64" >> consumeDead >> return SixtyFourBitInt)
<|> (M.chunk "-32" >> consumeDead >> return ThirtyTwoBitFloat)
<|> (M.chunk "-64" >> consumeDead >> return SixtyFourBitFloat))
parseAxisCount :: Parser Natural
parseAxisCount = M.string' "naxis" >> parseEquals >> parseNatural
parseNaxes :: Natural -> Parser [Axis]
parseNaxes n | n == 0 = return []
parseNaxes n = do
axisNum <- M.string' "naxis" >> parseNatural
elemCount <- parseEquals >> parseNatural
([buildAxis axisNum elemCount] ++) <$> parseNaxes (n - 1)
where
defAxis = def :: Axis
buildAxis an ec = defAxis { axisNumber = fromIntegral an
, axisElementCount = fromIntegral ec }
parseBzero :: Parser Int
parseBzero = M.string' "bzero" >> parseEquals >> parseInteger
parseBscale :: Parser Int
parseBscale = M.string' "bscale" >> parseEquals >> parseInteger
parseReference :: Parser StringValue
parseReference = M.string' "referenc" >> parseEquals >> parseStringValue
parseObserver :: Parser StringValue
parseObserver = M.string' "observer" >> parseEquals >> parseStringValue
parseInstrument :: Parser StringValue
parseInstrument = M.string' "instrume" >> parseEquals >> parseStringValue
parseTelescope :: Parser StringValue
parseTelescope = M.string' "telescop" >> parseEquals >> parseStringValue
parseObject :: Parser StringValue
parseObject = M.string' "object" >> parseEquals >> parseStringValue
parseCreator :: Parser StringValue
parseCreator = M.string' "creator" >> parseEquals >> parseStringValue
parseDate :: Parser StringValue
parseDate = M.string' "date" >> parseEquals >> parseStringValue
skipEmpty :: Parser ()
skipEmpty = void (M.many $ M.satisfy ('\0' ==))
consumeDead :: Parser ()
consumeDead = M.space >> skipEmpty
parseEnd :: Parser ()
parseEnd = M.string' "end" >> M.space <* M.eof
parseNatural :: Parser Natural
parseNatural = do
v <- MCL.decimal
consumeDead
return $ fromIntegral v
parseInteger :: Parser Int
parseInteger = do
v <- MCL.decimal
consumeDead
return v
parseStringValue :: Parser StringValue
parseStringValue = do
ls <- M.between (M.char '\'') (M.char '\'') $ M.many $ M.anySingleBut '\''
consumeDead
let v = M.tokensToChunk (Proxy :: Proxy Text) ls
if T.length v < 1
then return (StringValue EmptyString Nothing)
else return (StringValue DataString (Just v))
countHeaderDataUnits :: ByteString -> IO Natural
countHeaderDataUnits bs = fromIntegral . length <$> getAllHDUs bs
getAllHDUs :: ByteString -> IO [HeaderDataUnit]
getAllHDUs bs = do
(hdu, rest) <- getOneHDU bs
return [hdu]
getOneHDU :: ByteString -> IO (HeaderDataUnit, ByteString)
getOneHDU bs =
if isAscii header
then
case M.runParser headerBlockParse "FITS" (TE.decodeUtf8 header) of
Right mainHeader -> do
let (dataUnit, remainder) = BS.splitAt (fromIntegral $ dataSize mainHeader) rest
return (HeaderDataUnit mainHeader dataUnit, remainder)
Left e -> let err = M.errorBundlePretty e in error err
else error "Header data is not ASCII. Please Check your input file and try again"
where
(header, rest) = BS.splitAt hduBlockSize bs
dataSize :: HeaderData -> Natural
dataSize h = paddedsize
where
axesCount = length $ axes h
wordCount = product $ map axisElementCount $ axes h
wordsize = fromIntegral . bitPixToWordSize $ bitPixFormat h
datasize = wordsize * wordCount
padding = if axesCount == 0 then 0 else fromIntegral hduBlockSize - datasize `mod` fromIntegral hduBlockSize
paddedsize = fromIntegral (datasize + padding)