[project @ 2001-08-15 14:41:49 by simonmar]
[ghc-hetmet.git] / ghc / compiler / ghci / InteractiveUI.hs
index 3b295f1..9d6f6a1 100644 (file)
@@ -1,5 +1,5 @@
 -----------------------------------------------------------------------------
 -----------------------------------------------------------------------------
--- $Id: InteractiveUI.hs,v 1.26 2001/01/16 17:09:43 sewardj Exp $
+-- $Id: InteractiveUI.hs,v 1.87 2001/08/15 14:41:49 simonmar Exp $
 --
 -- GHC Interactive User Interface
 --
 --
 -- GHC Interactive User Interface
 --
@@ -7,24 +7,42 @@
 --
 -----------------------------------------------------------------------------
 
 --
 -----------------------------------------------------------------------------
 
-module InteractiveUI (interactiveUI) where
+{-# OPTIONS -#include "Linker.h" #-}
+{-# OPTIONS -#include "SchedAPI.h" #-}
+module InteractiveUI ( interactiveUI, ghciWelcomeMsg ) where
 
 
+#include "../includes/config.h"
 #include "HsVersions.h"
 
 #include "HsVersions.h"
 
+import Packages
 import CompManager
 import CompManager
-import CmStaticInfo
+import HscTypes                ( GhciMode(..), TyThing(..) )
+import MkIface          ( ifaceTyCls )
+import ByteCodeLink
 import DriverFlags
 import DriverState
 import DriverFlags
 import DriverState
+import DriverUtil
 import Linker
 import Linker
-import Module
-import Outputable
+import Finder          ( flushPackageCache )
 import Util
 import Util
-import PprType {- instance Outputable Type; do not delete -}
-import Panic   ( GhcException(..) )
+import Id              ( isDataConWrapId, idName )
+import Class           ( className )
+import TyCon           ( tyConName )
+import SrcLoc          ( isGoodSrcLoc )
+import Name            ( Name, isHomePackageName, nameSrcLoc )
+import Outputable
+import CmdLineOpts     ( DynFlag(..), getDynFlags, saveDynFlags, restoreDynFlags, dopt_unset )
+import Panic           ( GhcException(..) )
+import Config
+
+#ifndef mingw32_TARGET_OS
+import Posix
+#endif
 
 import Exception
 
 import Exception
-#ifndef NO_READLINE
-import Readline
+import Dynamic
+#if HAVE_READLINE_HEADERS && HAVE_READLINE_LIBS
+import Readline 
 #endif
 import IOExts
 
 #endif
 import IOExts
 
@@ -35,31 +53,38 @@ import CPUTime
 import Directory
 import IO
 import Char
 import Directory
 import IO
 import Char
-import Monad ( when )
+import Monad           ( when )
 
 
+import PrelGHC                 ( unsafeCoerce# )
+import Foreign         ( nullPtr )
+import CString         ( peekCString )
 
 -----------------------------------------------------------------------------
 
 ghciWelcomeMsg = "\ 
 
 -----------------------------------------------------------------------------
 
 ghciWelcomeMsg = "\ 
-\ _____  __   __  ____         _________________________________________________\n\ 
-\(|     ||   || (|  |)        GHC Interactive, version 5.00                    \n\ 
-\||  __  ||___|| ||     ()     For Haskell 98.                                 \n\ 
-\||   |) ||---|| ||     ||     http://www.haskell.org/ghc                      \n\ 
-\||   || ||   || ||     (|     Bug reports to: glasgow-haskell-bugs@haskell.org \n\ 
-\(|___|| ||   || (|__|) \\\\______________________________________________________\n"
-
-commands :: [(String, String -> GHCi Bool)]
-commands = [
+\   ___         ___ _\n\ 
+\  / _ \\ /\\  /\\/ __(_)\n\ 
+\ / /_\\// /_/ / /  | |      GHC Interactive, version " ++ cProjectVersion ++ ", for Haskell 98.\n\ 
+\/ /_\\\\/ __  / /___| |      http://www.haskell.org/ghc/\n\ 
+\\\____/\\/ /_/\\____/|_|      Type :? for help.\n"
+
+GLOBAL_VAR(commands, builtin_commands, [(String, String -> GHCi Bool)])
+
+builtin_commands :: [(String, String -> GHCi Bool)]
+builtin_commands = [
   ("add",      keepGoing addModule),
   ("cd",       keepGoing changeDirectory),
   ("add",      keepGoing addModule),
   ("cd",       keepGoing changeDirectory),
+  ("def",      keepGoing defineMacro),
   ("help",     keepGoing help),
   ("?",                keepGoing help),
   ("help",     keepGoing help),
   ("?",                keepGoing help),
+  ("info",      keepGoing info),
   ("load",     keepGoing loadModule),
   ("module",   keepGoing setContext),
   ("reload",   keepGoing reloadModule),
   ("set",      keepGoing setOptions),
   ("type",     keepGoing typeOfExpr),
   ("unset",    keepGoing unsetOptions),
   ("load",     keepGoing loadModule),
   ("module",   keepGoing setContext),
   ("reload",   keepGoing reloadModule),
   ("set",      keepGoing setOptions),
   ("type",     keepGoing typeOfExpr),
   ("unset",    keepGoing unsetOptions),
+  ("undef",     keepGoing undefineMacro),
   ("quit",     quit)
   ]
 
   ("quit",     quit)
   ]
 
@@ -70,141 +95,271 @@ shortHelpText = "use :? for help.\n"
 
 helpText = "\ 
 \ Commands available from the prompt:\n\ 
 
 helpText = "\ 
 \ Commands available from the prompt:\n\ 
-\\  
-\   <expr>             evaluate <expr>\n\ 
-\   :add <filename>     add a module to the current set\n\ 
-\   :cd <dir>          change directory to <dir>\n\ 
-\   :help, :?          display this list of commands\n\ 
-\   :load <filename>    load a module (and it dependents)\n\ 
-\   :module <mod>      set the context for expression evaluation to <mod>\n\ 
-\   :reload            reload the current module set\n\ 
-\   :set <option> ...  set options\n\ 
-\   :unset <option> ...        unset options\n\ 
-\   :type <expr>       show the type of <expr>\n\ 
-\   :quit              exit GHCi\n\ 
-\   :!<command>                run the shell command <command>\n\ 
+\\
+\   <stmt>                evaluate/run <stmt>\n\ 
+\   :add <filename> ...    add module(s) to the current target set\n\ 
+\   :cd <dir>             change directory to <dir>\n\ 
+\   :def <cmd> <expr>      define a command :<cmd>\n\ 
+\   :help, :?             display this list of commands\n\ 
+\   :info [<name> ...]     display information about the given names\n\ 
+\   :load <filename> ...   load module(s) and their dependents\n\ 
+\   :module <mod>         set the context for expression evaluation to <mod>\n\ 
+\   :reload               reload the current module set\n\ 
+\   :set <option> ...     set options\n\ 
+\   :undef <cmd>          undefine user-defined command :<cmd>\n\ 
+\   :type <expr>          show the type of <expr>\n\ 
+\   :unset <option> ...           unset options\n\ 
+\   :quit                 exit GHCi\n\ 
+\   :!<command>                   run the shell command <command>\n\ 
 \\ 
 \ Options for `:set' and `:unset':\n\ 
 \\ 
 \\ 
 \ Options for `:set' and `:unset':\n\ 
 \\ 
+\    +r                        revert top-level expressions after each evaluation\n\ 
 \    +s                 print timing/memory stats after each evaluation\n\ 
 \    +t                        print type after evaluation\n\ 
 \    -<flags>          most GHC command line flags can also be set here\n\ 
 \                         (eg. -v2, -fglasgow-exts, etc.)\n\ 
 \"
 
 \    +s                 print timing/memory stats after each evaluation\n\ 
 \    +t                        print type after evaluation\n\ 
 \    -<flags>          most GHC command line flags can also be set here\n\ 
 \                         (eg. -v2, -fglasgow-exts, etc.)\n\ 
 \"
 
-interactiveUI :: CmState -> Maybe FilePath -> IO ()
-interactiveUI cmstate mod = do
-   hPutStrLn stdout ghciWelcomeMsg
+interactiveUI :: CmState -> [FilePath] -> [LibrarySpec] -> IO ()
+interactiveUI cmstate paths cmdline_libs = do
    hFlush stdout
    hSetBuffering stdout NoBuffering
 
    -- link in the available packages
    pkgs <- getPackageInfo
    hFlush stdout
    hSetBuffering stdout NoBuffering
 
    -- link in the available packages
    pkgs <- getPackageInfo
-   linkPackages (reverse pkgs)
+   initLinker
+   linkPackages cmdline_libs pkgs
 
 
-   (cmstate', ok, mods) <-
-       case mod of
-            Nothing  -> return (cmstate, True, [])
-            Just m -> cmLoadModule cmstate m
+   (cmstate, ok, mods) <-
+       case paths of
+            [] -> return (cmstate, True, [])
+            _  -> cmLoadModule cmstate paths
 
 
-#ifndef NO_READLINE
+#if HAVE_READLINE_HEADERS && HAVE_READLINE_LIBS
    Readline.initialize
 #endif
    Readline.initialize
 #endif
-   let this_mod = case mods of 
-                       [] -> defaultCurrentModule
-                       m:ms -> m
-
-   (unGHCi uiLoop) GHCiState{ modules = mods,
-                             current_module = this_mod,
-                             target = mod,
-                             cmstate = cmstate',
-                             options = [ShowTiming],
-                              last_expr = Nothing}
+
+   dflags <- getDynFlags
+
+   (cmstate, maybe_hval) 
+       <- cmCompileExpr cmstate dflags "IO.hFlush PrelHandle.stderr"
+   case maybe_hval of
+       Just hval -> writeIORef flush_stderr (unsafeCoerce# hval :: IO ())
+       _ -> panic "interactiveUI:stderr"
+
+   (cmstate, maybe_hval) 
+       <- cmCompileExpr cmstate dflags "IO.hFlush PrelHandle.stdout"
+   case maybe_hval of
+       Just hval -> writeIORef flush_stdout (unsafeCoerce# hval :: IO ())
+       _ -> panic "interactiveUI:stdout"
+
+   startGHCi runGHCi GHCiState{ targets = paths,
+                               cmstate = cmstate,
+                               options = [] }
+
+#if HAVE_READLINE_HEADERS && HAVE_READLINE_LIBS
+   Readline.resetTerminal Nothing
+#endif
+
    return ()
 
    return ()
 
-uiLoop :: GHCi ()
-uiLoop = do
-  st <- getGHCiState
-#ifndef NO_READLINE
-  l <- io (readline (moduleNameUserString (current_module st) ++ "> "))
+
+runGHCi :: GHCi ()
+runGHCi = do
+  read_dot_files <- io (readIORef v_Read_DotGHCi)
+
+  when (read_dot_files) $ do
+    -- Read in ./.ghci.
+    let file = "./.ghci"
+    exists <- io (doesFileExist file)
+    when exists $ do
+       dir_ok  <- io (checkPerms ".")
+       file_ok <- io (checkPerms file)
+       when (dir_ok && file_ok) $ do
+         either_hdl <- io (IO.try (openFile "./.ghci" ReadMode))
+         case either_hdl of
+            Left e    -> return ()
+            Right hdl -> fileLoop hdl False
+    
+  when (read_dot_files) $ do
+    -- Read in $HOME/.ghci
+    either_dir <- io (IO.try (getEnv "HOME"))
+    case either_dir of
+       Left e -> return ()
+       Right dir -> do
+         cwd <- io (getCurrentDirectory)
+         when (dir /= cwd) $ do
+            let file = dir ++ "/.ghci"
+            ok <- io (checkPerms file)
+            when ok $ do
+              either_hdl <- io (IO.try (openFile file ReadMode))
+              case either_hdl of
+                 Left e    -> return ()
+                 Right hdl -> fileLoop hdl False
+
+  -- read commands from stdin
+#if HAVE_READLINE_HEADERS && HAVE_READLINE_LIBS
+  readlineLoop
 #else
 #else
-  l_ok <- io (hGetLine stdin)
-  let l = Just l_ok
+  fileLoop stdin True
 #endif
 #endif
-  case l of
-    Nothing -> exitGHCi
-    Just "" -> uiLoop
-    Just l  -> do
-#ifndef NO_READLINE
-          io (addHistory l)
+
+  -- and finally, exit
+  io $ do putStrLn "Leaving GHCi." 
+
+
+-- NOTE: We only read .ghci files if they are owned by the current user,
+-- and aren't world writable.  Otherwise, we could be accidentally 
+-- running code planted by a malicious third party.
+
+-- Furthermore, We only read ./.ghci if . is owned by the current user
+-- and isn't writable by anyone else.  I think this is sufficient: we
+-- don't need to check .. and ../.. etc. because "."  always refers to
+-- the same directory while a process is running.
+
+checkPerms :: String -> IO Bool
+checkPerms name =
+  handle (\_ -> return False) $ do
+#ifdef mingw32_TARGET_OS
+     doesFileExist name
+#else
+     st <- getFileStatus name
+     me <- getRealUserID
+     if fileOwner st /= me then do
+       putStrLn $ "WARNING: " ++ name ++ " is owned by someone else, IGNORING!"
+       return False
+      else do
+       let mode =  fileMode st
+       if (groupWriteMode == (mode `intersectFileModes` groupWriteMode))
+          || (otherWriteMode == (mode `intersectFileModes` otherWriteMode)) 
+          then do
+              putStrLn $ "*** WARNING: " ++ name ++ 
+                         " is writable by someone else, IGNORING!"
+              return False
+         else return True
 #endif
 #endif
-         quit <- runCommand l
-          if quit then exitGHCi else uiLoop
 
 
-exitGHCi = io $ do putStrLn "Leaving GHCi." 
+fileLoop :: Handle -> Bool -> GHCi ()
+fileLoop hdl prompt = do
+   st <- getGHCiState
+   mod <- io (cmGetContext (cmstate st))
+   when prompt (io (putStr (mod ++ "> ")))
+   l <- io (IO.try (hGetLine hdl))
+   case l of
+       Left e | isEOFError e -> return ()
+              | otherwise    -> throw e
+       Right l -> 
+         case remove_spaces l of
+           "" -> fileLoop hdl prompt
+           l  -> do quit <- runCommand l
+                    if quit then return () else fileLoop hdl prompt
+
+stringLoop :: [String] -> GHCi ()
+stringLoop [] = return ()
+stringLoop (s:ss) = do
+   st <- getGHCiState
+   case remove_spaces s of
+       "" -> stringLoop ss
+       l  -> do quit <- runCommand l
+                 if quit then return () else stringLoop ss
+
+#if HAVE_READLINE_HEADERS && HAVE_READLINE_LIBS
+readlineLoop :: GHCi ()
+readlineLoop = do
+   st <- getGHCiState
+   mod <- io (cmGetContext (cmstate st))
+   l <- io (readline (mod ++ "> "))
+   case l of
+       Nothing -> return ()
+       Just l  ->
+         case remove_spaces l of
+           "" -> readlineLoop
+           l  -> do
+                 io (addHistory l)
+                 quit <- runCommand l
+                 if quit then return () else readlineLoop
+#endif
 
 -- Top level exception handler, just prints out the exception 
 -- and carries on.
 runCommand :: String -> GHCi Bool
 runCommand c = 
 
 -- Top level exception handler, just prints out the exception 
 -- and carries on.
 runCommand :: String -> GHCi Bool
 runCommand c = 
-  ghciHandle ( 
-     \other_exception 
-        -> io (putStrLn (show other_exception)) >> return False
-  ) $
-  ghciHandleDyn
-    (\dyn -> case dyn of
-               PhaseFailed phase code ->
-                       io ( putStrLn ("Phase " ++ phase ++ " failed (code "
-                                       ++ show code ++ ")"))
-               Interrupted -> io (putStrLn "Interrupted.")
-               _ -> io (putStrLn (show (dyn :: GhcException)))
-             >> return False
-    ) $
-   doCommand c
+  ghciHandle ( \exception -> do
+               flushEverything
+               showException exception
+               return False
+            ) $
+  doCommand c
+
+showException (DynException dyn) =
+  case fromDynamic dyn of
+    Nothing -> 
+       io (putStrLn ("*** Exception: (unknown)"))
+    Just (PhaseFailed phase code) ->
+       io (putStrLn ("Phase " ++ phase ++ " failed (code "
+                      ++ show code ++ ")"))
+    Just Interrupted ->
+       io (putStrLn "Interrupted.")
+    Just (CmdLineError s) -> 
+       io (putStrLn s)  -- omit the location for CmdLineError
+    Just other_ghc_ex ->
+       io (putStrLn (show other_ghc_ex))
+showException other_exception
+  = io (putStrLn ("*** Exception: " ++ show other_exception))
 
 doCommand (':' : command) = specialCommand command
 
 doCommand (':' : command) = specialCommand command
-doCommand expr
-   = do expr_expanded <- expandExpr expr
-        -- io (putStrLn ( "Before: " ++ expr ++ "\nAfter:  " ++ expr_expanded))
-        expr_ok <- timeIt (do ok <- evalExpr expr_expanded
-                              when ok (evalExpr "PrelIO.putChar \'\\n\'" >> return ())
-                              return ok)
-        when expr_ok (rememberExpr expr_expanded)
+doCommand stmt
+   = do timeIt (do stuff <- runStmt stmt; finishEvalExpr stuff)
         return False
 
         return False
 
--- Returned Bool indicates whether or not the expr was successfully
--- parsed, renamed and typechecked.
-evalExpr :: String -> GHCi Bool
-evalExpr expr
- | null (filter (not.isSpace) expr)
- = return False
+-- Returns True if the expr was successfully parsed, renamed and
+-- typechecked.
+runStmt :: String -> GHCi (Maybe [Name])
+runStmt stmt
+ | null (filter (not.isSpace) stmt)
+ = return Nothing
  | otherwise
  = do st <- getGHCiState
  | otherwise
  = do st <- getGHCiState
-      dflags <- io (getDynFlags)
-      (new_cmstate, maybe_stuff) <- 
-        io (cmGetExpr (cmstate st) dflags (current_module st) expr True)
+      dflags <- io getDynFlags
+      let dflags' = dopt_unset dflags Opt_WarnUnusedBinds
+      (new_cmstate, names) <- io (cmRunStmt (cmstate st) dflags' stmt)
       setGHCiState st{cmstate = new_cmstate}
       setGHCiState st{cmstate = new_cmstate}
-      case maybe_stuff of
-        Nothing -> return False
-        Just (hv, unqual, ty)
-          -> do io (cmRunExpr hv)
-                b <- isOptionSet ShowType
-                io (when b (printForUser stdout unqual (text "::" <+> ppr ty)))
-                 return True
-       
-{-
-  let (mod,'.':str) = break (=='.') expr
-  case cmLookupSymbol (mkOrig varName (mkModuleName mod) (_PK_ str)) (cmstate st) of
-       Nothing -> io (putStrLn "nothing.")
-       Just e  -> io (
-  return ()
--}
+      return (Just names)
+
+-- possibly print the type and revert CAFs after evaluating an expression
+finishEvalExpr Nothing = return False
+finishEvalExpr (Just names)
+ = do b <- isOptionSet ShowType
+      st <- getGHCiState
+      when b (mapM_ (showTypeOfName (cmstate st)) names)
+
+      b <- isOptionSet RevertCAFs
+      io (when b revertCAFs)
+      flushEverything
+      return True
+
+showTypeOfName :: CmState -> Name -> GHCi ()
+showTypeOfName cmstate n
+   = do maybe_str <- io (cmTypeOfName cmstate n)
+       case maybe_str of
+         Nothing  -> return ()
+         Just str -> io (putStrLn (showSDoc (ppr n) ++ " :: " ++ str))
+
+flushEverything :: GHCi ()
+flushEverything
+   = io $ do flush_so <- readIORef flush_stdout
+            flush_so
+            flush_se <- readIORef flush_stdout
+            flush_se
+             return ()
 
 specialCommand :: String -> GHCi Bool
 
 specialCommand :: String -> GHCi Bool
-specialCommand ('!':str) = shellEscape (dropWhile isSpace str) 
+specialCommand ('!':str) = shellEscape (dropWhile isSpace str)
 specialCommand str = do
   let (cmd,rest) = break isSpace str
 specialCommand str = do
   let (cmd,rest) = break isSpace str
-  case [ (s,f) | (s,f) <- commands, prefixMatch cmd s ] of
+  cmds <- io (readIORef commands)
+  case [ (s,f) | (s,f) <- cmds, prefixMatch cmd s ] of
      []      -> io (hPutStr stdout ("unknown command `:" ++ cmd ++ "'\n" 
                                    ++ shortHelpText) >> return False)
      [(_,f)] -> f (dropWhile isSpace rest)
      []      -> io (hPutStr stdout ("unknown command `:" ++ cmd ++ "'\n" 
                                    ++ shortHelpText) >> return False)
      [(_,f)] -> f (dropWhile isSpace rest)
@@ -213,7 +368,7 @@ specialCommand str = do
                                       foldr1 (\a b -> a ++ ',':b) (map fst cs)
                                         ++ ")") >> return False)
 
                                       foldr1 (\a b -> a ++ ',':b) (map fst cs)
                                         ++ ")") >> return False)
 
-noArgs c = io (hPutStrLn stdout ("command `:" ++ c ++ "' takes no arguments"))
+noArgs c = throwDyn (CmdLineError ("command `" ++ c ++ "' takes no arguments"))
 
 -----------------------------------------------------------------------------
 -- Commands
 
 -----------------------------------------------------------------------------
 -- Commands
@@ -221,76 +376,169 @@ noArgs c = io (hPutStrLn stdout ("command `:" ++ c ++ "' takes no arguments"))
 help :: String -> GHCi ()
 help _ = io (putStr helpText)
 
 help :: String -> GHCi ()
 help _ = io (putStr helpText)
 
+info :: String -> GHCi ()
+info "" = throwDyn (CmdLineError "syntax: `:i <thing-you-want-info-about>'")
+info s = do
+  let names = words s
+  state <- getGHCiState
+  dflags <- io getDynFlags
+  let 
+    infoThings cms [] = return cms
+    infoThings cms (name:names) = do
+      (cms, unqual, ty_things) <- io (cmInfoThing cms dflags name)
+      io (putStrLn (showSDocForUser unqual (
+           vcat (intersperse (text "") (map showThing ty_things))))
+         )
+      infoThings cms names
+
+    showThing ty_thing = vcat [ text "-- " <> showTyThing ty_thing, 
+                               ppr (ifaceTyCls ty_thing) ]
+
+    showTyThing (AClass cl) 
+       = hcat [ppr cl, text " is a class", showSrcLoc (className cl)]
+    showTyThing (ATyCon ty)
+       = hcat [ppr ty, text " is a type constructor", showSrcLoc (tyConName ty)]
+    showTyThing (AnId   id)
+       | isDataConWrapId id 
+       = hcat [ppr id, text " is a data constructor", showSrcLoc (idName id)]
+       | otherwise
+       = hcat [ppr id, text " is a variable", showSrcLoc (idName id)]
+
+       -- also print out the source location for home things
+    showSrcLoc name
+       | isHomePackageName name && isGoodSrcLoc loc
+       = hsep [ text ", defined at", ppr loc ]
+       | otherwise
+       = empty
+       where loc = nameSrcLoc name
+
+  cms <- infoThings (cmstate state) names
+  setGHCiState state{ cmstate = cms }
+  return ()
+
+
 addModule :: String -> GHCi ()
 addModule :: String -> GHCi ()
-addModule _ = throwDyn (OtherError ":add not implemented")
+addModule str = do
+  let files = words str
+  state <- getGHCiState
+  dflags <- io (getDynFlags)
+  io (revertCAFs)                      -- always revert CAFs on load/add.
+  let new_targets = files ++ targets state 
+  (cmstate1, ok, mods) <- io (cmLoadModule (cmstate state) new_targets)
+  setGHCiState state{ cmstate = cmstate1, targets = new_targets }
+  modulesLoadedMsg ok mods
 
 setContext :: String -> GHCi ()
 setContext ""
 
 setContext :: String -> GHCi ()
 setContext ""
-  = throwDyn (OtherError "syntax: `:m <module>'")
-setContext m | not (isUpper (head m)) || not (all isAlphaNum (tail m))
-  = throwDyn (OtherError ("strange looking module name: `" ++ m ++ "'"))
-setContext m
+  = throwDyn (CmdLineError "syntax: `:m <module>'")
+setContext m | not (isUpper (head m)) || not (all isAlphaNumEx (tail m))
+  = throwDyn (CmdLineError ("strange looking module name: `" ++ m ++ "'"))
+    where
+       isAlphaNumEx c = isAlphaNum c || c == '_'
+setContext str
   = do st <- getGHCiState
   = do st <- getGHCiState
-       setGHCiState st{current_module = mkModuleName m}
+       new_cmstate <- io (cmSetContext (cmstate st) str)
+       setGHCiState st{cmstate=new_cmstate}
 
 changeDirectory :: String -> GHCi ()
 
 changeDirectory :: String -> GHCi ()
+changeDirectory ('~':d) = do
+   tilde <- io (getEnv "HOME") -- will fail if HOME not defined
+   io (setCurrentDirectory (tilde ++ '/':d))
 changeDirectory d = io (setCurrentDirectory d)
 
 changeDirectory d = io (setCurrentDirectory d)
 
+defineMacro :: String -> GHCi ()
+defineMacro s = do
+  let (macro_name, definition) = break isSpace s
+  cmds <- io (readIORef commands)
+  if (null macro_name) 
+       then throwDyn (CmdLineError "invalid macro name") 
+       else do
+  if (macro_name `elem` map fst cmds) 
+       then throwDyn (CmdLineError 
+               ("command `" ++ macro_name ++ "' is already defined"))
+       else do
+
+  -- give the expression a type signature, so we can be sure we're getting
+  -- something of the right type.
+  let new_expr = '(' : definition ++ ") :: String -> IO String"
+
+  -- compile the expression
+  st <- getGHCiState
+  dflags <- io getDynFlags
+  (new_cmstate, maybe_hv) <- io (cmCompileExpr (cmstate st) dflags new_expr)
+  setGHCiState st{cmstate = new_cmstate}
+  case maybe_hv of
+     Nothing -> return ()
+     Just hv -> io (writeIORef commands --
+                   ((macro_name, keepGoing (runMacro hv)) : cmds))
+
+runMacro :: HValue{-String -> IO String-} -> String -> GHCi ()
+runMacro fun s = do
+  str <- io ((unsafeCoerce# fun :: String -> IO String) s)
+  stringLoop (lines str)
+
+undefineMacro :: String -> GHCi ()
+undefineMacro macro_name = do
+  cmds <- io (readIORef commands)
+  if (macro_name `elem` map fst builtin_commands) 
+       then throwDyn (CmdLineError
+               ("command `" ++ macro_name ++ "' cannot be undefined"))
+       else do
+  if (macro_name `notElem` map fst cmds) 
+       then throwDyn (CmdLineError 
+               ("command `" ++ macro_name ++ "' not defined"))
+       else do
+  io (writeIORef commands (filter ((/= macro_name) . fst) cmds))
+
 loadModule :: String -> GHCi ()
 loadModule :: String -> GHCi ()
-loadModule path = timeIt (loadModule' path)
+loadModule str = timeIt (loadModule' str)
+
+loadModule' str = do
+  let files = words str
+  state <- getGHCiState
+  dflags <- io getDynFlags
+  cmstate1 <- io (cmUnload (cmstate state) dflags)
+  setGHCiState state{ cmstate = cmstate1, targets = [] }
+  io (revertCAFs)                      -- always revert CAFs on load.
+  (cmstate2, ok, mods) <- io (cmLoadModule cmstate1 files)
+  setGHCiState state{ cmstate = cmstate2, targets = files }
+  modulesLoadedMsg ok mods
 
 
-loadModule' path = do
+reloadModule :: String -> GHCi ()
+reloadModule "" = do
   state <- getGHCiState
   state <- getGHCiState
-  cmstate1 <- io (cmUnload (cmstate state))
-  (cmstate2, ok, mods) <- io (cmLoadModule cmstate1 path)
-
-  let new_state = state{
-                       cmstate = cmstate2,
-                       modules = mods,
-                       current_module = case mods of 
-                                          [] -> defaultCurrentModule
-                                          xs -> head xs,
-                       target = Just path
-                  }
-  setGHCiState new_state
+  case targets state of
+   [] -> io (putStr "no current target\n")
+   paths
+      -> do io (revertCAFs)            -- always revert CAFs on reload.
+           (new_cmstate, ok, mods) <- io (cmLoadModule (cmstate state) paths)
+            setGHCiState state{ cmstate=new_cmstate }
+           modulesLoadedMsg ok mods
+
+reloadModule _ = noArgs ":reload"
 
 
+
+modulesLoadedMsg ok mods = do
   let mod_commas 
        | null mods = text "none."
        | otherwise = hsep (
   let mod_commas 
        | null mods = text "none."
        | otherwise = hsep (
-           punctuate comma (map (text.moduleNameUserString) mods)) <> text "."
+           punctuate comma (map text mods)) <> text "."
   case ok of
     False -> 
        io (putStrLn (showSDoc (text "Failed, modules loaded: " <> mod_commas)))
     True  -> 
        io (putStrLn (showSDoc (text "Ok, modules loaded: " <> mod_commas)))
 
   case ok of
     False -> 
        io (putStrLn (showSDoc (text "Failed, modules loaded: " <> mod_commas)))
     True  -> 
        io (putStrLn (showSDoc (text "Ok, modules loaded: " <> mod_commas)))
 
-reloadModule :: String -> GHCi ()
-reloadModule "" = do
-  state <- getGHCiState
-  case target state of
-   Nothing -> io (putStr "no current target\n")
-   Just path
-      -> do (new_cmstate, ok, mods) <- io (cmLoadModule (cmstate state) path)
-            setGHCiState 
-               state{cmstate=new_cmstate,
-                     modules = mods,
-                     current_module = case mods of 
-                                         [] -> defaultCurrentModule
-                                         xs -> head xs
-                    }
-
-
-reloadModule _ = noArgs ":reload"
 
 typeOfExpr :: String -> GHCi ()
 typeOfExpr str 
   = do st <- getGHCiState
 
 typeOfExpr :: String -> GHCi ()
 typeOfExpr str 
   = do st <- getGHCiState
-       dflags <- io (getDynFlags)
-       (st, maybe_ty) <- io (cmGetExpr (cmstate st) dflags 
-                               (current_module st) str False)
-       case maybe_ty of
-        Nothing -> return ()
-        Just (_, unqual, ty) -> io (printForUser stdout unqual (ppr ty)) 
+       dflags <- io getDynFlags
+       (new_cmstate, maybe_tystr) <- io (cmTypeOfExpr (cmstate st) dflags str)
+       setGHCiState st{cmstate = new_cmstate}
+       case maybe_tystr of
+         Nothing    -> return ()
+         Just tystr -> io (putStrLn tystr)
 
 quit :: String -> GHCi Bool
 quit _ = return True
 
 quit :: String -> GHCi Bool
 quit _ = return True
@@ -320,28 +568,29 @@ setOptions ""
           ))
 setOptions str
   = do -- first, deal with the GHCi opts (+s, +t, etc.)
           ))
 setOptions str
   = do -- first, deal with the GHCi opts (+s, +t, etc.)
-       let opts = words str
-          (minus_opts, rest1) = partition isMinus opts
-          (plus_opts, rest2)  = partition isPlus rest1
-
-       if (not (null rest2)) 
-         then io (putStrLn ("unknown option: `" ++ head rest2 ++ "'"))
-         else do
-
-       mapM setOpt plus_opts
-
-       -- now, the GHC flags
-       io (do leftovers <- processArgs static_flags minus_opts []
-             dyn_flags <- readIORef v_InitDynFlags
-             writeIORef v_DynFlags dyn_flags
-             leftovers <- processArgs dynamic_flags leftovers []
-             dyn_flags <- readIORef v_DynFlags
-             writeIORef v_InitDynFlags dyn_flags
-              if (not (null leftovers))
-                then throwDyn (OtherError ("unrecognised flags: " ++ 
+      let (plus_opts, minus_opts)  = partition isPlus (words str)
+      mapM setOpt plus_opts
+
+      -- now, the GHC flags
+      pkgs_before <- io (readIORef v_Packages)
+      leftovers   <- io (processArgs static_flags minus_opts [])
+      pkgs_after  <- io (readIORef v_Packages)
+
+      -- update things if the users wants more packages
+      when (pkgs_before /= pkgs_after) $
+        newPackages (pkgs_after \\ pkgs_before)
+
+      -- then, dynamic flags
+      io $ do 
+       restoreDynFlags
+        leftovers <- processArgs dynamic_flags leftovers []
+       saveDynFlags
+
+        if (not (null leftovers))
+               then throwDyn (CmdLineError ("unrecognised flags: " ++ 
                                                unwords leftovers))
                                                unwords leftovers))
-                else return ()
-         )
+               else return ()
+
 
 unsetOptions :: String -> GHCi ()
 unsetOptions str
 
 unsetOptions :: String -> GHCi ()
 unsetOptions str
@@ -358,7 +607,7 @@ unsetOptions str
  
        -- can't do GHC flags for now
        if (not (null minus_opts))
  
        -- can't do GHC flags for now
        if (not (null minus_opts))
-         then throwDyn (OtherError "can't unset GHC command-line flags")
+         then throwDyn (CmdLineError "can't unset GHC command-line flags")
          else return ()
 
 isMinus ('-':s) = True
          else return ()
 
 isMinus ('-':s) = True
@@ -380,76 +629,57 @@ unsetOpt ('+':str)
 strToGHCiOpt :: String -> (Maybe GHCiOption)
 strToGHCiOpt "s" = Just ShowTiming
 strToGHCiOpt "t" = Just ShowType
 strToGHCiOpt :: String -> (Maybe GHCiOption)
 strToGHCiOpt "s" = Just ShowTiming
 strToGHCiOpt "t" = Just ShowType
+strToGHCiOpt "r" = Just RevertCAFs
 strToGHCiOpt _   = Nothing
 
 optToStr :: GHCiOption -> String
 optToStr ShowTiming = "s"
 optToStr ShowType   = "t"
 strToGHCiOpt _   = Nothing
 
 optToStr :: GHCiOption -> String
 optToStr ShowTiming = "s"
 optToStr ShowType   = "t"
+optToStr RevertCAFs = "r"
 
 
+newPackages new_pkgs = do
+  state <- getGHCiState
+  dflags <- io getDynFlags
+  cmstate1 <- io (cmUnload (cmstate state) dflags)
+  setGHCiState state{ cmstate = cmstate1, targets = [] }
 
 
------------------------------------------------------------------------------
--- Code to do last-expression-entered stuff.  (a.k.a the $$ facility)
-
--- Take a string and replace $$s in it with the last expr, if any.
-expandExpr :: String -> GHCi String
-expandExpr str
-   = do mle <- getLastExpr
-        return (outside mle str)
-     where
-        outside mle ('$':'$':cs)
-           = case mle of
-                Just le -> " (" ++ le ++ ") " ++ outside mle cs
-                Nothing -> outside mle cs
-
-        outside mle []           = []
-        outside mle ('"':str)    = '"' : inside2 mle str   -- "
-        outside mle ('\'':str)   = '\'' : inside1 mle str   -- '
-        outside mle (c:cs)       = c : outside mle cs
-
-        inside2 mle ('"':cs)  = '"' : outside mle cs   -- "
-        inside2 mle (c:cs)    = c : inside2 mle cs
-        inside2 mle []        = []
-
-        inside1 mle ('\'':cs) = '\'': outside mle cs
-        inside1 mle (c:cs)    = c : inside1 mle cs
-        inside1 mle []        = []
-
-
-rememberExpr :: String -> GHCi ()
-rememberExpr str
-   = do let cleaned = (clean . reverse . clean . reverse) str
-        let forget_me_not | null cleaned = Nothing
-                          | otherwise    = Just cleaned
-        setLastExpr forget_me_not
-     where
-        clean = dropWhile isSpace
-
+  io $ do
+    pkgs <- getPackageInfo
+    flushPackageCache pkgs
+   
+    new_pkg_info <- getPackageDetails new_pkgs
+    mapM_ (linkPackage False) (reverse new_pkg_info)
 
 -----------------------------------------------------------------------------
 -- GHCi monad
 
 data GHCiState = GHCiState
      { 
 
 -----------------------------------------------------------------------------
 -- GHCi monad
 
 data GHCiState = GHCiState
      { 
-       modules        :: [ModuleName],
-       current_module :: ModuleName,
-       target         :: Maybe FilePath,
+       targets        :: [FilePath],
        cmstate        :: CmState,
        cmstate        :: CmState,
-       options        :: [GHCiOption],
-        last_expr      :: Maybe String
+       options        :: [GHCiOption]
      }
 
      }
 
-data GHCiOption = ShowTiming | ShowType deriving Eq
+data GHCiOption 
+       = ShowTiming            -- show time/allocs after evaluation
+       | ShowType              -- show the type of expressions
+       | RevertCAFs            -- revert CAFs after every evaluation
+       deriving Eq
+
+GLOBAL_VAR(flush_stdout, error "no flush_stdout", IO ())
+GLOBAL_VAR(flush_stderr, error "no flush_stdout", IO ())
 
 
-defaultCurrentModule = mkModuleName "Prelude"
+newtype GHCi a = GHCi { unGHCi :: IORef GHCiState -> IO a }
 
 
-newtype GHCi a = GHCi { unGHCi :: GHCiState -> IO (GHCiState, a) }
+startGHCi :: GHCi a -> GHCiState -> IO a
+startGHCi g state = do ref <- newIORef state; unGHCi g ref
 
 instance Monad GHCi where
 
 instance Monad GHCi where
-  (GHCi m) >>= k  =  GHCi $ \s -> m s >>= \(s,a) -> unGHCi (k a) s
-  return a  = GHCi $ \s -> return (s,a)
+  (GHCi m) >>= k  =  GHCi $ \s -> m s >>= \a -> unGHCi (k a) s
+  return a  = GHCi $ \s -> return a
 
 
-getGHCiState   = GHCi $ \s -> return (s,s)
-setGHCiState s = GHCi $ \_ -> return (s,())
+getGHCiState   = GHCi $ \r -> readIORef r
+setGHCiState s = GHCi $ \r -> writeIORef r s
 
 isOptionSet :: GHCiOption -> GHCi Bool
 isOptionSet opt
 
 isOptionSet :: GHCiOption -> GHCi Bool
 isOptionSet opt
@@ -466,48 +696,152 @@ unsetOption opt
  = do st <- getGHCiState
       setGHCiState (st{ options = filter (/= opt) (options st) })
 
  = do st <- getGHCiState
       setGHCiState (st{ options = filter (/= opt) (options st) })
 
-getLastExpr :: GHCi (Maybe String)
-getLastExpr
- = do st <- getGHCiState ; return (last_expr st)
+io :: IO a -> GHCi a
+io m = GHCi { unGHCi = \s -> m >>= return }
 
 
-setLastExpr :: Maybe String -> GHCi ()
-setLastExpr last_expr
- = do st <- getGHCiState ; setGHCiState (st{last_expr = last_expr})
+-----------------------------------------------------------------------------
+-- recursive exception handlers
 
 
-io m = GHCi $ \s -> m >>= \a -> return (s,a)
+-- Don't forget to unblock async exceptions in the handler, or if we're
+-- in an exception loop (eg. let a = error a in a) the ^C exception
+-- may never be delivered.  Thanks to Marcin for pointing out the bug.
 
 
+ghciHandle :: (Exception -> GHCi a) -> GHCi a -> GHCi a
 ghciHandle h (GHCi m) = GHCi $ \s -> 
 ghciHandle h (GHCi m) = GHCi $ \s -> 
-   Exception.catch (m s) (\e -> unGHCi (h e) s)
-ghciHandleDyn h (GHCi m) = GHCi $ \s -> 
-   Exception.catchDyn (m s) (\e -> unGHCi (h e) s)
+   Exception.catch (m s) 
+       (\e -> unGHCi (ghciHandle h (ghciUnblock (h e))) s)
+
+ghciUnblock :: GHCi a -> GHCi a
+ghciUnblock (GHCi a) = GHCi $ \s -> Exception.unblock (a s)
 
 -----------------------------------------------------------------------------
 -- package loader
 
 
 -----------------------------------------------------------------------------
 -- package loader
 
-linkPackages :: [Package] -> IO ()
-linkPackages pkgs = mapM_ linkPackage pkgs
-
-linkPackage :: Package -> IO ()
+-- Left: full path name of a .o file, including trailing .o
+-- Right: "unadorned" name of a .DLL/.so
+--        e.g.    On unix     "qt"  denotes "libqt.so"
+--                On WinDoze  "burble"  denotes "burble.DLL"
+--        addDLL is platform-specific and adds the lib/.so/.DLL
+--        suffixes platform-dependently; we don't do that here.
+-- 
+-- For dynamic objects only, try to find the object file in all the 
+-- directories specified in v_Library_Paths before giving up.
+
+type LibrarySpec
+   = Either FilePath String
+
+showLS (Left nm)  = "(static) " ++ nm
+showLS (Right nm) = "(dynamic) " ++ nm
+
+linkPackages :: [LibrarySpec] -> [PackageConfig] -> IO ()
+linkPackages cmdline_lib_specs pkgs
+   = do sequence_ [ linkPackage (name p `elem` loaded) p | p <- reverse pkgs ]
+        lib_paths <- readIORef v_Library_paths
+        mapM_ (preloadLib lib_paths) cmdline_lib_specs
+       if (null cmdline_lib_specs)
+          then return ()
+          else do putStr "final link ... "
+                  ok <- resolveObjs
+                  if ok then putStrLn "done."
+                        else throwDyn (InstallationError "linking extra libraries/objects failed")
+     where
+       -- Packages that are already linked into GHCi.  For mingw32, we only
+       -- skip gmp and rts, since std and after need to load the msvcrt.dll
+       -- library which std depends on.
+       loaded 
+#          ifndef mingw32_TARGET_OS
+           = [ "gmp", "rts", "std", "concurrent", "posix", "text", "util" ]
+#          else
+           = [ "gmp", "rts" ]
+#          endif
+
+        preloadLib :: [String] -> LibrarySpec -> IO ()
+        preloadLib lib_paths lib_spec
+           = do putStr ("Loading object " ++ showLS lib_spec ++ " ... ")
+                case lib_spec of
+                   Left static_ish
+                      -> do b <- preload_static lib_paths static_ish
+                            putStrLn (if b then "done." else "not found")
+                   Right dll_unadorned
+                      -> -- We add "" to the set of paths to try, so that
+                         -- if none of the real paths match, we force addDLL
+                         -- to look in the default dynamic-link search paths.
+                         do b <- preload_dynamic (lib_paths++[""]) dll_unadorned
+                            when (not b) (cantFind lib_paths lib_spec)
+                            putStrLn "done"
+
+        cantFind :: [String] -> LibrarySpec -> IO ()
+        cantFind paths spec
+           = do putStr ("failed.\nCan't find " ++ showLS spec
+                        ++ " in directories:\n"
+                        ++ unlines (map ("   "++) paths) )
+                give_up
+
+        -- not interested in the paths in the static case.
+        preload_static paths name
+           = do b <- doesFileExist name
+                if not b then return False
+                         else loadObj name >> return True
+
+        preload_dynamic [] name
+           = return False
+        preload_dynamic (path:paths) rootname
+           = do maybe_errmsg <- addDLL path rootname
+                if    maybe_errmsg /= nullPtr
+                 then preload_dynamic paths rootname
+                 else return True
+
+        give_up 
+           = (throwDyn . CmdLineError)
+                "user specified .o/.so/.DLL could not be loaded."
+
+
+linkPackage :: Bool -> PackageConfig -> IO ()
 -- ignore rts and gmp for now (ToDo; better?)
 -- ignore rts and gmp for now (ToDo; better?)
-linkPackage pkg | name pkg `elem` ["rts", "gmp"] = return ()
-linkPackage pkg = do
-  putStr ("Loading package " ++ name pkg ++ " ... ")
-  let dirs = library_dirs pkg
-  let objs = map (++".o") (hs_libraries pkg ++ extra_libraries pkg)
-  mapM (linkOneObj dirs) objs
-  putStr "resolving ... "
-  resolveObjs
-  putStrLn "done."
-
-linkOneObj dirs obj = do
-  filename <- findFile dirs obj
-  loadObj filename
-
-findFile [] obj = throwDyn (OtherError ("can't find " ++ obj))
-findFile (d:ds) obj = do
-  let path = d ++ '/':obj
-  b <- doesFileExist path
-  if b then return path else findFile ds obj
+linkPackage loaded_in_ghci pkg
+   | name pkg `elem` ["rts", "gmp"] 
+   = return ()
+   | otherwise
+   = do putStr ("Loading package " ++ name pkg ++ " ... ")
+        -- For each obj, try obj.o and if that fails, obj.so.
+        -- Complication: all the .so's must be loaded before any of the .o's.  
+        let dirs      =  library_dirs pkg
+        let objs      =  hs_libraries pkg ++ extra_libraries pkg
+        classifieds   <- mapM (locateOneObj dirs) objs
+
+       -- Don't load the .so libs if this is a package GHCi is already
+       -- linked against, because we'll already have the .so linked in.
+       let (so_libs, obj_libs) = partition isRight classifieds
+        let sos_first | loaded_in_ghci = obj_libs
+                     | otherwise      = so_libs ++ obj_libs
+
+        mapM loadClassified sos_first
+        putStr "linking ... "
+        ok <- resolveObjs
+       if ok then putStrLn "done."
+             else panic ("can't load package `" ++ name pkg ++ "'")
+     where
+        isRight (Right _) = True
+        isRight (Left _)  = False
+
+loadClassified :: LibrarySpec -> IO ()
+loadClassified (Left obj_absolute_filename)
+   = do loadObj obj_absolute_filename
+loadClassified (Right dll_unadorned)
+   = do maybe_errmsg <- addDLL "" dll_unadorned -- doesn't seem right to me
+        if    maybe_errmsg == nullPtr
+         then return ()
+         else do str <- peekCString maybe_errmsg
+                 throwDyn (CmdLineError ("can't load .so/.DLL for: " 
+                                       ++ dll_unadorned ++ " (" ++ str ++ ")" ))
+
+locateOneObj :: [FilePath] -> String -> IO LibrarySpec
+locateOneObj []     obj 
+   = return (Right obj) -- we assume
+locateOneObj (d:ds) obj 
+   = do let path = d ++ '/':obj ++ ".o"
+        b <- doesFileExist path
+        if b then return (Left path) else locateOneObj ds obj
 
 -----------------------------------------------------------------------------
 -- timing & statistics
 
 -----------------------------------------------------------------------------
 -- timing & statistics
@@ -534,3 +868,8 @@ printTimes allocs psecs
        putStrLn (showSDoc (
                 parens (text (secs_str "") <+> text "secs" <> comma <+> 
                         int allocs <+> text "bytes")))
        putStrLn (showSDoc (
                 parens (text (secs_str "") <+> text "secs" <> comma <+> 
                         int allocs <+> text "bytes")))
+
+-----------------------------------------------------------------------------
+-- reverting CAFs
+       
+foreign import revertCAFs :: IO ()     -- make it "safe", just in case