{-# language BangPatterns #-}
{-# language DeriveFunctor #-}
{-# language DerivingStrategies #-}
{-# language LambdaCase #-}
{-# language NamedFieldPuns #-}
{-# language RankNTypes #-}
{-# language ScopedTypeVariables #-}
-- | Transform between Haskell values and the 'Value' type. The instance you
-- write for 'ToAsn' and 'FromAsn' assume a schema. I (Eric) think this is
-- reasonable because I expect each schema to be one-to-one with data types.
module Asn.Resolve
( Parser
, run
, MemberParser
-- * Combinators
, fail
, integer
-- TODO bitString
, octetString
, null
, oid
, utf8String
, printableString
, sequence
, index
, sequenceOf
, withTag
, chooseTag
-- * Error Breadcrumbs
, Path(..)
-- * Re-Exports
, Value
, Contents
, Class(..)
) where
import Prelude hiding (fail,null,reverse,null,sequence)
import Asn.Ber (Value(..), Contents(..), Class(..))
import Asn.Oid (Oid)
import Control.Applicative (Alternative(..))
import Control.Monad.ST (ST, runST)
import Data.Bifunctor (first)
import Data.Bytes (Bytes)
import Data.Int (Int64)
import Data.Primitive (SmallArray,SmallMutableArray)
import Data.Text.Short (ShortText)
import Data.Word (Word32)
import qualified Data.Primitive as PM
import qualified Asn.Ber as Ber
newtype Parser a = P { unP :: Path -> Either Path a }
deriving stock (Functor)
instance Applicative Parser where
pure x = P $ \_ -> Right x
a <*> b = P $ \p -> unP a p <*> unP b p
instance Monad Parser where
a >>= k = P $ \p -> unP a p >>= \x -> unP (k x) p
instance Alternative Parser where
empty = fail
a <|> b = P $ \p -> case unP a p of
Right val -> Right val
Left err1 -> case unP b p of
Right val -> Right val
Left err2 -> Left $ longerPath err1 err2
run :: Parser a -> Either Path a
run r = first reverse $ unP r Nil
newtype MemberParser a = MP
{ unMP :: SmallArray Value -> Path -> Either Path a }
deriving stock Functor
instance Applicative MemberParser where
pure a = MP (\_ _ -> Right a)
MP f <*> MP g = MP $ \p mbrs ->
f p mbrs <*> g p mbrs
fail :: Parser a
fail = P $ Left
unresolved :: (Bytes -> Either String a) -> Bytes -> Parser a
unresolved f bytes = either (const fail) pure (f bytes)
integer :: Value -> Parser Int64
integer = \case
Value{contents=Integer n} -> pure n
Value{contents=Unresolved bytes} -> unresolved Ber.decodeInteger bytes
_ -> fail
octetString :: Value -> Parser Bytes
octetString = \case
Value{contents=OctetString bs} -> pure bs
Value{contents=Unresolved bytes} -> unresolved Ber.decodeOctetString bytes
_ -> fail
null :: Value -> Parser ()
null = \case
Value{contents=Null} -> pure ()
Value{contents=Unresolved bytes} -> unresolved Ber.decodeNull bytes
_ -> fail
oid :: Value -> Parser Oid
oid = \case
Value{contents=ObjectIdentifier objId} -> pure objId
Value{contents=Unresolved bytes} -> unresolved Ber.decodeObjectId bytes
_ -> fail
utf8String :: Value -> Parser ShortText
utf8String = \case
Value{contents=Utf8String str} -> pure str
Value{contents=Unresolved bytes} -> unresolved Ber.decodeUtf8String bytes
_ -> fail
printableString :: Value -> Parser ShortText
printableString = \case
Value{contents=PrintableString str} -> pure str
Value{contents=Unresolved bytes} -> unresolved Ber.decodePrintableString bytes
_ -> fail
sequenceOf :: forall a. (Value -> Parser a) -> Value -> Parser (SmallArray a)
sequenceOf k = \case
Value{tagNumber=16, contents=Constructed vals} -> P $ \p -> runST $ do
dst <- PM.newSmallArray (PM.sizeofSmallArray vals) undefined
go vals dst p 0
_ -> fail
where
go :: forall s.
SmallArray Value
-> SmallMutableArray s a
-> Path
-> Int
-> ST s (Either Path (SmallArray a))
go src dst p0 ix
| ix < PM.sizeofSmallArray src = do
let val = PM.indexSmallArray src ix
case unP (k val) (Index ix p0) of
Left err -> pure $ Left err
Right rval -> do
PM.writeSmallArray dst ix rval
go src dst p0 (ix + 1)
| otherwise = Right <$> PM.unsafeFreezeSmallArray dst
sequence :: MemberParser a -> Value -> Parser a
sequence k = \case
Value{contents=Constructed vals} -> P (unMP k vals)
_ -> fail
index :: Int -> (Value -> Parser a) -> MemberParser a
index ix k = MP $ \vals p ->
let p' = Index ix p in
if ix < PM.sizeofSmallArray vals
then unP (k $ PM.indexSmallArray vals ix) p'
else Left p'
withTag :: Class -> Word32 -> (Value -> Parser a) -> Value -> Parser a
withTag cls num k v = case v of
Value{tagClass,tagNumber}
| tagClass == cls && tagNumber == num ->
P $ \p -> unP (k v) (Tag cls num p)
_ -> fail
chooseTag :: [(Class, Word32, Value -> Parser a)] -> Value -> Parser a
chooseTag tab0 v@Value{tagClass,tagNumber} = go tab0
where
go [] = fail
go ((cls, num, k) : rest)
| cls == tagClass && num == tagNumber
= P $ \p -> unP (k v) (Tag cls num p)
| otherwise = go rest
data Path
= Nil
| Index {-# UNPACK #-} !Int !Path
-- ^ into the nth field of a constructed type
| Tag !Class !Word32 !Path
-- ^ into a specific tag
deriving stock (Eq, Show)
longerPath :: Path -> Path -> Path
longerPath a b = if pathSize 0 a < pathSize 0 b then b else a
where
pathSize :: Int -> Path -> Int
pathSize !acc Nil = acc
pathSize !acc (Index _ rest) = pathSize (1 + acc) rest
pathSize !acc (Tag _ _ rest) = pathSize (1 + acc) rest
reverse :: Path -> Path
reverse = go Nil
where
go !acc Nil = acc
go !acc (Index ix rest) = go (Index ix acc) rest
go !acc (Tag cls num rest) = go (Tag cls num acc) rest