-- Some things could be improved, e.g.: -- * Check that each file given contains at least one instance of the -- function -- * Check that we are testing all functions -- * If a problem is found, give better location information, e.g. -- which problem the file is in module Main (main) where import Control.Concurrent import Control.Exception import Control.Monad import Control.Monad.State import Data.Char import Data.Set (Set) import qualified Data.Set as Set import System.Environment import System.Exit import System.IO import System.Process main :: IO () main = do args <- getArgs case args of function : files -> doit function files die :: String -> IO a die err = do hPutStrLn stderr err exitFailure type M = StateT St IO data St = St { stSeen :: Set Int, stLast :: Maybe Int, stHadAProblem :: Bool } emptyState :: St emptyState = St { stSeen = Set.empty, stLast = Nothing, stHadAProblem = False } use :: Int -> M () use n = do st <- get let seen = stSeen st put $ st { stSeen = Set.insert n seen, stLast = Just n } if (n `Set.member` seen) then problem ("Duplicate " ++ show n) else case stLast st of Just l | (l > n) -> problem ("Decreasing order for " ++ show l ++ " -> " ++ show n) _ -> return () problem :: String -> M () problem str = do lift $ putStrLn str st <- get put $ st { stHadAProblem = True } doit :: String -> [FilePath] -> IO () doit function files = do (hIn, hOut, hErr, ph) <- runInteractiveProcess "grep" ("-h" : function : files) Nothing Nothing hClose hIn strOut <- hGetContents hOut strErr <- hGetContents hErr forkIO $ do evaluate (length strOut) return () forkIO $ do evaluate (length strErr) return () ec <- waitForProcess ph case (ec, strErr) of (ExitSuccess, "") -> check function strOut _ -> error "grep failed" check :: String -> String -> IO () check function str = do let ls = lines str -- filter out lines that start with whitespace. They're -- from things like: -- import M ( ..., -- ..., , ... ls' = filter (not . all isSpace . take 1) ls ns <- mapM (parseLine function) ls' st <- execStateT (do mapM_ use ns st <- get when (Set.null (stSeen st)) $ problem "No values found") emptyState when (stHadAProblem st) exitFailure parseLine :: String -> String -> IO Int parseLine function str = -- words isn't necessarily quite right, e.g. we could have -- "var=" rather than "var =", but it works for the code -- we have case words str of _var : "=" : fun : numStr : rest | fun == function, null rest || "--" == head rest, [(num, "")] <- reads numStr -> return num _ -> error ("Bad line: " ++ show str)