1{-# LANGUAGE UnboxedTuples, KindSignatures, DataKinds #-}
2#ifdef PM36_HASKELL_SCRIPTING
3{-# LANGUAGE MagicHash #-}
5module ProjectM36.ScriptSession where
7#ifdef PM36_HASKELL_SCRIPTING
10import Control.Exception
13import System.Directory
14import Control.Monad.IO.Class
15import System.FilePath.Glob
17import System.Info (os, arch)
18import Data.Text (Text, unpack)
20import GHC.Paths (libdir)
21import System.Environment
22#if __GLASGOW_HASKELL__ >= 800
23import GHC.LanguageExtensions
28#if __GLASGOW_HASKELL__ >= 802
33import Outputable --hiding ((<>))
36#if __GLASGOW_HASKELL__ >= 802
38#elif __GLASGOW_HASKELL__ >= 710
39import Type hiding (pprTyThing)
42import GHC.Exts (addrToAny#)
43import GHC.Ptr (Ptr(..))
48data ScriptSession = ScriptSession {
49#ifdef PM36_HASKELL_SCRIPTING
51 atomFunctionBodyType :: Type,
52 dbcFunctionBodyType :: Type
56#ifdef PM36_HASKELL_SCRIPTING
57data ScriptSessionError = ScriptSessionLoadError GhcException
61data ScriptSessionError = ScriptingDisabled
65data LoadSymbolError = LoadSymbolError
69-- | Configure a GHC environment/session which we will use for all script compilation.
70initScriptSession :: [String] -> IO (Either ScriptSessionError ScriptSession)
71#if !defined(PM36_HASKELL_SCRIPTING)
72initScriptSession _ = pure (Left ScriptingDisabled)
74initScriptSession ghcPkgPaths = do
75 --for the sake of convenience, for developers' builds, include the local cabal sandbox package database and the cabal new-build package database
76 eHomeDir <- tryJust (guard . isDoesNotExistError) getHomeDirectory
77 let homeDir = either (const "/") id eHomeDir
78 let excHandler exc = pure $ Left (ScriptSessionLoadError exc)
79 handleGhcException excHandler $ runGhc (Just libdir) $ do
80 dflags <- getSessionDynFlags
81 let ghcVersion = projectVersion dflags
82 -- get nix packages dir, if available
83 mNixLibDir <- liftIO $ lookupEnv "NIX_GHC_LIBDIR"
85 sandboxPkgPaths <- liftIO $ concat <$> mapM glob [
86 --"./dist-newstyle/packagedb/ghc-" ++ ghcVersion, --rely on cabal 3's ghc.environment install: "cabal install --lib project-m36"
87 ".cabal-sandbox/*ghc-" ++ ghcVersion ++ "-packages.conf.d",
88 ".stack-work/install/*/*/" ++ ghcVersion ++ "/pkgdb",
89 ".stack-work/install/*/pkgdb/", --windows stack build
90 "C:/sr/snapshots/b201cfe6/pkgdb", --windows stack build- ideally, we could run `stack path --snapshot-pkg-db, but this is sufficient to pass CI
91 homeDir </> ".stack/snapshots/*/*/" ++ ghcVersion ++ "/pkgdb"
92 --homeDir </> ".cabal/store/ghc-" ++ ghcVersion ++ "/package.db"
95 let localPkgPaths = map PkgConfFile (ghcPkgPaths ++ sandboxPkgPaths ++ maybeToList mNixLibDir)
97 let dflags' = applyGopts . applyXopts $ dflags { hscTarget = HscInterpreted ,
98 ghcLink = LinkInMemory,
99 safeHaskell = Sf_Trustworthy,
103#if __GLASGOW_HASKELL__ >= 800
104 trustFlags = map TrustPackage required_packages,
106 packageFlags = packageFlags dflags ++ packages,
107#if __GLASGOW_HASKELL__ >= 802
108 packageDBFlags = map PackageDB localPkgPaths
110 extraPkgConfs = const (localPkgPaths ++ [UserPkgConf, GlobalPkgConf])
114 applyGopts flags = foldl gopt_set flags gopts
115 applyXopts flags = foldl xopt_set flags xopts
116#if __GLASGOW_HASKELL__ >= 800
117 xopts = [OverloadedStrings, ExtendedDefaultRules, ImplicitPrelude, ScopedTypeVariables]
119 xopts = [Opt_OverloadedStrings, Opt_ExtendedDefaultRules, Opt_ImplicitPrelude, Opt_ScopedTypeVariables]
121 gopts = [] --[Opt_DistrustAllPackages, Opt_PackageTrust]
122 required_packages = ["base",
126 "unordered-containers",
132 "vector-binary-instances",
136#if __GLASGOW_HASKELL__ >= 800
137 packages = map (\m -> ExposePackage ("-package " ++ m) (PackageArg m) (ModRenaming True [])) required_packages
139 packages = map TrustPackage required_packages
141 --liftIO $ traceShowM (showSDoc dflags' (ppr packages))
142 _ <- setSessionDynFlags dflags'
143 let safeImportDecl mn mQual = ImportDecl {
144#if __GLASGOW_HASKELL__ >= 802
145 ideclSourceSrc = NoSourceText,
147 ideclSourceSrc = Nothing,
150#if __GLASGOW_HASKELL__ >= 806
153 ideclName = noLoc mn,
154 ideclPkgQual = Nothing,
157 ideclImplicit = False,
158 ideclQualified = isJust mQual,
160 ideclHiding = Nothing
162#if __GLASGOW_HASKELL__ >= 806
163 :: ImportDecl (GhcPass (c :: Pass))
165 unqualifiedModules = map (\modn -> IIDecl $ safeImportDecl (mkModuleName modn) Nothing) [
169 "Data.Time.Calendar",
170 "Control.Monad.State",
172 "ProjectM36.Relation",
173 "ProjectM36.AtomFunctionError",
174 "ProjectM36.DatabaseContextFunctionError",
175 "ProjectM36.DatabaseContextFunctionUtils",
176 "ProjectM36.RelationalExpression"]
177#if __GLASGOW_HASKELL__ >= 802
178 mkModName = noLoc . mkModuleName
180 mkModName = mkModuleName
182 qualifiedModules = map (\(modn, qualNam) -> IIDecl $ safeImportDecl (mkModuleName modn) (Just (mkModName qualNam))) [
185 setContext (unqualifiedModules ++ qualifiedModules)
187 atomFuncType <- mkTypeForName "AtomFunctionBodyType"
188 dbcFuncType <- mkTypeForName "DatabaseContextFunctionBodyType"
189 pure (Right (ScriptSession env atomFuncType dbcFuncType))
191addImport :: String -> Ghc ()
192addImport moduleNam = do
194 setContext (IIDecl (simpleImportDecl (mkModuleName moduleNam)) : ctx)
196showType :: DynFlags -> Type -> String
197showType dflags ty = showSDocForUser dflags alwaysQualify (pprTypeForUser ty)
199mkTypeForName :: String -> Ghc Type
200mkTypeForName name = do
201 lBodyName <- parseName name
203 [] -> error ("failed to parse " ++ name)
204 _:_:_ -> error "too many name matches"
206 mThing <- lookupName bodyName
208 Nothing -> error ("failed to find " ++ name)
209 Just (ATyCon tyCon) -> case synTyConRhs_maybe tyCon of
211 Nothing -> error (name ++ " is not a type synonym")
212 Just _ -> error ("failed to find type synonym " ++ name)
214compileScript :: Type -> Text -> Ghc (Either ScriptCompilationError a)
215compileScript funcType script = do
216 let sScript = unpack script
217 mErr <- typeCheckScript funcType script
219 Just err -> pure (Left err)
221 --catch exception here
222 --we could potentially wrap the script with Atom pattern matching so that the script doesn't have to do it, but the change to an Atom ADT should make it easier. Still, it would be nice if the script didn't have to handle a list of arguments, for example.
223 -- we can't use dynCompileExpr here because
224 Right . unsafeCoerce <$> compileExpr sScript
226typeCheckScript :: Type -> Text -> Ghc (Maybe ScriptCompilationError)
227typeCheckScript expectedType inp = do
228 dflags <- getSessionDynFlags
229 --catch exception for SyntaxError
230#if __GLASGOW_HASKELL__ >= 802
231 funcType <- GHC.exprType TM_Inst (unpack inp)
233 funcType <- GHC.exprType (unpack inp)
235 --liftIO $ putStrLn $ showType dflags expectedType ++ ":::" ++ showType dflags funcType
236 if eqType funcType expectedType then
239 pure (Just (TypeCheckCompilationError (showType dflags expectedType) (showType dflags funcType)))
241mangleSymbol :: Maybe String -> String -> String -> String
242mangleSymbol pkg module' valsym =
244 maybe "" (\p -> zEncodeString p ++ "_") pkg ++
245 zEncodeString module' ++ "_" ++ zEncodeString valsym ++ "_closure"
247loadFunction :: ModName -> FuncName -> FilePath -> IO (Either LoadSymbolError a)
248loadFunction modName funcName objPath = do
249#if __GLASGOW_HASKELL__ >= 802
250 initObjLinker RetainCAFs
256 ptr <- lookupSymbol (mangleSymbol Nothing modName funcName)
258 Nothing -> pure (Left LoadSymbolError)
259 Just (Ptr addr) -> case addrToAny# addr of
260 (# hval #) -> pure (Right hval)
262prefixUnderscore :: String
265 ("mingw32","x86_64") -> ""
266 ("cygwin","x86_64") -> ""
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list