packages feed

g2-0.2.0.0: src/G2/QuasiQuotes/Internals/G2Rep.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}

module G2.QuasiQuotes.Internals.G2Rep ( G2Rep (..)
                                      , derivingG2Rep
                                      , derivingG2RepTuples
                                      , derivingG2RepTuple ) where

import G2.Language.Expr
import G2.Language.Support
import G2.Language.Syntax as G2
import G2.QuasiQuotes.Support
import G2.Language.Typing

import Control.Monad

import qualified Data.HashMap.Lazy as HM
import qualified Data.Text as T

import GHC.Exts

import Language.Haskell.TH as TH
import Language.Haskell.TH.Syntax as TH

class G2Rep g where
    g2Rep :: TypeEnv -> CleanedNames -> g -> Expr
    g2UnRep :: TypeEnv -> Expr -> g
    g2Type :: TypeEnv -> CleanedNames -> g -> G2.Type

-- Modeled after https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial
derivingG2Rep :: TH.Name -> Q [Dec]
derivingG2Rep n = do
    stv <- isExtEnabled ScopedTypeVariables
    case stv of
        True -> return ()
        False -> error "derivingG2Rep: derivingG2Rep requires the language ScopedTypeVariables to be enabled"
    derivingG2Rep' n

derivingG2Rep' :: TH.Name -> Q [Dec]
derivingG2Rep' ty = do
    TyConI tycon <- reify ty

    let (tyConName, tvs, cs) = case tycon of
            DataD _ nm tyvs _ css _ -> (nm, tyvs, css)
            NewtypeD _ nm tyvs _ c _ -> (nm, tyvs, [c])
            _ -> error "derivingG2Rep: Unsupported type"

    let instanceType = forallT [] (cxt $ map mkCxt tvs)
                        $ conT ''G2Rep `appT` foldl apply (conT tyConName) tvs

    sequence [instanceD (return []) instanceType [ genG2Rep tyConName tvs cs
                                                 , genG2UnRep (length tvs) cs
                                                 , genG2Type tyConName]]
    where
#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
        apply t (PlainTV name _)    = appT t (varT name)
        apply t (KindedTV name _ _) = appT t (varT name)

        mkCxt (PlainTV name _) = conT ''G2Rep `appT` varT name
        mkCxt (KindedTV name _ _) = conT ''G2Rep `appT` varT name
#else
        apply t (PlainTV name)    = appT t (varT name)
        apply t (KindedTV name _) = appT t (varT name)

        mkCxt (PlainTV name) = conT ''G2Rep `appT` varT name
        mkCxt (KindedTV name _) = conT ''G2Rep `appT` varT name
#endif

#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
genG2Rep :: TH.Name -> [TyVarBndr ()] -> [Con] -> Q Dec
#else
genG2Rep :: TH.Name -> [TyVarBndr] -> [Con] -> Q Dec
#endif
genG2Rep tyConName tvs cs = funD 'g2Rep (map (genG2RepClause tyConName tvs) cs)

#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
genG2RepClause :: TH.Name -> [TyVarBndr ()] -> Con -> Q Clause
#else
genG2RepClause :: TH.Name -> [TyVarBndr] -> Con -> Q Clause
#endif
genG2RepClause tyConName tvs (NormalC name fieldTypes) =
    genG2RepClause' tyConName tvs name fieldTypes
genG2RepClause tyConName tvs (InfixC st1 n st2) =
    genG2RepClause' tyConName tvs n [st1, st2]
genG2RepClause _ _ con = error $ "genG2RepClause: Unhandled case." ++ show con 

#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
genG2RepClause' :: TH.Name -> [TyVarBndr ()] -> TH.Name -> [StrictType] -> Q Clause
#else
genG2RepClause' :: TH.Name -> [TyVarBndr] -> TH.Name -> [StrictType] -> Q Clause
#endif
genG2RepClause' tyConName tvs dcNme fieldTypes = do
    tenv <- newName "tenv_rep"
    cleaned <- newName "cleaned"
    fieldNames <- replicateM (length fieldTypes) (newName "x")

    let pats = varP tenv:varP cleaned:[conP dcNme (map varP fieldNames)]
        qqTyConName = thNameToQQName tyConName
        qqName = thNameToQQName dcNme

    let g2R = conE 'Data 
                `appE` (varE 'qqDataConLookupFallBack
                    `appE` litE (integerL $ toInteger $ length tvs)
                    `appE` litE (integerL $ toInteger $ length fieldTypes)
                    `appE` qqNameToQExp qqTyConName
                    `appE` qqNameToQExp qqName
                    `appE` (varE 'qqMap `appE` varE cleaned `appE` [|HM.keys $(varE tenv)|])
                    `appE` (varE 'qqMap `appE` varE cleaned `appE` [|map dcName . concatMap dataCon . HM.elems $ $(varE tenv)|])
                    `appE` varE tenv)

        tys = map (\tyv -> conE 'Type
                            `appE` (varE 'g2Type
                                        `appE` varE tenv
                                        `appE` varE cleaned
                                        `appE` (sigE (varE 'undefined) (tyVBToType tyv)))
                  ) tvs

        body = normalB $ appE (varE 'mkApp) $ listE
                    (g2R:tys ++ map (newField tenv cleaned) (zip fieldNames fieldTypes))

    clause pats body []
    where
#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
        tyVBToType (PlainTV name _) = varT name
        tyVBToType (KindedTV name _ _) = varT name
#else
        tyVBToType (PlainTV name) = varT name
        tyVBToType (KindedTV name _) = varT name
#endif

-- | Looks up a `DataCon` with the given type and data constructor name.
-- Falls back to creating a data constructor from scratch, if the data constructor
-- is not in the given TypeEnv.
-- We do this because the user of a QuasiQuoter may pass in types that are not
-- available when the QuasiQuoter is compiled 
qqDataConLookupFallBack :: Int -- The number of TyVars
                        -> Int -- The number of arguments
                        -> QQName -> QQName -> QQMap -> QQMap -> TypeEnv -> DataCon
qqDataConLookupFallBack tyv_n arg_n qqtn qqdc type_nm_qqm dc_nm_qqm tenv
    | Just dc <- qqDataConLookup qqtn qqdc type_nm_qqm dc_nm_qqm tenv = dc
    | otherwise =
        let
            n = G2.Name "unknown" Nothing 0 Nothing
            i = Id n TYPE

            t = mkTyFun $ replicate (arg_n + 1) (TyCon n TYPE)
            t' = foldr TyForAll t (replicate tyv_n i)
        in
        DataCon (qqNameToName0 qqdc) t'

newField :: TH.Name -> TH.Name -> (TH.Name, StrictType) -> Q Exp
newField _ _ (x, (_, ConT n))
    | nameBase n == "Int#" = [|Lit . LitInt . toInteger $ $(conE 'I# `appE` varE x)|]
newField _ _ (x, (_, ConT n))
    | nameBase n == "Float#" = [|Lit . LitFloat . toRational $ $(conE 'F# `appE` varE x)|]
newField _ _ (x, (_, ConT n))
    | nameBase n == "Double#" = [|Lit . LitDouble . toRational $ $(conE 'D# `appE` varE x)|]
newField _ _ (x, (_, ConT n))
    | nameBase n == "Char#" = [|Lit . LitChar $ $(conE 'C# `appE` varE x)|]
newField tenv cleaned (x, _) = do
    return $ VarE 'g2Rep `AppE` VarE tenv `AppE` VarE cleaned `AppE` VarE x

genG2UnRep :: Int -> [Con] -> Q Dec
genG2UnRep tyVarNum cs = funD 'g2UnRep (map (genG2UnRepClause tyVarNum) cs ++ [g2UnRepCatchAllClause])

genG2UnRepClause :: Int -> Con -> Q Clause
genG2UnRepClause tyVarNum (NormalC name fieldTypes) =
    genG2UnRepClause' tyVarNum name fieldTypes
genG2UnRepClause tyVarNum (InfixC st1 n st2) =
    genG2UnRepClause' tyVarNum n [st1, st2]
genG2UnRepClause _ con = error $ "genG2RepClause: Unhandled case." ++ show con 

genG2UnRepClause' :: Int -> TH.Name -> [StrictType] -> Q Clause
genG2UnRepClause' tyVarNum dcNme fieldTypes = do
    expr <- newName "expr"

    fieldNames <- replicateM (length fieldTypes) (newName "x")
    g2DCName <- newName "g2_dc"
    let guardPat1 = listP $ [p|Data (DataCon (G2.Name $(varP g2DCName) _ _ _) _)|]:replicate tyVarNum wildP ++ map varP fieldNames
        guardPat2 = [|T.unpack $(varE g2DCName) ==  $(litE . stringL $ nameBase dcNme) |]
    
    guardPat <- patG [bindS guardPat1 (varE 'unApp `appE` varE expr), noBindS guardPat2]

    case zip fieldNames fieldTypes of
        [] -> do
            let pats = wildP:[varP expr]

            ret <- conE dcNme
            let guardRet = return (guardPat, ret)

            clause pats (guardedB [guardRet]) []
        fnt@(fnt_h:_) -> do
            tenv <- newName "tenv_unrep"
            let pats = if usesTEnvUnRep (snd fnt_h) then varP tenv:[varP expr] else wildP:[varP expr]

            ret <- appsE $ conE dcNme:map (newFieldUnRep tenv) fnt
            let guardRet = return (guardPat, ret)

            clause pats (guardedB [guardRet]) []

g2UnRepCatchAllClause :: Q Clause
g2UnRepCatchAllClause = do
    expr <- newName "expr"
    let pats = [wildP, varP expr]

    clause pats (normalB [|error $ "Unhandled case in g2UnRep " ++ show $(varE expr) |]) []

newFieldUnRep :: TH.Name -> (TH.Name, StrictType) -> Q Exp
newFieldUnRep _ (x, (_, ConT n))
    | nameBase n == "Int#" = [| intPrimFromLit $(varE x) |]
newFieldUnRep _ (x, (_, ConT n))
    | nameBase n == "Float#" = [| floatPrimFromLit $(varE x) |]
newFieldUnRep _ (x, (_, ConT n))
    | nameBase n == "Double#" = [| doublePrimFromLit $(varE x) |]
newFieldUnRep _ (x, (_, ConT n))
    | nameBase n == "Char#" = [| charPrimFromLit $(varE x) |]
newFieldUnRep tenv (x, _) = do
    varE 'g2UnRep `appE` varE tenv `appE` varE x

usesTEnvUnRep :: StrictType -> Bool
usesTEnvUnRep (_, ConT n) =
    let nb = nameBase n in
    not (nb == "Int#" || nb == "Float#" || nb == "Double#" || nb == "Char#")
usesTEnvUnRep _ = True

genG2Type :: TH.Name -> Q Dec
genG2Type tyConName = funD 'g2Type [genG2TypeClause tyConName]

genG2TypeClause :: TH.Name -> Q Clause
genG2TypeClause tyConName = do
    tenv <- newName "tenv_type"
    cleaned <- newName "cleaned"
    let pats = [varP tenv, varP cleaned, wildP]

    let qqTyConName = thNameToQQName tyConName
        exn = [|let
                    qqM = qqMap $(varE cleaned) $(varE tenv)
                    n = HM.lookup $(qqNameToQExp qqTyConName) qqM
               in
               case n of
                    Just tcn -> TyCon tcn TYPE
                    Nothing -> TyCon (G2.Name (T.pack "Unknown") Nothing 0 Nothing) TYPE|]

    clause pats (normalB exn) []

derivingG2RepTuples :: Int -> Int -> Q [Dec]
derivingG2RepTuples mi ma = return . concat =<< mapM derivingG2RepTuple [mi..ma]

derivingG2RepTuple :: Int -> Q [Dec]
derivingG2RepTuple n = derivingG2Rep (tupleTypeName n)

qqNameToQExp :: QQName -> Q Exp
qqNameToQExp (QQName n Nothing) =
    conE 'QQName `appE` textToQExp n `appE` conE 'Nothing
qqNameToQExp (QQName n (Just m)) =
    conE 'QQName `appE` textToQExp n `appE` (conE 'Just `appE` textToQExp m)

textToQExp :: T.Text -> Q Exp
textToQExp t = varE 'T.pack `appE` litE (stringL (T.unpack t))

intPrimFromLit :: G2.Expr -> Int#
intPrimFromLit (Lit (LitInt x)) =
    case fromInteger x of
        I# x' -> x'
intPrimFromLit e = error $ "intPrimFromLit: Unhandled Expr" ++ show e

floatPrimFromLit :: G2.Expr -> Float#
floatPrimFromLit (Lit (LitFloat x)) =
    case fromRational x of
        F# x' -> x'
floatPrimFromLit _ = error "floatPrimFromLit: Unhandled Expr"

doublePrimFromLit :: G2.Expr -> Double#
doublePrimFromLit (Lit (LitDouble x)) =
    case fromRational x of
        D# x' -> x'
doublePrimFromLit _ = error "intPrimFromLit: Unhandled Expr"

charPrimFromLit :: G2.Expr -> Char#
charPrimFromLit (Lit (LitChar (C# x))) = x
charPrimFromLit _ = error "charPrimFromLit: Unhandled Expr"