{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}

module Data.Elf.Constants.TH ( mkDeclarations
                             , BaseWord(..)
                             ) where

import Control.Monad
import Language.Haskell.TH
#if MIN_VERSION_template_haskell(2,18,0)
import Language.Haskell.TH.Syntax
#endif

data BaseWord = BaseWord8 | BaseWord16 | BaseWord32 | BaseWord64

newNamePE :: String -> Q (Q Pat, Q Exp)
newNamePE :: String -> Q (Q Pat, Q Exp)
newNamePE String
s = do
    Name
n <- forall (m :: * -> *). Quote m => String -> m Name
newName String
s
    forall (m :: * -> *) a. Monad m => a -> m a
return (forall (m :: * -> *). Quote m => Name -> m Pat
varP Name
n, forall (m :: * -> *). Quote m => Name -> m Exp
varE Name
n)

mkDeclarations :: BaseWord -> String -> String -> [(String, Integer, String)] -> Q [Dec]
mkDeclarations :: BaseWord
-> String -> String -> [(String, Integer, String)] -> Q [Dec]
mkDeclarations BaseWord
baseType String
typeNameString String
patternPrefixString [(String, Integer, String)]
enums = do

    let typeName :: Name
typeName = String -> Name
mkName String
typeNameString
    let patternName :: String -> Name
patternName String
s = String -> Name
mkName (String
patternPrefixString forall a. [a] -> [a] -> [a]
++ String
s)
    let
        baseTypeT :: Q Type
        baseTypeT :: Q Type
baseTypeT =
            case BaseWord
baseType of
                BaseWord
BaseWord8  -> forall (m :: * -> *). Quote m => Name -> m Type
conT forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"Word8"
                BaseWord
BaseWord16 -> forall (m :: * -> *). Quote m => Name -> m Type
conT forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"Word16"
                BaseWord
BaseWord32 -> forall (m :: * -> *). Quote m => Name -> m Type
conT forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"Word32"
                BaseWord
BaseWord64 -> forall (m :: * -> *). Quote m => Name -> m Type
conT forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"Word64"

    let
        newTypeDef :: Q Dec
newTypeDef =
            forall (m :: * -> *).
Quote m =>
m Cxt
-> Name
-> [TyVarBndr ()]
-> Maybe Type
-> m Con
-> [m DerivClause]
-> m Dec
newtypeD
                (forall (m :: * -> *). Quote m => [m Type] -> m Cxt
cxt [])
                Name
typeName
                []
                forall a. Maybe a
Nothing
                (forall (m :: * -> *). Quote m => Name -> [m BangType] -> m Con
normalC Name
typeName [ forall (m :: * -> *). Quote m => m Bang -> m Type -> m BangType
bangType (forall (m :: * -> *).
Quote m =>
m SourceUnpackedness -> m SourceStrictness -> m Bang
bang forall (m :: * -> *). Quote m => m SourceUnpackedness
noSourceUnpackedness forall (m :: * -> *). Quote m => m SourceStrictness
noSourceStrictness) Q Type
baseTypeT ])
                [ forall (m :: * -> *).
Quote m =>
Maybe DerivStrategy -> [m Type] -> m DerivClause
derivClause forall a. Maybe a
Nothing [ forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Eq")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Ord")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Enum")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Num")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Real")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Integral")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Bits")
                                      , forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"FiniteBits")
                                      ]
                ]

    let
        mkShowClause :: (t, Integer, c) -> m Clause
mkShowClause (t
s, Integer
n, c
_) =
            forall (m :: * -> *).
Quote m =>
[m Pat] -> m Body -> [m Dec] -> m Clause
clause
                [ forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP Name
typeName [forall (m :: * -> *). Quote m => Lit -> m Pat
litP forall a b. (a -> b) -> a -> b
$ Integer -> Lit
IntegerL Integer
n] ]
                (forall (m :: * -> *). Quote m => m Exp -> m Body
normalB [| patternPrefixString ++ s |])
                []

    let
        showClauses :: [Q Clause]
        showClauses :: [Q Clause]
showClauses = forall a b. (a -> b) -> [a] -> [b]
map forall {m :: * -> *} {t} {c}.
(Quote m, Lift t) =>
(t, Integer, c) -> m Clause
mkShowClause [(String, Integer, String)]
enums

    (Q Pat
nP, Q Exp
nE) <- String -> Q (Q Pat, Q Exp)
newNamePE String
"n"
    let
        defaultShowClause :: Q Clause
defaultShowClause =
            forall (m :: * -> *).
Quote m =>
[m Pat] -> m Body -> [m Dec] -> m Clause
clause
                [ forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP Name
typeName [Q Pat
nP] ]
                (forall (m :: * -> *). Quote m => m Exp -> m Body
normalB [| typeNameString ++ " " ++ show $(nE) |])
                []

    let showInstanceFunctions :: Q Dec
showInstanceFunctions = forall (m :: * -> *). Quote m => Name -> [m Clause] -> m Dec
funD (String -> Name
mkName String
"show") ([Q Clause]
showClauses forall a. [a] -> [a] -> [a]
++ [ Q Clause
defaultShowClause ])

    let showInstance :: Q Dec
showInstance = forall (m :: * -> *).
Quote m =>
m Cxt -> m Type -> [m Dec] -> m Dec
instanceD (forall (m :: * -> *). Quote m => [m Type] -> m Cxt
cxt []) (forall (m :: * -> *). Quote m => m Type -> m Type -> m Type
appT (forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Show")) (forall (m :: * -> *). Quote m => Name -> m Type
conT Name
typeName)) [ Q Dec
showInstanceFunctions ]

    let
        mkBinaryInstance :: Q Type -> Q Pat -> Q Exp -> Q Exp -> Q Dec
        mkBinaryInstance :: Q Type -> Q Pat -> Q Exp -> Q Exp -> Q Dec
mkBinaryInstance Q Type
typeT Q Pat
putP Q Exp
putE Q Exp
getE =
            forall (m :: * -> *).
Quote m =>
m Cxt -> m Type -> [m Dec] -> m Dec
instanceD
                (forall (m :: * -> *). Quote m => [m Type] -> m Cxt
cxt [])
                (forall (m :: * -> *). Quote m => m Type -> m Type -> m Type
appT (forall (m :: * -> *). Quote m => Name -> m Type
conT (String -> Name
mkName String
"Binary")) Q Type
typeT)
                [ Q Dec
binaryInstanceGet, Q Dec
binaryInstancePut ]
            where
                binaryInstancePut :: Q Dec
binaryInstancePut =
                    forall (m :: * -> *). Quote m => Name -> [m Clause] -> m Dec
funD
                        (String -> Name
mkName String
"put")
                        [ forall (m :: * -> *).
Quote m =>
[m Pat] -> m Body -> [m Dec] -> m Clause
clause
                            [Q Pat
putP]
                            (forall (m :: * -> *). Quote m => m Exp -> m Body
normalB Q Exp
putE)
                            []
                        ]
                binaryInstanceGet :: Q Dec
binaryInstanceGet =
                    forall (m :: * -> *). Quote m => Name -> [m Clause] -> m Dec
funD
                        (String -> Name
mkName String
"get")
                        [ forall (m :: * -> *).
Quote m =>
[m Pat] -> m Body -> [m Dec] -> m Clause
clause
                            []
                            (forall (m :: * -> *). Quote m => m Exp -> m Body
normalB Q Exp
getE)
                            []
                        ]

    let
        binaryInstancesXe :: Q Exp -> Q Exp -> Q Exp -> Q Exp -> [Q Dec]
binaryInstancesXe Q Exp
putLe Q Exp
getLe Q Exp
putBe Q Exp
getBe =
            [ do
                (Q Pat
n3P, Q Exp
n3E) <- String -> Q (Q Pat, Q Exp)
newNamePE String
"n"
                Q Type -> Q Pat -> Q Exp -> Q Exp -> Q Dec
mkBinaryInstance
                    (forall (m :: * -> *). Quote m => m Type -> m Type -> m Type
appT (forall (m :: * -> *). Quote m => Name -> m Type
conT forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"Le") (forall (m :: * -> *). Quote m => Name -> m Type
conT Name
typeName))
                    (forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP (String -> Name
mkName String
"Le") [forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP Name
typeName [Q Pat
n3P]])
                    [| $putLe $n3E |]
                    [| $(conE $ mkName "Le") <$> ($(conE typeName) <$> $getLe) |]
            , do
                (Q Pat
n3P, Q Exp
n3E) <- String -> Q (Q Pat, Q Exp)
newNamePE String
"n"
                Q Type -> Q Pat -> Q Exp -> Q Exp -> Q Dec
mkBinaryInstance
                    (forall (m :: * -> *). Quote m => m Type -> m Type -> m Type
appT (forall (m :: * -> *). Quote m => Name -> m Type
conT forall a b. (a -> b) -> a -> b
$ String -> Name
mkName String
"Be") (forall (m :: * -> *). Quote m => Name -> m Type
conT Name
typeName))
                    (forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP (String -> Name
mkName String
"Be") [forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP Name
typeName [Q Pat
n3P]])
                    [| $putBe $n3E |]
                    [| $(conE $ mkName "Be") <$> ($(conE typeName) <$> $getBe) |]
            ]

    let
        binaryInstances :: [Q Dec]
binaryInstances =
            case BaseWord
baseType of
                BaseWord
BaseWord8 ->
                    [ do
                        (Q Pat
n3P, Q Exp
n3E) <- String -> Q (Q Pat, Q Exp)
newNamePE String
"n"
                        Q Type -> Q Pat -> Q Exp -> Q Exp -> Q Dec
mkBinaryInstance
                            (forall (m :: * -> *). Quote m => Name -> m Type
conT Name
typeName)
                            (forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP Name
typeName [Q Pat
n3P])
                            [| putWord8 $n3E |]
                            [| $(conE typeName) <$> getWord8 |]
                    ]
                BaseWord
BaseWord16 -> Q Exp -> Q Exp -> Q Exp -> Q Exp -> [Q Dec]
binaryInstancesXe [| putWord16le |] [| getWord16le |] [| putWord16be |] [| getWord16be |]
                BaseWord
BaseWord32 -> Q Exp -> Q Exp -> Q Exp -> Q Exp -> [Q Dec]
binaryInstancesXe [| putWord32le |] [| getWord32le |] [| putWord32be |] [| getWord32be |]
                BaseWord
BaseWord64 -> Q Exp -> Q Exp -> Q Exp -> Q Exp -> [Q Dec]
binaryInstancesXe [| putWord64le |] [| getWord64le |] [| putWord64be |] [| getWord64be |]

    let
        mkPatterns :: (String, Integer, c) -> [m Dec]
mkPatterns (String
s, Integer
n, c
_) =
            [ forall (m :: * -> *). Quote m => Name -> m Type -> m Dec
patSynSigD
                (String -> Name
patternName String
s)
                (forall (m :: * -> *). Quote m => Name -> m Type
conT Name
typeName)
            , forall (m :: * -> *).
Quote m =>
Name -> m PatSynArgs -> m PatSynDir -> m Pat -> m Dec
patSynD
                (String -> Name
patternName String
s)
                (forall (m :: * -> *). Quote m => [Name] -> m PatSynArgs
prefixPatSyn [])
                forall (m :: * -> *). Quote m => m PatSynDir
implBidir
                (forall (m :: * -> *). Quote m => Name -> [m Pat] -> m Pat
conP Name
typeName [forall (m :: * -> *). Quote m => Lit -> m Pat
litP forall a b. (a -> b) -> a -> b
$ Integer -> Lit
IntegerL Integer
n])
            ]

    let patterns :: [Q Dec]
patterns = forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (forall a b. (a -> b) -> [a] -> [b]
map forall {m :: * -> *} {c}.
Quote m =>
(String, Integer, c) -> [m Dec]
mkPatterns [(String, Integer, String)]
enums)

#if MIN_VERSION_template_haskell(2,18,0)
    let mkPatternDocs :: (String, b, String) -> Q ()
mkPatternDocs (String
s, b
_, String
doc) = DocLoc -> String -> Q ()
putDoc (Name -> DocLoc
DeclDoc forall a b. (a -> b) -> a -> b
$ String -> Name
patternName String
s) String
doc

    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Q () -> Q ()
addModFinalizer forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {b}. (String, b, String) -> Q ()
mkPatternDocs) [(String, Integer, String)]
enums
#endif

    forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence forall a b. (a -> b) -> a -> b
$ Q Dec
newTypeDef forall a. a -> [a] -> [a]
: Q Dec
showInstance forall a. a -> [a] -> [a]
: [Q Dec]
patterns forall a. [a] -> [a] -> [a]
++ [Q Dec]
binaryInstances