{-# LANGUAGE CPP #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

-- | This module provides the 'ToExp' class, which can be used to convert values
-- into Template Haskell 'Exp' values that represent expressions that evaluate
-- to the original values. This makes it possible to persist/reify values
-- between compile-time and runtime.
module Language.Haskell.TH.ToExp
  ( ToExp(..)
  , toExpIntegral
  ) where

import GHC.Generics
import Language.Haskell.TH.Syntax as TH

import Data.Ratio (Ratio, (%), numerator, denominator)
import Data.Word (Word8)

--------------------------------------------------------------------------------
-- ToExp

-- | Converts arbitrary values to Template Haskell 'Exp' values, which can be
-- used in splices to reconstruct the original value. This is useful to persist
-- a compile-time value to a runtime one.
--
-- >>> toExp (Just 1)
-- AppE (ConE GHC.Base.Just) (LitE (IntegerL 1))
--
-- This class can be automatically derived for types with a 'Generic' instance.
class ToExp a where
  toExp :: a -> Exp

  default toExp :: (Generic a, GToExp (Rep a)) => a -> Exp
  toExp = gToExp . from

instance ToExp Integer where
  toExp n = SigE (LitE $ IntegerL n) (ConT ''Integer)

-- | An implementation of 'toExp' that can be used for types with an 'Integral'
-- instance.
--
-- >>> toExpIntegral (ConT ''Int) 3
-- SigE (LitE (IntegerL 3)) (ConT GHC.Types.Int)
toExpIntegral
  :: Integral a
  => Type -- ^ the Template Haskell 'Type' representing the type of @a@
  -> a    -- ^ the value to be converted to an 'Exp'
  -> Exp
toExpIntegral ty n = SigE (LitE $ IntegerL (toInteger n)) ty

instance ToExp Int where toExp = toExpIntegral (ConT ''Int)
instance ToExp Word8 where toExp = toExpIntegral (ConT ''Word8)

instance ToExp a => ToExp (Ratio a) where
  toExp r = AppE (AppE (VarE '(%)) (toExp $ numerator r)) (toExp $ denominator r)

instance ToExp Char where
  toExp = LitE . CharL

-- | produces expressions that use 'ListE' instead of 'AppE' and ':' to
-- make them prettier
instance ToExp a => ToExp [a] where
  toExp = ListE . map toExp

instance ToExp ()
instance ToExp Bool
instance ToExp Ordering
instance ToExp a => ToExp (Maybe a)
instance (ToExp a, ToExp b) => ToExp (Either a b)

instance (ToExp a, ToExp b) => ToExp (a, b)
instance (ToExp a, ToExp b, ToExp c) => ToExp (a, b, c)
instance (ToExp a, ToExp b, ToExp c, ToExp d) => ToExp (a, b, c, d)
instance (ToExp a, ToExp b, ToExp c, ToExp d, ToExp e) => ToExp (a, b, c, d, e)
instance (ToExp a, ToExp b, ToExp c, ToExp d, ToExp e, ToExp f) => ToExp (a, b, c, d, e, f)
instance (ToExp a, ToExp b, ToExp c, ToExp d, ToExp e, ToExp f, ToExp g) => ToExp (a, b, c, d, e, f, g)

-- instances for Template Haskell types
instance ToExp TH.AnnLookup
instance ToExp TH.AnnTarget
instance ToExp TH.Bang
instance ToExp TH.Body
instance ToExp TH.Callconv
instance ToExp TH.Clause
instance ToExp TH.Con
instance ToExp TH.Dec
instance ToExp TH.DecidedStrictness
instance ToExp TH.Exp
instance ToExp TH.Extension
instance ToExp TH.FamilyResultSig
instance ToExp TH.Fixity
instance ToExp TH.FixityDirection
instance ToExp TH.Foreign
instance ToExp TH.FunDep
instance ToExp TH.Guard
instance ToExp TH.Info
instance ToExp TH.InjectivityAnn
instance ToExp TH.Inline
instance ToExp TH.Lit
instance ToExp TH.Match
instance ToExp TH.ModName
instance ToExp TH.Module
instance ToExp TH.ModuleInfo
instance ToExp TH.Name
instance ToExp TH.NameFlavour
instance ToExp TH.NameSpace
instance ToExp TH.OccName
instance ToExp TH.Overlap
instance ToExp TH.Pat
instance ToExp TH.Phases
instance ToExp TH.PkgName
instance ToExp TH.Pragma
instance ToExp TH.Range
instance ToExp TH.Role
instance ToExp TH.RuleBndr
instance ToExp TH.RuleMatch
instance ToExp TH.Safety
instance ToExp TH.SourceStrictness
instance ToExp TH.SourceUnpackedness
instance ToExp TH.Stmt
instance ToExp TH.TyLit
instance ToExp TH.Type
instance ToExp TH.TypeFamilyHead
instance ToExp TH.TySynEqn
instance ToExp TH.TyVarBndr
#if MIN_VERSION_template_haskell(2,12,0)
instance ToExp TH.DerivClause
instance ToExp TH.DerivStrategy
instance ToExp TH.PatSynArgs
instance ToExp TH.PatSynDir
#endif

--------------------------------------------------------------------------------
-- GToExp

-- | Top-level class for converting generic representations to expressions, used
-- as the default implementation for 'toExp'. There is only one instance, over
-- 'D1', but that’s because gToExp' handles most of the details.
--
-- Since there’s only one instance, making this a class is not strictly
-- necessary, but keeping it a class makes the default signature for 'toExp'
-- simpler, which is useful for the documentation.
class GToExp f where
  gToExp :: f a -> Exp

-- | The top-level instance for converting generic representations to
-- expressions. This instance extracts the module name and package name from
-- a datatype and threads it along so that Template Haskell 'Name's can be
-- reconstructed from the constructor names.
instance (Datatype d, GToExp' (D1 d cs)) => GToExp (D1 d cs) where
  gToExp x = gToExp' x (Module package name)
    where name = ModName $ moduleName x
          package = PkgName $ packageName x

-- | The class that handles type constructors. The 'C1' instance performs the
-- bulk of the work, combining constructor information and the 'Module'
-- information produced by 'GToExp'.
class GToExp' f where
  gToExp' :: f a -> Module -> Exp

instance GToExp' f => GToExp' (D1 t f) where
  gToExp' (M1 x) = gToExp' x

instance (GToExp' f, GToExp' g) => GToExp' (f :+: g) where
  gToExp' (L1 x) = gToExp' x
  gToExp' (R1 x) = gToExp' x

-- | The instance that actually does most of the conversion to an 'Exp'. It
-- first constructs a 'Name' from the generic information provided, combined
-- with the 'Module' produced from the datatype information in the 'GToExp'
-- instance for 'D1'.
--
-- The 'Name' is subsequently applied to the necessary arguments by deferring to
-- the final class, 'GProductToExps', which just applies 'toExp' to each member
-- of the product type and returns a list of 'Exp' values.
instance (Constructor c, GProductToExps f) => GToExp' (C1 c f) where
  gToExp' x (Module pkgName modName) = foldl AppE (ConE name) (gProductToExps x)
    where nameStr = conName x
          nameFlavour = NameG DataName pkgName modName
          name = Name (OccName nameStr) nameFlavour

-- | As explained by the documentation for 'GToExp'', this class produces
-- expressions for each argument of a constructor by traversing the product type
-- information available.
class GProductToExps f where
  gProductToExps :: f a -> [Exp]

-- | Nullary constructors are easy — they have no args, so we just produce an
-- empty list.
instance GProductToExps U1 where
  gProductToExps _ = []

-- | For actual values, defer to 'toExp' to do the whole process over again, or
-- use a manually-defined instance for things like literals.
instance ToExp c => GProductToExps (K1 i c) where
  gProductToExps (K1 x) = [toExp x]

instance GProductToExps f => GProductToExps (M1 i t f) where
  gProductToExps (M1 x) = gProductToExps x

-- | For a tree of product type elements, recur over each side of the tree and
-- append the results.
instance (GProductToExps a, GProductToExps b) => GProductToExps (a :*: b) where
  gProductToExps (x :*: y) = gProductToExps x ++ gProductToExps y