{-# LANGUAGE CPP, RecordWildCards #-}

{-|
Module      : Language.C.Preprocessor.Remover.Internal.Preprocess
Description : Call GHC.preprocess at the Cpp phase
Copyright   : (c) Carlo Nucera, 2016
License     : BSD3
Maintainer  : meditans@gmail.com
Stability   : experimental
Portability : POSIX
-}

module Language.C.Preprocessor.Remover.Internal.Preprocess
   (
   -- * Entry point for parsing
     parseModuleWithCpp
   -- * Wrapper around GHC's preprocess
   , getPreprocessedSrcDirect
   ) where

import Control.Monad               (void)
import Data.Tuple.Extra            (fst3)
import Lens.Micro
import Language.C.Preprocessor.Remover.Internal.Types

#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif

import qualified DriverPhases   as GHC
import qualified DriverPipeline as GHC
import qualified DynFlags       as GHC
import qualified GHC
import qualified GHC.Paths      as GHC
import qualified HeaderInfo     as GHC
import qualified HscTypes       as GHC
import qualified MonadUtils     as GHC

# if __GLASGOW_HASKELL__ >= 800
import qualified Language.Haskell.TH.LanguageExtensions as LE
# endif

--------------------------------------------------------------------------------
-- Entry point for parsing
--------------------------------------------------------------------------------

-- | Parse a module with specific instructions for the C pre-processor.
parseModuleWithCpp :: CppOptions -> FilePath -> IO String
parseModuleWithCpp cppOptions file = GHC.runGhc (Just GHC.libdir) $ do
  dflags <- initDynFlags file
#if __GLASGOW_HASKELL__ >= 800
  let useCpp = GHC.xopt LE.Cpp dflags
#else
  let useCpp = GHC.xopt GHC.Opt_Cpp dflags
#endif
  if useCpp
    then getPreprocessedSrcDirect cppOptions file
    else GHC.liftIO (readFile file)

-- | Given a config and a file, this function returns the correct dynFlags. For
-- now, this is only used to check if the Cpp extension is enabled (see the test
-- in parseModuleWithCpp), and thus it could be implemented differently. I'm
-- keeping this version to have all the flags, in case I decide later to offer
-- an entry point for ghc-api based analysis.
initDynFlags :: GHC.GhcMonad m => FilePath -> m GHC.DynFlags
initDynFlags file = do
  dflags0  <- GHC.getSessionDynFlags
  src_opts <- GHC.liftIO $ GHC.getOptionsFromFile dflags0 file
  dflags1  <- fst3 <$> GHC.parseDynamicFilePragma dflags0 src_opts
  let dflags2 = dflags1 {GHC.log_action = GHC.defaultLogAction}
  void $ GHC.setSessionDynFlags dflags2
  return dflags2

--------------------------------------------------------------------------------
-- Wrapper around GHC's preprocess
--------------------------------------------------------------------------------

-- | Invoke GHC's 'GHC.preprocess' function at the cpp phase, adding the
-- options specified in the first argument.
getPreprocessedSrcDirect :: GHC.GhcMonad m => CppOptions -> FilePath -> m String
getPreprocessedSrcDirect cppOptions file = do
  hscEnv <- injectCppOptions cppOptions <$> GHC.getSession
  (_, tempFile) <- GHC.liftIO $ GHC.preprocess hscEnv (file, cppPhase)
  GHC.liftIO (readFile tempFile)
  where
    cppPhase = Just (GHC.Cpp GHC.HsSrcFile)

injectCppOptions :: CppOptions -> GHC.HscEnv -> GHC.HscEnv
injectCppOptions CppOptions{..} = over (_hsc_dflags . _settings . _sOpt_P)
                                       (encodedOptions ++)
  where
    encodedOptions = map ("-D" ++) cppDefine
                  ++ map ("-I" ++) cppInclude
                  ++ map ("-include" ++) cppFile

--------------------------------------------------------------------------------
-- Some lenses to manipulate conveniently a HscEnv value
--------------------------------------------------------------------------------

_hsc_dflags :: Lens' GHC.HscEnv GHC.DynFlags
_hsc_dflags = lens GHC.hsc_dflags
                   (\hscEnv dynFlags -> hscEnv {GHC.hsc_dflags = dynFlags})

_settings :: Lens' GHC.DynFlags GHC.Settings
_settings = lens GHC.settings
                 (\dynFlags setting -> dynFlags {GHC.settings = setting})

_sOpt_P :: Lens' GHC.Settings [String]
_sOpt_P = lens GHC.sOpt_P
               (\setting strings -> setting {GHC.sOpt_P = strings})