now-haskell-0.1.0.0: src/ModuleScanner.hs
{-# LANGUAGE ViewPatterns #-}
module ModuleScanner where
import Control.Effect
import Control.Effect.Reader
import Control.Monad
import Control.Monad.IO.Class
import Data.Aeson
import Data.Aeson.Encode.Pretty
import Data.Either (either)
import Data.Maybe
import Data.IORef
import qualified Data.Text as T
import qualified Data.ByteString.Lazy.Char8 as L
-- GHC lib tasks
import Config
import DynFlags
import FastString
import Fingerprint
import HeaderInfo
import HsSyn
import Module
import Lexer
import Outputable
import Pretty
import Parser
import Platform
import RdrName
import StringBuffer
import SrcLoc
import qualified Data.Map.Strict as Map
import System.IO
import System.FilePath
{-
TODO figure out how to 'flatten' GenericPackageDescription into PackageDescription
TODO figure out how to *write* package descriptions back out?
-}
fakeLlvmConfig :: (LlvmTargets, LlvmPasses)
fakeLlvmConfig = ([], [])
data Analyze = Analyze
{ moduleName :: T.Text
-- , moduleType :: ModuleType
, functionName :: ModuleExports
, watch :: [FilePath] -- Local modules
}
instance ToJSON Analyze where
toJSON (Analyze mn fn w) = object
[ "moduleName" .= mn
-- , "moduleType" .= mt
, "functionName" .= fn
, "watch" .= w
]
fakeSettings :: Settings
fakeSettings =
Settings
{ sTargetPlatform = platform
, sPlatformConstants = platformConstants
, sProjectVersion = cProjectVersion
, sProgramName = "ghc"
, sOpt_P_fingerprint = fingerprint0
, sTmpDir = "."
}
where
platform =
Platform
{ platformWordSize = 8
, platformOS = OSUnknown
, platformUnregisterised = True
}
-- This bit may need to be conditional on OS.
platformConstants =
PlatformConstants
{ pc_DYNAMIC_BY_DEFAULT = False
, pc_WORD_SIZE = 8
, pc_STD_HDR_SIZE = 1
, pc_TAG_BITS = 3
, pc_BLOCKS_PER_MBLOCK = 252
, pc_BLOCK_SIZE = 4096
, pc_MIN_PAYLOAD_SIZE = 1
, pc_MAX_Real_Vanilla_REG = 6
, pc_MAX_Vanilla_REG = 10
, pc_MAX_Real_Long_REG = 0
}
readDesiredModule :: FilePath -> IO Analyze
readDesiredModule file = do
(flags, res) <- loadModule file
case res of
POk _ lm -> runM $ runReader flags $ do
-- printForUser flags stdout neverQualify (ppr $ unLoc lm)
r <- findMainOrHandler $ unLoc lm
let modName = T.pack $ maybe "Main" (moduleNameString . unLoc) $ hsmodName $ unLoc lm
pure $ Analyze modName r [] -- TODO
_ -> error "Failed to parse module!"
mkDynFlags :: String -> String -> IO DynFlags
mkDynFlags filename s = do
dirs_to_clean <- newIORef Map.empty
files_to_clean <- newIORef emptyFilesToClean
next_temp_suffix <- newIORef 0
let baseFlags =
(defaultDynFlags fakeSettings fakeLlvmConfig) {
ghcLink = NoLink
, hscTarget = HscNothing
, pkgDatabase = Just []
, dirsToClean = dirs_to_clean
, filesToClean = files_to_clean
, nextTempSuffix = next_temp_suffix
, thisInstalledUnitId = toInstalledUnitId (stringToUnitId "module-scanner")
}
parsePragmasIntoDynFlags filename s baseFlags
where
parsePragmasIntoDynFlags :: String -> String -> DynFlags -> IO DynFlags
parsePragmasIntoDynFlags fp contents dflags0 = do
let opts = getOptions dflags0 (stringToStringBuffer contents) fp
(dflags, _, _) <- parseDynamicFilePragma dflags0 opts
return dflags
runParser :: FilePath -> DynFlags -> String -> P a -> Lexer.ParseResult a
runParser p flags str parser = unP parser parseState
where
filename = p
location = mkRealSrcLoc (mkFastString filename) 1 1
_buf = stringToStringBuffer str
parseState = mkPState flags _buf location
loadModule :: FilePath -> IO (DynFlags, Lexer.ParseResult (Located (HsModule GhcPs)))
loadModule fp = do
contents <- readFile fp
flags <- mkDynFlags fp contents
pure (flags, runParser fp flags contents parseModule)
data ModuleExports
= ExportsMain
| ExportsHandler
| NoSupportedExport
deriving (Show)
instance ToJSON ModuleExports where
toJSON e = case e of
ExportsMain -> String "main"
ExportsHandler -> String "handler"
NoSupportedExport -> Null
data ModuleType
= Executable
| Library
deriving (Show)
instance ToJSON ModuleType where
toJSON x = case x of
Executable -> String "Executable"
Library -> String "Library"
findMainOrHandler :: (Member (Reader DynFlags) sig, Carrier sig m, MonadIO m) => HsModule GhcPs -> m ModuleExports
findMainOrHandler m = case hsmodExports m of
Nothing -> scanListDecls $ hsmodDecls m
Just lExps -> case map unLoc $ unLoc lExps of
[] -> pure NoSupportedExport
-- TODO handle other cases
exps -> do
expMs <- mapM checkExport exps
pure $ case expMs of
(r:_) -> r
_ -> NoSupportedExport
rdrNameCheck :: RdrName -> ModuleExports
rdrNameCheck name = if mkVarUnqual (mkFastString "main") == name
then ExportsMain
else if mkVarUnqual (mkFastString "handler") == name
then ExportsHandler
else NoSupportedExport
prettyPrintDump :: (Member (Reader DynFlags) sig, Carrier sig m, MonadIO m, Outputable a) => a -> m ()
prettyPrintDump x = do
flags <- ask
liftIO $ printForUser flags stdout neverQualify (ppr x)
checkExport :: (Member (Reader DynFlags) sig, Carrier sig m, MonadIO m) => IE GhcPs -> m ModuleExports
checkExport ie = do
-- prettyPrintDump ie
case ie of
IEVar _ wrappedName -> case unLoc wrappedName of
IEName name -> pure $ rdrNameCheck $ unLoc name
IEPattern _ -> pure NoSupportedExport
IEType _ -> pure NoSupportedExport
IEThingAbs _ _ -> pure NoSupportedExport
IEThingAll _ _ -> pure NoSupportedExport -- TODO
IEThingWith _ _ _ _ _ -> pure NoSupportedExport -- TODO
IEModuleContents _ _ -> pure NoSupportedExport -- TODO
IEGroup _ _ _ -> pure NoSupportedExport
IEDoc _ _ -> pure NoSupportedExport
IEDocNamed _ _ -> pure NoSupportedExport
XIE _ -> pure NoSupportedExport
scanListDecls :: (Member (Reader DynFlags) sig, Carrier sig m, MonadIO m) => [LHsDecl GhcPs] -> m ModuleExports
scanListDecls decls = do
declMs <- mapM (scanDecl . unLoc) decls
pure $ case declMs of
[] -> NoSupportedExport
(r:_) -> r
scanDecl :: (Member (Reader DynFlags) sig, Carrier sig m, MonadIO m) => HsDecl GhcPs -> m ModuleExports
scanDecl (ValD _ bind) = case bind of
(FunBind _ fid _ _ _) -> do
-- prettyPrintDump fid
pure $ rdrNameCheck $ unLoc fid
_ -> pure NoSupportedExport
scanDecl _ = pure NoSupportedExport