packages feed

asn1-ber-syntax-0.1.0.0: src/Asn/Resolve.hs

{-# 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