packages feed

nomyx-core-1.0.0: src/Nomyx/Core/Engine/Interpret.hs

{-# LANGUAGE DoAndIfThenElse     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}

-- | This module interprets strings representing rules to convert them to plain Rules.
module Nomyx.Core.Engine.Interpret (
   interpretRule,
   interpretRule',
   showInterpreterError 
   )
   where

import           Control.Exception                   as CE
import           Control.Monad
import           Control.Monad.Catch  as MC
import           Data.List
import           Language.Haskell.Interpreter
import           Language.Haskell.Interpreter.Server
import           Language.Haskell.Interpreter.Unsafe (unsafeSetGhcOption)
import           Nomyx.Language
import           Nomyx.Core.Engine.Context
import           Nomyx.Core.Engine.Utils
import           System.FilePath                     (dropExtension, joinPath,
                                                      takeFileName, dropFileName,
                                                      splitDirectories, takeBaseName, (</>))
import           System.IO.Temp
import           System.IO.Unsafe
import           System.Directory
import           System.Log.Logger
#ifndef WINDOWS
import qualified System.Posix.Signals as S
#endif


#ifndef NOINTER

serverHandle :: ServerHandle
serverHandle = unsafePerformIO $ start

exts :: [String]
exts = ["Safe", "GADTs"] ++ map show namedExts

namedExts :: [Extension]
namedExts = [GADTs,
             ScopedTypeVariables,
             TypeFamilies,
             DeriveDataTypeable]

-- | initializes the interpreter by loading some modules.
initializeInterpreter :: [ModuleInfo] -> Interpreter ()
initializeInterpreter mods = do
   reset
   -- Interpreter options
   set [installedModulesInScope := False,
        languageExtensions := map readExt exts]
   -- GHC options
   unsafeSetGhcOption "-w"
   unsafeSetGhcOption "-fpackage-trust"
   forM_ (defaultPackages >>= words) $ \pkg -> unsafeSetGhcOption ("-trust " ++ pkg)
   -- Modules
   when (not $ null mods) $ do
      dir <- liftIO $ createTempDirectory "/tmp" "Nomyx"
      modPaths <- liftIO $ mapM (saveModule dir) mods
      let modNames = map (getModName . _modPath) mods
      info $ "Loading modules: " ++ (intercalate ", " modPaths)
      info $ "module names: " ++ (intercalate ", " modNames)
      loadModules modPaths
      setTopLevelModules modNames
   -- Imports
   let importMods = qualImports ++ zip (unQualImports) (repeat Nothing)
   setImportsQ importMods

getModName :: FilePath -> String
getModName fp = intercalate "." $ (filter (/= ".") $ splitDirectories $ dropFileName fp) ++ [takeBaseName fp]

---- | reads a Rule out of a string.
interpretRule :: RuleCode -> [ModuleInfo] -> IO (Either InterpreterError Rule)
interpretRule rc ms = runRule `catchIOError` handler where 
   runRule = protectHandlers $ runIn serverHandle $ do
      initializeInterpreter ms
      interpret rc (as :: Rule)
   handler (e::IOException) = return $ Left $ NotAllowed $ "Caught exception: " ++ (show e)

interpretRule' :: RuleCode -> [ModuleInfo] -> IO Rule
interpretRule' rc ms = do
   res <- interpretRule rc ms
   case res of
      Right rf -> return rf
      Left e -> error $ show e

showInterpreterError :: InterpreterError -> String
showInterpreterError (UnknownError s)  = "Unknown Error\n" ++ s
showInterpreterError (WontCompile ers) = "Won't Compile\n" ++ concatMap (\(GhcError errMsg) -> errMsg ++ "\n") ers
showInterpreterError (NotAllowed s)    = "Not Allowed (Probable cause: bad module or file name)\n" ++ s
showInterpreterError (GhcException s)  = "Ghc Exception\n" ++ s

readExt :: String -> Extension
readExt s = case reads s of
  [(e,[])] -> e
  _        -> UnknownExtension s

#else

interpretRule :: RuleCode -> [ModuleInfo] -> IO (Either InterpreterError Rule)
interpretRule rc ms = return $ Left $ NotAllowed "Interpreter not included"

interpretRule' :: RuleCode -> [ModuleInfo] -> IO Rule
interpretRule' rc ms = error "Interpreter not included"

showInterpreterError :: InterpreterError -> String
showInterpreterError (NotAllowed s)  = s

#endif


#ifdef WINDOWS

--no signals under windows
protectHandlers :: IO a -> IO a
protectHandlers = id

#else

installHandler' :: S.Handler -> S.Signal -> IO S.Handler
installHandler' handler signal = S.installHandler signal handler Nothing

signals :: [S.Signal]
signals = [ S.sigQUIT
          , S.sigINT
          , S.sigHUP
          , S.sigTERM
          ]

saveHandlers :: IO [S.Handler]
saveHandlers = liftIO $ mapM (installHandler' S.Ignore) signals

restoreHandlers :: [S.Handler] -> IO [S.Handler]
restoreHandlers h  = liftIO . sequence $ zipWith installHandler' h signals

protectHandlers :: IO a -> IO a
protectHandlers a = MC.bracket saveHandlers restoreHandlers $ const a

#endif

warn, info :: (MonadIO m) => String -> m ()
info s = liftIO $ infoM "Nomyx.Core.Engine.Interpret" s
warn s = liftIO $ warningM "Nomyx.Core.Engine.Interpret" s