toml-parser-2.0.2.0: src/Toml/Schema/Generic/FromValue.hs
{-# Language DataKinds, InstanceSigs, ScopedTypeVariables, TypeOperators #-}
{-|
Module : Toml.Schema.Generic.FromValue
Description : GHC.Generics derived table parsing
Copyright : (c) Eric Mertens, 2023
License : ISC
Maintainer : emertens@gmail.com
Generic implementations of matching tables and arrays.
-}
module Toml.Schema.Generic.FromValue (
-- * Record from table
GParseTable(..),
genericParseTable,
genericFromTable,
-- * Product type from array
GFromArray(..),
genericFromArray,
) where
import Control.Monad.Trans.State (StateT(..))
import Data.Coerce (coerce)
import Data.Text qualified as Text
import GHC.Generics
import Toml.Schema.FromValue (FromValue, fromValue, optKey, reqKey, parseTableFromValue, typeError)
import Toml.Schema.Matcher (Matcher, failAt)
import Toml.Schema.ParseTable (ParseTable)
import Toml.Semantics (Value'(List'))
-- | Match a 'Toml.Semantics.Table'' using the field names in a record.
genericParseTable :: (Generic a, GParseTable (Rep a)) => ParseTable l a
genericParseTable = to <$> gParseTable
{-# INLINE genericParseTable #-}
-- | Implementation of 'fromValue' using 'genericParseTable' to derive
-- a match from the record field names of the target type.
genericFromTable :: (Generic a, GParseTable (Rep a)) => Value' l -> Matcher l a
genericFromTable = parseTableFromValue genericParseTable
{-# INLINE genericFromTable #-}
-- | Match a 'Toml.Semantics.Value'' as an array positionally matching field fields
-- of a constructor to the elements of the array.
genericFromArray :: (Generic a, GFromArray (Rep a)) => Value' l -> Matcher l a
genericFromArray (List' a xs) =
do (gen, xs') <- runStateT gFromArray xs
if null xs' then
pure (to gen)
else
failAt a ("array " ++ show (length xs') ++ " elements too long")
genericFromArray v = typeError "array" v
{-# INLINE genericFromArray #-}
-- 'gParseTable' is written in continuation passing style because
-- it allows all the "GHC.Generics" constructors to inline into
-- a single location which allows the optimizer to optimize them
-- complete away.
-- | Supports conversion of TOML tables into record values using
-- field selector names as TOML keys.
class GParseTable f where
-- | Convert a value and apply the continuation to the result.
gParseTable :: ParseTable l (f a)
-- | Ignores type constructor name
instance GParseTable f => GParseTable (D1 c f) where
gParseTable = M1 <$> gParseTable
{-# INLINE gParseTable #-}
-- | Ignores value constructor name - only supports record constructors
instance GParseTable f => GParseTable (C1 ('MetaCons sym fix 'True) f) where
gParseTable = M1 <$> gParseTable
{-# INLINE gParseTable #-}
-- | Matches left then right component
instance (GParseTable f, GParseTable g) => GParseTable (f :*: g) where
gParseTable =
do x <- gParseTable
y <- gParseTable
pure (x :*: y)
{-# INLINE gParseTable #-}
-- | Omits the key from the table on nothing, includes it on just
instance {-# OVERLAPS #-} (Selector s, FromValue a) => GParseTable (S1 s (K1 i (Maybe a))) where
gParseTable =
do x <- optKey (Text.pack (selName (M1 [] :: S1 s [] ())))
pure (M1 (K1 x))
{-# INLINE gParseTable #-}
-- | Uses record selector name as table key
instance (Selector s, FromValue a) => GParseTable (S1 s (K1 i a)) where
gParseTable =
do x <- reqKey (Text.pack (selName (M1 [] :: S1 s [] ())))
pure (M1 (K1 x))
{-# INLINE gParseTable #-}
-- | Emits empty table
instance GParseTable U1 where
gParseTable = pure U1
{-# INLINE gParseTable #-}
-- | Supports conversion of TOML arrays into product-type values.
class GFromArray f where
gFromArray :: StateT [Value' l] (Matcher l) (f a)
instance GFromArray f => GFromArray (M1 i c f) where
gFromArray :: forall a l. StateT [Value' l] (Matcher l) (M1 i c f a)
gFromArray = coerce (gFromArray :: StateT [Value' l] (Matcher l) (f a))
{-# INLINE gFromArray #-}
instance (GFromArray f, GFromArray g) => GFromArray (f :*: g) where
gFromArray =
do x <- gFromArray
y <- gFromArray
pure (x :*: y)
{-# INLINE gFromArray #-}
instance FromValue a => GFromArray (K1 i a) where
gFromArray = StateT \case
[] -> fail "array too short"
x:xs -> (\v -> (K1 v, xs)) <$> fromValue x
{-# INLINE gFromArray #-}
-- | Uses no array elements
instance GFromArray U1 where
gFromArray = pure U1
{-# INLINE gFromArray #-}