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, isDynamicGhcLib,
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,
34 fstOf3, sndOf3, thirdOf3,
36 -- * List operations controlled by another list
37 takeList, dropList, splitAtList, split,
51 -- * Transitive closures
60 -- * Argument processing
61 getCmd, toCmdArgs, toArgs,
67 createDirectoryHierarchy,
69 modificationTimeIfExists,
71 global, consIORef, globalMVar, globalEmptyMVar,
73 -- * Filenames and paths
78 Direction(..), reslash,
81 #include "HsVersions.h"
85 import Data.IORef ( IORef, newIORef, atomicModifyIORef )
86 import System.IO.Unsafe ( unsafePerformIO )
87 import Data.List hiding (group)
88 import Control.Concurrent.MVar ( MVar, newMVar, newEmptyMVar )
94 import Control.Monad ( unless )
95 import System.IO.Error as IO ( catch, isDoesNotExistError )
96 import System.Directory ( doesDirectoryExist, createDirectory,
98 import System.FilePath
99 import Data.Char ( isUpper, isAlphaNum, isSpace, ord, isDigit )
100 import Data.Ratio ( (%) )
101 import System.Time ( ClockTime )
106 %************************************************************************
108 \subsection{Is DEBUG on, are we on Windows, etc?}
110 %************************************************************************
112 These booleans are global constants, set by CPP flags. They allow us to
113 recompile a single module (this one) to change whether or not debug output
114 appears. They sometimes let us avoid even running CPP elsewhere.
116 It's important that the flags are literal constants (True/False). Then,
117 with -0, tests of the flags in other modules will simplify to the correct
118 branch of the conditional, thereby dropping debug code altogether when
122 ghciSupported :: Bool
126 ghciSupported = False
136 ghciTablesNextToCode :: Bool
137 #ifdef GHCI_TABLES_NEXT_TO_CODE
138 ghciTablesNextToCode = True
140 ghciTablesNextToCode = False
143 isDynamicGhcLib :: Bool
145 isDynamicGhcLib = True
147 isDynamicGhcLib = False
150 isWindowsHost :: Bool
151 #ifdef mingw32_HOST_OS
154 isWindowsHost = False
157 isWindowsTarget :: Bool
158 #ifdef mingw32_TARGET_OS
159 isWindowsTarget = True
161 isWindowsTarget = False
164 isDarwinTarget :: Bool
165 #ifdef darwin_TARGET_OS
166 isDarwinTarget = True
168 isDarwinTarget = False
172 %************************************************************************
174 \subsection{A for loop}
176 %************************************************************************
179 -- | Compose a function with itself n times. (nth rather than twice)
180 nTimes :: Int -> (a -> a) -> (a -> a)
183 nTimes n f = f . nTimes (n-1) f
187 fstOf3 :: (a,b,c) -> a
188 sndOf3 :: (a,b,c) -> b
189 thirdOf3 :: (a,b,c) -> c
195 %************************************************************************
197 \subsection[Utils-lists]{General list processing}
199 %************************************************************************
202 filterOut :: (a->Bool) -> [a] -> [a]
203 -- ^ Like filter, only it reverses the sense of the test
205 filterOut p (x:xs) | p x = filterOut p xs
206 | otherwise = x : filterOut p xs
208 partitionWith :: (a -> Either b c) -> [a] -> ([b], [c])
209 -- ^ Uses a function to determine which of two output lists an input element should join
210 partitionWith _ [] = ([],[])
211 partitionWith f (x:xs) = case f x of
213 Right c -> (bs, c:cs)
214 where (bs,cs) = partitionWith f xs
216 splitEithers :: [Either a b] -> ([a], [b])
217 -- ^ Teases a list of 'Either's apart into two lists
218 splitEithers [] = ([],[])
219 splitEithers (e : es) = case e of
221 Right y -> (xs, y:ys)
222 where (xs,ys) = splitEithers es
225 A paranoid @zip@ (and some @zipWith@ friends) that checks the lists
226 are of equal length. Alastair Reid thinks this should only happen if
227 DEBUGging on; hey, why not?
230 zipEqual :: String -> [a] -> [b] -> [(a,b)]
231 zipWithEqual :: String -> (a->b->c) -> [a]->[b]->[c]
232 zipWith3Equal :: String -> (a->b->c->d) -> [a]->[b]->[c]->[d]
233 zipWith4Equal :: String -> (a->b->c->d->e) -> [a]->[b]->[c]->[d]->[e]
237 zipWithEqual _ = zipWith
238 zipWith3Equal _ = zipWith3
239 zipWith4Equal _ = zipWith4
241 zipEqual _ [] [] = []
242 zipEqual msg (a:as) (b:bs) = (a,b) : zipEqual msg as bs
243 zipEqual msg _ _ = panic ("zipEqual: unequal lists:"++msg)
245 zipWithEqual msg z (a:as) (b:bs)= z a b : zipWithEqual msg z as bs
246 zipWithEqual _ _ [] [] = []
247 zipWithEqual msg _ _ _ = panic ("zipWithEqual: unequal lists:"++msg)
249 zipWith3Equal msg z (a:as) (b:bs) (c:cs)
250 = z a b c : zipWith3Equal msg z as bs cs
251 zipWith3Equal _ _ [] [] [] = []
252 zipWith3Equal msg _ _ _ _ = panic ("zipWith3Equal: unequal lists:"++msg)
254 zipWith4Equal msg z (a:as) (b:bs) (c:cs) (d:ds)
255 = z a b c d : zipWith4Equal msg z as bs cs ds
256 zipWith4Equal _ _ [] [] [] [] = []
257 zipWith4Equal msg _ _ _ _ _ = panic ("zipWith4Equal: unequal lists:"++msg)
262 -- | 'zipLazy' is a kind of 'zip' that is lazy in the second list (observe the ~)
263 zipLazy :: [a] -> [b] -> [(a,b)]
265 -- We want to write this, but with GHC 6.4 we get a warning, so it
267 -- zipLazy (x:xs) ~(y:ys) = (x,y) : zipLazy xs ys
268 -- so we write this instead:
269 zipLazy (x:xs) zs = let y : ys = zs
270 in (x,y) : zipLazy xs ys
275 stretchZipWith :: (a -> Bool) -> b -> (a->b->c) -> [a] -> [b] -> [c]
276 -- ^ @stretchZipWith p z f xs ys@ stretches @ys@ by inserting @z@ in
277 -- the places where @p@ returns @True@
279 stretchZipWith _ _ _ [] _ = []
280 stretchZipWith p z f (x:xs) ys
281 | p x = f x z : stretchZipWith p z f xs ys
282 | otherwise = case ys of
284 (y:ys) -> f x y : stretchZipWith p z f xs ys
289 mapFst :: (a->c) -> [(a,b)] -> [(c,b)]
290 mapSnd :: (b->c) -> [(a,b)] -> [(a,c)]
292 mapFst f xys = [(f x, y) | (x,y) <- xys]
293 mapSnd f xys = [(x, f y) | (x,y) <- xys]
295 mapAndUnzip :: (a -> (b, c)) -> [a] -> ([b], [c])
297 mapAndUnzip _ [] = ([], [])
300 (rs1, rs2) = mapAndUnzip f xs
304 mapAndUnzip3 :: (a -> (b, c, d)) -> [a] -> ([b], [c], [d])
306 mapAndUnzip3 _ [] = ([], [], [])
307 mapAndUnzip3 f (x:xs)
308 = let (r1, r2, r3) = f x
309 (rs1, rs2, rs3) = mapAndUnzip3 f xs
311 (r1:rs1, r2:rs2, r3:rs3)
315 nOfThem :: Int -> a -> [a]
316 nOfThem n thing = replicate n thing
318 -- | @atLength atLen atEnd ls n@ unravels list @ls@ to position @n@. Precisely:
321 -- atLength atLenPred atEndPred ls n
322 -- | n < 0 = atLenPred n
323 -- | length ls < n = atEndPred (n - length ls)
324 -- | otherwise = atLenPred (drop n ls)
326 atLength :: ([a] -> b)
331 atLength atLenPred atEndPred ls n
332 | n < 0 = atEndPred n
333 | otherwise = go n ls
335 go n [] = atEndPred n
336 go 0 ls = atLenPred ls
337 go n (_:xs) = go (n-1) xs
339 -- Some special cases of atLength:
341 lengthExceeds :: [a] -> Int -> Bool
342 -- ^ > (lengthExceeds xs n) = (length xs > n)
343 lengthExceeds = atLength notNull (const False)
345 lengthAtLeast :: [a] -> Int -> Bool
346 lengthAtLeast = atLength notNull (== 0)
348 lengthIs :: [a] -> Int -> Bool
349 lengthIs = atLength null (==0)
351 listLengthCmp :: [a] -> Int -> Ordering
352 listLengthCmp = atLength atLen atEnd
356 | x > 0 = LT -- not yet seen 'n' elts, so list length is < n.
362 equalLength :: [a] -> [b] -> Bool
363 equalLength [] [] = True
364 equalLength (_:xs) (_:ys) = equalLength xs ys
365 equalLength _ _ = False
367 compareLength :: [a] -> [b] -> Ordering
368 compareLength [] [] = EQ
369 compareLength (_:xs) (_:ys) = compareLength xs ys
370 compareLength [] _ = LT
371 compareLength _ [] = GT
373 ----------------------------
374 singleton :: a -> [a]
377 isSingleton :: [a] -> Bool
378 isSingleton [_] = True
379 isSingleton _ = False
381 notNull :: [a] -> Bool
391 only _ = panic "Util: only"
394 Debugging/specialising versions of \tr{elem} and \tr{notElem}
397 isIn, isn'tIn :: Eq a => String -> a -> [a] -> Bool
400 isIn _msg x ys = x `elem` ys
401 isn'tIn _msg x ys = x `notElem` ys
405 = elem100 (_ILIT(0)) x ys
407 elem100 _ _ [] = False
409 | i ># _ILIT(100) = trace ("Over-long elem in " ++ msg)
411 | otherwise = x == y || elem100 (i +# _ILIT(1)) x ys
414 = notElem100 (_ILIT(0)) x ys
416 notElem100 _ _ [] = True
417 notElem100 i x (y:ys)
418 | i ># _ILIT(100) = trace ("Over-long notElem in " ++ msg)
420 | otherwise = x /= y && notElem100 (i +# _ILIT(1)) x ys
424 %************************************************************************
426 \subsubsection[Utils-Carsten-mergesort]{A mergesort from Carsten}
428 %************************************************************************
431 Date: Mon, 3 May 93 20:45:23 +0200
432 From: Carsten Kehler Holst <kehler@cs.chalmers.se>
433 To: partain@dcs.gla.ac.uk
434 Subject: natural merge sort beats quick sort [ and it is prettier ]
436 Here is a piece of Haskell code that I'm rather fond of. See it as an
437 attempt to get rid of the ridiculous quick-sort routine. group is
438 quite useful by itself I think it was John's idea originally though I
439 believe the lazy version is due to me [surprisingly complicated].
440 gamma [used to be called] is called gamma because I got inspired by
441 the Gamma calculus. It is not very close to the calculus but does
442 behave less sequentially than both foldr and foldl. One could imagine
443 a version of gamma that took a unit element as well thereby avoiding
444 the problem with empty lists.
446 I've tried this code against
448 1) insertion sort - as provided by haskell
449 2) the normal implementation of quick sort
450 3) a deforested version of quick sort due to Jan Sparud
451 4) a super-optimized-quick-sort of Lennart's
453 If the list is partially sorted both merge sort and in particular
454 natural merge sort wins. If the list is random [ average length of
455 rising subsequences = approx 2 ] mergesort still wins and natural
456 merge sort is marginally beaten by Lennart's soqs. The space
457 consumption of merge sort is a bit worse than Lennart's quick sort
458 approx a factor of 2. And a lot worse if Sparud's bug-fix [see his
459 fpca article ] isn't used because of group.
466 group :: (a -> a -> Bool) -> [a] -> [[a]]
467 -- Given a <= function, group finds maximal contiguous up-runs
468 -- or down-runs in the input list.
469 -- It's stable, in the sense that it never re-orders equal elements
471 -- Date: Mon, 12 Feb 1996 15:09:41 +0000
472 -- From: Andy Gill <andy@dcs.gla.ac.uk>
473 -- Here is a `better' definition of group.
476 group p (x:xs) = group' xs x x (x :)
478 group' [] _ _ s = [s []]
479 group' (x:xs) x_min x_max s
480 | x_max `p` x = group' xs x_min x (s . (x :))
481 | not (x_min `p` x) = group' xs x x_max ((x :) . s)
482 | otherwise = s [] : group' xs x x (x :)
483 -- NB: the 'not' is essential for stablity
484 -- x `p` x_min would reverse equal elements
486 generalMerge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
487 generalMerge _ xs [] = xs
488 generalMerge _ [] ys = ys
489 generalMerge p (x:xs) (y:ys) | x `p` y = x : generalMerge p xs (y:ys)
490 | otherwise = y : generalMerge p (x:xs) ys
492 -- gamma is now called balancedFold
494 balancedFold :: (a -> a -> a) -> [a] -> a
495 balancedFold _ [] = error "can't reduce an empty list using balancedFold"
496 balancedFold _ [x] = x
497 balancedFold f l = balancedFold f (balancedFold' f l)
499 balancedFold' :: (a -> a -> a) -> [a] -> [a]
500 balancedFold' f (x:y:xs) = f x y : balancedFold' f xs
501 balancedFold' _ xs = xs
503 generalNaturalMergeSort :: (a -> a -> Bool) -> [a] -> [a]
504 generalNaturalMergeSort _ [] = []
505 generalNaturalMergeSort p xs = (balancedFold (generalMerge p) . group p) xs
508 generalMergeSort p [] = []
509 generalMergeSort p xs = (balancedFold (generalMerge p) . map (: [])) xs
511 mergeSort, naturalMergeSort :: Ord a => [a] -> [a]
513 mergeSort = generalMergeSort (<=)
514 naturalMergeSort = generalNaturalMergeSort (<=)
516 mergeSortLe le = generalMergeSort le
519 sortLe :: (a->a->Bool) -> [a] -> [a]
520 sortLe le = generalNaturalMergeSort le
522 sortWith :: Ord b => (a->b) -> [a] -> [a]
523 sortWith get_key xs = sortLe le xs
525 x `le` y = get_key x < get_key y
527 on :: (a -> a -> c) -> (b -> a) -> b -> b -> c
528 on cmp sel = \x y -> sel x `cmp` sel y
532 %************************************************************************
534 \subsection[Utils-transitive-closure]{Transitive closure}
536 %************************************************************************
538 This algorithm for transitive closure is straightforward, albeit quadratic.
541 transitiveClosure :: (a -> [a]) -- Successor function
542 -> (a -> a -> Bool) -- Equality predicate
544 -> [a] -- The transitive closure
546 transitiveClosure succ eq xs
550 go done (x:xs) | x `is_in` done = go done xs
551 | otherwise = go (x:done) (succ x ++ xs)
554 x `is_in` (y:ys) | eq x y = True
555 | otherwise = x `is_in` ys
558 %************************************************************************
560 \subsection[Utils-accum]{Accumulating}
562 %************************************************************************
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
570 foldl2 _ _ _ _ = panic "Util: foldl2"
572 all2 :: (a -> b -> Bool) -> [a] -> [b] -> Bool
573 -- True if the lists are the same length, and
574 -- all corresponding elements satisfy the predicate
576 all2 p (x:xs) (y:ys) = p x y && all2 p xs ys
580 Count the number of times a predicate is true
583 count :: (a -> Bool) -> [a] -> Int
585 count p (x:xs) | p x = 1 + count p xs
586 | otherwise = count p xs
589 @splitAt@, @take@, and @drop@ but with length of another
590 list giving the break-off point:
593 takeList :: [b] -> [a] -> [a]
598 (y:ys) -> y : takeList xs ys
600 dropList :: [b] -> [a] -> [a]
602 dropList _ xs@[] = xs
603 dropList (_:xs) (_:ys) = dropList xs ys
606 splitAtList :: [b] -> [a] -> ([a], [a])
607 splitAtList [] xs = ([], xs)
608 splitAtList _ xs@[] = (xs, xs)
609 splitAtList (_:xs) (y:ys) = (y:ys', ys'')
611 (ys', ys'') = splitAtList xs ys
613 -- drop from the end of a list
614 dropTail :: Int -> [a] -> [a]
615 dropTail n = reverse . drop n . reverse
617 snocView :: [a] -> Maybe ([a],a)
618 -- Split off the last element
619 snocView [] = Nothing
620 snocView xs = go [] xs
622 -- Invariant: second arg is non-empty
623 go acc [x] = Just (reverse acc, x)
624 go acc (x:xs) = go (x:acc) xs
625 go _ [] = panic "Util: snocView"
627 split :: Char -> String -> [String]
628 split c s = case rest of
630 _:rest -> chunk : split c rest
631 where (chunk, rest) = break (==c) s
635 %************************************************************************
637 \subsection[Utils-comparison]{Comparisons}
639 %************************************************************************
642 isEqual :: Ordering -> Bool
643 -- Often used in (isEqual (a `compare` b))
648 thenCmp :: Ordering -> Ordering -> Ordering
649 {-# INLINE thenCmp #-}
650 thenCmp EQ ordering = ordering
651 thenCmp ordering _ = ordering
653 eqListBy :: (a->a->Bool) -> [a] -> [a] -> Bool
654 eqListBy _ [] [] = True
655 eqListBy eq (x:xs) (y:ys) = eq x y && eqListBy eq xs ys
656 eqListBy _ _ _ = False
658 cmpList :: (a -> a -> Ordering) -> [a] -> [a] -> Ordering
659 -- `cmpList' uses a user-specified comparer
664 cmpList cmp (a:as) (b:bs)
665 = case cmp a b of { EQ -> cmpList cmp as bs; xxx -> xxx }
669 removeSpaces :: String -> String
670 removeSpaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
673 %************************************************************************
675 \subsection[Utils-pairs]{Pairs}
677 %************************************************************************
680 unzipWith :: (a -> b -> c) -> [(a, b)] -> [c]
681 unzipWith f pairs = map ( \ (a, b) -> f a b ) pairs
685 seqList :: [a] -> b -> b
687 seqList (x:xs) b = x `seq` seqList xs b
693 global :: a -> IORef a
694 global a = unsafePerformIO (newIORef a)
698 consIORef :: IORef [a] -> a -> IO ()
700 atomicModifyIORef var (\xs -> (x:xs,()))
704 globalMVar :: a -> MVar a
705 globalMVar a = unsafePerformIO (newMVar a)
707 globalEmptyMVar :: MVar a
708 globalEmptyMVar = unsafePerformIO newEmptyMVar
714 looksLikeModuleName :: String -> Bool
715 looksLikeModuleName [] = False
716 looksLikeModuleName (c:cs) = isUpper c && go cs
718 go ('.':cs) = looksLikeModuleName cs
719 go (c:cs) = (isAlphaNum c || c == '_') && go cs
722 Akin to @Prelude.words@, but acts like the Bourne shell, treating
723 quoted strings as Haskell Strings, and also parses Haskell [String]
727 getCmd :: String -> Either String -- Error
728 (String, String) -- (Cmd, Rest)
729 getCmd s = case break isSpace $ dropWhile isSpace s of
730 ([], _) -> Left ("Couldn't find command in " ++ show s)
733 toCmdArgs :: String -> Either String -- Error
734 (String, [String]) -- (Cmd, Args)
735 toCmdArgs s = case getCmd s of
737 Right (cmd, s') -> case toArgs s' of
739 Right args -> Right (cmd, args)
741 toArgs :: String -> Either String -- Error
744 = case dropWhile isSpace str of
745 s@('[':_) -> case reads s of
747 | all isSpace spaces ->
750 Left ("Couldn't read " ++ show str ++ "as [String]")
753 toArgs' s = case dropWhile isSpace s of
755 ('"' : _) -> case reads s of
757 -- rest must either be [] or start with a space
758 | all isSpace (take 1 rest) ->
761 Right args -> Right (arg : args)
763 Left ("Couldn't read " ++ show s ++ "as String")
764 s' -> case break isSpace s' of
765 (arg, s'') -> case toArgs' s'' of
767 Right args -> Right (arg : args)
770 -- -----------------------------------------------------------------------------
774 readRational__ :: ReadS Rational -- NB: doesn't handle leading "-"
775 readRational__ r = do
778 return ((n%1)*10^^(k-d), t)
781 (ds,s) <- lexDecDigits r
782 (ds',t) <- lexDotDigits s
783 return (read (ds++ds'), length ds', t)
785 readExp (e:s) | e `elem` "eE" = readExp' s
786 readExp s = return (0,s)
788 readExp' ('+':s) = readDec s
789 readExp' ('-':s) = do (k,t) <- readDec s
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 | isDrive dir = return () -- XXX Hack
824 createDirectoryHierarchy dir = do
825 b <- doesDirectoryExist dir
826 unless b $ do createDirectoryHierarchy (takeDirectory dir)
829 -----------------------------------------------------------------------------
830 -- Verify that the 'dirname' portion of a FilePath exists.
832 doesDirNameExist :: FilePath -> IO Bool
833 doesDirNameExist fpath = case takeDirectory fpath of
834 "" -> return True -- XXX Hack
835 _ -> doesDirectoryExist (takeDirectory fpath)
837 -- --------------------------------------------------------------
838 -- check existence & modification time at the same time
840 modificationTimeIfExists :: FilePath -> IO (Maybe ClockTime)
841 modificationTimeIfExists f = do
842 (do t <- getModificationTime f; return (Just t))
843 `IO.catch` \e -> if isDoesNotExistError e
847 -- split a string at the last character where 'pred' is True,
848 -- returning a pair of strings. The first component holds the string
849 -- up (but not including) the last character for which 'pred' returned
850 -- True, the second whatever comes after (but also not including the
853 -- If 'pred' returns False for all characters in the string, the original
854 -- string is returned in the first component (and the second one is just
856 splitLongestPrefix :: String -> (Char -> Bool) -> (String,String)
857 splitLongestPrefix str pred
858 | null r_pre = (str, [])
859 | otherwise = (reverse (tail r_pre), reverse r_suf)
860 -- 'tail' drops the char satisfying 'pred'
861 where (r_suf, r_pre) = break pred (reverse str)
863 escapeSpaces :: String -> String
864 escapeSpaces = foldr (\c s -> if isSpace c then '\\':c:s else c:s) ""
868 --------------------------------------------------------------
870 --------------------------------------------------------------
872 -- | The function splits the given string to substrings
873 -- using the 'searchPathSeparator'.
874 parseSearchPath :: String -> [FilePath]
875 parseSearchPath path = split path
877 split :: String -> [String]
881 _:rest -> chunk : split rest
885 #ifdef mingw32_HOST_OS
886 ('\"':xs@(_:_)) | last xs == '\"' -> init xs
890 (chunk', rest') = break isSearchPathSeparator s
892 data Direction = Forwards | Backwards
894 reslash :: Direction -> FilePath -> FilePath
896 where f ('/' : xs) = slash : f xs
897 f ('\\' : xs) = slash : f xs
898 f (x : xs) = x : f xs