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,
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, prefixMatch, suffixMatch, 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
173 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
174 are of equal length. Alastair Reid thinks this should only happen if
175 DEBUGging on; hey, why not?
178 zipEqual :: String -> [a] -> [b] -> [(a,b)]
179 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
180 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
181 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
185 zipWithEqual _ = zipWith
186 zipWith3Equal _ = zipWith3
187 zipWith4Equal _ = zipWith4
189 zipEqual msg [] [] = []
190 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
191 zipEqual msg as bs = panic ("zipEqual: unequal lists:"++msg)
193 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
194 zipWithEqual msg _ [] [] = []
195 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
197 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
198 = z a b c : zipWith3Equal msg z as bs cs
199 zipWith3Equal msg _ [] [] [] = []
200 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
202 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
203 = z a b c d : zipWith4Equal msg z as bs cs ds
204 zipWith4Equal msg _ [] [] [] [] = []
205 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
210 -- zipLazy is lazy in the second list (observe the ~)
212 zipLazy :: [a] -> [b] -> [(a,b)]
214 zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
219 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
220 -- (stretchZipWith p z f xs ys) stretches ys by inserting z in
221 -- the places where p returns *True*
223 stretchZipWith p z f [] ys = []
224 stretchZipWith p z f (x:xs) ys
225 | p x = f x z : stretchZipWith p z f xs ys
226 | otherwise = case ys of
228 (y:ys) -> f x y : stretchZipWith p z f xs ys
233 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
234 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
236 mapFst f xys = [(f x, y) | (x,y) <- xys]
237 mapSnd f xys = [(x, f y) | (x,y) <- xys]
239 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
241 mapAndUnzip f [] = ([],[])
245 (rs1, rs2) = mapAndUnzip f xs
249 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
251 mapAndUnzip3 f [] = ([],[],[])
252 mapAndUnzip3 f (x:xs)
255 (rs1, rs2, rs3) = mapAndUnzip3 f xs
257 (r1:rs1, r2:rs2, r3:rs3)
261 nOfThem :: Int -> a -> [a]
262 nOfThem n thing = replicate n thing
264 -- 'atLength atLen atEnd ls n' unravels list 'ls' to position 'n';
267 -- atLength atLenPred atEndPred ls n
268 -- | n < 0 = atLenPred n
269 -- | length ls < n = atEndPred (n - length ls)
270 -- | otherwise = atLenPred (drop n ls)
272 atLength :: ([a] -> b)
277 atLength atLenPred atEndPred ls n
278 | n < 0 = atEndPred n
279 | otherwise = go n ls
281 go n [] = atEndPred n
282 go 0 ls = atLenPred ls
283 go n (_:xs) = go (n-1) xs
286 lengthExceeds :: [a] -> Int -> Bool
287 -- (lengthExceeds xs n) = (length xs > n)
288 lengthExceeds = atLength notNull (const False)
290 lengthAtLeast :: [a] -> Int -> Bool
291 lengthAtLeast = atLength notNull (== 0)
293 lengthIs :: [a] -> Int -> Bool
294 lengthIs = atLength null (==0)
296 listLengthCmp :: [a] -> Int -> Ordering
297 listLengthCmp = atLength atLen atEnd
301 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
307 equalLength :: [a] -> [b] -> Bool
308 equalLength [] [] = True
309 equalLength (_:xs) (_:ys) = equalLength xs ys
310 equalLength xs ys = False
312 compareLength :: [a] -> [b] -> Ordering
313 compareLength [] [] = EQ
314 compareLength (_:xs) (_:ys) = compareLength xs ys
315 compareLength [] _ys = LT
316 compareLength _xs [] = GT
318 ----------------------------
319 singleton :: a -> [a]
322 isSingleton :: [a] -> Bool
323 isSingleton [x] = True
324 isSingleton _ = False
326 notNull :: [a] -> Bool
338 Debugging/specialising versions of \tr{elem} and \tr{notElem}
341 isIn, isn'tIn :: (Eq a) => String -> a -> [a] -> Bool
344 isIn msg x ys = elem__ x ys
345 isn'tIn msg x ys = notElem__ x ys
347 --these are here to be SPECIALIZEd (automagically)
349 elem__ x (y:ys) = x==y || elem__ x ys
351 notElem__ x [] = True
352 notElem__ x (y:ys) = x /= y && notElem__ x ys
356 = elem (_ILIT 0) x ys
360 | i ># _ILIT 100 = trace ("Over-long elem in " ++ msg) $
362 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
365 = notElem (_ILIT 0) x ys
367 notElem i x [] = True
369 | i ># _ILIT 100 = trace ("Over-long notElem in " ++ msg) $
370 x `List.notElem` (y:ys)
371 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
375 %************************************************************************
377 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
379 %************************************************************************
382 Date: Mon, 3 May 93 20:45:23 +0200
383 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
384 To: partain@dcs.gla.ac.uk
385 Subject: natural merge sort beats quick sort [ and it is prettier ]
387 Here is a piece of Haskell code that I'm rather fond of. See it as an
388 attempt to get rid of the ridiculous quick-sort routine. group is
389 quite useful by itself I think it was John's idea originally though I
390 believe the lazy version is due to me [surprisingly complicated].
391 gamma [used to be called] is called gamma because I got inspired by
392 the Gamma calculus. It is not very close to the calculus but does
393 behave less sequentially than both foldr and foldl. One could imagine
394 a version of gamma that took a unit element as well thereby avoiding
395 the problem with empty lists.
397 I've tried this code against
399 1) insertion sort - as provided by haskell
400 2) the normal implementation of quick sort
401 3) a deforested version of quick sort due to Jan Sparud
402 4) a super-optimized-quick-sort of Lennart's
404 If the list is partially sorted both merge sort and in particular
405 natural merge sort wins. If the list is random [ average length of
406 rising subsequences = approx 2 ] mergesort still wins and natural
407 merge sort is marginally beaten by Lennart's soqs. The space
408 consumption of merge sort is a bit worse than Lennart's quick sort
409 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
410 fpca article ] isn't used because of group.
417 group :: (a -> a -> Bool) -> [a] -> [[a]]
418 -- Given a <= function, group finds maximal contiguous up-runs
419 -- or down-runs in the input list.
420 -- It's stable, in the sense that it never re-orders equal elements
422 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
423 -- From: Andy Gill <andy@dcs.gla.ac.uk>
424 -- Here is a `better' definition of group.
427 group p (x:xs) = group' xs x x (x :)
429 group' [] _ _ s = [s []]
430 group' (x:xs) x_min x_max s
431 | x_max `p` x = group' xs x_min x (s . (x :))
432 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
433 | otherwise = s [] : group' xs x x (x :)
434 -- NB: the 'not' is essential for stablity
435 -- x `p` x_min would reverse equal elements
437 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
438 generalMerge p xs [] = xs
439 generalMerge p [] ys = ys
440 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
441 | otherwise = y : generalMerge p (x:xs) ys
443 -- gamma is now called balancedFold
445 balancedFold :: (a -> a -> a) -> [a] -> a
446 balancedFold f [] = error "can't reduce an empty list using balancedFold"
447 balancedFold f [x] = x
448 balancedFold f l = balancedFold f (balancedFold' f l)
450 balancedFold' :: (a -> a -> a) -> [a] -> [a]
451 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
452 balancedFold' f xs = xs
454 generalNaturalMergeSort p [] = []
455 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
458 generalMergeSort p [] = []
459 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
461 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
463 mergeSort = generalMergeSort (<=)
464 naturalMergeSort = generalNaturalMergeSort (<=)
466 mergeSortLe le = generalMergeSort le
469 sortLe :: (a->a->Bool) -> [a] -> [a]
470 sortLe le = generalNaturalMergeSort le
472 sortWith :: Ord b => (a->b) -> [a] -> [a]
473 sortWith get_key xs = sortLe le xs
475 x `le` y = get_key x < get_key y
478 %************************************************************************
480 \subsection[Utils-transitive-closure]{Transitive closure}
482 %************************************************************************
484 This algorithm for transitive closure is straightforward, albeit quadratic.
487 transitiveClosure :: (a -> [a]) -- Successor function
488 -> (a -> a -> Bool) -- Equality predicate
490 -> [a] -- The transitive closure
492 transitiveClosure succ eq xs
496 go done (x:xs) | x `is_in` done = go done xs
497 | otherwise = go (x:done) (succ x ++ xs)
500 x `is_in` (y:ys) | eq x y = True
501 | otherwise = x `is_in` ys
504 %************************************************************************
506 \subsection[Utils-accum]{Accumulating}
508 %************************************************************************
510 @mapAccumL@ behaves like a combination
511 of @map@ and @foldl@;
512 it applies a function to each element of a list, passing an accumulating
513 parameter from left to right, and returning a final value of this
514 accumulator together with the new list.
517 mapAccumL :: (acc -> x -> (acc, y)) -- Function of elt of input list
518 -- and accumulator, returning new
519 -- accumulator and elt of result list
520 -> acc -- Initial accumulator
522 -> (acc, [y]) -- Final accumulator and result list
524 mapAccumL f b [] = (b, [])
525 mapAccumL f b (x:xs) = (b'', x':xs') where
527 (b'', xs') = mapAccumL f b' xs
530 @mapAccumR@ does the same, but working from right to left instead. Its type is
531 the same as @mapAccumL@, though.
534 mapAccumR :: (acc -> x -> (acc, y)) -- Function of elt of input list
535 -- and accumulator, returning new
536 -- accumulator and elt of result list
537 -> acc -- Initial accumulator
539 -> (acc, [y]) -- Final accumulator and result list
541 mapAccumR f b [] = (b, [])
542 mapAccumR f b (x:xs) = (b'', x':xs') where
544 (b', xs') = mapAccumR f b xs
547 Here is the bi-directional version, that works from both left and right.
550 mapAccumB :: (accl -> accr -> x -> (accl, accr,y))
551 -- Function of elt of input list
552 -- and accumulator, returning new
553 -- accumulator and elt of result list
554 -> accl -- Initial accumulator from left
555 -> accr -- Initial accumulator from right
557 -> (accl, accr, [y]) -- Final accumulators and result list
559 mapAccumB f a b [] = (a,b,[])
560 mapAccumB f a b (x:xs) = (a'',b'',y:ys)
562 (a',b'',y) = f a b' x
563 (a'',b',ys) = mapAccumB f a' b xs
566 A strict version of foldl.
569 foldl' :: (a -> b -> a) -> a -> [b] -> a
570 foldl' f z xs = lgo z xs
573 lgo z (x:xs) = (lgo $! (f z x)) xs
576 A combination of foldl with zip. It works with equal length lists.
579 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
581 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
583 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
584 -- True if the lists are the same length, and
585 -- all corresponding elements satisfy the predicate
587 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
591 Count the number of times a predicate is true
594 count :: (a -> Bool) -> [a] -> Int
596 count p (x:xs) | p x = 1 + count p xs
597 | otherwise = count p xs
600 @splitAt@, @take@, and @drop@ but with length of another
601 list giving the break-off point:
604 takeList :: [b] -> [a] -> [a]
609 (y:ys) -> y : takeList xs ys
611 dropList :: [b] -> [a] -> [a]
613 dropList _ xs@[] = xs
614 dropList (_:xs) (_:ys) = dropList xs ys
617 splitAtList :: [b] -> [a] -> ([a], [a])
618 splitAtList [] xs = ([], xs)
619 splitAtList _ xs@[] = (xs, xs)
620 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
622 (ys', ys'') = splitAtList xs ys
624 snocView :: [a] -> Maybe ([a],a)
625 -- Split off the last element
626 snocView [] = Nothing
627 snocView xs = go [] xs
629 -- Invariant: second arg is non-empty
630 go acc [x] = Just (reverse acc, x)
631 go acc (x:xs) = go (x:acc) xs
633 split :: Char -> String -> [String]
634 split c s = case rest of
636 _:rest -> chunk : split c rest
637 where (chunk, rest) = break (==c) s
641 %************************************************************************
643 \subsection[Utils-comparison]{Comparisons}
645 %************************************************************************
648 isEqual :: Ordering -> Bool
649 -- Often used in (isEqual (a `compare` b))
654 thenCmp :: Ordering -> Ordering -> Ordering
655 {-# INLINE thenCmp #-}
657 thenCmp other any = other
659 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
660 eqListBy eq [] [] = True
661 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
662 eqListBy eq xs ys = False
664 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
665 -- `cmpList' uses a user-specified comparer
667 cmpList cmp [] [] = EQ
668 cmpList cmp [] _ = LT
669 cmpList cmp _ [] = GT
670 cmpList cmp (a:as) (b:bs)
671 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
675 prefixMatch :: Eq a => [a] -> [a] -> Bool
676 prefixMatch [] _str = True
677 prefixMatch _pat [] = False
678 prefixMatch (p:ps) (s:ss) | p == s = prefixMatch ps ss
681 maybePrefixMatch :: String -> String -> Maybe String
682 maybePrefixMatch [] rest = Just rest
683 maybePrefixMatch (_:_) [] = Nothing
684 maybePrefixMatch (p:pat) (r:rest)
685 | p == r = maybePrefixMatch pat rest
686 | otherwise = Nothing
688 suffixMatch :: Eq a => [a] -> [a] -> Bool
689 suffixMatch pat str = prefixMatch (reverse pat) (reverse str)
691 removeSpaces :: String -> String
692 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
695 %************************************************************************
697 \subsection[Utils-pairs]{Pairs}
699 %************************************************************************
701 The following are curried versions of @fst@ and @snd@.
705 cfst :: a -> b -> a -- stranal-sem only (Note)
710 The following provide us higher order functions that, when applied
711 to a function, operate on pairs.
715 applyToPair :: ((a -> c),(b -> d)) -> (a,b) -> (c,d)
716 applyToPair (f,g) (x,y) = (f x, g y)
718 applyToFst :: (a -> c) -> (a,b)-> (c,b)
719 applyToFst f (x,y) = (f x,y)
721 applyToSnd :: (b -> d) -> (a,b) -> (a,d)
722 applyToSnd f (x,y) = (x,f y)
727 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
728 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
732 seqList :: [a] -> b -> b
734 seqList (x:xs) b = x `seq` seqList xs b
740 global :: a -> IORef a
741 global a = unsafePerformIO (newIORef a)
745 consIORef :: IORef [a] -> a -> IO ()
748 writeIORef var (x:xs)
754 looksLikeModuleName [] = False
755 looksLikeModuleName (c:cs) = isUpper c && go cs
757 go ('.':cs) = looksLikeModuleName cs
758 go (c:cs) = (isAlphaNum c || c == '_') && go cs
761 Akin to @Prelude.words@, but acts like the Bourne shell, treating
762 quoted strings and escaped characters within the input as solid blocks
763 of characters. Doesn't raise any exceptions on malformed escapes or
767 toArgs :: String -> [String]
770 case dropWhile isSpace s of -- drop initial spacing
771 [] -> [] -- empty, so no more tokens
772 rem -> let (tok,aft) = token rem [] in tok : toArgs aft
774 -- Grab a token off the string, given that the first character exists and
775 -- isn't whitespace. The second argument is an accumulator which has to be
776 -- reversed at the end.
777 token [] acc = (reverse acc,[]) -- out of characters
778 token ('\\':c:aft) acc -- escapes
779 = token aft ((escape c) : acc)
780 token (q:aft) acc | q == '"' || q == '\'' -- open quotes
781 = let (aft',acc') = quote q aft acc in token aft' acc'
782 token (c:aft) acc | isSpace c -- unescaped, unquoted spacing
784 token (c:aft) acc -- anything else goes in the token
787 -- Get the appropriate character for a single-character escape.
793 -- Read into accumulator until a quote character is found.
795 let quote' [] acc = ([],acc)
796 quote' ('\\':c:aft) acc = quote' aft ((escape c) : acc)
797 quote' (c:aft) acc | c == qc = (aft,acc)
798 quote' (c:aft) acc = quote' aft (c:acc)
802 -- -----------------------------------------------------------------------------
806 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
807 readRational__ r = do
810 return ((n%1)*10^^(k-d), t)
813 (ds,s) <- lexDecDigits r
814 (ds',t) <- lexDotDigits s
815 return (read (ds++ds'), length ds', t)
817 readExp (e:s) | e `elem` "eE" = readExp' s
818 readExp s = return (0,s)
820 readExp' ('+':s) = readDec s
821 readExp' ('-':s) = do
824 readExp' s = readDec s
827 (ds,r) <- nonnull isDigit s
828 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
831 lexDecDigits = nonnull isDigit
833 lexDotDigits ('.':s) = return (span isDigit s)
834 lexDotDigits s = return ("",s)
836 nonnull p s = do (cs@(_:_),t) <- return (span p s)
839 readRational :: String -> Rational -- NB: *does* handle a leading "-"
842 '-' : xs -> - (read_me xs)
846 = case (do { (x,"") <- readRational__ s ; return x }) of
848 [] -> error ("readRational: no parse:" ++ top_s)
849 _ -> error ("readRational: ambiguous parse:" ++ top_s)
852 -----------------------------------------------------------------------------
853 -- Create a hierarchy of directories
855 createDirectoryHierarchy :: FilePath -> IO ()
856 createDirectoryHierarchy dir = do
857 b <- doesDirectoryExist dir
859 createDirectoryHierarchy (directoryOf dir)
862 -----------------------------------------------------------------------------
863 -- Verify that the 'dirname' portion of a FilePath exists.
865 doesDirNameExist :: FilePath -> IO Bool
866 doesDirNameExist fpath = doesDirectoryExist (directoryOf fpath)
868 -- -----------------------------------------------------------------------------
873 handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
874 handleDyn = flip catchDyn
876 handle :: (Exception -> IO a) -> IO a -> IO a
877 #if __GLASGOW_HASKELL__ < 501
878 handle = flip Exception.catchAllIO
880 handle h f = f `Exception.catch` \e -> case e of
881 ExitException _ -> throw e
885 -- --------------------------------------------------------------
886 -- check existence & modification time at the same time
888 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
889 modificationTimeIfExists f = do
890 (do t <- getModificationTime f; return (Just t))
891 `IO.catch` \e -> if isDoesNotExistError e
895 -- --------------------------------------------------------------
896 -- Filename manipulation
898 -- Filenames are kept "normalised" inside GHC, using '/' as the path
899 -- separator. On Windows these functions will also recognise '\\' as
900 -- the path separator, but will generally construct paths using '/'.
904 splitFilename :: String -> (String,Suffix)
905 splitFilename f = splitLongestPrefix f (=='.')
907 basenameOf :: FilePath -> String
908 basenameOf = fst . splitFilename
910 suffixOf :: FilePath -> Suffix
911 suffixOf = snd . splitFilename
913 joinFileExt :: String -> String -> FilePath
914 joinFileExt path "" = path
915 joinFileExt path ext = path ++ '.':ext
917 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy.ext")
918 splitFilenameDir :: String -> (String,String)
920 = let (dir, rest) = splitLongestPrefix str isPathSeparator
921 (dir', rest') | null rest = (".", dir)
922 | otherwise = (dir, rest)
925 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext")
926 splitFilename3 :: String -> (String,String,Suffix)
928 = let (dir, rest) = splitFilenameDir str
929 (name, ext) = splitFilename rest
932 joinFileName :: String -> String -> FilePath
933 joinFileName "" fname = fname
934 joinFileName "." fname = fname
935 joinFileName dir "" = dir
936 joinFileName dir fname = dir ++ '/':fname
938 -- split a string at the last character where 'pred' is True,
939 -- returning a pair of strings. The first component holds the string
940 -- up (but not including) the last character for which 'pred' returned
941 -- True, the second whatever comes after (but also not including the
944 -- If 'pred' returns False for all characters in the string, the original
945 -- string is returned in the first component (and the second one is just
947 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
948 splitLongestPrefix str pred
949 | null r_pre = (str, [])
950 | otherwise = (reverse (tail r_pre), reverse r_suf)
951 -- 'tail' drops the char satisfying 'pred'
953 (r_suf, r_pre) = break pred (reverse str)
955 replaceFilenameSuffix :: FilePath -> Suffix -> FilePath
956 replaceFilenameSuffix file suf = basenameOf file `joinFileExt` suf
958 -- directoryOf strips the filename off the input string, returning
960 directoryOf :: FilePath -> String
961 directoryOf = fst . splitFilenameDir
963 -- filenameOf strips the directory off the input string, returning
965 filenameOf :: FilePath -> String
966 filenameOf = snd . splitFilenameDir
968 replaceFilenameDirectory :: FilePath -> String -> FilePath
969 replaceFilenameDirectory path dir = dir `joinFileName` filenameOf path
971 escapeSpaces :: String -> String
972 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
974 isPathSeparator :: Char -> Bool
976 #ifdef mingw32_TARGET_OS
977 ch == '/' || ch == '\\'
982 --------------------------------------------------------------
984 --------------------------------------------------------------
986 -- | The function splits the given string to substrings
987 -- using the 'searchPathSeparator'.
988 parseSearchPath :: String -> [FilePath]
989 parseSearchPath path = split path
991 split :: String -> [String]
995 _:rest -> chunk : split rest
999 #ifdef mingw32_HOST_OS
1000 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
1004 (chunk', rest') = break (==searchPathSeparator) s
1006 -- | A platform-specific character used to separate search path strings in
1007 -- environment variables. The separator is a colon (\":\") on Unix and Macintosh,
1008 -- and a semicolon (\";\") on the Windows operating system.
1009 searchPathSeparator :: Char
1010 #if mingw32_HOST_OS || mingw32_TARGET_OS
1011 searchPathSeparator = ';'
1013 searchPathSeparator = ':'
1016 -----------------------------------------------------------------------------
1017 -- Convert filepath into platform / MSDOS form.
1019 -- We maintain path names in Unix form ('/'-separated) right until
1020 -- the last moment. On Windows we dos-ify them just before passing them
1021 -- to the Windows command.
1023 -- The alternative, of using '/' consistently on Unix and '\' on Windows,
1024 -- proved quite awkward. There were a lot more calls to platformPath,
1025 -- and even on Windows we might invoke a unix-like utility (eg 'sh'), which
1026 -- interpreted a command line 'foo\baz' as 'foobaz'.
1028 normalisePath :: String -> String
1029 -- Just changes '\' to '/'
1031 pgmPath :: String -- Directory string in Unix format
1032 -> String -- Program name with no directory separators
1034 -> String -- Program invocation string in native format
1036 #if defined(mingw32_HOST_OS)
1037 --------------------- Windows version ------------------
1038 normalisePath xs = subst '\\' '/' xs
1039 pgmPath dir pgm = platformPath dir ++ '\\' : pgm
1040 platformPath p = subst '/' '\\' p
1042 subst a b ls = map (\ x -> if x == a then b else x) ls
1044 --------------------- Non-Windows version --------------
1045 normalisePath xs = xs
1046 pgmPath dir pgm = dir ++ '/' : pgm
1047 platformPath stuff = stuff
1048 --------------------------------------------------------