{-# LANGUAGE CPP #-} module Property ( runProperty , PropertyResult (..) #ifdef TEST , freeVariables , parseNotInScope #endif ) where import Data.List import Util import Interpreter (Interpreter) import qualified Interpreter import Parse -- | The result of evaluating an interaction. data PropertyResult = Success | Failure String | Error String deriving (Eq, Show) runProperty :: Interpreter -> Expression -> IO PropertyResult runProperty repl expression = do _ <- Interpreter.eval repl "import Test.QuickCheck (quickCheck, (==>))" r <- closeTerm expression >>= (Interpreter.safeEval repl . quickCheck) case r of Left err -> do return (Error err) Right res | "OK, passed" `isInfixOf` res -> return Success | otherwise -> do let msg = stripEnd (takeWhileEnd (/= '\b') res) return (Failure msg) where quickCheck term = "quickCheck (" ++ term ++ ")" -- | Find all free variables in given term, and close it by abstrating over -- them. closeTerm :: String -> IO String closeTerm term = do r <- freeVariables repl (quickCheck term) case r of [] -> return term vars -> return ("\\" ++ unwords vars ++ "-> (" ++ term ++ ")") -- | Find all free variables in given term. -- -- GHCi is used to detect free variables. freeVariables :: Interpreter -> String -> IO [String] freeVariables repl term = do r <- Interpreter.safeEval repl (":type " ++ term) return (either (const []) (nub . parseNotInScope) r) -- | Parse and return all variables that are not in scope from a ghc error -- message. -- -- >>> parseNotInScope ":4:1: Not in scope: `foo'" -- ["foo"] parseNotInScope :: String -> [String] parseNotInScope = nub . map extractVariable . filter ("Not in scope: " `isInfixOf`) . lines where -- | Extract variable name from a "Not in scope"-error. extractVariable :: String -> String extractVariable = unquote . takeWhileEnd (/= ' ') -- | Remove quotes from given name, if any. unquote ('`':xs) = init xs #if __GLASGOW_HASKELL__ >= 707 unquote ('\8219':xs) = init xs #endif unquote xs = xs