% % @(#) $Docid: Mar. 31th 2003 08:33 Sigbjorn Finne $ % @(#) $Contactid: sof@galois.com $ % \begin{code} module Utils ( showOct , showHex , mapFromMb , mapMb , mapMbM , concMaybe , toMaybe , split , splitLast , splitLastBy , prefix , traceIf , elemBy , mapUnzip , diff , deEscapeString , ( # ) --,UNUSED: catMapMaybes , dropSuffix -- re-exported , trace , tryOpen , basename , splitdir , prefixDir , hdirect_root , bailIf , decons , safe_init , snoc , mapAccumLM , notNull -- :: [a] -> Bool ) where import Char (chr, ord, readLitChar) import System.IO import IO import Int {- BEGIN_GHC_ONLY import Directory END_GHC_ONLY -} import Monad ( when ) import List ( mapAccumL, isPrefixOf ) import Debug.Trace infixl 1 # \end{code} A convenience operator for invoking methods on objects: \begin{code} ( # ) :: a -> (a -> b) -> b obj # meth = meth obj \end{code} Until NumExts is commonly available, we define the following show functions here: \begin{code} showIntAtBase :: Integral a => a -> (a -> Char) -> a -> ShowS showIntAtBase base toChr n r | n < 0 = '-':showIntAtBase 10 toChr (negate n) r | otherwise = case quotRem n base of { (n', d) -> case toChr d of { ch -> let r' = ch : r in if n' == 0 then r' else showIntAtBase base toChr n' r' }} showHex :: Integral a => a -> ShowS showHex n r = showString "0x" $ showIntAtBase 16 (toChrHex) n r where toChrHex d | d < 10 = chr (ord_0 + fromIntegral d) | otherwise = chr (ord 'a' + fromIntegral (d - 10)) showOct :: Integral a => a -> ShowS showOct n r = showString "0o" $ showIntAtBase 8 (toChrOct) n r where toChrOct d = chr (ord_0 + fromIntegral d) ord_0 :: Num a => a ord_0 = fromIntegral (ord '0') \end{code} Mapping from a Maybe: \begin{code} mapFromMb :: b -> (a -> b) -> Maybe a -> b mapFromMb d f mb = case mb of Nothing -> d ; Just v -> f v \end{code} \begin{code} split :: Eq a => a -> [a] -> [[a]] split _ [] = [] split a as = case break (==a) as of (xs,[]) -> [xs] (xs,_:ys) -> xs:split a ys \end{code} Split at last occurrence of substring. \begin{code} splitLast :: Eq a => [a] -> [a] -> ([a],[a]) splitLast [] ls = (ls,[]) splitLast sep@(_:ss) ls = splitLastBy (sep `isPrefixOf`) (drop (length ss)) ls splitLastBy :: ([a] -> Bool) -- True => current suffix satisifies -> ([a] -> [a]) -- for the last match, transform the result coming back. -> [a] -> ([a],[a]) splitLastBy predic munge ls = case (chomp (-1) (0::Int) ls) of (_,bef,aft) -> (bef,aft) where chomp lst _ [] = (lst, [], []) chomp lst n as@(x:xs) = case chomp new_last_pos (n+1) xs of (last_found, bef, aft) -> case (compare last_found n) of GT -> (last_found, x:bef, aft) LT -> (last_found, bef , x:aft) EQ -> (last_found, bef , munge aft) where new_last_pos | predic as = n | otherwise = lst \end{code} \begin{code} prefix :: Eq a => [a] -> [a] -> Maybe [a] -- what's left prefix [] ls = Just ls prefix _ [] = Nothing prefix (x:xs) (y:ys) | x == y = prefix xs ys | otherwise = Nothing \end{code} \begin{code} traceIf :: Bool -> String -> a -> a traceIf True str v = trace str v traceIf _ _ v = v elemBy :: (a -> Bool) -> [a] -> Bool elemBy _ [] = False elemBy isEqual (y:ys) = isEqual y || elemBy isEqual ys mapUnzip :: (a -> (b,c)) -> [a] -> ([b],[c]) mapUnzip _ [] = ([],[]) mapUnzip f (x:xs) = let (a, b) = f x (as,bs) = mapUnzip f xs in (a:as,b:bs) \end{code} Returns list of deltas, i.e, @ diff [x0,x1..xp,xn] = [x0, x1-x0, .., xp - xn] @ \begin{code} diff :: Num a => [a] -> [a] diff ls = snd (mapAccumL ( \ acc v -> (v, v - acc)) 0 ls) \end{code} begin{code} catMapMaybes :: (a -> b) -> [Maybe a] -> [b] catMapMaybes f ls = [f x | Just x <- ls] end{code} Dropping the extension off of a filename: \begin{code} dropSuffix :: String -> String dropSuffix str = case dropWhile (\ch -> ch /= '.' && ch /= '/' && ch /= '\\' ) (reverse str) of ('.':rs) -> reverse rs _ -> str -- give up if we reach a separator (/ or \) or end of list. {- UNUSED: dropPrefix :: Eq a => [a] -> [a] -> [a] dropPrefix [] ys = ys dropPrefix _ [] = [] dropPrefix (x:xs) (y:ys) | x == y = dropPrefix xs ys | otherwise = y:ys -} \end{code} Slightly generalised version of code found in GreenCard's front end: \begin{code} tryOpen :: Bool -> [FilePath] -> [String] -> FilePath -> IO (Maybe FilePath) tryOpen verbose path exts name = doUntil (mbOpenFile verbose) (allFileNames path name exts) doUntil :: (a -> IO (Maybe b)) -> [a] -> IO (Maybe b) doUntil _ [] = return Nothing doUntil f (a:as) = do v <- f a case v of Nothing -> doUntil f as _ -> return v allFileNames :: [String] -> String -> [String] -> [String] allFileNames path file exts = [addSuffix '/' d ++ file ++ (prefixWith '.' ext) | d <- path, ext <- exts] where addSuffix _ [] = [] addSuffix ch ls = case (decons ls) of (_,x) | x == ch -> ls | otherwise -> ls++[ch] prefixWith _ [] = [] prefixWith ch ls@(x:_) | ch == x = ls | otherwise = ch:ls \end{code} Combining last and init into one (pass over the list): \begin{code} decons :: [a] -> ([a],a) decons ds = trundle ds where trundle [] = error "decons: empty list" trundle [x] = ([], x) trundle (x:xs) = let (ls, l) = trundle xs in (x:ls, l) \end{code} Try reading a file: \begin{code} mbOpenFile :: Bool -> FilePath -> IO (Maybe FilePath) mbOpenFile verbose fpath = do -- I seem to remember that Hugs doesn't support Directory... {- BEGIN_GHC_ONLY flg <- doesFileExist fpath END_GHC_ONLY -} {- BEGIN_NOT_FOR_GHC -} flg <- (openFile fpath ReadMode >>= \ h -> hClose h >> return True) `catch` (\ _ -> return False) {- END_NOT_FOR_GHC -} if not flg then return Nothing else do when verbose (hPutStrLn stderr ("Reading file: " ++ show fpath)) return (Just fpath) \end{code} \begin{code} basename :: String -> String basename str = snd $ splitLastBy (\ (x:_) -> x == '/' || x == '\\') id str -- bi-lingual, the upshot of which is that -- / isn't allowed in DOS-style paths (and vice -- versa \ isn't allowed in POSIX(?) style pathnames). splitdir :: String -> (String, String) splitdir = splitLastBy (\ (x:_) -> x == '/' || x == '\\') id prefixDir :: String -> String -> String prefixDir [] rest = rest prefixDir ['/'] rest = '/':rest prefixDir ['\\'] rest = '/':rest prefixDir [x] rest = x:'/':rest prefixDir (x:xs) rest = x : prefixDir xs rest \end{code} Removing escape char from double quotes: \begin{code} deEscapeString :: String -> String deEscapeString [] = [] deEscapeString ls@('\\':x:xs) = case x of '"' -> x : deEscapeString xs -- " _ -> case readLitChar ls of ((ch,rs):_) -> ch : deEscapeString rs _ -> '\\':x: deEscapeString xs deEscapeString (x:xs) = x: deEscapeString xs \end{code} The top of the HaskellDirect Registry tree: \begin{code} hdirect_root :: String hdirect_root = "Software\\Haskell\\HaskellDirect" -- sporadically handy in a monadic context. bailIf :: Bool -> a -> a -> a bailIf True a _ = a bailIf _ _ b = b \end{code} Avoids Haskell version trouble: \begin{code} mapMb :: (a -> b) -> Maybe a -> Maybe b mapMb _ Nothing = Nothing mapMb f (Just c) = Just (f c) mapMbM :: (Monad m) => (a -> m b) -> Maybe a -> m (Maybe b) mapMbM _ Nothing = return Nothing mapMbM f (Just c) = f c >>= return.Just concMaybe :: Maybe a -> Maybe a -> Maybe a concMaybe v@(Just _) _ = v concMaybe _ v = v -- If predicate is false, represent it as Nothing. toMaybe :: (a -> Bool) -> a -> Maybe a toMaybe predic x | predic x = Nothing | otherwise = Just x \end{code} \begin{code} safe_init :: [a] -> [a] safe_init [] = [] safe_init ls = init ls \end{code} \begin{code} snoc :: [a] -> a -> [a] snoc [] y = [y] snoc (x:xs) y = x : snoc xs y \end{code} \begin{code} mapAccumLM :: (Monad m) => (acc -> x -> m (acc, y)) -- Function of elt of input list -- and accumulator, returning new -- accumulator and elt of result list -> acc -- Initial accumulator -> [x] -- Input list -> m (acc, [y]) -- Final accumulator and result list mapAccumLM _ s [] = return (s, []) mapAccumLM f s (x:xs) = do (s', y) <- f s x (s'',ys) <- mapAccumLM f s' xs return (s'',y:ys) \end{code} The simplest of defns; usefule, but not provided as standard: \begin{code} notNull :: [a] -> Bool notNull [] = False notNull _ = True \end{code}