2 % (c) The University of Glasgow 2006
3 % (c) The University of Glasgow 1992-2002
5 \section[Util]{Highly random utility functions}
9 -- The above warning supression flag is a temporary kludge.
10 -- While working on this module you are encouraged to remove it and fix
11 -- any warnings in the module. See
12 -- http://hackage.haskell.org/trac/ghc/wiki/Commentary/CodingStyle#Warnings
19 -- general list processing
20 zipEqual, zipWithEqual, zipWith3Equal, zipWith4Equal,
21 zipLazy, stretchZipWith,
23 mapAndUnzip, mapAndUnzip3,
24 nOfThem, filterOut, partitionWith, splitEithers,
27 lengthExceeds, lengthIs, lengthAtLeast,
28 listLengthCmp, atLength, equalLength, compareLength,
30 isSingleton, only, singleton,
41 -- transitive closures
47 takeList, dropList, splitAtList, split,
51 thenCmp, cmpList, maybePrefixMatch,
67 -- Floating point stuff
71 createDirectoryHierarchy,
73 modificationTimeIfExists,
75 later, handleDyn, handle,
79 splitFilename, suffixOf, basenameOf, joinFileExt,
80 splitFilenameDir, joinFileName,
83 replaceFilenameSuffix, directoryOf, filenameOf,
84 replaceFilenameDirectory,
85 escapeSpaces, isPathSeparator,
87 normalisePath, platformPath, pgmPath,
90 #include "HsVersions.h"
94 #if defined(DEBUG) || __GLASGOW_HASKELL__ < 604
98 import Control.Exception ( Exception(..), finally, catchDyn, throw )
99 import qualified Control.Exception as Exception
100 import Data.Dynamic ( Typeable )
101 import Data.IORef ( IORef, newIORef )
102 import System.IO.Unsafe ( unsafePerformIO )
103 import Data.IORef ( readIORef, writeIORef )
104 import Data.List hiding (group)
106 import qualified Data.List as List ( elem )
108 import qualified Data.List as List ( notElem )
111 import Control.Monad ( when )
112 import SYSTEM_IO_ERROR as IO ( catch, isDoesNotExistError )
113 import System.Directory ( doesDirectoryExist, createDirectory,
114 getModificationTime )
115 import Data.Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
116 import Data.Ratio ( (%) )
117 import System.Time ( ClockTime )
123 #if __GLASGOW_HASKELL__ < 603
124 -- foldl1' was introduce in GHC 6.4
125 foldl1' :: (a -> a -> a) -> [a] -> a
126 foldl1' f (x:xs) = foldl' f x xs
127 foldl1' _ [] = errorEmptyList "foldl1'"
131 %************************************************************************
133 \subsection{A for loop}
135 %************************************************************************
138 -- Compose a function with itself n times. (nth rather than twice)
139 nTimes :: Int -> (a -> a) -> (a -> a)
142 nTimes n f = f . nTimes (n-1) f
145 %************************************************************************
147 \subsection[Utils-lists]{General list processing}
149 %************************************************************************
152 filterOut :: (a->Bool) -> [a] -> [a]
153 -- Like filter, only reverses the sense of the test
155 filterOut p (x:xs) | p x = filterOut p xs
156 | otherwise = x : filterOut p xs
158 partitionWith :: (a -> Either b c) -> [a] -> ([b], [c])
159 partitionWith f [] = ([],[])
160 partitionWith f (x:xs) = case f x of
162 Right c -> (bs, c:cs)
164 (bs,cs) = partitionWith f xs
166 splitEithers :: [Either a b] -> ([a], [b])
167 splitEithers [] = ([],[])
168 splitEithers (e : es) = case e of
170 Right y -> (xs, y:ys)
172 (xs,ys) = splitEithers es
175 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
176 are of equal length. Alastair Reid thinks this should only happen if
177 DEBUGging on; hey, why not?
180 zipEqual :: String -> [a] -> [b] -> [(a,b)]
181 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
182 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
183 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
187 zipWithEqual _ = zipWith
188 zipWith3Equal _ = zipWith3
189 zipWith4Equal _ = zipWith4
191 zipEqual msg [] [] = []
192 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
193 zipEqual msg as bs = panic ("zipEqual: unequal lists:"++msg)
195 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
196 zipWithEqual msg _ [] [] = []
197 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
199 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
200 = z a b c : zipWith3Equal msg z as bs cs
201 zipWith3Equal msg _ [] [] [] = []
202 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
204 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
205 = z a b c d : zipWith4Equal msg z as bs cs ds
206 zipWith4Equal msg _ [] [] [] [] = []
207 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
212 -- zipLazy is lazy in the second list (observe the ~)
214 zipLazy :: [a] -> [b] -> [(a,b)]
216 zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
221 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
222 -- (stretchZipWith p z f xs ys) stretches ys by inserting z in
223 -- the places where p returns *True*
225 stretchZipWith p z f [] ys = []
226 stretchZipWith p z f (x:xs) ys
227 | p x = f x z : stretchZipWith p z f xs ys
228 | otherwise = case ys of
230 (y:ys) -> f x y : stretchZipWith p z f xs ys
235 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
236 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
238 mapFst f xys = [(f x, y) | (x,y) <- xys]
239 mapSnd f xys = [(x, f y) | (x,y) <- xys]
241 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
243 mapAndUnzip f [] = ([],[])
247 (rs1, rs2) = mapAndUnzip f xs
251 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
253 mapAndUnzip3 f [] = ([],[],[])
254 mapAndUnzip3 f (x:xs)
257 (rs1, rs2, rs3) = mapAndUnzip3 f xs
259 (r1:rs1, r2:rs2, r3:rs3)
263 nOfThem :: Int -> a -> [a]
264 nOfThem n thing = replicate n thing
266 -- 'atLength atLen atEnd ls n' unravels list 'ls' to position 'n';
269 -- atLength atLenPred atEndPred ls n
270 -- | n < 0 = atLenPred n
271 -- | length ls < n = atEndPred (n - length ls)
272 -- | otherwise = atLenPred (drop n ls)
274 atLength :: ([a] -> b)
279 atLength atLenPred atEndPred ls n
280 | n < 0 = atEndPred n
281 | otherwise = go n ls
283 go n [] = atEndPred n
284 go 0 ls = atLenPred ls
285 go n (_:xs) = go (n-1) xs
288 lengthExceeds :: [a] -> Int -> Bool
289 -- (lengthExceeds xs n) = (length xs > n)
290 lengthExceeds = atLength notNull (const False)
292 lengthAtLeast :: [a] -> Int -> Bool
293 lengthAtLeast = atLength notNull (== 0)
295 lengthIs :: [a] -> Int -> Bool
296 lengthIs = atLength null (==0)
298 listLengthCmp :: [a] -> Int -> Ordering
299 listLengthCmp = atLength atLen atEnd
303 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
309 equalLength :: [a] -> [b] -> Bool
310 equalLength [] [] = True
311 equalLength (_:xs) (_:ys) = equalLength xs ys
312 equalLength xs ys = False
314 compareLength :: [a] -> [b] -> Ordering
315 compareLength [] [] = EQ
316 compareLength (_:xs) (_:ys) = compareLength xs ys
317 compareLength [] _ys = LT
318 compareLength _xs [] = GT
320 ----------------------------
321 singleton :: a -> [a]
324 isSingleton :: [a] -> Bool
325 isSingleton [x] = True
326 isSingleton _ = False
328 notNull :: [a] -> Bool
340 Debugging/specialising versions of \tr{elem} and \tr{notElem}
343 isIn, isn'tIn :: (Eq a) => String -> a -> [a] -> Bool
346 isIn msg x ys = elem__ x ys
347 isn'tIn msg x ys = notElem__ x ys
349 --these are here to be SPECIALIZEd (automagically)
351 elem__ x (y:ys) = x==y || elem__ x ys
353 notElem__ x [] = True
354 notElem__ x (y:ys) = x /= y && notElem__ x ys
358 = elem (_ILIT 0) x ys
362 | i ># _ILIT 100 = trace ("Over-long elem in " ++ msg) $
364 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
367 = notElem (_ILIT 0) x ys
369 notElem i x [] = True
371 | i ># _ILIT 100 = trace ("Over-long notElem in " ++ msg) $
372 x `List.notElem` (y:ys)
373 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
377 foldl1' was added in GHC 6.4
380 #if __GLASGOW_HASKELL__ < 604
381 foldl1' :: (a -> a -> a) -> [a] -> a
382 foldl1' f (x:xs) = foldl' f x xs
383 foldl1' _ [] = panic "foldl1'"
387 %************************************************************************
389 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
391 %************************************************************************
394 Date: Mon, 3 May 93 20:45:23 +0200
395 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
396 To: partain@dcs.gla.ac.uk
397 Subject: natural merge sort beats quick sort [ and it is prettier ]
399 Here is a piece of Haskell code that I'm rather fond of. See it as an
400 attempt to get rid of the ridiculous quick-sort routine. group is
401 quite useful by itself I think it was John's idea originally though I
402 believe the lazy version is due to me [surprisingly complicated].
403 gamma [used to be called] is called gamma because I got inspired by
404 the Gamma calculus. It is not very close to the calculus but does
405 behave less sequentially than both foldr and foldl. One could imagine
406 a version of gamma that took a unit element as well thereby avoiding
407 the problem with empty lists.
409 I've tried this code against
411 1) insertion sort - as provided by haskell
412 2) the normal implementation of quick sort
413 3) a deforested version of quick sort due to Jan Sparud
414 4) a super-optimized-quick-sort of Lennart's
416 If the list is partially sorted both merge sort and in particular
417 natural merge sort wins. If the list is random [ average length of
418 rising subsequences = approx 2 ] mergesort still wins and natural
419 merge sort is marginally beaten by Lennart's soqs. The space
420 consumption of merge sort is a bit worse than Lennart's quick sort
421 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
422 fpca article ] isn't used because of group.
429 group :: (a -> a -> Bool) -> [a] -> [[a]]
430 -- Given a <= function, group finds maximal contiguous up-runs
431 -- or down-runs in the input list.
432 -- It's stable, in the sense that it never re-orders equal elements
434 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
435 -- From: Andy Gill <andy@dcs.gla.ac.uk>
436 -- Here is a `better' definition of group.
439 group p (x:xs) = group' xs x x (x :)
441 group' [] _ _ s = [s []]
442 group' (x:xs) x_min x_max s
443 | x_max `p` x = group' xs x_min x (s . (x :))
444 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
445 | otherwise = s [] : group' xs x x (x :)
446 -- NB: the 'not' is essential for stablity
447 -- x `p` x_min would reverse equal elements
449 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
450 generalMerge p xs [] = xs
451 generalMerge p [] ys = ys
452 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
453 | otherwise = y : generalMerge p (x:xs) ys
455 -- gamma is now called balancedFold
457 balancedFold :: (a -> a -> a) -> [a] -> a
458 balancedFold f [] = error "can't reduce an empty list using balancedFold"
459 balancedFold f [x] = x
460 balancedFold f l = balancedFold f (balancedFold' f l)
462 balancedFold' :: (a -> a -> a) -> [a] -> [a]
463 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
464 balancedFold' f xs = xs
466 generalNaturalMergeSort p [] = []
467 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
470 generalMergeSort p [] = []
471 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
473 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
475 mergeSort = generalMergeSort (<=)
476 naturalMergeSort = generalNaturalMergeSort (<=)
478 mergeSortLe le = generalMergeSort le
481 sortLe :: (a->a->Bool) -> [a] -> [a]
482 sortLe le = generalNaturalMergeSort le
484 sortWith :: Ord b => (a->b) -> [a] -> [a]
485 sortWith get_key xs = sortLe le xs
487 x `le` y = get_key x < get_key y
489 on :: (a -> a -> Ordering) -> (b -> a) -> b -> b -> Ordering
490 on cmp sel = \x y -> sel x `cmp` sel 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 A combination of foldl with zip. It works with equal length lists.
529 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
531 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
533 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
534 -- True if the lists are the same length, and
535 -- all corresponding elements satisfy the predicate
537 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
541 Count the number of times a predicate is true
544 count :: (a -> Bool) -> [a] -> Int
546 count p (x:xs) | p x = 1 + count p xs
547 | otherwise = count p xs
550 @splitAt@, @take@, and @drop@ but with length of another
551 list giving the break-off point:
554 takeList :: [b] -> [a] -> [a]
559 (y:ys) -> y : takeList xs ys
561 dropList :: [b] -> [a] -> [a]
563 dropList _ xs@[] = xs
564 dropList (_:xs) (_:ys) = dropList xs ys
567 splitAtList :: [b] -> [a] -> ([a], [a])
568 splitAtList [] xs = ([], xs)
569 splitAtList _ xs@[] = (xs, xs)
570 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
572 (ys', ys'') = splitAtList xs ys
574 snocView :: [a] -> Maybe ([a],a)
575 -- Split off the last element
576 snocView [] = Nothing
577 snocView xs = go [] xs
579 -- Invariant: second arg is non-empty
580 go acc [x] = Just (reverse acc, x)
581 go acc (x:xs) = go (x:acc) xs
583 split :: Char -> String -> [String]
584 split c s = case rest of
586 _:rest -> chunk : split c rest
587 where (chunk, rest) = break (==c) s
591 %************************************************************************
593 \subsection[Utils-comparison]{Comparisons}
595 %************************************************************************
598 isEqual :: Ordering -> Bool
599 -- Often used in (isEqual (a `compare` b))
604 thenCmp :: Ordering -> Ordering -> Ordering
605 {-# INLINE thenCmp #-}
607 thenCmp other any = other
609 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
610 eqListBy eq [] [] = True
611 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
612 eqListBy eq xs ys = False
614 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
615 -- `cmpList' uses a user-specified comparer
617 cmpList cmp [] [] = EQ
618 cmpList cmp [] _ = LT
619 cmpList cmp _ [] = GT
620 cmpList cmp (a:as) (b:bs)
621 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
625 -- This (with a more general type) is Data.List.stripPrefix from GHC 6.8.
626 -- This definition can be removed once we require at least 6.8 to build.
627 maybePrefixMatch :: String -> String -> Maybe String
628 maybePrefixMatch [] rest = Just rest
629 maybePrefixMatch (_:_) [] = Nothing
630 maybePrefixMatch (p:pat) (r:rest)
631 | p == r = maybePrefixMatch pat rest
632 | otherwise = Nothing
634 removeSpaces :: String -> String
635 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
638 %************************************************************************
640 \subsection[Utils-pairs]{Pairs}
642 %************************************************************************
645 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
646 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
650 seqList :: [a] -> b -> b
652 seqList (x:xs) b = x `seq` seqList xs b
658 global :: a -> IORef a
659 global a = unsafePerformIO (newIORef a)
663 consIORef :: IORef [a] -> a -> IO ()
666 writeIORef var (x:xs)
672 looksLikeModuleName :: String -> Bool
673 looksLikeModuleName [] = False
674 looksLikeModuleName (c:cs) = isUpper c && go cs
676 go ('.':cs) = looksLikeModuleName cs
677 go (c:cs) = (isAlphaNum c || c == '_') && go cs
680 Akin to @Prelude.words@, but acts like the Bourne shell, treating
681 quoted strings and escaped characters within the input as solid blocks
682 of characters. Doesn't raise any exceptions on malformed escapes or
686 toArgs :: String -> [String]
689 case dropWhile isSpace s of -- drop initial spacing
690 [] -> [] -- empty, so no more tokens
691 rem -> let (tok,aft) = token rem [] in tok : toArgs aft
693 -- Grab a token off the string, given that the first character exists and
694 -- isn't whitespace. The second argument is an accumulator which has to be
695 -- reversed at the end.
696 token [] acc = (reverse acc,[]) -- out of characters
697 token ('\\':c:aft) acc -- escapes
698 = token aft ((escape c) : acc)
699 token (q:aft) acc | q == '"' || q == '\'' -- open quotes
700 = let (aft',acc') = quote q aft acc in token aft' acc'
701 token (c:aft) acc | isSpace c -- unescaped, unquoted spacing
703 token (c:aft) acc -- anything else goes in the token
706 -- Get the appropriate character for a single-character escape.
712 -- Read into accumulator until a quote character is found.
714 let quote' [] acc = ([],acc)
715 quote' ('\\':c:aft) acc = quote' aft ((escape c) : acc)
716 quote' (c:aft) acc | c == qc = (aft,acc)
717 quote' (c:aft) acc = quote' aft (c:acc)
721 -- -----------------------------------------------------------------------------
725 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
726 readRational__ r = do
729 return ((n%1)*10^^(k-d), t)
732 (ds,s) <- lexDecDigits r
733 (ds',t) <- lexDotDigits s
734 return (read (ds++ds'), length ds', t)
736 readExp (e:s) | e `elem` "eE" = readExp' s
737 readExp s = return (0,s)
739 readExp' ('+':s) = readDec s
740 readExp' ('-':s) = do
743 readExp' s = readDec s
746 (ds,r) <- nonnull isDigit s
747 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
750 lexDecDigits = nonnull isDigit
752 lexDotDigits ('.':s) = return (span isDigit s)
753 lexDotDigits s = return ("",s)
755 nonnull p s = do (cs@(_:_),t) <- return (span p s)
758 readRational :: String -> Rational -- NB: *does* handle a leading "-"
761 '-' : xs -> - (read_me xs)
765 = case (do { (x,"") <- readRational__ s ; return x }) of
767 [] -> error ("readRational: no parse:" ++ top_s)
768 _ -> error ("readRational: ambiguous parse:" ++ top_s)
771 -----------------------------------------------------------------------------
772 -- Create a hierarchy of directories
774 createDirectoryHierarchy :: FilePath -> IO ()
775 createDirectoryHierarchy dir = do
776 b <- doesDirectoryExist dir
778 createDirectoryHierarchy (directoryOf dir)
781 -----------------------------------------------------------------------------
782 -- Verify that the 'dirname' portion of a FilePath exists.
784 doesDirNameExist :: FilePath -> IO Bool
785 doesDirNameExist fpath = doesDirectoryExist (directoryOf fpath)
787 -- -----------------------------------------------------------------------------
792 handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
793 handleDyn = flip catchDyn
795 handle :: (Exception -> IO a) -> IO a -> IO a
796 handle h f = f `Exception.catch` \e -> case e of
797 ExitException _ -> throw e
800 -- --------------------------------------------------------------
801 -- check existence & modification time at the same time
803 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
804 modificationTimeIfExists f = do
805 (do t <- getModificationTime f; return (Just t))
806 `IO.catch` \e -> if isDoesNotExistError e
810 -- --------------------------------------------------------------
811 -- Filename manipulation
813 -- Filenames are kept "normalised" inside GHC, using '/' as the path
814 -- separator. On Windows these functions will also recognise '\\' as
815 -- the path separator, but will generally construct paths using '/'.
819 splitFilename :: String -> (String,Suffix)
820 splitFilename f = splitLongestPrefix f (=='.')
822 basenameOf :: FilePath -> String
823 basenameOf = fst . splitFilename
825 suffixOf :: FilePath -> Suffix
826 suffixOf = snd . splitFilename
828 joinFileExt :: String -> String -> FilePath
829 joinFileExt path "" = path
830 joinFileExt path ext = path ++ '.':ext
832 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy.ext")
833 splitFilenameDir :: String -> (String,String)
835 = let (dir, rest) = splitLongestPrefix str isPathSeparator
836 (dir', rest') | null rest = (".", dir)
837 | otherwise = (dir, rest)
840 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext")
841 splitFilename3 :: String -> (String,String,Suffix)
843 = let (dir, rest) = splitFilenameDir str
844 (name, ext) = splitFilename rest
847 joinFileName :: String -> String -> FilePath
848 joinFileName "" fname = fname
849 joinFileName "." fname = fname
850 joinFileName dir "" = dir
851 joinFileName dir fname = dir ++ '/':fname
853 -- split a string at the last character where 'pred' is True,
854 -- returning a pair of strings. The first component holds the string
855 -- up (but not including) the last character for which 'pred' returned
856 -- True, the second whatever comes after (but also not including the
859 -- If 'pred' returns False for all characters in the string, the original
860 -- string is returned in the first component (and the second one is just
862 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
863 splitLongestPrefix str pred
864 | null r_pre = (str, [])
865 | otherwise = (reverse (tail r_pre), reverse r_suf)
866 -- 'tail' drops the char satisfying 'pred'
868 (r_suf, r_pre) = break pred (reverse str)
870 replaceFilenameSuffix :: FilePath -> Suffix -> FilePath
871 replaceFilenameSuffix file suf = basenameOf file `joinFileExt` suf
873 -- directoryOf strips the filename off the input string, returning
875 directoryOf :: FilePath -> String
876 directoryOf = fst . splitFilenameDir
878 -- filenameOf strips the directory off the input string, returning
880 filenameOf :: FilePath -> String
881 filenameOf = snd . splitFilenameDir
883 replaceFilenameDirectory :: FilePath -> String -> FilePath
884 replaceFilenameDirectory path dir = dir `joinFileName` filenameOf path
886 escapeSpaces :: String -> String
887 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
889 isPathSeparator :: Char -> Bool
891 #ifdef mingw32_TARGET_OS
892 ch == '/' || ch == '\\'
897 --------------------------------------------------------------
899 --------------------------------------------------------------
901 -- | The function splits the given string to substrings
902 -- using the 'searchPathSeparator'.
903 parseSearchPath :: String -> [FilePath]
904 parseSearchPath path = split path
906 split :: String -> [String]
910 _:rest -> chunk : split rest
914 #ifdef mingw32_HOST_OS
915 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
919 (chunk', rest') = break (==searchPathSeparator) s
921 -- | A platform-specific character used to separate search path strings in
922 -- environment variables. The separator is a colon (\":\") on Unix and Macintosh,
923 -- and a semicolon (\";\") on the Windows operating system.
924 searchPathSeparator :: Char
925 #if mingw32_HOST_OS || mingw32_TARGET_OS
926 searchPathSeparator = ';'
928 searchPathSeparator = ':'
931 -----------------------------------------------------------------------------
932 -- Convert filepath into platform / MSDOS form.
934 -- We maintain path names in Unix form ('/'-separated) right until
935 -- the last moment. On Windows we dos-ify them just before passing them
936 -- to the Windows command.
938 -- The alternative, of using '/' consistently on Unix and '\' on Windows,
939 -- proved quite awkward. There were a lot more calls to platformPath,
940 -- and even on Windows we might invoke a unix-like utility (eg 'sh'), which
941 -- interpreted a command line 'foo\baz' as 'foobaz'.
943 normalisePath :: String -> String
944 -- Just changes '\' to '/'
946 pgmPath :: String -- Directory string in Unix format
947 -> String -- Program name with no directory separators
949 -> String -- Program invocation string in native format
951 #if defined(mingw32_HOST_OS)
952 --------------------- Windows version ------------------
953 normalisePath xs = subst '\\' '/' xs
954 pgmPath dir pgm = platformPath dir ++ '\\' : pgm
955 platformPath p = subst '/' '\\' p
957 subst a b ls = map (\ x -> if x == a then b else x) ls
959 --------------------- Non-Windows version --------------
960 normalisePath xs = xs
961 pgmPath dir pgm = dir ++ '/' : pgm
962 platformPath stuff = stuff
963 --------------------------------------------------------