{-# OPTIONS -Wall #-}

--------------------------------------------------------------------------------
-- |
-- Module      :  Data.ParserCombinators.Kangaroo.Prim
-- Copyright   :  (c) Stephen Tetley 2009-2010
-- License     :  BSD3
--
-- Maintainer  :  Stephen Tetley <stephen.tetley@gmail.com>
-- Stability   :  highly unstable
-- Portability :  to be determined.
--
-- Primitive parsers - charactors, numbers...
--
--------------------------------------------------------------------------------

module Data.ParserCombinators.Kangaroo.Prim 
  (
  -- * Char and String parsers
    char
  , anyChar
  , text
  , string
  , cstring
  
  -- * Byte parsers
  , w8Zero
  , getBytes

  -- * Number parsers
  , int8

  -- ** Word - big endian 
  , word16be
  , word24be
  , word32be
  , word64be
  
  -- ** Word - little endian  
  , word16le
  , word24le
  , word32le

  -- ** Int - big endian
  , int16be
  , int32be

  -- ** Int - little endian  
  , int16le
  , int32le

  -- ** IEEE754 single precision float
  , ieeeFloatSP

  ) where

import Data.ParserCombinators.Kangaroo.Combinators
import Data.ParserCombinators.Kangaroo.IEEEFloat
import Data.ParserCombinators.Kangaroo.ParseMonad
import Data.ParserCombinators.Kangaroo.Utils

import Control.Applicative
import Data.Char
import Data.Int
import Data.Word



-- | Attempt to parse the supplied single character (the supplied 
-- char must be in the ASCII range 0-255).
--
-- If the parse succeeds return the char, otherwise a parse-error
-- will be thrown with 'reportError'.
--
char :: Char -> GenKangaroo ust Char
char ch = satisfy (==chi) >> return ch 
  where 
    chi = fromIntegral $ ord ch 


-- | Parse any single character. The parser consumes one byte and
-- uses 'chr' to convert it.
--
anyChar :: GenKangaroo ust Char
anyChar = (chr . fromIntegral) <$> word8 


-- | Parse a string of the supplied length @n@.
--
-- If @n@ is less than or equal to zero the empty string is 
-- returned.
--
text :: Int -> GenKangaroo ust String
text i | i <= 0    = return ""
       | otherwise = count i anyChar

-- | Parse the supplied string. All characters should be within 
-- the range 0-255.
-- 
-- If the parse succeeds return the char, otherwise a parse-error
-- will be thrown with 'reportError'.
--
string :: String -> GenKangaroo ust String
string = (substError `flip` "string" ) . mapM char 
 

-- | Parse a null-terminated C-style string.
--
cstring :: GenKangaroo ust String
cstring = manyTill anyChar w8Zero

--------------------------------------------------------------------------------
-- Byte parsers


-- | Parse the literal @0x00@.
--
w8Zero :: GenKangaroo ust Word8
w8Zero = satisfy (==0)


-- | Get @n@ bytes. 
-- 
-- If @n@ is less than or equal to zero an empty list is returned.
--
getBytes :: Integral a => a -> GenKangaroo ust [Word8]
getBytes i | i < 0     = return []
           | otherwise = count (fromIntegral i) word8


--------------------------------------------------------------------------------
-- Numbers


-- | Parse a single byte, returning it as an Int8.
-- 
-- The conversion from a byte (0-255) to an Int8 uses the Prelude 
-- function 'fromIntegral'. 
-- 
-- The conversion is summarized as:
--
-- > 0..127   = 0..127
-- > 128      = -128
-- > 129      = -127
-- > 130      = -126
-- > ...
-- > 254      = -2
-- > 255      = -1   
-- >
-- > wtoi :: Word8 -> Int8
-- > wtoi i | i < 128   = i
-- >        | otherwise = -128 + (clearBit i 7)
--
int8 :: GenKangaroo ust Int8
int8 = fromIntegral <$> word8

--------------------------------------------------------------------------------
-- Data.Word 

-- | Parse a Word16 in big endian form.
--
word16be   :: GenKangaroo ust Word16
word16be   = w16be     <$> word8 <*> word8  

-- | Parse a \"Word24\" in big endian form.
-- 
-- 3 bytes are read - the answer is returned as a Word32.
--
word24be   :: GenKangaroo ust Word32
word24be   = w32be 0 <$> word8 <*> word8 <*> word8

-- | Parse a Word32 in big endian form.
--
word32be   :: GenKangaroo ust Word32
word32be   = w32be     <$> word8 <*> word8 <*> word8 <*> word8

-- | Parse a Word64 in big endian form.
--
word64be   :: GenKangaroo ust Word64
word64be   = w64be <$> word8 <*> word8 <*> word8 <*> word8
                   <*> word8 <*> word8 <*> word8 <*> word8


-- | Parse a Word16 in little endian form.
--
word16le   :: GenKangaroo ust Word16
word16le   = w16le     <$> word8 <*> word8  

-- | Parse a \"Word24\" in little endian form.
--
-- 3 bytes are read - the answer is returned as a Word32.
--
word24le   :: GenKangaroo ust Word32
word24le   = w24le     <$> word8 <*> word8 <*> word8


-- | Parse a Word32 in little endian form.
--
word32le   :: GenKangaroo ust Word32
word32le   = w32le     <$> word8 <*> word8 <*> word8 <*> word8


--------------------------------------------------------------------------------
-- Data.Int



-- | Parse an Int16 in big endian form.
-- 
-- The ans is parsed as a Word16 (big endian) then converted to 
-- an Int16 using the Prelude function 'fromIntegral'.
--
int16be   :: GenKangaroo ust Int16
int16be   = i16be <$> word8 <*> word8
  

-- | Parse an Int32 in big endian form.
-- 
-- The ans is parsed as a Word32 (big endian) then converted to 
-- an Int32 using the Prelude function 'fromIntegral'.
--
int32be   :: GenKangaroo ust Int32
int32be   = i32be <$> word8 <*> word8 <*> word8 <*> word8

                         
-- | Parse an Int16 in little endian form.
-- 
-- The ans is parsed as a Word16 (little endian) then converted 
-- to an Int16 using the Prelude function 'fromIntegral'.
--
int16le   :: GenKangaroo ust Int16
int16le   = i16le <$> word8 <*> word8
                         
-- | Parse an Int32 in little endian form.
-- 
-- The ans is parsed as a Word32 (little endian) then converted 
-- to an Int32 using the Prelude function 'fromIntegral'.
--
int32le   :: GenKangaroo ust Int32
int32le   = i32le <$> word8 <*> word8 <*> word8 <*> word8


-- | Parse an 4-byte IEEE single precision float.
-- 
-- NOTE - THIS FUNCTION IS UNTESTED!
--
ieeeFloatSP :: Fractional a => GenKangaroo ust a
ieeeFloatSP = unpackIEEESingle <$> word8 <*> word8 <*> word8 <*> word8