2 % (c) The University of Glasgow 2006
3 % (c) The University of Glasgow 1992-2002
7 -- | Highly random utility functions
9 -- * Flags dependent on the compiler build
10 ghciSupported, debugIsOn, ghciTablesNextToCode, picIsOn,
11 isWindowsHost, isWindowsTarget, isDarwinTarget,
13 -- * General list processing
14 zipEqual, zipWithEqual, zipWith3Equal, zipWith4Equal,
15 zipLazy, stretchZipWith,
20 mapAndUnzip, mapAndUnzip3,
21 nOfThem, filterOut, partitionWith, splitEithers,
23 foldl1', foldl2, count, all2,
25 lengthExceeds, lengthIs, lengthAtLeast,
26 listLengthCmp, atLength, equalLength, compareLength,
28 isSingleton, only, singleton,
33 -- * List operations controlled by another list
34 takeList, dropList, splitAtList, split,
48 -- * Transitive closures
57 -- * Argument processing
58 getCmd, toCmdArgs, toArgs,
64 createDirectoryHierarchy,
66 modificationTimeIfExists,
68 global, consIORef, globalMVar, globalEmptyMVar,
70 -- * Filenames and paths
75 Direction(..), reslash,
78 #include "HsVersions.h"
82 import Data.IORef ( IORef, newIORef, atomicModifyIORef )
83 import System.IO.Unsafe ( unsafePerformIO )
84 import Data.List hiding (group)
85 import Control.Concurrent.MVar ( MVar, newMVar, newEmptyMVar )
88 import qualified Data.List as List ( elem, notElem )
92 import Control.Monad ( unless )
93 import System.IO.Error as IO ( catch, isDoesNotExistError )
94 import System.Directory ( doesDirectoryExist, createDirectory,
96 import System.FilePath
97 import Data.Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
98 import Data.Ratio ( (%) )
99 import System.Time ( ClockTime )
104 %************************************************************************
106 \subsection{Is DEBUG on, are we on Windows, etc?}
108 %************************************************************************
110 These booleans are global constants, set by CPP flags. They allow us to
111 recompile a single module (this one) to change whether or not debug output
112 appears. They sometimes let us avoid even running CPP elsewhere.
114 It's important that the flags are literal constants (True/False). Then,
115 with -0, tests of the flags in other modules will simplify to the correct
116 branch of the conditional, thereby dropping debug code altogether when
120 ghciSupported :: Bool
124 ghciSupported = False
134 ghciTablesNextToCode :: Bool
135 #ifdef GHCI_TABLES_NEXT_TO_CODE
136 ghciTablesNextToCode = True
138 ghciTablesNextToCode = False
148 isWindowsHost :: Bool
149 #ifdef mingw32_HOST_OS
152 isWindowsHost = False
155 isWindowsTarget :: Bool
156 #ifdef mingw32_TARGET_OS
157 isWindowsTarget = True
159 isWindowsTarget = False
162 isDarwinTarget :: Bool
163 #ifdef darwin_TARGET_OS
164 isDarwinTarget = True
166 isDarwinTarget = False
170 %************************************************************************
172 \subsection{A for loop}
174 %************************************************************************
177 -- | Compose a function with itself n times. (nth rather than twice)
178 nTimes :: Int -> (a -> a) -> (a -> a)
181 nTimes n f = f . nTimes (n-1) f
184 %************************************************************************
186 \subsection[Utils-lists]{General list processing}
188 %************************************************************************
191 filterOut :: (a->Bool) -> [a] -> [a]
192 -- ^ Like filter, only it reverses the sense of the test
194 filterOut p (x:xs) | p x = filterOut p xs
195 | otherwise = x : filterOut p xs
197 partitionWith :: (a -> Either b c) -> [a] -> ([b], [c])
198 -- ^ Uses a function to determine which of two output lists an input element should join
199 partitionWith _ [] = ([],[])
200 partitionWith f (x:xs) = case f x of
202 Right c -> (bs, c:cs)
203 where (bs,cs) = partitionWith f xs
205 splitEithers :: [Either a b] -> ([a], [b])
206 -- ^ Teases a list of 'Either's apart into two lists
207 splitEithers [] = ([],[])
208 splitEithers (e : es) = case e of
210 Right y -> (xs, y:ys)
211 where (xs,ys) = splitEithers es
214 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
215 are of equal length. Alastair Reid thinks this should only happen if
216 DEBUGging on; hey, why not?
219 zipEqual :: String -> [a] -> [b] -> [(a,b)]
220 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
221 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
222 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
226 zipWithEqual _ = zipWith
227 zipWith3Equal _ = zipWith3
228 zipWith4Equal _ = zipWith4
230 zipEqual _ [] [] = []
231 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
232 zipEqual msg _ _ = panic ("zipEqual: unequal lists:"++msg)
234 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
235 zipWithEqual _ _ [] [] = []
236 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
238 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
239 = z a b c : zipWith3Equal msg z as bs cs
240 zipWith3Equal _ _ [] [] [] = []
241 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
243 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
244 = z a b c d : zipWith4Equal msg z as bs cs ds
245 zipWith4Equal _ _ [] [] [] [] = []
246 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
251 -- | 'zipLazy' is a kind of 'zip' that is lazy in the second list (observe the ~)
252 zipLazy :: [a] -> [b] -> [(a,b)]
254 -- We want to write this, but with GHC 6.4 we get a warning, so it
256 -- zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
257 -- so we write this instead:
258 zipLazy (x:xs) zs = let y : ys = zs
259 in (x,y) : zipLazy xs ys
264 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
265 -- ^ @stretchZipWith p z f xs ys@ stretches @ys@ by inserting @z@ in
266 -- the places where @p@ returns @True@
268 stretchZipWith _ _ _ [] _ = []
269 stretchZipWith p z f (x:xs) ys
270 | p x = f x z : stretchZipWith p z f xs ys
271 | otherwise = case ys of
273 (y:ys) -> f x y : stretchZipWith p z f xs ys
278 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
279 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
281 mapFst f xys = [(f x, y) | (x,y) <- xys]
282 mapSnd f xys = [(x, f y) | (x,y) <- xys]
284 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
286 mapAndUnzip _ [] = ([], [])
289 (rs1, rs2) = mapAndUnzip f xs
293 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
295 mapAndUnzip3 _ [] = ([], [], [])
296 mapAndUnzip3 f (x:xs)
297 = let (r1, r2, r3) = f x
298 (rs1, rs2, rs3) = mapAndUnzip3 f xs
300 (r1:rs1, r2:rs2, r3:rs3)
304 nOfThem :: Int -> a -> [a]
305 nOfThem n thing = replicate n thing
307 -- | @atLength atLen atEnd ls n@ unravels list @ls@ to position @n@. Precisely:
310 -- atLength atLenPred atEndPred ls n
311 -- | n < 0 = atLenPred n
312 -- | length ls < n = atEndPred (n - length ls)
313 -- | otherwise = atLenPred (drop n ls)
315 atLength :: ([a] -> b)
320 atLength atLenPred atEndPred ls n
321 | n < 0 = atEndPred n
322 | otherwise = go n ls
324 go n [] = atEndPred n
325 go 0 ls = atLenPred ls
326 go n (_:xs) = go (n-1) xs
328 -- Some special cases of atLength:
330 lengthExceeds :: [a] -> Int -> Bool
331 -- ^ > (lengthExceeds xs n) = (length xs > n)
332 lengthExceeds = atLength notNull (const False)
334 lengthAtLeast :: [a] -> Int -> Bool
335 lengthAtLeast = atLength notNull (== 0)
337 lengthIs :: [a] -> Int -> Bool
338 lengthIs = atLength null (==0)
340 listLengthCmp :: [a] -> Int -> Ordering
341 listLengthCmp = atLength atLen atEnd
345 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
351 equalLength :: [a] -> [b] -> Bool
352 equalLength [] [] = True
353 equalLength (_:xs) (_:ys) = equalLength xs ys
354 equalLength _ _ = False
356 compareLength :: [a] -> [b] -> Ordering
357 compareLength [] [] = EQ
358 compareLength (_:xs) (_:ys) = compareLength xs ys
359 compareLength [] _ = LT
360 compareLength _ [] = GT
362 ----------------------------
363 singleton :: a -> [a]
366 isSingleton :: [a] -> Bool
367 isSingleton [_] = True
368 isSingleton _ = False
370 notNull :: [a] -> Bool
380 only _ = panic "Util: only"
383 Debugging/specialising versions of \tr{elem} and \tr{notElem}
386 isIn, isn'tIn :: Eq a => String -> a -> [a] -> Bool
389 isIn _msg x ys = elem__ x ys
390 isn'tIn _msg x ys = notElem__ x ys
392 --these are here to be SPECIALIZEd (automagically)
393 elem__ :: Eq a => a -> [a] -> Bool
395 elem__ x (y:ys) = x == y || elem__ x ys
397 notElem__ :: Eq a => a -> [a] -> Bool
398 notElem__ _ [] = True
399 notElem__ x (y:ys) = x /= y && notElem__ x ys
403 = elem (_ILIT(0)) x ys
407 | i ># _ILIT(100) = trace ("Over-long elem in " ++ msg)
408 (x `List.elem` (y:ys))
409 | otherwise = x == y || elem (i +# _ILIT(1)) x ys
412 = notElem (_ILIT(0)) x ys
414 notElem _ _ [] = True
416 | i ># _ILIT(100) = trace ("Over-long notElem in " ++ msg)
417 (x `List.notElem` (y:ys))
418 | otherwise = x /= y && notElem (i +# _ILIT(1)) x ys
422 %************************************************************************
424 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
426 %************************************************************************
429 Date: Mon, 3 May 93 20:45:23 +0200
430 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
431 To: partain@dcs.gla.ac.uk
432 Subject: natural merge sort beats quick sort [ and it is prettier ]
434 Here is a piece of Haskell code that I'm rather fond of. See it as an
435 attempt to get rid of the ridiculous quick-sort routine. group is
436 quite useful by itself I think it was John's idea originally though I
437 believe the lazy version is due to me [surprisingly complicated].
438 gamma [used to be called] is called gamma because I got inspired by
439 the Gamma calculus. It is not very close to the calculus but does
440 behave less sequentially than both foldr and foldl. One could imagine
441 a version of gamma that took a unit element as well thereby avoiding
442 the problem with empty lists.
444 I've tried this code against
446 1) insertion sort - as provided by haskell
447 2) the normal implementation of quick sort
448 3) a deforested version of quick sort due to Jan Sparud
449 4) a super-optimized-quick-sort of Lennart's
451 If the list is partially sorted both merge sort and in particular
452 natural merge sort wins. If the list is random [ average length of
453 rising subsequences = approx 2 ] mergesort still wins and natural
454 merge sort is marginally beaten by Lennart's soqs. The space
455 consumption of merge sort is a bit worse than Lennart's quick sort
456 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
457 fpca article ] isn't used because of group.
464 group :: (a -> a -> Bool) -> [a] -> [[a]]
465 -- Given a <= function, group finds maximal contiguous up-runs
466 -- or down-runs in the input list.
467 -- It's stable, in the sense that it never re-orders equal elements
469 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
470 -- From: Andy Gill <andy@dcs.gla.ac.uk>
471 -- Here is a `better' definition of group.
474 group p (x:xs) = group' xs x x (x :)
476 group' [] _ _ s = [s []]
477 group' (x:xs) x_min x_max s
478 | x_max `p` x = group' xs x_min x (s . (x :))
479 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
480 | otherwise = s [] : group' xs x x (x :)
481 -- NB: the 'not' is essential for stablity
482 -- x `p` x_min would reverse equal elements
484 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
485 generalMerge _ xs [] = xs
486 generalMerge _ [] ys = ys
487 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
488 | otherwise = y : generalMerge p (x:xs) ys
490 -- gamma is now called balancedFold
492 balancedFold :: (a -> a -> a) -> [a] -> a
493 balancedFold _ [] = error "can't reduce an empty list using balancedFold"
494 balancedFold _ [x] = x
495 balancedFold f l = balancedFold f (balancedFold' f l)
497 balancedFold' :: (a -> a -> a) -> [a] -> [a]
498 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
499 balancedFold' _ xs = xs
501 generalNaturalMergeSort :: (a -> a -> Bool) -> [a] -> [a]
502 generalNaturalMergeSort _ [] = []
503 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
506 generalMergeSort p [] = []
507 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
509 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
511 mergeSort = generalMergeSort (<=)
512 naturalMergeSort = generalNaturalMergeSort (<=)
514 mergeSortLe le = generalMergeSort le
517 sortLe :: (a->a->Bool) -> [a] -> [a]
518 sortLe le = generalNaturalMergeSort le
520 sortWith :: Ord b => (a->b) -> [a] -> [a]
521 sortWith get_key xs = sortLe le xs
523 x `le` y = get_key x < get_key y
525 on :: (a -> a -> c) -> (b -> a) -> b -> b -> c
526 on cmp sel = \x y -> sel x `cmp` sel y
530 %************************************************************************
532 \subsection[Utils-transitive-closure]{Transitive closure}
534 %************************************************************************
536 This algorithm for transitive closure is straightforward, albeit quadratic.
539 transitiveClosure :: (a -> [a]) -- Successor function
540 -> (a -> a -> Bool) -- Equality predicate
542 -> [a] -- The transitive closure
544 transitiveClosure succ eq xs
548 go done (x:xs) | x `is_in` done = go done xs
549 | otherwise = go (x:done) (succ x ++ xs)
552 x `is_in` (y:ys) | eq x y = True
553 | otherwise = x `is_in` ys
556 %************************************************************************
558 \subsection[Utils-accum]{Accumulating}
560 %************************************************************************
562 A combination of foldl with zip. It works with equal length lists.
565 foldl2 :: (acc -> a -> b -> acc) -> acc -> [a] -> [b] -> acc
567 foldl2 k z (a:as) (b:bs) = foldl2 k (k z a b) as bs
568 foldl2 _ _ _ _ = panic "Util: foldl2"
570 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
571 -- True if the lists are the same length, and
572 -- all corresponding elements satisfy the predicate
574 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
578 Count the number of times a predicate is true
581 count :: (a -> Bool) -> [a] -> Int
583 count p (x:xs) | p x = 1 + count p xs
584 | otherwise = count p xs
587 @splitAt@, @take@, and @drop@ but with length of another
588 list giving the break-off point:
591 takeList :: [b] -> [a] -> [a]
596 (y:ys) -> y : takeList xs ys
598 dropList :: [b] -> [a] -> [a]
600 dropList _ xs@[] = xs
601 dropList (_:xs) (_:ys) = dropList xs ys
604 splitAtList :: [b] -> [a] -> ([a], [a])
605 splitAtList [] xs = ([], xs)
606 splitAtList _ xs@[] = (xs, xs)
607 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
609 (ys', ys'') = splitAtList xs ys
611 -- drop from the end of a list
612 dropTail :: Int -> [a] -> [a]
613 dropTail n = reverse . drop n . reverse
615 snocView :: [a] -> Maybe ([a],a)
616 -- Split off the last element
617 snocView [] = Nothing
618 snocView xs = go [] xs
620 -- Invariant: second arg is non-empty
621 go acc [x] = Just (reverse acc, x)
622 go acc (x:xs) = go (x:acc) xs
623 go _ [] = panic "Util: snocView"
625 split :: Char -> String -> [String]
626 split c s = case rest of
628 _:rest -> chunk : split c rest
629 where (chunk, rest) = break (==c) s
633 %************************************************************************
635 \subsection[Utils-comparison]{Comparisons}
637 %************************************************************************
640 isEqual :: Ordering -> Bool
641 -- Often used in (isEqual (a `compare` b))
646 thenCmp :: Ordering -> Ordering -> Ordering
647 {-# INLINE thenCmp #-}
648 thenCmp EQ ordering = ordering
649 thenCmp ordering _ = ordering
651 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
652 eqListBy _ [] [] = True
653 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
654 eqListBy _ _ _ = False
656 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
657 -- `cmpList' uses a user-specified comparer
662 cmpList cmp (a:as) (b:bs)
663 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
667 removeSpaces :: String -> String
668 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
671 %************************************************************************
673 \subsection[Utils-pairs]{Pairs}
675 %************************************************************************
678 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
679 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
683 seqList :: [a] -> b -> b
685 seqList (x:xs) b = x `seq` seqList xs b
691 global :: a -> IORef a
692 global a = unsafePerformIO (newIORef a)
696 consIORef :: IORef [a] -> a -> IO ()
698 atomicModifyIORef var (\xs -> (x:xs,()))
702 globalMVar :: a -> MVar a
703 globalMVar a = unsafePerformIO (newMVar a)
705 globalEmptyMVar :: MVar a
706 globalEmptyMVar = unsafePerformIO newEmptyMVar
712 looksLikeModuleName :: String -> Bool
713 looksLikeModuleName [] = False
714 looksLikeModuleName (c:cs) = isUpper c && go cs
716 go ('.':cs) = looksLikeModuleName cs
717 go (c:cs) = (isAlphaNum c || c == '_') && go cs
720 Akin to @Prelude.words@, but acts like the Bourne shell, treating
721 quoted strings as Haskell Strings, and also parses Haskell [String]
725 getCmd :: String -> Either String -- Error
726 (String, String) -- (Cmd, Rest)
727 getCmd s = case break isSpace $ dropWhile isSpace s of
728 ([], _) -> Left ("Couldn't find command in " ++ show s)
731 toCmdArgs :: String -> Either String -- Error
732 (String, [String]) -- (Cmd, Args)
733 toCmdArgs s = case getCmd s of
735 Right (cmd, s') -> case toArgs s' of
737 Right args -> Right (cmd, args)
739 toArgs :: String -> Either String -- Error
742 = case dropWhile isSpace str of
743 s@('[':_) -> case reads s of
745 | all isSpace spaces ->
748 Left ("Couldn't read " ++ show str ++ "as [String]")
751 toArgs' s = case dropWhile isSpace s of
753 ('"' : _) -> case reads s of
755 -- rest must either be [] or start with a space
756 | all isSpace (take 1 rest) ->
759 Right args -> Right (arg : args)
761 Left ("Couldn't read " ++ show s ++ "as String")
762 s' -> case break isSpace s' of
763 (arg, s'') -> case toArgs' s'' of
765 Right args -> Right (arg : args)
768 -- -----------------------------------------------------------------------------
772 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
773 readRational__ r = do
776 return ((n%1)*10^^(k-d), t)
779 (ds,s) <- lexDecDigits r
780 (ds',t) <- lexDotDigits s
781 return (read (ds++ds'), length ds', t)
783 readExp (e:s) | e `elem` "eE" = readExp' s
784 readExp s = return (0,s)
786 readExp' ('+':s) = readDec s
787 readExp' ('-':s) = do (k,t) <- readDec s
789 readExp' s = readDec s
792 (ds,r) <- nonnull isDigit s
793 return (foldl1 (\n d -> n * 10 + d) [ ord d - ord '0' | d <- ds ],
796 lexDecDigits = nonnull isDigit
798 lexDotDigits ('.':s) = return (span isDigit s)
799 lexDotDigits s = return ("",s)
801 nonnull p s = do (cs@(_:_),t) <- return (span p s)
804 readRational :: String -> Rational -- NB: *does* handle a leading "-"
807 '-' : xs -> - (read_me xs)
811 = case (do { (x,"") <- readRational__ s ; return x }) of
813 [] -> error ("readRational: no parse:" ++ top_s)
814 _ -> error ("readRational: ambiguous parse:" ++ top_s)
817 -----------------------------------------------------------------------------
818 -- Create a hierarchy of directories
820 createDirectoryHierarchy :: FilePath -> IO ()
821 createDirectoryHierarchy dir | isDrive dir = return () -- XXX Hack
822 createDirectoryHierarchy dir = do
823 b <- doesDirectoryExist dir
824 unless b $ do createDirectoryHierarchy (takeDirectory dir)
827 -----------------------------------------------------------------------------
828 -- Verify that the 'dirname' portion of a FilePath exists.
830 doesDirNameExist :: FilePath -> IO Bool
831 doesDirNameExist fpath = case takeDirectory fpath of
832 "" -> return True -- XXX Hack
833 _ -> doesDirectoryExist (takeDirectory fpath)
835 -- --------------------------------------------------------------
836 -- check existence & modification time at the same time
838 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
839 modificationTimeIfExists f = do
840 (do t <- getModificationTime f; return (Just t))
841 `IO.catch` \e -> if isDoesNotExistError e
845 -- split a string at the last character where 'pred' is True,
846 -- returning a pair of strings. The first component holds the string
847 -- up (but not including) the last character for which 'pred' returned
848 -- True, the second whatever comes after (but also not including the
851 -- If 'pred' returns False for all characters in the string, the original
852 -- string is returned in the first component (and the second one is just
854 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
855 splitLongestPrefix str pred
856 | null r_pre = (str, [])
857 | otherwise = (reverse (tail r_pre), reverse r_suf)
858 -- 'tail' drops the char satisfying 'pred'
859 where (r_suf, r_pre) = break pred (reverse str)
861 escapeSpaces :: String -> String
862 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
866 --------------------------------------------------------------
868 --------------------------------------------------------------
870 -- | The function splits the given string to substrings
871 -- using the 'searchPathSeparator'.
872 parseSearchPath :: String -> [FilePath]
873 parseSearchPath path = split path
875 split :: String -> [String]
879 _:rest -> chunk : split rest
883 #ifdef mingw32_HOST_OS
884 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
888 (chunk', rest') = break isSearchPathSeparator s
890 data Direction = Forwards | Backwards
892 reslash :: Direction -> FilePath -> FilePath
894 where f ('/' : xs) = slash : f xs
895 f ('\\' : xs) = slash : f xs
896 f (x : xs) = x : f xs