{-# LANGUAGE CPP #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
module PlutusTx.Blueprint.TH where
import Prelude
import Data.Data (Data)
import Data.List (nub)
import Data.List.NonEmpty qualified as NE
import Data.Set (Set)
import Data.Text qualified as Text
import GHC.Natural (naturalToInteger)
import Language.Haskell.TH qualified as TH
import Language.Haskell.TH.Datatype qualified as TH
import Numeric.Natural (Natural)
import PlutusPrelude (for, join, (<&>), (<<$>>))
import PlutusTx.Blueprint.Argument (ArgumentBlueprint (..))
import PlutusTx.Blueprint.Class (HasBlueprintSchema (..))
import PlutusTx.Blueprint.Definition.Internal (HasSchemaDefinition)
import PlutusTx.Blueprint.Definition.Unroll (HasBlueprintDefinition)
import PlutusTx.Blueprint.Parameter (ParameterBlueprint (..))
import PlutusTx.Blueprint.Purpose (Purpose)
import PlutusTx.Blueprint.Schema (ConstructorSchema (..), Schema (..))
import PlutusTx.Blueprint.Schema.Annotation (SchemaAnn (..), SchemaComment, SchemaDescription,
SchemaInfo (..), SchemaTitle, annotationsToSchemaInfo,
schemaDescriptionToString, schemaTitleToString)
import PlutusTx.IsData.TH (makeIsDataIndexed)
makeIsDataSchemaIndexed :: TH.Name -> [(TH.Name, Natural)] -> TH.Q [TH.InstanceDec]
makeIsDataSchemaIndexed :: Name -> [(Name, Natural)] -> Q [InstanceDec]
makeIsDataSchemaIndexed Name
dataTypeName [(Name, Natural)]
indices = do
[InstanceDec]
dataInstances <- Name -> [(Name, Int)] -> Q [InstanceDec]
makeIsDataIndexed Name
dataTypeName ((Natural -> Int) -> (Name, Natural) -> (Name, Int)
forall a b. (a -> b) -> (Name, a) -> (Name, b)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Natural -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral ((Name, Natural) -> (Name, Int))
-> [(Name, Natural)] -> [(Name, Int)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [(Name, Natural)]
indices)
[InstanceDec]
hasSchemaInstance <- Name -> [(Name, Natural)] -> Q [InstanceDec]
makeHasSchemaInstance Name
dataTypeName [(Name, Natural)]
indices
[InstanceDec] -> Q [InstanceDec]
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([InstanceDec] -> Q [InstanceDec])
-> [InstanceDec] -> Q [InstanceDec]
forall a b. (a -> b) -> a -> b
$ [InstanceDec]
hasSchemaInstance [InstanceDec] -> [InstanceDec] -> [InstanceDec]
forall a. [a] -> [a] -> [a]
++ [InstanceDec]
dataInstances
unstableMakeIsDataSchema :: TH.Name -> TH.Q [TH.Dec]
unstableMakeIsDataSchema :: Name -> Q [InstanceDec]
unstableMakeIsDataSchema Name
name = do
DatatypeInfo
info <- Name -> Q DatatypeInfo
TH.reifyDatatype Name
name
let defaultIndex :: [(Name, Natural)]
defaultIndex = [Name] -> [Natural] -> [(Name, Natural)]
forall a b. [a] -> [b] -> [(a, b)]
zip (ConstructorInfo -> Name
TH.constructorName (ConstructorInfo -> Name) -> [ConstructorInfo] -> [Name]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DatatypeInfo -> [ConstructorInfo]
TH.datatypeCons DatatypeInfo
info) [Natural
0..]
Name -> [(Name, Natural)] -> Q [InstanceDec]
makeIsDataSchemaIndexed Name
name [(Name, Natural)]
defaultIndex
makeHasSchemaInstance :: TH.Name -> [(TH.Name, Natural)] -> TH.Q [TH.InstanceDec]
makeHasSchemaInstance :: Name -> [(Name, Natural)] -> Q [InstanceDec]
makeHasSchemaInstance Name
dataTypeName [(Name, Natural)]
indices = do
DatatypeInfo
dataTypeInfo <- Name -> Q DatatypeInfo
TH.reifyDatatype Name
dataTypeName
let appliedType :: Type
appliedType = DatatypeInfo -> Type
TH.datatypeType DatatypeInfo
dataTypeInfo
let nonOverlapInstance :: Cxt -> Type -> [InstanceDec] -> InstanceDec
nonOverlapInstance = Maybe Overlap -> Cxt -> Type -> [InstanceDec] -> InstanceDec
TH.InstanceD Maybe Overlap
forall a. Maybe a
Nothing
[(ConstructorInfo, SchemaInfo, Natural)]
indexedCons :: [(TH.ConstructorInfo, SchemaInfo, Natural)] <- do
[ConstructorInfo]
-> (ConstructorInfo -> Q (ConstructorInfo, SchemaInfo, Natural))
-> Q [(ConstructorInfo, SchemaInfo, Natural)]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
t a -> (a -> f b) -> f (t b)
for (DatatypeInfo -> [ConstructorInfo]
TH.datatypeCons DatatypeInfo
dataTypeInfo) ((ConstructorInfo -> Q (ConstructorInfo, SchemaInfo, Natural))
-> Q [(ConstructorInfo, SchemaInfo, Natural)])
-> (ConstructorInfo -> Q (ConstructorInfo, SchemaInfo, Natural))
-> Q [(ConstructorInfo, SchemaInfo, Natural)]
forall a b. (a -> b) -> a -> b
$ \ConstructorInfo
ctorInfo -> do
let ctorName :: Name
ctorName = ConstructorInfo -> Name
TH.constructorName ConstructorInfo
ctorInfo
case Name -> [(Name, Natural)] -> Maybe Natural
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup Name
ctorName [(Name, Natural)]
indices of
Maybe Natural
Nothing -> String -> Q (ConstructorInfo, SchemaInfo, Natural)
forall a. String -> Q a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Q (ConstructorInfo, SchemaInfo, Natural))
-> String -> Q (ConstructorInfo, SchemaInfo, Natural)
forall a b. (a -> b) -> a -> b
$ String
"No index given for constructor " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Name -> String
forall a. Show a => a -> String
show (ConstructorInfo -> Name
TH.constructorName ConstructorInfo
ctorInfo)
Just Natural
index -> do
[SchemaAnn]
ctorSchemaAnns <- Name -> Q [SchemaAnn]
lookupSchemaAnns Name
ctorName
SchemaInfo
schemaInfo <- [SchemaAnn] -> Q SchemaInfo
schemaInfoFromAnns [SchemaAnn]
ctorSchemaAnns
(ConstructorInfo, SchemaInfo, Natural)
-> Q (ConstructorInfo, SchemaInfo, Natural)
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ConstructorInfo
ctorInfo, SchemaInfo
schemaInfo, Natural
index)
let referencedTypes :: Type
referencedTypes = Name -> Type
TH.VarT (String -> Name
TH.mkName String
"referencedTypes")
let constraints :: Cxt
constraints =
Cxt -> Cxt
forall a. Eq a => [a] -> [a]
nub (Cxt -> Cxt) -> ([Cxt] -> Cxt) -> [Cxt] -> Cxt
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Cxt] -> Cxt
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join ([Cxt] -> Cxt) -> [Cxt] -> Cxt
forall a b. (a -> b) -> a -> b
$
[ ( case Type
fieldType of
TH.VarT {} -> (Name -> Cxt -> Type
TH.classPred ''HasBlueprintDefinition [Type
fieldType] Type -> Cxt -> Cxt
forall a. a -> [a] -> [a]
:)
Type
_ -> Cxt -> Cxt
forall a. a -> a
id
) [ Name -> Cxt -> Type
TH.classPred ''HasSchemaDefinition [Type
fieldType, Type
referencedTypes] ]
| (TH.ConstructorInfo{Cxt
constructorFields :: Cxt
constructorFields :: ConstructorInfo -> Cxt
constructorFields}, SchemaInfo
_info, Natural
_index) <- [(ConstructorInfo, SchemaInfo, Natural)]
indexedCons
, Type
fieldType <- Cxt
constructorFields
]
InstanceDec
schemaPrag <- Name -> [Q Clause] -> Q InstanceDec
forall (m :: * -> *).
Quote m =>
Name -> [m Clause] -> m InstanceDec
TH.funD 'schema [Type -> [(ConstructorInfo, SchemaInfo, Natural)] -> Q Clause
mkSchemaClause Type
referencedTypes [(ConstructorInfo, SchemaInfo, Natural)]
indexedCons]
InstanceDec
schemaDecl <- Name -> Inline -> RuleMatch -> Phases -> Q InstanceDec
forall (m :: * -> *).
Quote m =>
Name -> Inline -> RuleMatch -> Phases -> m InstanceDec
TH.pragInlD 'schema Inline
TH.Inlinable RuleMatch
TH.FunLike Phases
TH.AllPhases
[InstanceDec] -> Q [InstanceDec]
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
[ Cxt -> Type -> [InstanceDec] -> InstanceDec
nonOverlapInstance
Cxt
constraints
(Name -> Cxt -> Type
TH.classPred ''HasBlueprintSchema [Type
appliedType, Type
referencedTypes])
[InstanceDec
schemaPrag, InstanceDec
schemaDecl]
]
where
lookupSchemaAnns :: TH.Name -> TH.Q [SchemaAnn]
lookupSchemaAnns :: Name -> Q [SchemaAnn]
lookupSchemaAnns Name
name = do
[SchemaAnn]
title <- SchemaTitle -> SchemaAnn
MkSchemaAnnTitle (SchemaTitle -> SchemaAnn) -> Q [SchemaTitle] -> Q [SchemaAnn]
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> forall a. Data a => Name -> Q [a]
lookupAnn @SchemaTitle Name
name
[SchemaAnn]
description <- SchemaDescription -> SchemaAnn
MkSchemaAnnDescription (SchemaDescription -> SchemaAnn)
-> Q [SchemaDescription] -> Q [SchemaAnn]
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> forall a. Data a => Name -> Q [a]
lookupAnn @SchemaDescription Name
name
[SchemaAnn]
comment <- SchemaComment -> SchemaAnn
MkSchemaAnnComment (SchemaComment -> SchemaAnn) -> Q [SchemaComment] -> Q [SchemaAnn]
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> forall a. Data a => Name -> Q [a]
lookupAnn @SchemaComment Name
name
[SchemaAnn] -> Q [SchemaAnn]
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([SchemaAnn] -> Q [SchemaAnn]) -> [SchemaAnn] -> Q [SchemaAnn]
forall a b. (a -> b) -> a -> b
$ [SchemaAnn]
title [SchemaAnn] -> [SchemaAnn] -> [SchemaAnn]
forall a. [a] -> [a] -> [a]
++ [SchemaAnn]
description [SchemaAnn] -> [SchemaAnn] -> [SchemaAnn]
forall a. [a] -> [a] -> [a]
++ [SchemaAnn]
comment
schemaInfoFromAnns :: [SchemaAnn] -> TH.Q SchemaInfo
schemaInfoFromAnns :: [SchemaAnn] -> Q SchemaInfo
schemaInfoFromAnns = (String -> Q SchemaInfo)
-> (SchemaInfo -> Q SchemaInfo)
-> Either String SchemaInfo
-> Q SchemaInfo
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> Q SchemaInfo
forall a. String -> Q a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail SchemaInfo -> Q SchemaInfo
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either String SchemaInfo -> Q SchemaInfo)
-> ([SchemaAnn] -> Either String SchemaInfo)
-> [SchemaAnn]
-> Q SchemaInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [SchemaAnn] -> Either String SchemaInfo
annotationsToSchemaInfo
mkSchemaClause ::
TH.Type ->
[(TH.ConstructorInfo, SchemaInfo, Natural)] ->
TH.ClauseQ
mkSchemaClause :: Type -> [(ConstructorInfo, SchemaInfo, Natural)] -> Q Clause
mkSchemaClause Type
ts [(ConstructorInfo, SchemaInfo, Natural)]
ctorIndexes =
case [(ConstructorInfo, SchemaInfo, Natural)]
ctorIndexes of
[] -> String -> Q Clause
forall a. String -> Q a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"At least one constructor index must be specified."
[(ConstructorInfo, SchemaInfo, Natural)
ctorIndex] -> ExpQ -> Q Clause
mkBody ((ConstructorInfo, SchemaInfo, Natural) -> ExpQ
mkSchemaConstructor (ConstructorInfo, SchemaInfo, Natural)
ctorIndex)
[(ConstructorInfo, SchemaInfo, Natural)]
_ -> ExpQ -> Q Clause
mkBody [|SchemaOneOf (NE.fromList $([ExpQ] -> ExpQ
forall (m :: * -> *). Quote m => [m Exp] -> m Exp
TH.listE (((ConstructorInfo, SchemaInfo, Natural) -> ExpQ)
-> [(ConstructorInfo, SchemaInfo, Natural)] -> [ExpQ]
forall a b. (a -> b) -> [a] -> [b]
map (ConstructorInfo, SchemaInfo, Natural) -> ExpQ
mkSchemaConstructor [(ConstructorInfo, SchemaInfo, Natural)]
ctorIndexes)))|]
where
mkBody :: TH.ExpQ -> TH.ClauseQ
mkBody :: ExpQ -> Q Clause
mkBody ExpQ
body = do
let patterns :: [a]
patterns = []
let whereDecls :: [a]
whereDecls = []
[Q Pat] -> Q Body -> [Q InstanceDec] -> Q Clause
forall (m :: * -> *).
Quote m =>
[m Pat] -> m Body -> [m InstanceDec] -> m Clause
TH.clause [Q Pat]
forall a. [a]
patterns (ExpQ -> Q Body
forall (m :: * -> *). Quote m => m Exp -> m Body
TH.normalB ExpQ
body) [Q InstanceDec]
forall a. [a]
whereDecls
mkSchemaConstructor :: (TH.ConstructorInfo, SchemaInfo, Natural) -> TH.ExpQ
mkSchemaConstructor :: (ConstructorInfo, SchemaInfo, Natural) -> ExpQ
mkSchemaConstructor (TH.ConstructorInfo{Cxt
[TyVarBndrUnit]
[FieldStrictness]
Name
ConstructorVariant
constructorName :: ConstructorInfo -> Name
constructorFields :: ConstructorInfo -> Cxt
constructorName :: Name
constructorVars :: [TyVarBndrUnit]
constructorContext :: Cxt
constructorFields :: Cxt
constructorStrictness :: [FieldStrictness]
constructorVariant :: ConstructorVariant
constructorVars :: ConstructorInfo -> [TyVarBndrUnit]
constructorContext :: ConstructorInfo -> Cxt
constructorStrictness :: ConstructorInfo -> [FieldStrictness]
constructorVariant :: ConstructorInfo -> ConstructorVariant
..}, SchemaInfo
info, Natural -> Integer
naturalToInteger -> Integer
ctorIndex) = do
[Exp]
fields <- Cxt -> (Type -> ExpQ) -> Q [Exp]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
t a -> (a -> f b) -> f (t b)
for Cxt
constructorFields ((Type -> ExpQ) -> Q [Exp]) -> (Type -> ExpQ) -> Q [Exp]
forall a b. (a -> b) -> a -> b
$ \Type
t -> [|definitionRef @($(Type -> Q Type
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Type
t)) @($(Type -> Q Type
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Type
ts))|]
[|SchemaConstructor info (MkConstructorSchema ctorIndex $(Exp -> ExpQ
forall a. a -> Q a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([Exp] -> Exp
TH.ListE [Exp]
fields)))|]
deriveParameterBlueprint :: TH.Name -> Set Purpose -> TH.ExpQ
deriveParameterBlueprint :: Name -> Set Purpose -> ExpQ
deriveParameterBlueprint Name
tyName Set Purpose
purpose = do
Maybe Text
title <- String -> Text
Text.pack (String -> Text) -> (SchemaTitle -> String) -> SchemaTitle -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SchemaTitle -> String
schemaTitleToString (SchemaTitle -> Text) -> Q (Maybe SchemaTitle) -> Q (Maybe Text)
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> Name -> Q (Maybe SchemaTitle)
lookupSchemaTitle Name
tyName
Maybe Text
description <- String -> Text
Text.pack (String -> Text)
-> (SchemaDescription -> String) -> SchemaDescription -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SchemaDescription -> String
schemaDescriptionToString (SchemaDescription -> Text)
-> Q (Maybe SchemaDescription) -> Q (Maybe Text)
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> Name -> Q (Maybe SchemaDescription)
lookupSchemaDescription Name
tyName
[| MkParameterBlueprint
{ parameterTitle = title
, parameterDescription = description
, parameterPurpose = purpose
, parameterSchema = definitionRef @($(Name -> Q Type
forall (m :: * -> *). Quote m => Name -> m Type
TH.conT Name
tyName))
}
|]
deriveArgumentBlueprint :: TH.Name -> Set Purpose -> TH.ExpQ
deriveArgumentBlueprint :: Name -> Set Purpose -> ExpQ
deriveArgumentBlueprint Name
tyName Set Purpose
purpose = do
Maybe Text
title <- String -> Text
Text.pack (String -> Text) -> (SchemaTitle -> String) -> SchemaTitle -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SchemaTitle -> String
schemaTitleToString (SchemaTitle -> Text) -> Q (Maybe SchemaTitle) -> Q (Maybe Text)
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> Name -> Q (Maybe SchemaTitle)
lookupSchemaTitle Name
tyName
Maybe Text
description <- String -> Text
Text.pack (String -> Text)
-> (SchemaDescription -> String) -> SchemaDescription -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SchemaDescription -> String
schemaDescriptionToString (SchemaDescription -> Text)
-> Q (Maybe SchemaDescription) -> Q (Maybe Text)
forall (f1 :: * -> *) (f2 :: * -> *) a b.
(Functor f1, Functor f2) =>
(a -> b) -> f1 (f2 a) -> f1 (f2 b)
<<$>> Name -> Q (Maybe SchemaDescription)
lookupSchemaDescription Name
tyName
[| MkArgumentBlueprint
{ argumentTitle = title
, argumentDescription = description
, argumentPurpose = purpose
, argumentSchema = definitionRef @($(Name -> Q Type
forall (m :: * -> *). Quote m => Name -> m Type
TH.conT Name
tyName))
}
|]
lookupAnn :: (Data a) => TH.Name -> TH.Q [a]
lookupAnn :: forall a. Data a => Name -> Q [a]
lookupAnn = AnnLookup -> Q [a]
forall a. Data a => AnnLookup -> Q [a]
TH.reifyAnnotations (AnnLookup -> Q [a]) -> (Name -> AnnLookup) -> Name -> Q [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Name -> AnnLookup
TH.AnnLookupName
lookupSchemaTitle :: TH.Name -> TH.Q (Maybe SchemaTitle)
lookupSchemaTitle :: Name -> Q (Maybe SchemaTitle)
lookupSchemaTitle Name
tyName = forall a. Data a => Name -> Q [a]
lookupAnn @SchemaTitle Name
tyName Q [SchemaTitle]
-> ([SchemaTitle] -> Maybe SchemaTitle) -> Q (Maybe SchemaTitle)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
[SchemaTitle
x] -> SchemaTitle -> Maybe SchemaTitle
forall a. a -> Maybe a
Just SchemaTitle
x
[] -> Maybe SchemaTitle
forall a. Maybe a
Nothing
[SchemaTitle]
_ -> String -> Maybe SchemaTitle
forall a. String -> Maybe a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Maybe SchemaTitle) -> String -> Maybe SchemaTitle
forall a b. (a -> b) -> a -> b
$ String
"Multiple SchemTitle annotations found for " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Name -> String
forall a. Show a => a -> String
show Name
tyName
lookupSchemaDescription :: TH.Name -> TH.Q (Maybe SchemaDescription)
lookupSchemaDescription :: Name -> Q (Maybe SchemaDescription)
lookupSchemaDescription Name
tyName = forall a. Data a => Name -> Q [a]
lookupAnn @SchemaDescription Name
tyName Q [SchemaDescription]
-> ([SchemaDescription] -> Maybe SchemaDescription)
-> Q (Maybe SchemaDescription)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
[SchemaDescription
x] -> SchemaDescription -> Maybe SchemaDescription
forall a. a -> Maybe a
Just SchemaDescription
x
[] -> Maybe SchemaDescription
forall a. Maybe a
Nothing
[SchemaDescription]
_ -> String -> Maybe SchemaDescription
forall a. String -> Maybe a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Maybe SchemaDescription)
-> String -> Maybe SchemaDescription
forall a b. (a -> b) -> a -> b
$ String
"Multiple SchemaDescription annotations found for " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Name -> String
forall a. Show a => a -> String
show Name
tyName