packages feed

bytezap-1.5.0: src/Bytezap/Parser/Struct/Generic.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-} -- thanks to type manipulation

-- TODO pass metadata to parser for errors. not hard just cba

-- TODO copies a lot of stuff from generic-data-functions. some should be kept
-- in a separate library (ReifyMaybeSymbol, ProdArity, natVal'' etc.)

module Bytezap.Parser.Struct.Generic where

import Bytezap.Parser.Struct
import GHC.Generics
import GHC.Exts
import Data.Kind
import GHC.TypeNats
import GHC.TypeLits ( KnownSymbol, symbolVal' )
import Bytezap.Common.TypeNats ( natValInt )
import Bytezap.Common.Generic ( type GTFoldMapCAddition )
import DeFun.Core ( type (~>) )

class GParseBase tag where
    -- | The state token of the parser.
    type GParseBaseSt tag :: ZeroBitType
    type GParseBaseC tag a :: Constraint
    type GParseBaseE tag :: Type

    -- unlike the serializer we stay newtyped because we want our Functor
    --
    -- TODO this is where we need to pass a bunch of metadata. see gdf
    gParseBase
        :: GParseBaseC tag a
        => String       {- ^ data type name -}
        -> String       {- ^ constructor name -}
        -> Maybe String {- ^ record name (if present) -}
        -> Natural      {- ^ field index -}
        -> ParserT (GParseBaseSt tag) (GParseBaseE tag) a

    -- | Defunctionalization symbol for a type family turning 'Type's into
    --   'Natural's. (Needed as we can't partially apply type families.)
    type GParseBaseLenTF tag :: Type ~> Natural

class GParse tag gf where
    gParse :: ParserT (GParseBaseSt tag) (GParseBaseE tag) (gf p)

instance GParseC tag dtName cstrName 0 gf
  => GParse tag (D1 (MetaData dtName _md2 _md3 _md4) (C1 (MetaCons cstrName _mc2 _mc3) gf)) where
    gParse = M1 <$> M1 <$> gParseC @tag @dtName @cstrName @0

class GParseC tag (cd :: Symbol) (cc :: Symbol) (si :: Natural) gf where
    gParseC :: ParserT (GParseBaseSt tag) (GParseBaseE tag) (gf p)

instance
  ( GParseC tag cd cc si                 l
  , GParseC tag cd cc (si + ProdArity r) r
  , GParseBase tag
  , lenL ~ GTFoldMapCAddition (GParseBaseLenTF tag) l
  , KnownNat lenL
  ) => GParseC tag cd cc si (l :*: r) where
    gParseC = sequenceParsers len (:*:)
        (gParseC @tag @cd @cc @si)
        (gParseC @tag @cd @cc @(si + ProdArity r))
      where
        len = natValInt @lenL

instance
  ( GParseBase tag, GParseBaseC tag a
  , KnownNat si, ReifyMaybeSymbol mSelName, KnownSymbol cc, KnownSymbol cd
  ) => GParseC tag cd cc si (S1 (MetaSel mSelName _ms2 _ms3 _ms4) (Rec0 a)) where
    gParseC = (M1 . K1) <$> gParseBase @tag cd cc cs si
      where
        cs = reifyMaybeSymbol @mSelName
        cd = symbolVal'' @cd
        cc = symbolVal'' @cc
        si = natVal'' @si

-- | Wow, look! Nothing!
instance GParseC tag cd cc 0 U1 where gParseC = constParse U1

type family ProdArity (f :: Type -> Type) :: Natural where
    ProdArity (S1 c f)  = 1
    ProdArity (l :*: r) = ProdArity l + ProdArity r

class ReifyMaybeSymbol (mstr :: Maybe Symbol) where
    reifyMaybeSymbol :: Maybe String
instance ReifyMaybeSymbol Nothing where reifyMaybeSymbol = Nothing
instance KnownSymbol str => ReifyMaybeSymbol (Just str) where
    reifyMaybeSymbol = Just (symbolVal'' @str)

natVal'' :: forall n. KnownNat n => Natural
natVal'' = natVal' (proxy# :: Proxy# n)
{-# INLINE natVal'' #-}

symbolVal'' :: forall sym. KnownSymbol sym => String
symbolVal'' = symbolVal' (proxy# :: Proxy# sym)
{-# INLINE symbolVal'' #-}