[project @ 2005-11-04 15:48:25 by simonmar]
[ghc-hetmet.git] / ghc / compiler / utils / Util.lhs
index 6d2be04..1598c12 100644 (file)
@@ -9,10 +9,11 @@ module Util (
        -- general list processing
        zipEqual, zipWithEqual, zipWith3Equal, zipWith4Equal,
         zipLazy, stretchZipWith,
+       mapFst, mapSnd,
        mapAndUnzip, mapAndUnzip3,
        nOfThem, filterOut,
        lengthExceeds, lengthIs, lengthAtLeast, listLengthCmp, atLength,
-       isSingleton, only,
+       isSingleton, only, singleton,
        notNull, snocView,
 
        isIn, isn'tIn,
@@ -21,7 +22,7 @@ module Util (
        nTimes,
 
        -- sorting
-       sortLe,
+       sortLe, sortWith,
 
        -- transitive closures
        transitiveClosure,
@@ -30,11 +31,12 @@ module Util (
        mapAccumL, mapAccumR, mapAccumB, 
        foldl2, count,
        
-       takeList, dropList, splitAtList,
+       takeList, dropList, splitAtList, split,
 
        -- comparisons
        isEqual, eqListBy, equalLength, compareLength,
        thenCmp, cmpList, prefixMatch, suffixMatch, maybePrefixMatch,
+       removeSpaces,
 
        -- strictness
        foldl', seqList,
@@ -42,7 +44,7 @@ module Util (
        -- pairs
        unzipWith,
 
-       global,
+       global, consIORef,
 
        -- module names
        looksLikeModuleName,
@@ -51,19 +53,38 @@ module Util (
 
        -- Floating point stuff
        readRational,
+
+       -- IO-ish utilities
+       createDirectoryHierarchy,
+       doesDirNameExist,
+       modificationTimeIfExists,
+
+       later, handleDyn, handle,
+
+       -- Filename utils
+       Suffix,
+       splitFilename, suffixOf, basenameOf, joinFileExt,
+       splitFilenameDir, joinFileName,
+       splitFilename3,
+       splitLongestPrefix,
+       replaceFilenameSuffix, directoryOf, filenameOf,
+       replaceFilenameDirectory,
+       escapeSpaces, isPathSeparator,
+       parseSearchPath,
+       normalisePath, platformPath, pgmPath,
     ) where
 
-#include "../includes/ghcconfig.h"
 #include "HsVersions.h"
 
 import Panic           ( panic, trace )
 import FastTypes
 
-#if __GLASGOW_HASKELL__ <= 408
-import EXCEPTION       ( catchIO, justIoErrors, raiseInThread )
-#endif
+import EXCEPTION       ( Exception(..), finally, throwDyn, catchDyn, throw )
+import qualified EXCEPTION as Exception
+import DYNAMIC         ( Typeable )
 import DATA_IOREF      ( IORef, newIORef )
 import UNSAFE_IO       ( unsafePerformIO )
+import DATA_IOREF      ( readIORef, writeIORef )
 
 import qualified List  ( elem, notElem )
 
@@ -71,8 +92,13 @@ import qualified List        ( elem, notElem )
 import List            ( zipWith4 )
 #endif
 
+import Monad           ( when )
+import IO              ( catch, isDoesNotExistError )
+import Directory       ( doesDirectoryExist, createDirectory )
 import Char            ( isUpper, isAlphaNum, isSpace, ord, isDigit )
 import Ratio           ( (%) )
+import Time            ( ClockTime )
+import Directory       ( getModificationTime )
 
 infixr 9 `thenCmp`
 \end{code}
@@ -200,6 +226,12 @@ stretchZipWith p z f (x:xs) ys
 
 
 \begin{code}
+mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
+mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
+
+mapFst f xys = [(f x, y) | (x,y) <- xys]
+mapSnd f xys = [(x, f y) | (x,y) <- xys]
+
 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
 
 mapAndUnzip f [] = ([],[])
@@ -268,6 +300,9 @@ listLengthCmp = atLength atLen atEnd
   atLen []     = EQ
   atLen _      = GT
 
+singleton :: a -> [a]
+singleton x = [x]
+
 isSingleton :: [a] -> Bool
 isSingleton [x] = True
 isSingleton  _  = False
@@ -426,6 +461,11 @@ mergeSortLe le = generalMergeSort le
 
 sortLe :: (a->a->Bool) -> [a] -> [a]
 sortLe le = generalNaturalMergeSort le
+
+sortWith :: Ord b => (a->b) -> [a] -> [a]
+sortWith get_key xs = sortLe le xs
+  where
+    x `le` y = get_key x < get_key y   
 \end{code}
 
 %************************************************************************
@@ -567,6 +607,11 @@ splitAtList (_:xs) (y:ys) = (y:ys', ys'')
     where
       (ys', ys'') = splitAtList xs ys
 
+split :: Char -> String -> [String]
+split c s = case rest of
+               []     -> [chunk] 
+               _:rest -> chunk : split c rest
+  where (chunk, rest) = break (==c) s
 \end{code}
 
 
@@ -630,6 +675,9 @@ maybePrefixMatch (p:pat) (r:rest)
 
 suffixMatch :: Eq a => [a] -> [a] -> Bool
 suffixMatch pat str = prefixMatch (reverse pat) (reverse str)
+
+removeSpaces :: String -> String
+removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
 \end{code}
 
 %************************************************************************
@@ -681,6 +729,13 @@ global :: a -> IORef a
 global a = unsafePerformIO (newIORef a)
 \end{code}
 
+\begin{code}
+consIORef :: IORef [a] -> a -> IO ()
+consIORef var x = do
+  xs <- readIORef var
+  writeIORef var (x:xs)
+\end{code}
+
 Module names:
 
 \begin{code}
@@ -764,4 +819,204 @@ readRational top_s
          [x] -> x
          []  -> error ("readRational: no parse:"        ++ top_s)
          _   -> error ("readRational: ambiguous parse:" ++ top_s)
+
+
+-----------------------------------------------------------------------------
+-- Create a hierarchy of directories
+
+createDirectoryHierarchy :: FilePath -> IO ()
+createDirectoryHierarchy dir = do
+  b <- doesDirectoryExist dir
+  when (not b) $ do
+       createDirectoryHierarchy (directoryOf dir)
+       createDirectory dir
+
+-----------------------------------------------------------------------------
+-- Verify that the 'dirname' portion of a FilePath exists.
+-- 
+doesDirNameExist :: FilePath -> IO Bool
+doesDirNameExist fpath = doesDirectoryExist (directoryOf fpath)
+
+-- -----------------------------------------------------------------------------
+-- Exception utils
+
+later = flip finally
+
+handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
+handleDyn = flip catchDyn
+
+handle :: (Exception -> IO a) -> IO a -> IO a
+#if __GLASGOW_HASKELL__ < 501
+handle = flip Exception.catchAllIO
+#else
+handle h f = f `Exception.catch` \e -> case e of
+    ExitException _ -> throw e
+    _               -> h e
+#endif
+
+-- --------------------------------------------------------------
+-- check existence & modification time at the same time
+
+modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
+modificationTimeIfExists f = do
+  (do t <- getModificationTime f; return (Just t))
+       `IO.catch` \e -> if isDoesNotExistError e 
+                       then return Nothing 
+                       else ioError e
+
+-- --------------------------------------------------------------
+-- Filename manipulation
+               
+-- Filenames are kept "normalised" inside GHC, using '/' as the path
+-- separator.  On Windows these functions will also recognise '\\' as
+-- the path separator, but will generally construct paths using '/'.
+
+type Suffix = String
+
+splitFilename :: String -> (String,Suffix)
+splitFilename f = splitLongestPrefix f (=='.')
+
+basenameOf :: FilePath -> String
+basenameOf = fst . splitFilename
+
+suffixOf :: FilePath -> Suffix
+suffixOf = snd . splitFilename
+
+joinFileExt :: String -> String -> FilePath
+joinFileExt path ""  = path
+joinFileExt path ext = path ++ '.':ext
+
+-- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy.ext")
+splitFilenameDir :: String -> (String,String)
+splitFilenameDir str
+   = let (dir, rest) = splitLongestPrefix str isPathSeparator
+        (dir', rest') | null rest = (".", dir)
+                      | otherwise = (dir, rest)
+     in  (dir', rest')
+
+-- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext")
+splitFilename3 :: String -> (String,String,Suffix)
+splitFilename3 str
+   = let (dir, rest) = splitFilenameDir str
+        (name, ext) = splitFilename rest
+     in  (dir, name, ext)
+
+joinFileName :: String -> String -> FilePath
+joinFileName ""  fname = fname
+joinFileName "." fname = fname
+joinFileName dir ""    = dir
+joinFileName dir fname = dir ++ '/':fname
+
+-- 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 first component (and the second one is just
+-- empty).
+splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
+splitLongestPrefix str pred
+  | null r_pre = (str,           [])
+  | otherwise  = (reverse (tail r_pre), reverse r_suf)
+       -- 'tail' drops the char satisfying 'pred'
+  where 
+    (r_suf, r_pre) = break pred (reverse str)
+
+replaceFilenameSuffix :: FilePath -> Suffix -> FilePath
+replaceFilenameSuffix file suf = basenameOf file `joinFileExt` suf
+
+-- directoryOf strips the filename off the input string, returning
+-- the directory.
+directoryOf :: FilePath -> String
+directoryOf = fst . splitFilenameDir
+
+-- filenameOf strips the directory off the input string, returning
+-- the filename.
+filenameOf :: FilePath -> String
+filenameOf = snd . splitFilenameDir
+
+replaceFilenameDirectory :: FilePath -> String -> FilePath
+replaceFilenameDirectory path dir = dir `joinFileName` filenameOf path
+
+escapeSpaces :: String -> String
+escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
+
+isPathSeparator :: Char -> Bool
+isPathSeparator ch =
+#ifdef mingw32_TARGET_OS
+  ch == '/' || ch == '\\'
+#else
+  ch == '/'
+#endif
+
+--------------------------------------------------------------
+-- * Search path
+--------------------------------------------------------------
+
+-- | The function splits the given string to substrings
+-- using the 'searchPathSeparator'.
+parseSearchPath :: String -> [FilePath]
+parseSearchPath path = split path
+  where
+    split :: String -> [String]
+    split s =
+      case rest' of
+        []     -> [chunk] 
+        _:rest -> chunk : split rest
+      where
+        chunk = 
+          case chunk' of
+#ifdef mingw32_HOST_OS
+            ('\"':xs@(_:_)) | last xs == '\"' -> init xs
+#endif
+            _                                 -> chunk'
+
+        (chunk', rest') = break (==searchPathSeparator) s
+
+-- | A platform-specific character used to separate search path strings in 
+-- environment variables. The separator is a colon (\":\") on Unix and Macintosh, 
+-- and a semicolon (\";\") on the Windows operating system.
+searchPathSeparator :: Char
+#if mingw32_HOST_OS || mingw32_TARGET_OS
+searchPathSeparator = ';'
+#else
+searchPathSeparator = ':'
+#endif
+
+-----------------------------------------------------------------------------
+-- Convert filepath into platform / MSDOS form.
+
+-- We maintain path names in Unix form ('/'-separated) right until 
+-- the last moment.  On Windows we dos-ify them just before passing them
+-- to the Windows command.
+-- 
+-- The alternative, of using '/' consistently on Unix and '\' on Windows,
+-- proved quite awkward.  There were a lot more calls to platformPath,
+-- and even on Windows we might invoke a unix-like utility (eg 'sh'), which
+-- interpreted a command line 'foo\baz' as 'foobaz'.
+
+normalisePath :: String -> String
+-- Just changes '\' to '/'
+
+pgmPath :: String              -- Directory string in Unix format
+       -> String               -- Program name with no directory separators
+                               --      (e.g. copy /y)
+       -> String               -- Program invocation string in native format
+
+#if defined(mingw32_HOST_OS)
+--------------------- Windows version ------------------
+normalisePath xs = subst '\\' '/' xs
+pgmPath dir pgm  = platformPath dir ++ '\\' : pgm
+platformPath p   = subst '/' '\\' p
+
+subst a b ls = map (\ x -> if x == a then b else x) ls
+#else
+--------------------- Non-Windows version --------------
+normalisePath xs   = xs
+pgmPath dir pgm    = dir ++ '/' : pgm
+platformPath stuff = stuff
+--------------------------------------------------------
+#endif
 \end{code}