{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module Dhall.TH
(
staticDhallExpression
, makeHaskellTypeFromUnion
, makeHaskellTypes
, HaskellType(..)
) where
import Data.Monoid ((<>))
import Data.Text (Text)
import Data.Text.Prettyprint.Doc (Pretty)
import Dhall.Syntax (Expr(..))
import Language.Haskell.TH.Quote (dataToExpQ)
import Language.Haskell.TH.Syntax
( Con(..)
, Dec(..)
, Exp(..)
, Q
, Type(..)
, Bang(..)
, SourceStrictness(..)
, SourceUnpackedness(..)
)
import qualified Data.List as List
import qualified Data.Text as Text
import qualified Data.Text.Prettyprint.Doc.Render.String as Pretty
import qualified Data.Typeable as Typeable
import qualified Dhall
import qualified Dhall.Core as Core
import qualified Dhall.Map
import qualified Dhall.Pretty
import qualified Dhall.Util
import qualified GHC.IO.Encoding
import qualified Numeric.Natural
import qualified System.IO
import qualified Language.Haskell.TH.Syntax as Syntax
staticDhallExpression :: Text -> Q Exp
staticDhallExpression text = do
Syntax.runIO (GHC.IO.Encoding.setLocaleEncoding System.IO.utf8)
expression <- Syntax.runIO (Dhall.inputExpr text)
dataToExpQ (\a -> liftText <$> Typeable.cast a) expression
where
liftText = fmap (AppE (VarE 'Text.pack)) . Syntax.lift . Text.unpack
toNestedHaskellType
:: (Eq a, Pretty a)
=> [HaskellType (Expr s a)]
-> Expr s a
-> Q Type
toNestedHaskellType haskellTypes = loop
where
loop dhallType = case dhallType of
Bool -> do
return (ConT ''Bool)
Double -> do
return (ConT ''Double)
Integer -> do
return (ConT ''Integer)
Natural -> do
return (ConT ''Numeric.Natural.Natural)
Text -> do
return (ConT ''Text)
App List dhallElementType -> do
haskellElementType <- loop dhallElementType
return (AppT (ConT ''[]) haskellElementType)
App Optional dhallElementType -> do
haskellElementType <- loop dhallElementType
return (AppT (ConT ''Maybe) haskellElementType)
_ | Just haskellType <- List.find predicate haskellTypes -> do
let name = Syntax.mkName (Text.unpack (typeName haskellType))
return (ConT name)
| otherwise -> do
let document =
mconcat
[ "Unsupported nested type\n"
, " \n"
, "Explanation: Not all Dhall types can be nested within Haskell datatype \n"
, "declarations. Specifically, only the following simple Dhall types are supported\n"
, "as a nested type inside of a data declaration: \n"
, " \n"
, "• ❰Bool❱ \n"
, "• ❰Double❱ \n"
, "• ❰Integer❱ \n"
, "• ❰Natural❱ \n"
, "• ❰Text❱ \n"
, "• ❰List a❱ (where ❰a❱ is also a simple type) \n"
, "• ❰Optional a❱ (where ❰a❱ is also a simple type) \n"
, "• Another matching datatype declaration \n"
, " \n"
, "The Haskell datatype generation logic encountered the following Dhall type: \n"
, " \n"
, " " <> Dhall.Util.insert dhallType <> "\n"
, " \n"
, "... which did not fit any of the above criteria."
]
let message = Pretty.renderString (Dhall.Pretty.layout document)
fail message
where
predicate haskellType =
Core.judgmentallyEqual (code haskellType) dhallType
toDeclaration
:: (Eq a, Pretty a)
=> [HaskellType (Expr s a)]
-> HaskellType (Expr s a)
-> Q Dec
toDeclaration haskellTypes MultipleConstructors{..} = do
case code of
Union kts -> do
let name = Syntax.mkName (Text.unpack typeName)
constructors <- traverse (toConstructor haskellTypes) (Dhall.Map.toList kts )
return (DataD [] name [] Nothing constructors [])
_ -> do
let document =
mconcat
[ "Dhall.TH.makeHaskellTypes: Not a union type\n"
, " \n"
, "Explanation: This function expects the ❰code❱ field of ❰MultipleConstructors❱ to\n"
, "evaluate to a union type. \n"
, " \n"
, "For example, this is a valid Dhall union type that this function would accept: \n"
, " \n"
, " \n"
, " ┌──────────────────────────────────────────────────────────────────┐ \n"
, " │ Dhall.TH.makeHaskellTypes (MultipleConstructors \"T\" \"< A | B >\") │ \n"
, " └──────────────────────────────────────────────────────────────────┘ \n"
, " \n"
, " \n"
, "... which corresponds to this Haskell type declaration: \n"
, " \n"
, " \n"
, " ┌────────────────┐ \n"
, " │ data T = A | B │ \n"
, " └────────────────┘ \n"
, " \n"
, " \n"
, "... but the following Dhall type is rejected due to being a bare record type: \n"
, " \n"
, " \n"
, " ┌──────────────────────────────────────────────┐ \n"
, " │ Dhall.TH.makeHaskellTypes \"T\" \"{ x : Bool }\" │ Not valid \n"
, " └──────────────────────────────────────────────┘ \n"
, " \n"
, " \n"
, "The Haskell datatype generation logic encountered the following Dhall type: \n"
, " \n"
, " " <> Dhall.Util.insert code <> "\n"
, " \n"
, "... which is not a union type."
]
let message = Pretty.renderString (Dhall.Pretty.layout document)
fail message
toDeclaration haskellTypes SingleConstructor{..} = do
let name = Syntax.mkName (Text.unpack typeName)
constructor <- toConstructor haskellTypes (constructorName, Just code)
return (DataD [] name [] Nothing [constructor] [])
toConstructor
:: (Eq a, Pretty a)
=> [HaskellType (Expr s a)]
-> (Text, Maybe (Expr s a))
-> Q Con
toConstructor haskellTypes (constructorName, maybeAlternativeType) = do
let name = Syntax.mkName (Text.unpack constructorName)
let bang = Bang NoSourceUnpackedness NoSourceStrictness
case maybeAlternativeType of
Just (Record kts) -> do
let process (key, dhallFieldType) = do
haskellFieldType <- toNestedHaskellType haskellTypes dhallFieldType
return (Syntax.mkName (Text.unpack key), bang, haskellFieldType)
varBangTypes <- traverse process (Dhall.Map.toList kts)
return (RecC name varBangTypes)
Just dhallAlternativeType -> do
haskellAlternativeType <- toNestedHaskellType haskellTypes dhallAlternativeType
return (NormalC name [ (bang, haskellAlternativeType) ])
Nothing -> do
return (NormalC name [])
makeHaskellTypeFromUnion
:: Text
-> Text
-> Q [Dec]
makeHaskellTypeFromUnion typeName code =
makeHaskellTypes [ MultipleConstructors{..} ]
data HaskellType code
= MultipleConstructors
{ typeName :: Text
, code :: code
}
| SingleConstructor
{ typeName :: Text
, constructorName :: Text
, code :: code
}
deriving (Functor, Foldable, Traversable)
makeHaskellTypes :: [HaskellType Text] -> Q [Dec]
makeHaskellTypes haskellTypes = do
Syntax.runIO (GHC.IO.Encoding.setLocaleEncoding System.IO.utf8)
haskellTypes' <- traverse (traverse (Syntax.runIO . Dhall.inputExpr)) haskellTypes
traverse (toDeclaration haskellTypes') haskellTypes'