Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
sol committed Sep 30, 2024
1 parent 50f148d commit 442e0db
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 44 deletions.
3 changes: 3 additions & 0 deletions src/Imports.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ infixl 1 <&>
pass :: Monad m => m ()
pass = return ()

equals :: Eq a => a -> a -> Bool
equals = (==)

strip :: String -> String
strip = reverse . dropWhile isSpace . reverse . dropWhile isSpace

Expand Down
9 changes: 5 additions & 4 deletions src/Interpreter.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{-# LANGUAGE CPP #-}
module Interpreter (
Interpreter
, PreserveIt(..)
, safeEval
, safeEvalIt
, safeEvalWith
, withInterpreter
, ghc
, interpreterSupported
Expand Down Expand Up @@ -65,10 +66,10 @@ xTemplateHaskell = "-XTemplateHaskell"
--
-- An exception may e.g. be caused on unterminated multiline expressions.
safeEval :: Interpreter -> String -> IO (Either String String)
safeEval repl = either (return . Left) (fmap Right . eval repl) . filterExpression
safeEval = safeEvalWith NoPreserveIt

safeEvalIt :: Interpreter -> String -> IO (Either String String)
safeEvalIt repl = either (return . Left) (fmap Right . evalIt repl) . filterExpression
safeEvalWith :: PreserveIt -> Interpreter -> String -> IO (Either String String)
safeEvalWith preserveIt repl = either (return . Left) (fmap Right . evalWith preserveIt repl) . filterExpression

filterExpression :: String -> Either String String
filterExpression e =
Expand Down
23 changes: 13 additions & 10 deletions src/Language/Haskell/GhciWrapper.hs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{-# LANGUAGE ViewPatterns #-}
module Language.Haskell.GhciWrapper (
Interpreter
, Config(..)
, defaultConfig
, PreserveIt(..)
, new
, close
, eval
, evalIt
, evalWith
, evalEcho
) where

Expand All @@ -29,6 +31,9 @@ defaultConfig = Config {
, configIgnoreDotGhci = True
}

data PreserveIt = NoPreserveIt | PreserveIt
deriving Eq

-- | Truly random marker, used to separate expressions.
--
-- IMPORTANT: This module relies upon the fact that this marker is unique. It
Expand Down Expand Up @@ -109,8 +114,8 @@ close repl = do
when (e /= ExitSuccess) $ do
throwIO (userError $ "Language.Haskell.GhciWrapper.close: Interpreter exited with an error (" ++ show e ++ ")")

putExpression :: Interpreter -> Bool -> String -> IO ()
putExpression Interpreter{hIn = stdin} preserveIt e = do
putExpression :: Interpreter -> PreserveIt -> String -> IO ()
putExpression Interpreter{hIn = stdin} (equals PreserveIt -> preserveIt) e = do
hPutStrLn stdin e
when preserveIt $ hPutStrLn stdin $ "let " ++ itMarker ++ " = it"
hPutStrLn stdin (marker ++ " :: Data.String.String")
Expand Down Expand Up @@ -140,18 +145,16 @@ getResult echoMode Interpreter{hOut = stdout} = go

-- | Evaluate an expression
eval :: Interpreter -> String -> IO String
eval repl expr = do
putExpression repl False expr
getResult False repl
eval = evalWith NoPreserveIt

-- | Like 'eval', but try to preserve the @it@ variable
evalIt :: Interpreter -> String -> IO String
evalIt repl expr = do
putExpression repl True expr
evalWith :: PreserveIt -> Interpreter -> String -> IO String
evalWith preserveIt repl expr = do
putExpression repl preserveIt expr
getResult False repl

-- | Evaluate an expression
evalEcho :: Interpreter -> String -> IO String
evalEcho repl expr = do
putExpression repl False expr
putExpression repl NoPreserveIt expr
getResult True repl
8 changes: 5 additions & 3 deletions src/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ doctestWithResult config = do
runDocTests :: Config -> [Module [Located DocTest]] -> IO Result
runDocTests Config{..} modules = do
Interpreter.withInterpreter ((<> ghcOptions) <$> repl) $ \ interpreter -> withCP65001 $ do
let
v = if verbose then Verbose else NonVerbose
runModules fastMode preserveIt v interpreter modules
runModules
(if fastMode then FastMode else NoFastMode)
(if preserveIt then PreserveIt else NoPreserveIt)
(if verbose then Verbose else NonVerbose)
interpreter modules
41 changes: 20 additions & 21 deletions src/Runner.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
{-# LANGUAGE LambdaCase #-}
module Runner (
runModules
, FastMode(..)
, PreserveIt(..)
, Verbose(..)
, Summary(..)
, formatSummary
Expand All @@ -25,7 +27,7 @@ import Control.Monad.Trans.State
import Control.Monad.IO.Class
import Data.IORef

import Interpreter (Interpreter)
import Interpreter (Interpreter, PreserveIt(..), safeEvalWith)
import qualified Interpreter
import Parse
import Location
Expand Down Expand Up @@ -64,7 +66,7 @@ withLineBuffering h action = bracket (hGetBuffering h) (hSetBuffering h) $ \ _ -
action

-- | Run all examples from a list of modules.
runModules :: Bool -> Bool -> Verbose -> Interpreter -> [Module [Located DocTest]] -> IO Summary
runModules :: FastMode -> PreserveIt -> Verbose -> Interpreter -> [Module [Located DocTest]] -> IO Summary
runModules fastMode preserveIt verbose repl modules = withLineBuffering stderr $ do

interactive <- hIsTerminalDevice stderr <&> \ case
Expand Down Expand Up @@ -99,8 +101,9 @@ type Report = StateT ReportState IO

data Interactive = NonInteractive | Interactive

data FastMode = NoFastMode | FastMode

data Verbose = NonVerbose | Verbose
deriving (Eq, Show)

data ReportState = ReportState {
reportStateInteractive :: Interactive
Expand Down Expand Up @@ -128,7 +131,7 @@ reportTransient msg = gets reportStateInteractive >>= \ case
hPutStr stderr $ '\r' : (replicate (length msg) ' ') ++ "\r"

-- | Run all examples from given module.
runModule :: Bool -> Bool -> Interpreter -> Module [Located DocTest] -> Report ()
runModule :: FastMode -> PreserveIt -> Interpreter -> Module [Located DocTest] -> Report ()
runModule fastMode preserveIt repl (Module module_ setup examples) = do

Summary _ _ e0 f0 <- getSummary
Expand All @@ -145,18 +148,19 @@ runModule fastMode preserveIt repl (Module module_ setup examples) = do
where
reload :: IO ()
reload = do
unless fastMode $
-- NOTE: It is important to do the :reload first! See
-- https://gitlab.haskell.org/ghc/ghc/-/issues/5904, which results in a
-- panic on GHC 7.4.1 if you do the :reload second.
void $ Interpreter.safeEval repl ":reload"
case fastMode of
NoFastMode -> void $ Interpreter.safeEval repl ":reload"
FastMode -> pass
void $ Interpreter.safeEval repl $ ":m *" ++ module_

when preserveIt $
-- Evaluate a dumb expression to populate the 'it' variable NOTE: This is
-- one reason why we cannot have safeEval = safeEvalIt: 'it' isn't set in
-- a fresh GHCi session.
void $ Interpreter.safeEval repl $ "()"
case preserveIt of
NoPreserveIt -> pass
PreserveIt -> do
-- Evaluate a dumb expression to populate the 'it' variable.
--
-- NOTE: This is one reason why we cannot just always use PreserveIt:
-- 'it' isn't set in a fresh GHCi session.
void $ Interpreter.safeEval repl $ "()"

setup_ :: IO ()
setup_ = do
Expand Down Expand Up @@ -210,7 +214,7 @@ reportProgress = gets reportStateVerbose >>= \ case
--
-- The interpreter state is zeroed with @:reload@ first. This means that you
-- can reuse the same 'Interpreter' for several test groups.
runTestGroup :: Bool -> Interpreter -> IO () -> [Located DocTest] -> Report ()
runTestGroup :: PreserveIt -> Interpreter -> IO () -> [Located DocTest] -> Report ()
runTestGroup preserveIt repl setup tests = do
liftIO setup
runExampleGroup preserveIt repl examples
Expand Down Expand Up @@ -238,7 +242,7 @@ type Interaction = (Expression, ExpectedResult)
-- |
-- Execute all expressions from given example in given 'Interpreter' and verify
-- the output.
runExampleGroup :: Bool -> Interpreter -> [Located Interaction] -> Report ()
runExampleGroup :: PreserveIt -> Interpreter -> [Located Interaction] -> Report ()
runExampleGroup preserveIt repl = go
where
go ((Located loc (expression, expected)) : xs) = do
Expand All @@ -254,8 +258,3 @@ runExampleGroup preserveIt repl = go
reportSuccess
go xs
go [] = return ()

safeEvalWith :: Bool -> Interpreter -> String -> IO (Either String String)
safeEvalWith preserveIt
| preserveIt = Interpreter.safeEvalIt
| otherwise = Interpreter.safeEval
13 changes: 7 additions & 6 deletions test/Language/Haskell/GhciWrapperSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import System.IO.Silently

import Data.List

import Language.Haskell.GhciWrapper (Interpreter, Config(..), defaultConfig)
import Language.Haskell.GhciWrapper (Interpreter, Config(..), defaultConfig, PreserveIt(..))
import qualified Language.Haskell.GhciWrapper as Interpreter

main :: IO ()
Expand All @@ -30,11 +30,12 @@ spec = do
withInterpreterConfig defaultConfig [] $ \ghci -> do
(capture $ Interpreter.evalEcho ghci ("putStr" ++ show "foo\nbar")) `shouldReturn` ("foo\nbar", "foo\nbar")

describe "evalIt" $ do
it "preserves it" $ do
withInterpreterConfig defaultConfig [] $ \ghci -> do
Interpreter.evalIt ghci "23" `shouldReturn` "23\n"
Interpreter.eval ghci "it" `shouldReturn` "23\n"
describe "evalWith" $ do
context "with PreserveIt" $ do
it "preserves it" $ do
withInterpreterConfig defaultConfig [] $ \ghci -> do
Interpreter.evalWith PreserveIt ghci "23" `shouldReturn` "23\n"
Interpreter.eval ghci "it" `shouldReturn` "23\n"

describe "eval" $ do
it "shows literals" $ withInterpreter $ \ghci -> do
Expand Down

0 comments on commit 442e0db

Please sign in to comment.