{-# LANGUAGE LambdaCase #-}
{- |
Module : Data.ASN1.Parse
License : BSD-style
Copyright : (c) 2010-2013 Vincent Hanquez <vincent@snarc.org>
Stability : experimental
Portability : unknown
A monadic parser combinator for a stream of ASN.1 items.
-}
module Data.ASN1.Parse
( ParseASN1
-- * Run 'ParseASN1'
, runParseASN1State
, runParseASN1
, throwParseError
-- * Combinators
, onNextContainer
, onNextContainerMaybe
, getNextContainer
, getNextContainerMaybe
, getNext
, getNextMaybe
, hasNext
, getObject
, getMany
) where
import Control.Applicative ( Alternative (..) )
import Control.Arrow ( first )
import Control.Monad ( liftM2 )
import Data.ASN1.Types
( ASN1 (..), ASN1ConstructionType (..), ASN1Object (..) )
import Data.ASN1.Stream ( getConstructedEnd )
-- | Type representing a parser combinator for a stream of ASN.1 items.
newtype ParseASN1 a = P { runP :: [ASN1] -> Either String (a, [ASN1]) }
instance Functor ParseASN1 where
fmap f m = P (fmap (first f) . runP m)
instance Applicative ParseASN1 where
pure a = P $ \s -> Right (a, s)
(<*>) mf ma = P $ \s ->
case runP mf s of
Left err -> Left err
Right (f, s2) ->
case runP ma s2 of
Left err -> Left err
Right (a, s3) -> Right (f a, s3)
instance Monad ParseASN1 where
return = pure
(>>=) m1 m2 = P $ \s ->
case runP m1 s of
Left err -> Left err
Right (a, s2) -> runP (m2 a) s2
instance Alternative ParseASN1 where
empty = P $ \_ -> Left "empty Alternative"
(<|>) m1 m2 = P $ \s ->
case runP m1 s of
Left _ -> runP m2 s
Right (a, s2) -> Right (a, s2)
instance MonadFail ParseASN1 where
fail = throwParseError
-- | Run the given parse monad over the given list of ASN.1 items. Returns the
-- result and a list of the ASN.1 items remaining in the stream (if successful).
runParseASN1State :: ParseASN1 a -> [ASN1] -> Either String (a, [ASN1])
runParseASN1State = runP
-- | Run the given parse monad over the given list of ASN.1 items and returns
-- the result (if successful).
--
-- If ASN.1 items remain in the stream after doing so, returns an error.
runParseASN1 :: ParseASN1 a -> [ASN1] -> Either String a
runParseASN1 f list =
case runP f list of
Left err -> Left err
Right (result, []) -> Right result
Right (_, rest) -> Left ("runParseASN1: remaining state " ++ show rest)
-- | Throw a parse error.
throwParseError ::
String
-- ^ Error message.
-> ParseASN1 a
throwParseError err = P $ \_ -> Left err
-- | Returns a list of ASN.1 items in the stream.
get :: ParseASN1 [ASN1]
get = P $ \list -> Right (list, list)
-- | Puts the given list of ASN.1 items as the remainder of the stream and
-- returns @()@.
put :: [ASN1] -> ParseASN1 ()
put list = P $ \_ -> Right ((), list)
-- | Run the parse monad over the elements of the next container of specified
-- type. Throws an error if there is no next container of the specified type.
onNextContainer :: ASN1ConstructionType -> ParseASN1 a -> ParseASN1 a
onNextContainer ty f =
getNextContainer ty >>= either throwParseError pure . runParseASN1 f
-- | As for 'onNextContainer', except that it does not throw an error if there
-- is no next container of the specified type.
onNextContainerMaybe ::
ASN1ConstructionType
-> ParseASN1 a
-> ParseASN1 (Maybe a)
onNextContainerMaybe ty f =
getNextContainerMaybe ty >>= \case
Nothing -> pure Nothing
Just list -> either throwParseError (pure . Just) $ runParseASN1 f list
-- | Get the next container of the specified type and return a list of all its
-- ASN.1 elements. Throws a parse error if there is no next container of the
-- specified type.
getNextContainer :: ASN1ConstructionType -> ParseASN1 [ASN1]
getNextContainer ty =
get >>= \case
[] -> throwParseError "empty"
(item : rest)
| item == Start ty -> do
let (list, rest') = getConstructedEnd 0 rest
put rest' >> pure list
| otherwise -> throwParseError "not an expected container"
-- | As for 'getNextContainer', except that it does not throw an error if there
-- is no next container of the specified type.
getNextContainerMaybe :: ASN1ConstructionType -> ParseASN1 (Maybe [ASN1])
getNextContainerMaybe ty =
get >>= \case
[] -> pure Nothing
(item : rest)
| item == Start ty -> do
let (list, rest') = getConstructedEnd 0 rest
put rest' >> pure (Just list)
| otherwise -> pure Nothing
-- | Get the next ASN.1 item in a stream of ASN.1 items.
getNext :: ParseASN1 ASN1
getNext =
get >>= \case
[] -> throwParseError "empty"
(item : rest) -> put rest >> pure item
-- | Applies the given function to the next ASN.1 item in a stream of ASN.1
-- items, if there is one.
getNextMaybe :: (ASN1 -> Maybe a) -> ParseASN1 (Maybe a)
getNextMaybe f =
get >>= \case
[] -> pure Nothing
list@(item : rest) -> do
let result = f item
case result of
Nothing -> put list
Just _ -> put rest
pure result
-- | Are there any more ASN.1 items in the stream?
hasNext :: ParseASN1 Bool
hasNext = not . null <$> get
-- | Get the object from the next ASN.1 item in a stream of ASN.1 items.
-- Throws a parse error if the object cannot be obtained from the item.
getObject :: ASN1Object a => ParseASN1 a
getObject = do
list <- get
case fromASN1 list of
Left err -> throwParseError err
Right (object, rest) -> put rest >> pure object
-- | Get many items from the stream until there are none left.
getMany :: ParseASN1 a -> ParseASN1 [a]
getMany getOne =
hasNext >>= \case
True -> liftM2 (:) getOne (getMany getOne)
False -> pure []