ariadne-0.1.2.2: Ariadne/ModuleDB/ParseResolve.hs
{-# LANGUAGE FlexibleContexts, MultiParamTypeClasses #-}
module Ariadne.ModuleDB.ParseResolve where
import Ariadne.GlobalNameIndex
import Ariadne.Index
import Ariadne.Types
import qualified Ariadne.SrcMap as SrcMap
import Ariadne.ModuleDB.Types
import Language.Haskell.Names
import Language.Haskell.Names.Interfaces
import Language.Haskell.Names.SyntaxUtils
import Language.Haskell.Names.Imports
import qualified Language.Haskell.Names.GlobalSymbolTable as Global
import Language.Haskell.Exts.Annotated hiding (parse)
import Language.Haskell.Exts.Annotated.CPP
import Distribution.HaskellSuite.Packages
import Distribution.Simple.Compiler (PackageDB(..))
import Control.Applicative
import Control.Arrow
import Control.Monad.Trans
import Control.Monad
import Control.Monad.State
import Control.Exception
import Text.Printf
import System.FilePath
import System.Directory
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified System.Log.Logger as L
import Data.Maybe
import qualified Data.Foldable as F
import Data.Proxy
import Data.Lens
-- | Start to watch the path, and if/when it exists, parse and record it
include :: (MonadState Storage m, MonadIO m) => FilePath -> m ()
include path = do
alreadyPresent <- gets (Set.member path . getL watchedFiles)
unless alreadyPresent $ do
liftIO . L.debugM "ariadne.moduledb" $ printf "Including %s in the set of watched files" path
exists <- liftIO $ doesFileExist path
watchedFiles %= Set.insert path
when exists $ update path
-- | Read/parse/analyse the given path and update the internal state
-- (Storage) accordingly
update
:: (MonadState Storage m, MonadIO m)
=> FilePath -- the file to update
-> m ()
update path = do
readSources path
(paths, sources) <- (Map.keys &&& Map.elems) `liftM` access moduleSources
pkgs <- liftIO $
((F.fold <$> mapM (getInstalledPackages (Proxy :: Proxy NamesDB)) [GlobalPackageDB, UserPackageDB])
:: IO Packages)
(resolved, impTbls) <- liftIO (liftM unzip $
(flip evalNamesModuleT pkgs $ do
errs <- computeInterfaces defaultLang defaultExts sources
forM sources $ \parsed -> do
let extSet = moduleExtensions defaultLang defaultExts parsed
impTbl <- fmap snd $ processImports extSet $ getImports parsed
resolved <- annotateModule defaultLang defaultExts parsed
return (resolved, impTbl))
:: IO ([Module (Scoped SrcSpan)], [Global.Table]))
let
gIndex :: GlobalNameIndex
gIndex = Map.unions $
zipWith
(\src impTbl -> mkGlobalNameIndex impTbl (getPointLoc <$> src))
sources impTbls
srcMaps :: [SrcMap.SrcMap Origin]
srcMaps = map (mkSrcMap gIndex) resolved
moduleSrcMaps ~= Map.fromAscList (zip paths srcMaps)
return ()
-- | Helper for update. Reads/parses the source and also calls include on
-- every imprted module
readSources
:: (MonadState Storage m, MonadIO m)
=> FilePath -- the file to update
-> m ()
readSources path = do
exists <- liftIO $ doesFileExist path
if not exists
then do
{-liftIO . L.debugM "ariadne.parser" $
printf "%s: not found at %s" modname path-}
return ()
else do
parseResult <- liftIO $ parse path
case parseResult of
ParseFailed loc msg -> do
liftIO . L.warningM "ariadne.parser" $
printf "Failed to parse %s (%s: %s)" path (prettyPrint loc) msg
return ()
ParseOk parsed -> do
let
modname@(ModuleName _ modnameS) = getModuleName parsed
root = rootPath path modname
liftIO . L.debugM "ariadne.parser" $
printf "Parsed %s at %s" modnameS path
moduleSources %= Map.insert path (srcInfoSpan <$> parsed)
mapM_ (include . modNameToPath root) (importedModules parsed)
-- these should probably come from the Cabal file
defaultLang = Haskell2010
defaultExts = []
parse :: FilePath -> IO (ParseResult (Module SrcSpanInfo))
parse path = do
ast <- fmap fst <$>
parseFileWithCommentsAndCPP
defaultCpphsOptions
defaultParseMode
{ parseFilename = path
, ignoreLinePragmas = False
, fixities = Just [] }
path
-- Sometimes the AST throws an exception when forcing the result, such
-- as "Ambiguous infix expression". Very annoying!
catch (evaluate ast) $ \e ->
return $ ParseFailed noLoc { srcFilename = path } (show (e :: ErrorCall))
-- | Get the module's root path, based on its path and the module name
rootPath :: FilePath -> ModuleName l -> FilePath
rootPath path (ModuleName _ modname) =
-- the algorithm is simple: count the number of components in the module
-- name, and go that number of levels up
let
numLevels = length $ filter (== '.') modname
root = (foldr (.) id $ replicate (numLevels+1) takeDirectory) path
in root
-- FIXME support lhs etc.
modNameToPath
:: FilePath -- ^ root path
-> ModuleNameS -- ^ module name
-> FilePath -- ^ module path
modNameToPath root name = root </> map dotToSlash name <.> "hs"
where
dotToSlash '.' = '/'
dotToSlash c = c
importedModules :: Module a -> [ModuleNameS]
importedModules mod =
map ((\(ModuleName _ s) -> s) . importModule) $ getImports mod