module GHCSearch (findModule, explicitFilename) where import Data.Maybe import Data.Char -- This source file embodies GHC's file searching strategy. There are -- two entry points: -- -- * findModule takes a module name and returns a list of (path,location) -- pairs: if the file named in the first component exists, then the -- location in the second component is used for that module. It is an -- error (currently unchecked by GHC) for more than one of the locations -- to be valid. -- -- * explicitFilenmae takes a string passed as an argument on the GHC -- command-line (either one-shot or --make), or an argument from the -- :load command in GHC. It returns a list of the same form as findModule. -- -- This module is basically a standalone version of -- ghc/compiler/main/Finder.lhs in the compiler itself. -- Extensions relative to GHC: -- - for a module A.B.C, we search for files named /A.B.C. -- as well as /A/B/C.. This applies both to package and -- home locations. -- -- - the .hi file for an explicitly-named source file is now the -- full module name rather than just the last component. -- -- Tunables: search_paths = [".", "D1","D2"] -- modified by the -i option src_sufs = [ ".hs", ".lhs" ] -- (not tunable in GHC) hi_suf = ".hi" -- or value of -hisuf flag obj_suf = ".o" -- or value of -osuf flag hi_dir = Nothing -- set to (Just d) for -hidir d. obj_dir = Nothing -- set to (Just d) for -odir d. -- For each package, we list the package name and its import path. packages :: [(String, [FilePath])] packages = [ ("package1", ["P1"]), ("package1", ["P2"]) ] package_hi_suf = ".hi" -- not affected by the -hisuf flag, but changed -- by -prof to ".p_hi", and similarly for other "ways". type Module = String -- Information about the files associated with a module. data ModLocation = ModLocation { locSource :: Maybe FilePath, -- the source, if it exists. Modules in a package don't -- normally have source files. locHi :: FilePath, -- The interface file. This might not exist (eg. in GHCi -- we don't normally write out interface files), but if we -- do create one, this is where it goes. locObj :: Maybe FilePath -- The object file. Again, this might not exist, but if -- it does exist, its timestamp is used in deciding whether -- to recompile or not. -- -- Modules in a package don't normally have separate object -- files, so in that case this field is set to Nothing. } deriving Show findModule :: Module -> [(FilePath,ModLocation)] findModule mod = home_locs mod ++ package_locs mod explicitFilename :: FilePath -> [(FilePath,ModLocation)] explicitFilename str | looks_like_a_module = findModule str | looks_like_a_src_filename = [mk_src_loc (Just module_name) p m ext] | otherwise = [] where looks_like_a_module = isUpper (head str) && all isModId (tail str) isModId c = isAlphaNum c || c == '\'' (p,m,e) = splitFilename3 str ext = '.':e looks_like_a_src_filename = ext `elem` src_sufs module_name = getModuleName str -- Should return the module name for a given source file by peeking in -- the file. XXX bogus for now. getModuleName str = "A.B.C" -- The possible locations in the "home package" for a given module home_locs mod = [ mk_src_loc Nothing p m e | p <- search_paths, m <- [mod, dots_to_slashes mod], e <- src_sufs ] where dots_to_slashes = map f where f '.' = '/' f c = c -- The possible locations in other packages for a given module package_locs mod = [ mk_hi_loc p m hi_suf | (pkg_name, paths) <- packages, p <- paths, m <- [mod, dots_to_slashes mod] ] where dots_to_slashes = map f where f '.' = '/' f c = c -- Make a ModLocation given the name of the source file. mk_src_loc :: Maybe Module -- Just mod <=> use mod as the basename for -- the .hi file. -> String -- The directory containing the source file -> String -- The basename of the source file -> String -- The suffix of the source file -> (FilePath, ModLocation) mk_src_loc maybe_mod path basename suf = (src_file, ModLocation { locSource = Just src_file, locHi = hi_file, locObj = Just obj_file } ) where src_file = path ++ '/':basename ++ suf hi_file = hi_path ++ '/':hi_basename ++ hi_suf hi_path | isJust hi_dir = fromJust hi_dir | otherwise = path hi_basename | isJust maybe_mod = fromJust maybe_mod | otherwise = basename obj_file = obj_path ++ '/':basename ++ obj_suf obj_path | isJust obj_dir = fromJust obj_dir | otherwise = path mk_hi_loc path basename suf = (hi_file, ModLocation { locSource = Nothing, locHi = hi_file, locObj = Nothing } ) where hi_file = path ++ '/':basename ++ package_hi_suf -- ----------------------------------------------------------------------------- -- Utils type Suffix = String -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext") splitFilename3 :: String -> (String,String,Suffix) splitFilename3 str = let (dir, rest) = split_longest_prefix str (=='/') (name, ext) = splitFilename rest real_dir | null dir = "." | otherwise = dir in (real_dir, name, ext) splitFilename :: String -> (String,Suffix) splitFilename f = split_longest_prefix f (=='.') -- split a string at the last character where 'pred' is True, -- returning a pair of strings. The first component holds the string -- up (but not including) the last character for which 'pred' returned -- True, the second whatever comes after (but also not including the -- last character). -- -- If 'pred' returns False for all characters in the string, the original -- string is returned in the second component (and the first one is just -- empty). split_longest_prefix :: String -> (Char -> Bool) -> (String,String) split_longest_prefix s pred = case pre of [] -> ([], reverse suf) (_:pre) -> (reverse pre, reverse suf) where (suf,pre) = break pred (reverse s)