module ProjectM36.ScriptSession where
import ProjectM36.Error
import Control.Exception
import Control.Monad
import System.IO.Error
import System.Directory
import Control.Monad.IO.Class
import System.FilePath.Glob
import System.FilePath
import Data.Text (Text, unpack)
import Data.Maybe
import GHC
import GHC.Paths (libdir)
#if __GLASGOW_HASKELL__ >= 800
import GHC.LanguageExtensions
#endif
import DynFlags
import Panic
import Outputable
import PprTyThing
import Unsafe.Coerce
import Type hiding (pprTyThing)
data ScriptSession = ScriptSession {
hscEnv :: HscEnv,
atomFunctionBodyType :: Type,
dbcFunctionBodyType :: Type
}
newtype ScriptSessionError = ScriptSessionLoadError GhcException
deriving (Show)
initScriptSession :: [String] -> IO (Either ScriptSessionError ScriptSession)
initScriptSession ghcPkgPaths = do
eHomeDir <- tryJust (guard . isDoesNotExistError) getHomeDirectory
let homeDir = either (const "/") id eHomeDir
let excHandler exc = pure $ Left (ScriptSessionLoadError exc)
handleGhcException excHandler $ runGhc (Just libdir) $ do
dflags <- getSessionDynFlags
let ghcVersion = projectVersion dflags
sandboxPkgPaths <- liftIO $ concat <$> mapM glob [
"./dist-newstyle/packagedb/ghc-" ++ ghcVersion,
".cabal-sandbox/*ghc-" ++ ghcVersion ++ "-packages.conf.d",
homeDir </> ".cabal/store/ghc-" ++ ghcVersion ++ "/package.db"
]
let localPkgPaths = map PkgConfFile (ghcPkgPaths ++ sandboxPkgPaths)
let dflags' = applyGopts . applyXopts $ dflags { hscTarget = HscInterpreted ,
ghcLink = LinkInMemory,
safeHaskell = Sf_Trustworthy,
safeInfer = True,
safeInferred = True,
#if __GLASGOW_HASKELL__ >= 800
trustFlags = map TrustPackage required_packages,
#endif
packageFlags = packageFlags dflags ++ packages,
extraPkgConfs = const (localPkgPaths ++ [UserPkgConf, GlobalPkgConf])
}
applyGopts flags = foldl gopt_set flags gopts
applyXopts flags = foldl xopt_set flags xopts
#if __GLASGOW_HASKELL__ >= 800
xopts = [OverloadedStrings, ExtendedDefaultRules, ImplicitPrelude, ScopedTypeVariables]
#else
xopts = [Opt_OverloadedStrings, Opt_ExtendedDefaultRules, Opt_ImplicitPrelude, Opt_ScopedTypeVariables]
#endif
gopts = []
required_packages = ["base",
"containers",
"Glob",
"directory",
"unordered-containers",
"hashable",
"uuid",
"vector",
"text",
"binary",
"vector-binary-instances",
"time",
"project-m36",
"bytestring"]
#if __GLASGOW_HASKELL__ >= 800
packages = map (\m -> ExposePackage ("-package " ++ m) (PackageArg m) (ModRenaming True [])) required_packages
#else
packages = map TrustPackage required_packages
#endif
_ <- setSessionDynFlags dflags'
let safeImportDecl mn mQual = ImportDecl {
ideclSourceSrc = Nothing,
ideclName = noLoc mn,
ideclPkgQual = Nothing,
ideclSource = False,
ideclSafe = True,
ideclImplicit = False,
ideclQualified = isJust mQual,
ideclAs = mQual,
ideclHiding = Nothing
}
unqualifiedModules = map (\modn -> IIDecl $ safeImportDecl (mkModuleName modn) Nothing) [
"Prelude",
"Data.Map",
"Data.Either",
"Data.Time.Calendar",
"Control.Monad.State",
"ProjectM36.Base",
"ProjectM36.Relation",
"ProjectM36.AtomFunctionError",
"ProjectM36.DatabaseContextFunctionError",
"ProjectM36.DatabaseContextFunctionUtils",
"ProjectM36.RelationalExpression"]
qualifiedModules = map (\(modn, qualNam) -> IIDecl $ safeImportDecl (mkModuleName modn) (Just (mkModuleName qualNam))) [
("Data.Text", "T")
]
setContext (unqualifiedModules ++ qualifiedModules)
env <- getSession
atomFuncType <- mkTypeForName "AtomFunctionBodyType"
dbcFuncType <- mkTypeForName "DatabaseContextFunctionBodyType"
pure (Right (ScriptSession env atomFuncType dbcFuncType))
addImport :: String -> Ghc ()
addImport moduleNam = do
ctx <- getContext
setContext ( (IIDecl $ simpleImportDecl (mkModuleName moduleNam)) : ctx )
showType :: DynFlags -> Type -> String
showType dflags ty = showSDocForUser dflags alwaysQualify (pprTypeForUser ty)
mkTypeForName :: String -> Ghc Type
mkTypeForName name = do
lBodyName <- parseName name
case lBodyName of
[] -> error ("failed to parse " ++ name)
_:_:_ -> error "too many name matches"
[bodyName] -> do
mThing <- lookupName bodyName
case mThing of
Nothing -> error ("failed to find " ++ name)
Just (ATyCon tyCon) -> case synTyConRhs_maybe tyCon of
Just typ -> pure typ
Nothing -> error (name ++ " is not a type synonym")
Just _ -> error ("failed to find type synonym " ++ name)
compileScript :: Type -> Text -> Ghc (Either ScriptCompilationError a)
compileScript funcType script = do
let sScript = unpack script
mErr <- typeCheckScript funcType script
case mErr of
Just err -> pure (Left err)
Nothing -> do
func <- compileExpr sScript
pure $ Right (unsafeCoerce func)
typeCheckScript :: Type -> Text -> Ghc (Maybe ScriptCompilationError)
typeCheckScript expectedType inp = do
dflags <- getSessionDynFlags
funcType <- GHC.exprType (unpack inp)
if eqType funcType expectedType then
pure Nothing
else
pure (Just (TypeCheckCompilationError (showType dflags expectedType) (showType dflags funcType)))