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
37 takeList, dropList, splitAtList, split,
41 thenCmp, cmpList, maybePrefixMatch,
57 -- Floating point stuff
61 createDirectoryHierarchy,
63 modificationTimeIfExists,
65 later, handleDyn, handle,
69 splitFilename, suffixOf, basenameOf, joinFileExt,
70 splitFilenameDir, joinFileName,
73 replaceFilenameSuffix, directoryOf, filenameOf,
74 replaceFilenameDirectory,
75 escapeSpaces, isPathSeparator,
77 normalisePath, platformPath, pgmPath,
80 #include "HsVersions.h"
85 import Panic ( panic, trace )
88 import Control.Exception ( Exception(..), finally, catchDyn, throw )
89 import qualified Control.Exception as Exception
90 import Data.Dynamic ( Typeable )
91 import Data.IORef ( IORef, newIORef )
92 import System.IO.Unsafe ( unsafePerformIO )
93 import Data.IORef ( readIORef, writeIORef )
95 import qualified Data.List as List ( elem )
97 import Data.List ( zipWith4 )
99 import qualified Data.List as List ( notElem )
102 import Control.Monad ( when )
103 import SYSTEM_IO_ERROR as IO ( catch, isDoesNotExistError )
104 import System.Directory ( doesDirectoryExist, createDirectory,
105 getModificationTime )
106 import Data.Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
107 import Data.Ratio ( (%) )
108 import System.Time ( ClockTime )
113 %************************************************************************
115 \subsection{A for loop}
117 %************************************************************************
120 -- Compose a function with itself n times. (nth rather than twice)
121 nTimes :: Int -> (a -> a) -> (a -> a)
124 nTimes n f = f . nTimes (n-1) f
127 %************************************************************************
129 \subsection[Utils-lists]{General list processing}
131 %************************************************************************
134 filterOut :: (a->Bool) -> [a] -> [a]
135 -- Like filter, only reverses the sense of the test
137 filterOut p (x:xs) | p x = filterOut p xs
138 | otherwise = x : filterOut p xs
140 partitionWith :: (a -> Either b c) -> [a] -> ([b], [c])
141 partitionWith f [] = ([],[])
142 partitionWith f (x:xs) = case f x of
144 Right c -> (bs, c:cs)
146 (bs,cs) = partitionWith f xs
148 splitEithers :: [Either a b] -> ([a], [b])
149 splitEithers [] = ([],[])
150 splitEithers (e : es) = case e of
152 Right y -> (xs, y:ys)
154 (xs,ys) = splitEithers es
157 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
158 are of equal length. Alastair Reid thinks this should only happen if
159 DEBUGging on; hey, why not?
162 zipEqual :: String -> [a] -> [b] -> [(a,b)]
163 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
164 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
165 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
169 zipWithEqual _ = zipWith
170 zipWith3Equal _ = zipWith3
171 zipWith4Equal _ = zipWith4
173 zipEqual msg [] [] = []
174 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
175 zipEqual msg as bs = panic ("zipEqual: unequal lists:"++msg)
177 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
178 zipWithEqual msg _ [] [] = []
179 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
181 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
182 = z a b c : zipWith3Equal msg z as bs cs
183 zipWith3Equal msg _ [] [] [] = []
184 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
186 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
187 = z a b c d : zipWith4Equal msg z as bs cs ds
188 zipWith4Equal msg _ [] [] [] [] = []
189 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
194 -- zipLazy is lazy in the second list (observe the ~)
196 zipLazy :: [a] -> [b] -> [(a,b)]
198 zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
203 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
204 -- (stretchZipWith p z f xs ys) stretches ys by inserting z in
205 -- the places where p returns *True*
207 stretchZipWith p z f [] ys = []
208 stretchZipWith p z f (x:xs) ys
209 | p x = f x z : stretchZipWith p z f xs ys
210 | otherwise = case ys of
212 (y:ys) -> f x y : stretchZipWith p z f xs ys
217 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
218 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
220 mapFst f xys = [(f x, y) | (x,y) <- xys]
221 mapSnd f xys = [(x, f y) | (x,y) <- xys]
223 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
225 mapAndUnzip f [] = ([],[])
229 (rs1, rs2) = mapAndUnzip f xs
233 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
235 mapAndUnzip3 f [] = ([],[],[])
236 mapAndUnzip3 f (x:xs)
239 (rs1, rs2, rs3) = mapAndUnzip3 f xs
241 (r1:rs1, r2:rs2, r3:rs3)
245 nOfThem :: Int -> a -> [a]
246 nOfThem n thing = replicate n thing
248 -- 'atLength atLen atEnd ls n' unravels list 'ls' to position 'n';
251 -- atLength atLenPred atEndPred ls n
252 -- | n < 0 = atLenPred n
253 -- | length ls < n = atEndPred (n - length ls)
254 -- | otherwise = atLenPred (drop n ls)
256 atLength :: ([a] -> b)
261 atLength atLenPred atEndPred ls n
262 | n < 0 = atEndPred n
263 | otherwise = go n ls
265 go n [] = atEndPred n
266 go 0 ls = atLenPred ls
267 go n (_:xs) = go (n-1) xs
270 lengthExceeds :: [a] -> Int -> Bool
271 -- (lengthExceeds xs n) = (length xs > n)
272 lengthExceeds = atLength notNull (const False)
274 lengthAtLeast :: [a] -> Int -> Bool
275 lengthAtLeast = atLength notNull (== 0)
277 lengthIs :: [a] -> Int -> Bool
278 lengthIs = atLength null (==0)
280 listLengthCmp :: [a] -> Int -> Ordering
281 listLengthCmp = atLength atLen atEnd
285 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
291 equalLength :: [a] -> [b] -> Bool
292 equalLength [] [] = True
293 equalLength (_:xs) (_:ys) = equalLength xs ys
294 equalLength xs ys = False
296 compareLength :: [a] -> [b] -> Ordering
297 compareLength [] [] = EQ
298 compareLength (_:xs) (_:ys) = compareLength xs ys
299 compareLength [] _ys = LT
300 compareLength _xs [] = GT
302 ----------------------------
303 singleton :: a -> [a]
306 isSingleton :: [a] -> Bool
307 isSingleton [x] = True
308 isSingleton _ = False
310 notNull :: [a] -> Bool
322 Debugging/specialising versions of \tr{elem} and \tr{notElem}
325 isIn, isn'tIn :: (Eq a) => String -> a -> [a] -> Bool
328 isIn msg x ys = elem__ x ys
329 isn'tIn msg x ys = notElem__ x ys
331 --these are here to be SPECIALIZEd (automagically)
333 elem__ x (y:ys) = x==y || elem__ x ys
335 notElem__ x [] = True
336 notElem__ x (y:ys) = x /= y && notElem__ x ys
340 = elem (_ILIT 0) x ys
344 | i ># _ILIT 100 = trace ("Over-long elem in " ++ msg) $
346 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
349 = notElem (_ILIT 0) x ys
351 notElem i x [] = True
353 | i ># _ILIT 100 = trace ("Over-long notElem in " ++ msg) $
354 x `List.notElem` (y:ys)
355 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
359 %************************************************************************
361 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
363 %************************************************************************
366 Date: Mon, 3 May 93 20:45:23 +0200
367 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
368 To: partain@dcs.gla.ac.uk
369 Subject: natural merge sort beats quick sort [ and it is prettier ]
371 Here is a piece of Haskell code that I'm rather fond of. See it as an
372 attempt to get rid of the ridiculous quick-sort routine. group is
373 quite useful by itself I think it was John's idea originally though I
374 believe the lazy version is due to me [surprisingly complicated].
375 gamma [used to be called] is called gamma because I got inspired by
376 the Gamma calculus. It is not very close to the calculus but does
377 behave less sequentially than both foldr and foldl. One could imagine
378 a version of gamma that took a unit element as well thereby avoiding
379 the problem with empty lists.
381 I've tried this code against
383 1) insertion sort - as provided by haskell
384 2) the normal implementation of quick sort
385 3) a deforested version of quick sort due to Jan Sparud
386 4) a super-optimized-quick-sort of Lennart's
388 If the list is partially sorted both merge sort and in particular
389 natural merge sort wins. If the list is random [ average length of
390 rising subsequences = approx 2 ] mergesort still wins and natural
391 merge sort is marginally beaten by Lennart's soqs. The space
392 consumption of merge sort is a bit worse than Lennart's quick sort
393 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
394 fpca article ] isn't used because of group.
401 group :: (a -> a -> Bool) -> [a] -> [[a]]
402 -- Given a <= function, group finds maximal contiguous up-runs
403 -- or down-runs in the input list.
404 -- It's stable, in the sense that it never re-orders equal elements
406 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
407 -- From: Andy Gill <andy@dcs.gla.ac.uk>
408 -- Here is a `better' definition of group.
411 group p (x:xs) = group' xs x x (x :)
413 group' [] _ _ s = [s []]
414 group' (x:xs) x_min x_max s
415 | x_max `p` x = group' xs x_min x (s . (x :))
416 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
417 | otherwise = s [] : group' xs x x (x :)
418 -- NB: the 'not' is essential for stablity
419 -- x `p` x_min would reverse equal elements
421 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
422 generalMerge p xs [] = xs
423 generalMerge p [] ys = ys
424 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
425 | otherwise = y : generalMerge p (x:xs) ys
427 -- gamma is now called balancedFold
429 balancedFold :: (a -> a -> a) -> [a] -> a
430 balancedFold f [] = error "can't reduce an empty list using balancedFold"
431 balancedFold f [x] = x
432 balancedFold f l = balancedFold f (balancedFold' f l)
434 balancedFold' :: (a -> a -> a) -> [a] -> [a]
435 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
436 balancedFold' f xs = xs
438 generalNaturalMergeSort p [] = []
439 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
442 generalMergeSort p [] = []
443 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
445 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
447 mergeSort = generalMergeSort (<=)
448 naturalMergeSort = generalNaturalMergeSort (<=)
450 mergeSortLe le = generalMergeSort le
453 sortLe :: (a->a->Bool) -> [a] -> [a]
454 sortLe le = generalNaturalMergeSort le
456 sortWith :: Ord b => (a->b) -> [a] -> [a]
457 sortWith get_key xs = sortLe le xs
459 x `le` y = get_key x < get_key y
462 %************************************************************************
464 \subsection[Utils-transitive-closure]{Transitive closure}
466 %************************************************************************
468 This algorithm for transitive closure is straightforward, albeit quadratic.
471 transitiveClosure :: (a -> [a]) -- Successor function
472 -> (a -> a -> Bool) -- Equality predicate
474 -> [a] -- The transitive closure
476 transitiveClosure succ eq xs
480 go done (x:xs) | x `is_in` done = go done xs
481 | otherwise = go (x:done) (succ x ++ xs)
484 x `is_in` (y:ys) | eq x y = True
485 | otherwise = x `is_in` ys
488 %************************************************************************
490 \subsection[Utils-accum]{Accumulating}
492 %************************************************************************
494 A combination of foldl with zip. It works with equal length lists.
497 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
499 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
501 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
502 -- True if the lists are the same length, and
503 -- all corresponding elements satisfy the predicate
505 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
509 Count the number of times a predicate is true
512 count :: (a -> Bool) -> [a] -> Int
514 count p (x:xs) | p x = 1 + count p xs
515 | otherwise = count p xs
518 @splitAt@, @take@, and @drop@ but with length of another
519 list giving the break-off point:
522 takeList :: [b] -> [a] -> [a]
527 (y:ys) -> y : takeList xs ys
529 dropList :: [b] -> [a] -> [a]
531 dropList _ xs@[] = xs
532 dropList (_:xs) (_:ys) = dropList xs ys
535 splitAtList :: [b] -> [a] -> ([a], [a])
536 splitAtList [] xs = ([], xs)
537 splitAtList _ xs@[] = (xs, xs)
538 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
540 (ys', ys'') = splitAtList xs ys
542 snocView :: [a] -> Maybe ([a],a)
543 -- Split off the last element
544 snocView [] = Nothing
545 snocView xs = go [] xs
547 -- Invariant: second arg is non-empty
548 go acc [x] = Just (reverse acc, x)
549 go acc (x:xs) = go (x:acc) xs
551 split :: Char -> String -> [String]
552 split c s = case rest of
554 _:rest -> chunk : split c rest
555 where (chunk, rest) = break (==c) s
559 %************************************************************************
561 \subsection[Utils-comparison]{Comparisons}
563 %************************************************************************
566 isEqual :: Ordering -> Bool
567 -- Often used in (isEqual (a `compare` b))
572 thenCmp :: Ordering -> Ordering -> Ordering
573 {-# INLINE thenCmp #-}
575 thenCmp other any = other
577 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
578 eqListBy eq [] [] = True
579 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
580 eqListBy eq xs ys = False
582 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
583 -- `cmpList' uses a user-specified comparer
585 cmpList cmp [] [] = EQ
586 cmpList cmp [] _ = LT
587 cmpList cmp _ [] = GT
588 cmpList cmp (a:as) (b:bs)
589 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
593 maybePrefixMatch :: String -> String -> Maybe String
594 maybePrefixMatch [] rest = Just rest
595 maybePrefixMatch (_:_) [] = Nothing
596 maybePrefixMatch (p:pat) (r:rest)
597 | p == r = maybePrefixMatch pat rest
598 | otherwise = Nothing
600 removeSpaces :: String -> String
601 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
604 %************************************************************************
606 \subsection[Utils-pairs]{Pairs}
608 %************************************************************************
611 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
612 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
616 seqList :: [a] -> b -> b
618 seqList (x:xs) b = x `seq` seqList xs b
624 global :: a -> IORef a
625 global a = unsafePerformIO (newIORef a)
629 consIORef :: IORef [a] -> a -> IO ()
632 writeIORef var (x:xs)
638 looksLikeModuleName :: String -> Bool
639 looksLikeModuleName [] = False
640 looksLikeModuleName (c:cs) = isUpper c && go cs
642 go ('.':cs) = looksLikeModuleName cs
643 go (c:cs) = (isAlphaNum c || c == '_') && go cs
646 Akin to @Prelude.words@, but acts like the Bourne shell, treating
647 quoted strings and escaped characters within the input as solid blocks
648 of characters. Doesn't raise any exceptions on malformed escapes or
652 toArgs :: String -> [String]
655 case dropWhile isSpace s of -- drop initial spacing
656 [] -> [] -- empty, so no more tokens
657 rem -> let (tok,aft) = token rem [] in tok : toArgs aft
659 -- Grab a token off the string, given that the first character exists and
660 -- isn't whitespace. The second argument is an accumulator which has to be
661 -- reversed at the end.
662 token [] acc = (reverse acc,[]) -- out of characters
663 token ('\\':c:aft) acc -- escapes
664 = token aft ((escape c) : acc)
665 token (q:aft) acc | q == '"' || q == '\'' -- open quotes
666 = let (aft',acc') = quote q aft acc in token aft' acc'
667 token (c:aft) acc | isSpace c -- unescaped, unquoted spacing
669 token (c:aft) acc -- anything else goes in the token
672 -- Get the appropriate character for a single-character escape.
678 -- Read into accumulator until a quote character is found.
680 let quote' [] acc = ([],acc)
681 quote' ('\\':c:aft) acc = quote' aft ((escape c) : acc)
682 quote' (c:aft) acc | c == qc = (aft,acc)
683 quote' (c:aft) acc = quote' aft (c:acc)
687 -- -----------------------------------------------------------------------------
691 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
692 readRational__ r = do
695 return ((n%1)*10^^(k-d), t)
698 (ds,s) <- lexDecDigits r
699 (ds',t) <- lexDotDigits s
700 return (read (ds++ds'), length ds', t)
702 readExp (e:s) | e `elem` "eE" = readExp' s
703 readExp s = return (0,s)
705 readExp' ('+':s) = readDec s
706 readExp' ('-':s) = do
709 readExp' s = readDec s
712 (ds,r) <- nonnull isDigit s
713 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
716 lexDecDigits = nonnull isDigit
718 lexDotDigits ('.':s) = return (span isDigit s)
719 lexDotDigits s = return ("",s)
721 nonnull p s = do (cs@(_:_),t) <- return (span p s)
724 readRational :: String -> Rational -- NB: *does* handle a leading "-"
727 '-' : xs -> - (read_me xs)
731 = case (do { (x,"") <- readRational__ s ; return x }) of
733 [] -> error ("readRational: no parse:" ++ top_s)
734 _ -> error ("readRational: ambiguous parse:" ++ top_s)
737 -----------------------------------------------------------------------------
738 -- Create a hierarchy of directories
740 createDirectoryHierarchy :: FilePath -> IO ()
741 createDirectoryHierarchy dir = do
742 b <- doesDirectoryExist dir
744 createDirectoryHierarchy (directoryOf dir)
747 -----------------------------------------------------------------------------
748 -- Verify that the 'dirname' portion of a FilePath exists.
750 doesDirNameExist :: FilePath -> IO Bool
751 doesDirNameExist fpath = doesDirectoryExist (directoryOf fpath)
753 -- -----------------------------------------------------------------------------
758 handleDyn :: Typeable ex => (ex -> IO a) -> IO a -> IO a
759 handleDyn = flip catchDyn
761 handle :: (Exception -> IO a) -> IO a -> IO a
762 handle h f = f `Exception.catch` \e -> case e of
763 ExitException _ -> throw e
766 -- --------------------------------------------------------------
767 -- check existence & modification time at the same time
769 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
770 modificationTimeIfExists f = do
771 (do t <- getModificationTime f; return (Just t))
772 `IO.catch` \e -> if isDoesNotExistError e
776 -- --------------------------------------------------------------
777 -- Filename manipulation
779 -- Filenames are kept "normalised" inside GHC, using '/' as the path
780 -- separator. On Windows these functions will also recognise '\\' as
781 -- the path separator, but will generally construct paths using '/'.
785 splitFilename :: String -> (String,Suffix)
786 splitFilename f = splitLongestPrefix f (=='.')
788 basenameOf :: FilePath -> String
789 basenameOf = fst . splitFilename
791 suffixOf :: FilePath -> Suffix
792 suffixOf = snd . splitFilename
794 joinFileExt :: String -> String -> FilePath
795 joinFileExt path "" = path
796 joinFileExt path ext = path ++ '.':ext
798 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy.ext")
799 splitFilenameDir :: String -> (String,String)
801 = let (dir, rest) = splitLongestPrefix str isPathSeparator
802 (dir', rest') | null rest = (".", dir)
803 | otherwise = (dir, rest)
806 -- "foo/bar/xyzzy.ext" -> ("foo/bar", "xyzzy", ".ext")
807 splitFilename3 :: String -> (String,String,Suffix)
809 = let (dir, rest) = splitFilenameDir str
810 (name, ext) = splitFilename rest
813 joinFileName :: String -> String -> FilePath
814 joinFileName "" fname = fname
815 joinFileName "." fname = fname
816 joinFileName dir "" = dir
817 joinFileName dir fname = dir ++ '/':fname
819 -- split a string at the last character where 'pred' is True,
820 -- returning a pair of strings. The first component holds the string
821 -- up (but not including) the last character for which 'pred' returned
822 -- True, the second whatever comes after (but also not including the
825 -- If 'pred' returns False for all characters in the string, the original
826 -- string is returned in the first component (and the second one is just
828 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
829 splitLongestPrefix str pred
830 | null r_pre = (str, [])
831 | otherwise = (reverse (tail r_pre), reverse r_suf)
832 -- 'tail' drops the char satisfying 'pred'
834 (r_suf, r_pre) = break pred (reverse str)
836 replaceFilenameSuffix :: FilePath -> Suffix -> FilePath
837 replaceFilenameSuffix file suf = basenameOf file `joinFileExt` suf
839 -- directoryOf strips the filename off the input string, returning
841 directoryOf :: FilePath -> String
842 directoryOf = fst . splitFilenameDir
844 -- filenameOf strips the directory off the input string, returning
846 filenameOf :: FilePath -> String
847 filenameOf = snd . splitFilenameDir
849 replaceFilenameDirectory :: FilePath -> String -> FilePath
850 replaceFilenameDirectory path dir = dir `joinFileName` filenameOf path
852 escapeSpaces :: String -> String
853 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
855 isPathSeparator :: Char -> Bool
857 #ifdef mingw32_TARGET_OS
858 ch == '/' || ch == '\\'
863 --------------------------------------------------------------
865 --------------------------------------------------------------
867 -- | The function splits the given string to substrings
868 -- using the 'searchPathSeparator'.
869 parseSearchPath :: String -> [FilePath]
870 parseSearchPath path = split path
872 split :: String -> [String]
876 _:rest -> chunk : split rest
880 #ifdef mingw32_HOST_OS
881 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
885 (chunk', rest') = break (==searchPathSeparator) s
887 -- | A platform-specific character used to separate search path strings in
888 -- environment variables. The separator is a colon (\":\") on Unix and Macintosh,
889 -- and a semicolon (\";\") on the Windows operating system.
890 searchPathSeparator :: Char
891 #if mingw32_HOST_OS || mingw32_TARGET_OS
892 searchPathSeparator = ';'
894 searchPathSeparator = ':'
897 -----------------------------------------------------------------------------
898 -- Convert filepath into platform / MSDOS form.
900 -- We maintain path names in Unix form ('/'-separated) right until
901 -- the last moment. On Windows we dos-ify them just before passing them
902 -- to the Windows command.
904 -- The alternative, of using '/' consistently on Unix and '\' on Windows,
905 -- proved quite awkward. There were a lot more calls to platformPath,
906 -- and even on Windows we might invoke a unix-like utility (eg 'sh'), which
907 -- interpreted a command line 'foo\baz' as 'foobaz'.
909 normalisePath :: String -> String
910 -- Just changes '\' to '/'
912 pgmPath :: String -- Directory string in Unix format
913 -> String -- Program name with no directory separators
915 -> String -- Program invocation string in native format
917 #if defined(mingw32_HOST_OS)
918 --------------------- Windows version ------------------
919 normalisePath xs = subst '\\' '/' xs
920 pgmPath dir pgm = platformPath dir ++ '\\' : pgm
921 platformPath p = subst '/' '\\' p
923 subst a b ls = map (\ x -> if x == a then b else x) ls
925 --------------------- Non-Windows version --------------
926 normalisePath xs = xs
927 pgmPath dir pgm = dir ++ '/' : pgm
928 platformPath stuff = stuff
929 --------------------------------------------------------