packages feed

packstream-0.1.0.0: src/Data/PackStream/Parser.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Data.PackStream.Parser
(
  null, bool, integer, float,
  bytes, string, list, dict, structure,
  value
) where

import Data.PackStream.Internal.Type ( PackStream, PackStreamError(..), Structure(..), Value(..) )
import Data.PackStream.Internal.Code
import Data.PackStream.Internal.Binary ( Interpret(..) )

import Prelude hiding (null)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS (head, tail, drop, take, singleton)
import Data.Text (Text)
import Data.Map.Strict (Map, fromList)
import Data.Binary (Word8)
import Control.Monad.State ( gets, modify )
import Control.Monad.Except (throwError)
import Control.Monad (replicateM, (>=>))


-- |Parse '()'
null :: PackStream ()
null = bite1 >>= select NotNull [ (isNull, pure ()) ]

-- |Parse 'Bool'
bool :: PackStream Bool
bool = bite1 >>= select NotBool [ (isTrue, pure True)
                                , (isFalse, pure False)
                                ]

-- |Parse 'Int'
integer :: PackStream Int
integer = bite1 >>= \x -> select NotInt [ (isTinyInt, interpret $ BS.singleton x)
                                        , (isInt8,  bite 1 >>= interpret)
                                        , (isInt16, bite 2 >>= interpret)
                                        , (isInt32, bite 4 >>= interpret)
                                        , (isInt64, bite 8 >>= interpret)
                                        ] x

-- |Parse 'Double'
float :: PackStream Double
float = bite1 >>= select NotFloat [ (isFloat, bite 8 >>= interpret) ]

-- |Parse 'ByteString'
bytes :: PackStream ByteString
bytes = bite1 >>= select NotBytes [ (isBytes8,  bite'by'bytes 1)
                                  , (isBytes16, bite'by'bytes 2)
                                  , (isBytes32, bite'by'bytes 4)
                                  ]

-- |Parse 'Text'
string :: PackStream Text
string = bite1 >>= \x -> select NotBytes [ (isTinyString, bite (tinySize x) >>= interpret)
                                         , (isString8,    bite'by'bytes 1 >>= interpret)
                                         , (isString16,   bite'by'bytes 2 >>= interpret)
                                         , (isString32,   bite'by'bytes 4 >>= interpret)
                                         ] x

-- |Parse a list of specified 'Value's (e.g. `list integer` will parse some '[Int]')
list :: PackStream a -> PackStream [a]
list action = bite1 >>= \x -> select NotList [ (isTinyList, multiple action (tinySize x))
                                             , (isList8,    collectionSize 1 >>= multiple action)
                                             , (isList16,   collectionSize 2 >>= multiple action)
                                             , (isList32,   collectionSize 4 >>= multiple action)
                                             ] x

-- |Parse a dictionary of specified 'Value's (e.g. `dict integer` will parse some 'Map Text Int')
dict :: forall a.PackStream a -> PackStream (Map Text a)
dict action = bite1 >>= \x -> select NotDict [ (isTinyDict, makeDict (tinySize x))
                                             , (isDict8,    collectionSize 1 >>= makeDict)
                                             , (isDict16,   collectionSize 2 >>= makeDict)
                                             , (isDict32,   collectionSize 4 >>= makeDict)
                                             ] x
  where
    makeDict :: Int -> PackStream (Map Text a)
    makeDict = (fromList <$>) . multiple ((,) <$> string <*> action)

-- |Parse 'Structure'
structure :: PackStream Structure
structure = bite1 >>= \x -> select NotStructure [ (isStructure, makeStructure (tinySize x)) ] x
  where
    makeStructure :: Int -> PackStream Structure
    makeStructure n = Structure <$> bite1 <*> multiple value n

-- |Parse any valid 'Value'
value :: PackStream Value
value = look1 >>= byMarker
  where
    byMarker :: Word8 -> PackStream Value
    byMarker n | isNull n      = N <$ null
               | isBool n      = B <$> bool
               | isInt n       = I <$> integer
               | isFloat n     = F <$> float
               | isBytes n     = U <$> bytes
               | isString n    = T <$> string
               | isList n      = L <$> list value
               | isDict n      = D <$> dict value
               | isStructure n = S <$> structure
               | otherwise     = throwError NotValue

-- |Selects a parser to use by the marker byte predicate. Raises the 'PackStreamError' if nothing is suitable
select :: PackStreamError -> [(Word8 -> Bool, PackStream a)] -> Word8 -> PackStream a
select e []               _ = throwError e
select e ((p, action):xs) w | p w       = action
                            | otherwise = select e xs w

-- |Gets one byte from the 'ByteString'
bite1 :: PackStream Word8
bite1 = do b <- gets BS.head
           modify BS.tail
           pure b

-- |Gets the specified number of bytes from the 'ByteString'
bite :: Int -> PackStream ByteString
bite n = do bs <- gets $ BS.take n
            modify $ BS.drop n
            pure bs

-- |Looks at the first byte of the 'ByteString' without modifying it
look1 :: PackStream Word8
look1 = gets BS.head

-- |Gets the specified number of bytes from the 'ByteString', 
-- interprets them as some unsigned int ('Word') and then get the specified number of
-- further bytes from the 'ByteString'
bite'by'bytes :: Int -> PackStream ByteString
bite'by'bytes n = collectionSize n >>= bite

-- |Performs some 'PackStream' parser combinator the specified number of times
multiple :: PackStream a -> Int -> PackStream [a]
multiple action n = replicateM n action

-- |Gets the specified number of bytes from the 'ByteString' and
-- interprets them as some unsigned int ('Word')
collectionSize :: Int -> PackStream Int
collectionSize n = bite n >>= fmap fromIntegral . interpret @Word