2 % (c) The University of Glasgow 1992-2002
4 \section[Util]{Highly random utility functions}
9 -- general list processing
10 zipEqual, zipWithEqual, zipWith3Equal, zipWith4Equal,
11 zipLazy, stretchZipWith,
13 mapAndUnzip, mapAndUnzip3,
15 lengthExceeds, lengthIs, lengthAtLeast, listLengthCmp, atLength,
27 -- transitive closures
31 mapAccumL, mapAccumR, mapAccumB,
34 takeList, dropList, splitAtList, split,
37 isEqual, eqListBy, equalLength, compareLength,
38 thenCmp, cmpList, prefixMatch, suffixMatch, maybePrefixMatch,
54 -- Floating point stuff
58 createDirectoryHierarchy,
60 modificationTimeIfExists,
62 later, handleDyn, handle,
66 splitFilename, getFileSuffix, splitFilenameDir, joinFileExt,
67 splitFilename3, removeSuffix,
68 dropLongestPrefix, takeLongestPrefix, splitLongestPrefix,
69 replaceFilenameSuffix, directoryOf, filenameOf,
70 replaceFilenameDirectory,
71 escapeSpaces, isPathSeparator,
72 normalisePath, platformPath, pgmPath,
75 #include "HsVersions.h"
77 import Panic ( panic, trace )
80 import EXCEPTION ( Exception(..), finally, throwDyn, catchDyn, throw )
81 import qualified EXCEPTION as Exception
82 import DYNAMIC ( Typeable )
83 import DATA_IOREF ( IORef, newIORef )
84 import UNSAFE_IO ( unsafePerformIO )
85 import DATA_IOREF ( readIORef, writeIORef )
87 import qualified List ( elem, notElem )
90 import List ( zipWith4 )
94 import IO ( catch, isDoesNotExistError )
95 import Directory ( doesDirectoryExist, createDirectory )
96 import Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
98 import Time ( ClockTime )
99 import Directory ( getModificationTime )
104 %************************************************************************
106 \subsection{The Eager monad}
108 %************************************************************************
110 The @Eager@ monad is just an encoding of continuation-passing style,
111 used to allow you to express "do this and then that", mainly to avoid
112 space leaks. It's done with a type synonym to save bureaucracy.
117 type Eager ans a = (a -> ans) -> ans
119 runEager :: Eager a a -> a
120 runEager m = m (\x -> x)
122 appEager :: Eager ans a -> (a -> ans) -> ans
123 appEager m cont = m cont
125 thenEager :: Eager ans a -> (a -> Eager ans b) -> Eager ans b
126 thenEager m k cont = m (\r -> k r cont)
128 returnEager :: a -> Eager ans a
129 returnEager v cont = cont v
131 mapEager :: (a -> Eager ans b) -> [a] -> Eager ans [b]
132 mapEager f [] = returnEager []
133 mapEager f (x:xs) = f x `thenEager` \ y ->
134 mapEager f xs `thenEager` \ ys ->
139 %************************************************************************
141 \subsection{A for loop}
143 %************************************************************************
146 -- Compose a function with itself n times. (nth rather than twice)
147 nTimes :: Int -> (a -> a) -> (a -> a)
150 nTimes n f = f . nTimes (n-1) f
153 %************************************************************************
155 \subsection[Utils-lists]{General list processing}
157 %************************************************************************
160 filterOut :: (a->Bool) -> [a] -> [a]
161 -- Like filter, only reverses the sense of the test
163 filterOut p (x:xs) | p x = filterOut p xs
164 | otherwise = x : filterOut p xs
167 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
168 are of equal length. Alastair Reid thinks this should only happen if
169 DEBUGging on; hey, why not?
172 zipEqual :: String -> [a] -> [b] -> [(a,b)]
173 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
174 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
175 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
179 zipWithEqual _ = zipWith
180 zipWith3Equal _ = zipWith3
181 zipWith4Equal _ = zipWith4
183 zipEqual msg [] [] = []
184 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
185 zipEqual msg as bs = panic ("zipEqual: unequal lists:"++msg)
187 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
188 zipWithEqual msg _ [] [] = []
189 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
191 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
192 = z a b c : zipWith3Equal msg z as bs cs
193 zipWith3Equal msg _ [] [] [] = []
194 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
196 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
197 = z a b c d : zipWith4Equal msg z as bs cs ds
198 zipWith4Equal msg _ [] [] [] [] = []
199 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
204 -- zipLazy is lazy in the second list (observe the ~)
206 zipLazy :: [a] -> [b] -> [(a,b)]
208 zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
213 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
214 -- (stretchZipWith p z f xs ys) stretches ys by inserting z in
215 -- the places where p returns *True*
217 stretchZipWith p z f [] ys = []
218 stretchZipWith p z f (x:xs) ys
219 | p x = f x z : stretchZipWith p z f xs ys
220 | otherwise = case ys of
222 (y:ys) -> f x y : stretchZipWith p z f xs ys
227 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
228 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
230 mapFst f xys = [(f x, y) | (x,y) <- xys]
231 mapSnd f xys = [(x, f y) | (x,y) <- xys]
233 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
235 mapAndUnzip f [] = ([],[])
239 (rs1, rs2) = mapAndUnzip f xs
243 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
245 mapAndUnzip3 f [] = ([],[],[])
246 mapAndUnzip3 f (x:xs)
249 (rs1, rs2, rs3) = mapAndUnzip3 f xs
251 (r1:rs1, r2:rs2, r3:rs3)
255 nOfThem :: Int -> a -> [a]
256 nOfThem n thing = replicate n thing
258 -- 'atLength atLen atEnd ls n' unravels list 'ls' to position 'n';
261 -- atLength atLenPred atEndPred ls n
262 -- | n < 0 = atLenPred n
263 -- | length ls < n = atEndPred (n - length ls)
264 -- | otherwise = atLenPred (drop n ls)
266 atLength :: ([a] -> b)
271 atLength atLenPred atEndPred ls n
272 | n < 0 = atEndPred n
273 | otherwise = go n ls
275 go n [] = atEndPred n
276 go 0 ls = atLenPred ls
277 go n (_:xs) = go (n-1) xs
280 lengthExceeds :: [a] -> Int -> Bool
281 -- (lengthExceeds xs n) = (length xs > n)
282 lengthExceeds = atLength notNull (const False)
284 lengthAtLeast :: [a] -> Int -> Bool
285 lengthAtLeast = atLength notNull (== 0)
287 lengthIs :: [a] -> Int -> Bool
288 lengthIs = atLength null (==0)
290 listLengthCmp :: [a] -> Int -> Ordering
291 listLengthCmp = atLength atLen atEnd
295 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
301 isSingleton :: [a] -> Bool
302 isSingleton [x] = True
303 isSingleton _ = False
305 notNull :: [a] -> Bool
309 snocView :: [a] -> Maybe ([a],a)
310 -- Split off the last element
311 snocView [] = Nothing
312 snocView xs = go [] xs
314 -- Invariant: second arg is non-empty
315 go acc [x] = Just (reverse acc, x)
316 go acc (x:xs) = go (x:acc) xs
326 Debugging/specialising versions of \tr{elem} and \tr{notElem}
329 isIn, isn'tIn :: (Eq a) => String -> a -> [a] -> Bool
332 isIn msg x ys = elem__ x ys
333 isn'tIn msg x ys = notElem__ x ys
335 --these are here to be SPECIALIZEd (automagically)
337 elem__ x (y:ys) = x==y || elem__ x ys
339 notElem__ x [] = True
340 notElem__ x (y:ys) = x /= y && notElem__ x ys
344 = elem (_ILIT 0) x ys
348 | i ># _ILIT 100 = trace ("Over-long elem in " ++ msg) $
350 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
353 = notElem (_ILIT 0) x ys
355 notElem i x [] = True
357 | i ># _ILIT 100 = trace ("Over-long notElem in " ++ msg) $
358 x `List.notElem` (y:ys)
359 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
363 %************************************************************************
365 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
367 %************************************************************************
370 Date: Mon, 3 May 93 20:45:23 +0200
371 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
372 To: partain@dcs.gla.ac.uk
373 Subject: natural merge sort beats quick sort [ and it is prettier ]
375 Here is a piece of Haskell code that I'm rather fond of. See it as an
376 attempt to get rid of the ridiculous quick-sort routine. group is
377 quite useful by itself I think it was John's idea originally though I
378 believe the lazy version is due to me [surprisingly complicated].
379 gamma [used to be called] is called gamma because I got inspired by
380 the Gamma calculus. It is not very close to the calculus but does
381 behave less sequentially than both foldr and foldl. One could imagine
382 a version of gamma that took a unit element as well thereby avoiding
383 the problem with empty lists.
385 I've tried this code against
387 1) insertion sort - as provided by haskell
388 2) the normal implementation of quick sort
389 3) a deforested version of quick sort due to Jan Sparud
390 4) a super-optimized-quick-sort of Lennart's
392 If the list is partially sorted both merge sort and in particular
393 natural merge sort wins. If the list is random [ average length of
394 rising subsequences = approx 2 ] mergesort still wins and natural
395 merge sort is marginally beaten by Lennart's soqs. The space
396 consumption of merge sort is a bit worse than Lennart's quick sort
397 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
398 fpca article ] isn't used because of group.
405 group :: (a -> a -> Bool) -> [a] -> [[a]]
406 -- Given a <= function, group finds maximal contiguous up-runs
407 -- or down-runs in the input list.
408 -- It's stable, in the sense that it never re-orders equal elements
410 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
411 -- From: Andy Gill <andy@dcs.gla.ac.uk>
412 -- Here is a `better' definition of group.
415 group p (x:xs) = group' xs x x (x :)
417 group' [] _ _ s = [s []]
418 group' (x:xs) x_min x_max s
419 | x_max `p` x = group' xs x_min x (s . (x :))
420 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
421 | otherwise = s [] : group' xs x x (x :)
422 -- NB: the 'not' is essential for stablity
423 -- x `p` x_min would reverse equal elements
425 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
426 generalMerge p xs [] = xs
427 generalMerge p [] ys = ys
428 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
429 | otherwise = y : generalMerge p (x:xs) ys
431 -- gamma is now called balancedFold
433 balancedFold :: (a -> a -> a) -> [a] -> a
434 balancedFold f [] = error "can't reduce an empty list using balancedFold"
435 balancedFold f [x] = x
436 balancedFold f l = balancedFold f (balancedFold' f l)
438 balancedFold' :: (a -> a -> a) -> [a] -> [a]
439 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
440 balancedFold' f xs = xs
442 generalNaturalMergeSort p [] = []
443 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
446 generalMergeSort p [] = []
447 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
449 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
451 mergeSort = generalMergeSort (<=)
452 naturalMergeSort = generalNaturalMergeSort (<=)
454 mergeSortLe le = generalMergeSort le
457 sortLe :: (a->a->Bool) -> [a] -> [a]
458 sortLe le = generalNaturalMergeSort le
460 sortWith :: Ord b => (a->b) -> [a] -> [a]
461 sortWith get_key xs = sortLe le xs
463 x `le` y = get_key x < get_key y
466 %************************************************************************
468 \subsection[Utils-transitive-closure]{Transitive closure}
470 %************************************************************************
472 This algorithm for transitive closure is straightforward, albeit quadratic.
475 transitiveClosure :: (a -> [a]) -- Successor function
476 -> (a -> a -> Bool) -- Equality predicate
478 -> [a] -- The transitive closure
480 transitiveClosure succ eq xs
484 go done (x:xs) | x `is_in` done = go done xs
485 | otherwise = go (x:done) (succ x ++ xs)
488 x `is_in` (y:ys) | eq x y = True
489 | otherwise = x `is_in` ys
492 %************************************************************************
494 \subsection[Utils-accum]{Accumulating}
496 %************************************************************************
498 @mapAccumL@ behaves like a combination
499 of @map@ and @foldl@;
500 it applies a function to each element of a list, passing an accumulating
501 parameter from left to right, and returning a final value of this
502 accumulator together with the new list.
505 mapAccumL :: (acc -> x -> (acc, y)) -- Function of elt of input list
506 -- and accumulator, returning new
507 -- accumulator and elt of result list
508 -> acc -- Initial accumulator
510 -> (acc, [y]) -- Final accumulator and result list
512 mapAccumL f b [] = (b, [])
513 mapAccumL f b (x:xs) = (b'', x':xs') where
515 (b'', xs') = mapAccumL f b' xs
518 @mapAccumR@ does the same, but working from right to left instead. Its type is
519 the same as @mapAccumL@, though.
522 mapAccumR :: (acc -> x -> (acc, y)) -- Function of elt of input list
523 -- and accumulator, returning new
524 -- accumulator and elt of result list
525 -> acc -- Initial accumulator
527 -> (acc, [y]) -- Final accumulator and result list
529 mapAccumR f b [] = (b, [])
530 mapAccumR f b (x:xs) = (b'', x':xs') where
532 (b', xs') = mapAccumR f b xs
535 Here is the bi-directional version, that works from both left and right.
538 mapAccumB :: (accl -> accr -> x -> (accl, accr,y))
539 -- Function of elt of input list
540 -- and accumulator, returning new
541 -- accumulator and elt of result list
542 -> accl -- Initial accumulator from left
543 -> accr -- Initial accumulator from right
545 -> (accl, accr, [y]) -- Final accumulators and result list
547 mapAccumB f a b [] = (a,b,[])
548 mapAccumB f a b (x:xs) = (a'',b'',y:ys)
550 (a',b'',y) = f a b' x
551 (a'',b',ys) = mapAccumB f a' b xs
554 A strict version of foldl.
557 foldl' :: (a -> b -> a) -> a -> [b] -> a
558 foldl' f z xs = lgo z xs
561 lgo z (x:xs) = (lgo $! (f z x)) xs
564 A combination of foldl with zip. It works with equal length lists.
567 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
569 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
572 Count the number of times a predicate is true
575 count :: (a -> Bool) -> [a] -> Int
577 count p (x:xs) | p x = 1 + count p xs
578 | otherwise = count p xs
581 @splitAt@, @take@, and @drop@ but with length of another
582 list giving the break-off point:
585 takeList :: [b] -> [a] -> [a]
590 (y:ys) -> y : takeList xs ys
592 dropList :: [b] -> [a] -> [a]
594 dropList _ xs@[] = xs
595 dropList (_:xs) (_:ys) = dropList xs ys
598 splitAtList :: [b] -> [a] -> ([a], [a])
599 splitAtList [] xs = ([], xs)
600 splitAtList _ xs@[] = (xs, xs)
601 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
603 (ys', ys'') = splitAtList xs ys
605 split :: Char -> String -> [String]
606 split c s = case rest of
608 _:rest -> chunk : split c rest
609 where (chunk, rest) = break (==c) s
613 %************************************************************************
615 \subsection[Utils-comparison]{Comparisons}
617 %************************************************************************
620 isEqual :: Ordering -> Bool
621 -- Often used in (isEqual (a `compare` b))
626 thenCmp :: Ordering -> Ordering -> Ordering
627 {-# INLINE thenCmp #-}
629 thenCmp other any = other
631 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
632 eqListBy eq [] [] = True
633 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
634 eqListBy eq xs ys = False
636 equalLength :: [a] -> [b] -> Bool
637 equalLength [] [] = True
638 equalLength (_:xs) (_:ys) = equalLength xs ys
639 equalLength xs ys = False
641 compareLength :: [a] -> [b] -> Ordering
642 compareLength [] [] = EQ
643 compareLength (_:xs) (_:ys) = compareLength xs ys
644 compareLength [] _ys = LT
645 compareLength _xs [] = GT
647 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
648 -- `cmpList' uses a user-specified comparer
650 cmpList cmp [] [] = EQ
651 cmpList cmp [] _ = LT
652 cmpList cmp _ [] = GT
653 cmpList cmp (a:as) (b:bs)
654 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
658 prefixMatch :: Eq a => [a] -> [a] -> Bool
659 prefixMatch [] _str = True
660 prefixMatch _pat [] = False
661 prefixMatch (p:ps) (s:ss) | p == s = prefixMatch ps ss
664 maybePrefixMatch :: String -> String -> Maybe String
665 maybePrefixMatch [] rest = Just rest
666 maybePrefixMatch (_:_) [] = Nothing
667 maybePrefixMatch (p:pat) (r:rest)
668 | p == r = maybePrefixMatch pat rest
669 | otherwise = Nothing
671 suffixMatch :: Eq a => [a] -> [a] -> Bool
672 suffixMatch pat str = prefixMatch (reverse pat) (reverse str)
674 removeSpaces :: String -> String
675 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
678 %************************************************************************
680 \subsection[Utils-pairs]{Pairs}
682 %************************************************************************
684 The following are curried versions of @fst@ and @snd@.
688 cfst :: a -> b -> a -- stranal-sem only (Note)
693 The following provide us higher order functions that, when applied
694 to a function, operate on pairs.
698 applyToPair :: ((a -> c),(b -> d)) -> (a,b) -> (c,d)
699 applyToPair (f,g) (x,y) = (f x, g y)
701 applyToFst :: (a -> c) -> (a,b)-> (c,b)
702 applyToFst f (x,y) = (f x,y)
704 applyToSnd :: (b -> d) -> (a,b) -> (a,d)
705 applyToSnd f (x,y) = (x,f y)
710 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
711 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
715 seqList :: [a] -> b -> b
717 seqList (x:xs) b = x `seq` seqList xs b
723 global :: a -> IORef a
724 global a = unsafePerformIO (newIORef a)
728 consIORef :: IORef [a] -> a -> IO ()
731 writeIORef var (x:xs)
737 looksLikeModuleName [] = False
738 looksLikeModuleName (c:cs) = isUpper c && go cs
740 go ('.':cs) = looksLikeModuleName cs
741 go (c:cs) = (isAlphaNum c || c == '_') && go cs
744 Akin to @Prelude.words@, but sensitive to dquoted entities treating
745 them as single words.
748 toArgs :: String -> [String]
751 case break (\ ch -> isSpace ch || ch == '"') (dropWhile isSpace s) of -- "
753 (\ ws -> if null w then ws else w : ws) $
757 | x /= '"' -> toArgs xs
760 ((str,rs):_) -> stripQuotes str : toArgs rs
763 -- strip away dquotes; assume first and last chars contain quotes.
764 stripQuotes :: String -> String
765 stripQuotes ('"':xs) = init xs
769 -- -----------------------------------------------------------------------------
773 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
774 readRational__ r = do
777 return ((n%1)*10^^(k-d), t)
780 (ds,s) <- lexDecDigits r
781 (ds',t) <- lexDotDigits s
782 return (read (ds++ds'), length ds', t)
784 readExp (e:s) | e `elem` "eE" = readExp' s
785 readExp s = return (0,s)
787 readExp' ('+':s) = readDec s
788 readExp' ('-':s) = do
791 readExp' s = readDec s
794 (ds,r) <- nonnull isDigit s
795 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
798 lexDecDigits = nonnull isDigit
800 lexDotDigits ('.':s) = return (span isDigit s)
801 lexDotDigits s = return ("",s)
803 nonnull p s = do (cs@(_:_),t) <- return (span p s)
806 readRational :: String -> Rational -- NB: *does* handle a leading "-"
809 '-' : xs -> - (read_me xs)
813 = case (do { (x,"") <- readRational__ s ; return x }) of
815 [] -> error ("readRational: no parse:" ++ top_s)
816 _ -> error ("readRational: ambiguous parse:" ++ top_s)
819 -----------------------------------------------------------------------------
820 -- Create a hierarchy of directories
822 createDirectoryHierarchy :: FilePath -> IO ()
823 createDirectoryHierarchy dir = do
824 b <- doesDirectoryExist dir
826 createDirectoryHierarchy (directoryOf dir)
829 -----------------------------------------------------------------------------
830 -- Verify that the 'dirname' portion of a FilePath exists.
832 doesDirNameExist :: FilePath -> IO Bool
833 doesDirNameExist fpath = doesDirectoryExist (directoryOf fpath)
835 -- -----------------------------------------------------------------------------
840 handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
841 handleDyn = flip catchDyn
843 handle :: (Exception -> IO a) -> IO a -> IO a
844 #if __GLASGOW_HASKELL__ < 501
845 handle = flip Exception.catchAllIO
847 handle h f = f `Exception.catch` \e -> case e of
848 ExitException _ -> throw e
852 -- --------------------------------------------------------------
853 -- check existence & modification time at the same time
855 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
856 modificationTimeIfExists f = do
857 (do t <- getModificationTime f; return (Just t))
858 `IO.catch` \e -> if isDoesNotExistError e
862 -- --------------------------------------------------------------
863 -- Filename manipulation
867 splitFilename :: String -> (String,Suffix)
868 splitFilename f = splitLongestPrefix f (=='.')
870 getFileSuffix :: String -> Suffix
871 getFileSuffix f = dropLongestPrefix f (=='.')
873 joinFileExt :: String -> String -> FilePath
874 joinFileExt path "" = path
875 joinFileExt path ext = path ++ '.':ext
877 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy.ext")
878 splitFilenameDir :: String -> (String,String)
880 = let (dir, rest) = splitLongestPrefix str isPathSeparator
881 (dir', rest') | null rest = (".", dir)
882 | otherwise = (dir, rest)
885 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext")
886 splitFilename3 :: String -> (String,String,Suffix)
888 = let (dir, rest) = splitLongestPrefix str isPathSeparator
889 (dir', rest') | null rest = (".", dir)
890 | otherwise = (dir, rest)
891 (name, ext) = splitFilename rest'
894 removeSuffix :: Char -> String -> Suffix
895 removeSuffix c s = takeLongestPrefix s (==c)
897 dropLongestPrefix :: String -> (Char -> Bool) -> String
898 dropLongestPrefix s pred = snd (splitLongestPrefix s pred)
900 takeLongestPrefix :: String -> (Char -> Bool) -> String
901 takeLongestPrefix s pred = fst (splitLongestPrefix s pred)
903 -- split a string at the last character where 'pred' is True,
904 -- returning a pair of strings. The first component holds the string
905 -- up (but not including) the last character for which 'pred' returned
906 -- True, the second whatever comes after (but also not including the
909 -- If 'pred' returns False for all characters in the string, the original
910 -- string is returned in the second component (and the first one is just
912 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
913 splitLongestPrefix s pred
915 [] -> (reverse suf, [])
916 (_:pre) -> (reverse pre, reverse suf)
917 where (suf,pre) = break pred (reverse s)
919 replaceFilenameSuffix :: FilePath -> Suffix -> FilePath
920 replaceFilenameSuffix s suf = removeSuffix '.' s ++ '.':suf
922 -- directoryOf strips the filename off the input string, returning
924 directoryOf :: FilePath -> String
925 directoryOf = fst . splitFilenameDir
927 -- filenameOf strips the directory off the input string, returning
929 filenameOf :: FilePath -> String
930 filenameOf = snd . splitFilenameDir
932 replaceFilenameDirectory :: FilePath -> String -> FilePath
933 replaceFilenameDirectory s dir
934 = dir ++ '/':dropLongestPrefix s isPathSeparator
936 escapeSpaces :: String -> String
937 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
939 isPathSeparator :: Char -> Bool
941 #ifdef mingw32_TARGET_OS
942 ch == '/' || ch == '\\'
947 -----------------------------------------------------------------------------
948 -- Convert filepath into platform / MSDOS form.
950 -- We maintain path names in Unix form ('/'-separated) right until
951 -- the last moment. On Windows we dos-ify them just before passing them
952 -- to the Windows command.
954 -- The alternative, of using '/' consistently on Unix and '\' on Windows,
955 -- proved quite awkward. There were a lot more calls to platformPath,
956 -- and even on Windows we might invoke a unix-like utility (eg 'sh'), which
957 -- interpreted a command line 'foo\baz' as 'foobaz'.
959 normalisePath :: String -> String
960 -- Just changes '\' to '/'
962 pgmPath :: String -- Directory string in Unix format
963 -> String -- Program name with no directory separators
965 -> String -- Program invocation string in native format
967 #if defined(mingw32_HOST_OS)
968 --------------------- Windows version ------------------
969 normalisePath xs = subst '\\' '/' xs
970 pgmPath dir pgm = platformPath dir ++ '\\' : pgm
971 platformPath p = subst '/' '\\' p
973 subst a b ls = map (\ x -> if x == a then b else x) ls
975 --------------------- Non-Windows version --------------
976 normalisePath xs = xs
977 pgmPath dir pgm = dir ++ '/' : pgm
978 platformPath stuff = stuff
979 --------------------------------------------------------