2 % (c) The University of Glasgow 2006
3 % (c) The University of Glasgow 1992-2002
5 \section[Util]{Highly random utility functions}
10 -- general list processing
11 zipEqual, zipWithEqual, zipWith3Equal, zipWith4Equal,
12 zipLazy, stretchZipWith,
14 mapAndUnzip, mapAndUnzip3,
15 nOfThem, filterOut, partitionWith, splitEithers,
17 lengthExceeds, lengthIs, lengthAtLeast,
18 listLengthCmp, atLength, equalLength, compareLength,
20 isSingleton, only, singleton,
31 -- transitive closures
35 mapAccumL, mapAccumR, mapAccumB,
38 takeList, dropList, splitAtList, split,
42 thenCmp, cmpList, maybePrefixMatch,
58 -- Floating point stuff
62 createDirectoryHierarchy,
64 modificationTimeIfExists,
66 later, handleDyn, handle,
70 splitFilename, suffixOf, basenameOf, joinFileExt,
71 splitFilenameDir, joinFileName,
74 replaceFilenameSuffix, directoryOf, filenameOf,
75 replaceFilenameDirectory,
76 escapeSpaces, isPathSeparator,
78 normalisePath, platformPath, pgmPath,
81 #include "HsVersions.h"
83 import Panic ( panic, trace )
86 import Control.Exception ( Exception(..), finally, catchDyn, throw )
87 import qualified Control.Exception as Exception
88 import Data.Dynamic ( Typeable )
89 import Data.IORef ( IORef, newIORef )
90 import System.IO.Unsafe ( unsafePerformIO )
91 import Data.IORef ( readIORef, writeIORef )
93 import qualified Data.List as List ( elem, notElem )
96 import Data.List ( zipWith4 )
99 import Control.Monad ( when )
100 import SYSTEM_IO_ERROR as IO ( catch, isDoesNotExistError )
101 import System.Directory ( doesDirectoryExist, createDirectory,
102 getModificationTime )
103 import Data.Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
104 import Data.Ratio ( (%) )
105 import System.Time ( ClockTime )
110 %************************************************************************
112 \subsection{The Eager monad}
114 %************************************************************************
116 The @Eager@ monad is just an encoding of continuation-passing style,
117 used to allow you to express "do this and then that", mainly to avoid
118 space leaks. It's done with a type synonym to save bureaucracy.
123 type Eager ans a = (a -> ans) -> ans
125 runEager :: Eager a a -> a
126 runEager m = m (\x -> x)
128 appEager :: Eager ans a -> (a -> ans) -> ans
129 appEager m cont = m cont
131 thenEager :: Eager ans a -> (a -> Eager ans b) -> Eager ans b
132 thenEager m k cont = m (\r -> k r cont)
134 returnEager :: a -> Eager ans a
135 returnEager v cont = cont v
137 mapEager :: (a -> Eager ans b) -> [a] -> Eager ans [b]
138 mapEager f [] = returnEager []
139 mapEager f (x:xs) = f x `thenEager` \ y ->
140 mapEager f xs `thenEager` \ ys ->
145 %************************************************************************
147 \subsection{A for loop}
149 %************************************************************************
152 -- Compose a function with itself n times. (nth rather than twice)
153 nTimes :: Int -> (a -> a) -> (a -> a)
156 nTimes n f = f . nTimes (n-1) f
159 %************************************************************************
161 \subsection[Utils-lists]{General list processing}
163 %************************************************************************
166 filterOut :: (a->Bool) -> [a] -> [a]
167 -- Like filter, only reverses the sense of the test
169 filterOut p (x:xs) | p x = filterOut p xs
170 | otherwise = x : filterOut p xs
172 partitionWith :: (a -> Either b c) -> [a] -> ([b], [c])
173 partitionWith f [] = ([],[])
174 partitionWith f (x:xs) = case f x of
176 Right c -> (bs, c:cs)
178 (bs,cs) = partitionWith f xs
180 splitEithers :: [Either a b] -> ([a], [b])
181 splitEithers [] = ([],[])
182 splitEithers (e : es) = case e of
184 Right y -> (xs, y:ys)
186 (xs,ys) = splitEithers es
189 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
190 are of equal length. Alastair Reid thinks this should only happen if
191 DEBUGging on; hey, why not?
194 zipEqual :: String -> [a] -> [b] -> [(a,b)]
195 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
196 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
197 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
201 zipWithEqual _ = zipWith
202 zipWith3Equal _ = zipWith3
203 zipWith4Equal _ = zipWith4
205 zipEqual msg [] [] = []
206 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
207 zipEqual msg as bs = panic ("zipEqual: unequal lists:"++msg)
209 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
210 zipWithEqual msg _ [] [] = []
211 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
213 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
214 = z a b c : zipWith3Equal msg z as bs cs
215 zipWith3Equal msg _ [] [] [] = []
216 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
218 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
219 = z a b c d : zipWith4Equal msg z as bs cs ds
220 zipWith4Equal msg _ [] [] [] [] = []
221 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
226 -- zipLazy is lazy in the second list (observe the ~)
228 zipLazy :: [a] -> [b] -> [(a,b)]
230 zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
235 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
236 -- (stretchZipWith p z f xs ys) stretches ys by inserting z in
237 -- the places where p returns *True*
239 stretchZipWith p z f [] ys = []
240 stretchZipWith p z f (x:xs) ys
241 | p x = f x z : stretchZipWith p z f xs ys
242 | otherwise = case ys of
244 (y:ys) -> f x y : stretchZipWith p z f xs ys
249 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
250 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
252 mapFst f xys = [(f x, y) | (x,y) <- xys]
253 mapSnd f xys = [(x, f y) | (x,y) <- xys]
255 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
257 mapAndUnzip f [] = ([],[])
261 (rs1, rs2) = mapAndUnzip f xs
265 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
267 mapAndUnzip3 f [] = ([],[],[])
268 mapAndUnzip3 f (x:xs)
271 (rs1, rs2, rs3) = mapAndUnzip3 f xs
273 (r1:rs1, r2:rs2, r3:rs3)
277 nOfThem :: Int -> a -> [a]
278 nOfThem n thing = replicate n thing
280 -- 'atLength atLen atEnd ls n' unravels list 'ls' to position 'n';
283 -- atLength atLenPred atEndPred ls n
284 -- | n < 0 = atLenPred n
285 -- | length ls < n = atEndPred (n - length ls)
286 -- | otherwise = atLenPred (drop n ls)
288 atLength :: ([a] -> b)
293 atLength atLenPred atEndPred ls n
294 | n < 0 = atEndPred n
295 | otherwise = go n ls
297 go n [] = atEndPred n
298 go 0 ls = atLenPred ls
299 go n (_:xs) = go (n-1) xs
302 lengthExceeds :: [a] -> Int -> Bool
303 -- (lengthExceeds xs n) = (length xs > n)
304 lengthExceeds = atLength notNull (const False)
306 lengthAtLeast :: [a] -> Int -> Bool
307 lengthAtLeast = atLength notNull (== 0)
309 lengthIs :: [a] -> Int -> Bool
310 lengthIs = atLength null (==0)
312 listLengthCmp :: [a] -> Int -> Ordering
313 listLengthCmp = atLength atLen atEnd
317 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
323 equalLength :: [a] -> [b] -> Bool
324 equalLength [] [] = True
325 equalLength (_:xs) (_:ys) = equalLength xs ys
326 equalLength xs ys = False
328 compareLength :: [a] -> [b] -> Ordering
329 compareLength [] [] = EQ
330 compareLength (_:xs) (_:ys) = compareLength xs ys
331 compareLength [] _ys = LT
332 compareLength _xs [] = GT
334 ----------------------------
335 singleton :: a -> [a]
338 isSingleton :: [a] -> Bool
339 isSingleton [x] = True
340 isSingleton _ = False
342 notNull :: [a] -> Bool
354 Debugging/specialising versions of \tr{elem} and \tr{notElem}
357 isIn, isn'tIn :: (Eq a) => String -> a -> [a] -> Bool
360 isIn msg x ys = elem__ x ys
361 isn'tIn msg x ys = notElem__ x ys
363 --these are here to be SPECIALIZEd (automagically)
365 elem__ x (y:ys) = x==y || elem__ x ys
367 notElem__ x [] = True
368 notElem__ x (y:ys) = x /= y && notElem__ x ys
372 = elem (_ILIT 0) x ys
376 | i ># _ILIT 100 = trace ("Over-long elem in " ++ msg) $
378 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
381 = notElem (_ILIT 0) x ys
383 notElem i x [] = True
385 | i ># _ILIT 100 = trace ("Over-long notElem in " ++ msg) $
386 x `List.notElem` (y:ys)
387 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
391 %************************************************************************
393 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
395 %************************************************************************
398 Date: Mon, 3 May 93 20:45:23 +0200
399 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
400 To: partain@dcs.gla.ac.uk
401 Subject: natural merge sort beats quick sort [ and it is prettier ]
403 Here is a piece of Haskell code that I'm rather fond of. See it as an
404 attempt to get rid of the ridiculous quick-sort routine. group is
405 quite useful by itself I think it was John's idea originally though I
406 believe the lazy version is due to me [surprisingly complicated].
407 gamma [used to be called] is called gamma because I got inspired by
408 the Gamma calculus. It is not very close to the calculus but does
409 behave less sequentially than both foldr and foldl. One could imagine
410 a version of gamma that took a unit element as well thereby avoiding
411 the problem with empty lists.
413 I've tried this code against
415 1) insertion sort - as provided by haskell
416 2) the normal implementation of quick sort
417 3) a deforested version of quick sort due to Jan Sparud
418 4) a super-optimized-quick-sort of Lennart's
420 If the list is partially sorted both merge sort and in particular
421 natural merge sort wins. If the list is random [ average length of
422 rising subsequences = approx 2 ] mergesort still wins and natural
423 merge sort is marginally beaten by Lennart's soqs. The space
424 consumption of merge sort is a bit worse than Lennart's quick sort
425 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
426 fpca article ] isn't used because of group.
433 group :: (a -> a -> Bool) -> [a] -> [[a]]
434 -- Given a <= function, group finds maximal contiguous up-runs
435 -- or down-runs in the input list.
436 -- It's stable, in the sense that it never re-orders equal elements
438 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
439 -- From: Andy Gill <andy@dcs.gla.ac.uk>
440 -- Here is a `better' definition of group.
443 group p (x:xs) = group' xs x x (x :)
445 group' [] _ _ s = [s []]
446 group' (x:xs) x_min x_max s
447 | x_max `p` x = group' xs x_min x (s . (x :))
448 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
449 | otherwise = s [] : group' xs x x (x :)
450 -- NB: the 'not' is essential for stablity
451 -- x `p` x_min would reverse equal elements
453 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
454 generalMerge p xs [] = xs
455 generalMerge p [] ys = ys
456 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
457 | otherwise = y : generalMerge p (x:xs) ys
459 -- gamma is now called balancedFold
461 balancedFold :: (a -> a -> a) -> [a] -> a
462 balancedFold f [] = error "can't reduce an empty list using balancedFold"
463 balancedFold f [x] = x
464 balancedFold f l = balancedFold f (balancedFold' f l)
466 balancedFold' :: (a -> a -> a) -> [a] -> [a]
467 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
468 balancedFold' f xs = xs
470 generalNaturalMergeSort p [] = []
471 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
474 generalMergeSort p [] = []
475 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
477 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
479 mergeSort = generalMergeSort (<=)
480 naturalMergeSort = generalNaturalMergeSort (<=)
482 mergeSortLe le = generalMergeSort le
485 sortLe :: (a->a->Bool) -> [a] -> [a]
486 sortLe le = generalNaturalMergeSort le
488 sortWith :: Ord b => (a->b) -> [a] -> [a]
489 sortWith get_key xs = sortLe le xs
491 x `le` y = get_key x < get_key y
494 %************************************************************************
496 \subsection[Utils-transitive-closure]{Transitive closure}
498 %************************************************************************
500 This algorithm for transitive closure is straightforward, albeit quadratic.
503 transitiveClosure :: (a -> [a]) -- Successor function
504 -> (a -> a -> Bool) -- Equality predicate
506 -> [a] -- The transitive closure
508 transitiveClosure succ eq xs
512 go done (x:xs) | x `is_in` done = go done xs
513 | otherwise = go (x:done) (succ x ++ xs)
516 x `is_in` (y:ys) | eq x y = True
517 | otherwise = x `is_in` ys
520 %************************************************************************
522 \subsection[Utils-accum]{Accumulating}
524 %************************************************************************
526 @mapAccumL@ behaves like a combination
527 of @map@ and @foldl@;
528 it applies a function to each element of a list, passing an accumulating
529 parameter from left to right, and returning a final value of this
530 accumulator together with the new list.
533 mapAccumL :: (acc -> x -> (acc, y)) -- Function of elt of input list
534 -- and accumulator, returning new
535 -- accumulator and elt of result list
536 -> acc -- Initial accumulator
538 -> (acc, [y]) -- Final accumulator and result list
540 mapAccumL f b [] = (b, [])
541 mapAccumL f b (x:xs) = (b'', x':xs') where
543 (b'', xs') = mapAccumL f b' xs
546 @mapAccumR@ does the same, but working from right to left instead. Its type is
547 the same as @mapAccumL@, though.
550 mapAccumR :: (acc -> x -> (acc, y)) -- Function of elt of input list
551 -- and accumulator, returning new
552 -- accumulator and elt of result list
553 -> acc -- Initial accumulator
555 -> (acc, [y]) -- Final accumulator and result list
557 mapAccumR f b [] = (b, [])
558 mapAccumR f b (x:xs) = (b'', x':xs') where
560 (b', xs') = mapAccumR f b xs
563 Here is the bi-directional version, that works from both left and right.
566 mapAccumB :: (accl -> accr -> x -> (accl, accr,y))
567 -- Function of elt of input list
568 -- and accumulator, returning new
569 -- accumulator and elt of result list
570 -> accl -- Initial accumulator from left
571 -> accr -- Initial accumulator from right
573 -> (accl, accr, [y]) -- Final accumulators and result list
575 mapAccumB f a b [] = (a,b,[])
576 mapAccumB f a b (x:xs) = (a'',b'',y:ys)
578 (a',b'',y) = f a b' x
579 (a'',b',ys) = mapAccumB f a' b xs
582 A strict version of foldl.
585 foldl' :: (a -> b -> a) -> a -> [b] -> a
586 foldl' f z xs = lgo z xs
589 lgo z (x:xs) = (lgo $! (f z x)) xs
592 A combination of foldl with zip. It works with equal length lists.
595 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
597 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
599 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
600 -- True if the lists are the same length, and
601 -- all corresponding elements satisfy the predicate
603 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
607 Count the number of times a predicate is true
610 count :: (a -> Bool) -> [a] -> Int
612 count p (x:xs) | p x = 1 + count p xs
613 | otherwise = count p xs
616 @splitAt@, @take@, and @drop@ but with length of another
617 list giving the break-off point:
620 takeList :: [b] -> [a] -> [a]
625 (y:ys) -> y : takeList xs ys
627 dropList :: [b] -> [a] -> [a]
629 dropList _ xs@[] = xs
630 dropList (_:xs) (_:ys) = dropList xs ys
633 splitAtList :: [b] -> [a] -> ([a], [a])
634 splitAtList [] xs = ([], xs)
635 splitAtList _ xs@[] = (xs, xs)
636 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
638 (ys', ys'') = splitAtList xs ys
640 snocView :: [a] -> Maybe ([a],a)
641 -- Split off the last element
642 snocView [] = Nothing
643 snocView xs = go [] xs
645 -- Invariant: second arg is non-empty
646 go acc [x] = Just (reverse acc, x)
647 go acc (x:xs) = go (x:acc) xs
649 split :: Char -> String -> [String]
650 split c s = case rest of
652 _:rest -> chunk : split c rest
653 where (chunk, rest) = break (==c) s
657 %************************************************************************
659 \subsection[Utils-comparison]{Comparisons}
661 %************************************************************************
664 isEqual :: Ordering -> Bool
665 -- Often used in (isEqual (a `compare` b))
670 thenCmp :: Ordering -> Ordering -> Ordering
671 {-# INLINE thenCmp #-}
673 thenCmp other any = other
675 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
676 eqListBy eq [] [] = True
677 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
678 eqListBy eq xs ys = False
680 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
681 -- `cmpList' uses a user-specified comparer
683 cmpList cmp [] [] = EQ
684 cmpList cmp [] _ = LT
685 cmpList cmp _ [] = GT
686 cmpList cmp (a:as) (b:bs)
687 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
691 maybePrefixMatch :: String -> String -> Maybe String
692 maybePrefixMatch [] rest = Just rest
693 maybePrefixMatch (_:_) [] = Nothing
694 maybePrefixMatch (p:pat) (r:rest)
695 | p == r = maybePrefixMatch pat rest
696 | otherwise = Nothing
698 removeSpaces :: String -> String
699 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
702 %************************************************************************
704 \subsection[Utils-pairs]{Pairs}
706 %************************************************************************
708 The following provide us higher order functions that, when applied
709 to a function, operate on pairs.
713 applyToPair :: ((a -> c),(b -> d)) -> (a,b) -> (c,d)
714 applyToPair (f,g) (x,y) = (f x, g y)
716 applyToFst :: (a -> c) -> (a,b)-> (c,b)
717 applyToFst f (x,y) = (f x,y)
719 applyToSnd :: (b -> d) -> (a,b) -> (a,d)
720 applyToSnd f (x,y) = (x,f y)
725 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
726 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
730 seqList :: [a] -> b -> b
732 seqList (x:xs) b = x `seq` seqList xs b
738 global :: a -> IORef a
739 global a = unsafePerformIO (newIORef a)
743 consIORef :: IORef [a] -> a -> IO ()
746 writeIORef var (x:xs)
752 looksLikeModuleName [] = False
753 looksLikeModuleName (c:cs) = isUpper c && go cs
755 go ('.':cs) = looksLikeModuleName cs
756 go (c:cs) = (isAlphaNum c || c == '_') && go cs
759 Akin to @Prelude.words@, but acts like the Bourne shell, treating
760 quoted strings and escaped characters within the input as solid blocks
761 of characters. Doesn't raise any exceptions on malformed escapes or
765 toArgs :: String -> [String]
768 case dropWhile isSpace s of -- drop initial spacing
769 [] -> [] -- empty, so no more tokens
770 rem -> let (tok,aft) = token rem [] in tok : toArgs aft
772 -- Grab a token off the string, given that the first character exists and
773 -- isn't whitespace. The second argument is an accumulator which has to be
774 -- reversed at the end.
775 token [] acc = (reverse acc,[]) -- out of characters
776 token ('\\':c:aft) acc -- escapes
777 = token aft ((escape c) : acc)
778 token (q:aft) acc | q == '"' || q == '\'' -- open quotes
779 = let (aft',acc') = quote q aft acc in token aft' acc'
780 token (c:aft) acc | isSpace c -- unescaped, unquoted spacing
782 token (c:aft) acc -- anything else goes in the token
785 -- Get the appropriate character for a single-character escape.
791 -- Read into accumulator until a quote character is found.
793 let quote' [] acc = ([],acc)
794 quote' ('\\':c:aft) acc = quote' aft ((escape c) : acc)
795 quote' (c:aft) acc | c == qc = (aft,acc)
796 quote' (c:aft) acc = quote' aft (c:acc)
800 -- -----------------------------------------------------------------------------
804 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
805 readRational__ r = do
808 return ((n%1)*10^^(k-d), t)
811 (ds,s) <- lexDecDigits r
812 (ds',t) <- lexDotDigits s
813 return (read (ds++ds'), length ds', t)
815 readExp (e:s) | e `elem` "eE" = readExp' s
816 readExp s = return (0,s)
818 readExp' ('+':s) = readDec s
819 readExp' ('-':s) = do
822 readExp' s = readDec s
825 (ds,r) <- nonnull isDigit s
826 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
829 lexDecDigits = nonnull isDigit
831 lexDotDigits ('.':s) = return (span isDigit s)
832 lexDotDigits s = return ("",s)
834 nonnull p s = do (cs@(_:_),t) <- return (span p s)
837 readRational :: String -> Rational -- NB: *does* handle a leading "-"
840 '-' : xs -> - (read_me xs)
844 = case (do { (x,"") <- readRational__ s ; return x }) of
846 [] -> error ("readRational: no parse:" ++ top_s)
847 _ -> error ("readRational: ambiguous parse:" ++ top_s)
850 -----------------------------------------------------------------------------
851 -- Create a hierarchy of directories
853 createDirectoryHierarchy :: FilePath -> IO ()
854 createDirectoryHierarchy dir = do
855 b <- doesDirectoryExist dir
857 createDirectoryHierarchy (directoryOf dir)
860 -----------------------------------------------------------------------------
861 -- Verify that the 'dirname' portion of a FilePath exists.
863 doesDirNameExist :: FilePath -> IO Bool
864 doesDirNameExist fpath = doesDirectoryExist (directoryOf fpath)
866 -- -----------------------------------------------------------------------------
871 handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
872 handleDyn = flip catchDyn
874 handle :: (Exception -> IO a) -> IO a -> IO a
875 handle h f = f `Exception.catch` \e -> case e of
876 ExitException _ -> throw e
879 -- --------------------------------------------------------------
880 -- check existence & modification time at the same time
882 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
883 modificationTimeIfExists f = do
884 (do t <- getModificationTime f; return (Just t))
885 `IO.catch` \e -> if isDoesNotExistError e
889 -- --------------------------------------------------------------
890 -- Filename manipulation
892 -- Filenames are kept "normalised" inside GHC, using '/' as the path
893 -- separator. On Windows these functions will also recognise '\\' as
894 -- the path separator, but will generally construct paths using '/'.
898 splitFilename :: String -> (String,Suffix)
899 splitFilename f = splitLongestPrefix f (=='.')
901 basenameOf :: FilePath -> String
902 basenameOf = fst . splitFilename
904 suffixOf :: FilePath -> Suffix
905 suffixOf = snd . splitFilename
907 joinFileExt :: String -> String -> FilePath
908 joinFileExt path "" = path
909 joinFileExt path ext = path ++ '.':ext
911 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy.ext")
912 splitFilenameDir :: String -> (String,String)
914 = let (dir, rest) = splitLongestPrefix str isPathSeparator
915 (dir', rest') | null rest = (".", dir)
916 | otherwise = (dir, rest)
919 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext")
920 splitFilename3 :: String -> (String,String,Suffix)
922 = let (dir, rest) = splitFilenameDir str
923 (name, ext) = splitFilename rest
926 joinFileName :: String -> String -> FilePath
927 joinFileName "" fname = fname
928 joinFileName "." fname = fname
929 joinFileName dir "" = dir
930 joinFileName dir fname = dir ++ '/':fname
932 -- split a string at the last character where 'pred' is True,
933 -- returning a pair of strings. The first component holds the string
934 -- up (but not including) the last character for which 'pred' returned
935 -- True, the second whatever comes after (but also not including the
938 -- If 'pred' returns False for all characters in the string, the original
939 -- string is returned in the first component (and the second one is just
941 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
942 splitLongestPrefix str pred
943 | null r_pre = (str, [])
944 | otherwise = (reverse (tail r_pre), reverse r_suf)
945 -- 'tail' drops the char satisfying 'pred'
947 (r_suf, r_pre) = break pred (reverse str)
949 replaceFilenameSuffix :: FilePath -> Suffix -> FilePath
950 replaceFilenameSuffix file suf = basenameOf file `joinFileExt` suf
952 -- directoryOf strips the filename off the input string, returning
954 directoryOf :: FilePath -> String
955 directoryOf = fst . splitFilenameDir
957 -- filenameOf strips the directory off the input string, returning
959 filenameOf :: FilePath -> String
960 filenameOf = snd . splitFilenameDir
962 replaceFilenameDirectory :: FilePath -> String -> FilePath
963 replaceFilenameDirectory path dir = dir `joinFileName` filenameOf path
965 escapeSpaces :: String -> String
966 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
968 isPathSeparator :: Char -> Bool
970 #ifdef mingw32_TARGET_OS
971 ch == '/' || ch == '\\'
976 --------------------------------------------------------------
978 --------------------------------------------------------------
980 -- | The function splits the given string to substrings
981 -- using the 'searchPathSeparator'.
982 parseSearchPath :: String -> [FilePath]
983 parseSearchPath path = split path
985 split :: String -> [String]
989 _:rest -> chunk : split rest
993 #ifdef mingw32_HOST_OS
994 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
998 (chunk', rest') = break (==searchPathSeparator) s
1000 -- | A platform-specific character used to separate search path strings in
1001 -- environment variables. The separator is a colon (\":\") on Unix and Macintosh,
1002 -- and a semicolon (\";\") on the Windows operating system.
1003 searchPathSeparator :: Char
1004 #if mingw32_HOST_OS || mingw32_TARGET_OS
1005 searchPathSeparator = ';'
1007 searchPathSeparator = ':'
1010 -----------------------------------------------------------------------------
1011 -- Convert filepath into platform / MSDOS form.
1013 -- We maintain path names in Unix form ('/'-separated) right until
1014 -- the last moment. On Windows we dos-ify them just before passing them
1015 -- to the Windows command.
1017 -- The alternative, of using '/' consistently on Unix and '\' on Windows,
1018 -- proved quite awkward. There were a lot more calls to platformPath,
1019 -- and even on Windows we might invoke a unix-like utility (eg 'sh'), which
1020 -- interpreted a command line 'foo\baz' as 'foobaz'.
1022 normalisePath :: String -> String
1023 -- Just changes '\' to '/'
1025 pgmPath :: String -- Directory string in Unix format
1026 -> String -- Program name with no directory separators
1028 -> String -- Program invocation string in native format
1030 #if defined(mingw32_HOST_OS)
1031 --------------------- Windows version ------------------
1032 normalisePath xs = subst '\\' '/' xs
1033 pgmPath dir pgm = platformPath dir ++ '\\' : pgm
1034 platformPath p = subst '/' '\\' p
1036 subst a b ls = map (\ x -> if x == a then b else x) ls
1038 --------------------- Non-Windows version --------------
1039 normalisePath xs = xs
1040 pgmPath dir pgm = dir ++ '/' : pgm
1041 platformPath stuff = stuff
1042 --------------------------------------------------------