packages feed

binrep-0.3.0: src/Binrep/Generic/Get.hs

{-# LANGUAGE UndecidableInstances #-} -- required for TypeError >:(

module Binrep.Generic.Get where

import GHC.Generics
import GHC.TypeLits ( TypeError )

import Binrep.Get
import Binrep.Generic.Internal
import Util.Generic

import FlatParse.Basic qualified as FP
import Control.Applicative ( (<|>) )

import Numeric.Natural

getGeneric :: (Generic a, GGetD (Rep a), Get w) => Cfg w -> Getter a
getGeneric cfg = to <$> ggetD cfg

class GGetD f where
    ggetD :: Get w => Cfg w -> Getter (f a)

instance (GGetC f, Datatype d) => GGetD (D1 d f) where
    ggetD cfg = M1 <$> ggetC cfg (datatypeName' @d)

class GGetC f where
    ggetC :: Get w => Cfg w -> String -> Getter (f a)

-- | Refuse to derive instance for empty data types.
instance TypeError GErrRefuseVoid => GGetC V1 where
    ggetC = undefined

-- | TODO: Non-sum data types.
instance (GGetS f, Constructor c) => GGetC (C1 c f) where
    ggetC cfg dStr = (M1 . snd) <$> ggetS cfg dStr (conName' @c) 0

class GGetS f where
    ggetS :: Get w => Cfg w -> String -> String -> Natural -> Getter (Natural, (f a))

-- | The empty constructor trivially succeeds without parsing anything.
instance GGetS U1 where
    ggetS _ _ _ fIdx = pure (fIdx, U1)

instance (GGetS l, GGetS r) => GGetS (l :*: r) where
    ggetS cfg dStr cStr fIdx = do
        (fIdx',  l) <- ggetS cfg dStr cStr fIdx
        (fIdx'', r) <- ggetS cfg dStr cStr (fIdx'+1)
        pure (fIdx'', l :*: r)

instance (Get a, Selector s) => GGetS (S1 s (Rec0 a)) where
    ggetS _ dStr cStr fIdx = do
        a <- getEWrap $ EGeneric dStr . EGenericField cStr sStr fIdx
        pure (fIdx, M1 (K1 a))
      where
        sStr = selName'' @s

--------------------------------------------------------------------------------

-- | Constructor sums are differentiated by a prefix tag.
instance GGetCSum (l :+: r) => GGetC (l :+: r) where
    ggetC cfg dStr = do
        tag <- getEWrap $ EGeneric dStr . EGenericSum . EGenericSumTag
        case ggetCSum cfg dStr tag of
          Just parser -> parser
          Nothing -> do
            let tagPretty = cSumTagShow cfg $ tag
            FP.err $ EGeneric dStr $ EGenericSum $ EGenericSumTagNoMatch [] tagPretty

-- | TODO: Want to return an @Either [(String, Text)]@ indicating the
-- constructors and their expected tags tested, but needs fiddling (can't use
-- 'Alternative'). Pretty minor, but Aeson does it and it's nice.
class GGetCSum f where
    ggetCSum :: Get w => Cfg w -> String -> w -> Maybe (Getter (f a))

instance (GGetCSum l, GGetCSum r) => GGetCSum (l :+: r) where
    ggetCSum cfg dStr tag = l <|> r
      where
        l = fmap L1 <$> ggetCSum cfg dStr tag
        r = fmap R1 <$> ggetCSum cfg dStr tag

instance (GGetS f, Constructor c) => GGetCSum (C1 c f) where
    ggetCSum cfg dStr tag =
        let cStr = conName' @c
            cTag = (cSumTag cfg) cStr
        in  if   (cSumTagEq cfg) tag cTag
            then Just ((M1 . snd) <$> ggetS cfg dStr cStr 0)
            else Nothing