{- a very simple interactive Haskell expression evaluator successfully compiles with GHC 6.6 using "ghc --make -package ghc-6.6 Interactive.hs" This program shows some results of our experiments with using GHC as a library (see also http://www.haskell.org/haskellwiki/GHC/As_a_library). One of our goals was to figure out to what extent it is possible to replace some of the standard prelude functions with our own modified versions. This could be used, for example, in a GUI for GHCi (something like WinHugs) to redirect "putStrLn" output from a haskell program to the GUI window and to appropriately react on "getLine" prompts. We still have to find out how the user's response to such a prompt can be sent back to the underlying GHCi instance. Bastian Hackler and Matthias Stemmler University of Marburg, Germany version of 2006-12-03 Albert Y. C. Lai (Treblacy on haskellwiki) adds a bit to disable *.hi and *.o production 2007-01-07 Gwern Branwen *Get working with 6.8 Sat Dec 1 13:25:02 EST 2007 -} module Main where -- qualified imports so that we can see where the GHC api is used import qualified GHC import qualified Outputable import qualified Packages import qualified PackageConfig import System.IO import Control.Monad import MyPrelude import Data.Maybe {- the path of our GHC 6.8 installation; this obviously needs to be edited to point to the right place - the current value happens to be right for me, but for you it might be in /usr/lib/, ~/lib, ~/bin/lib... depending on where your GHC is installed. -} path :: FilePath path = "/usr/lib64/ghc-6.8.1.20071127/" {- Our program does the following: * start an interactive GHC session * initialize the default packages * load some modified versions of "putStrLn" and "getLine" from MyPrelude.hs * load the standard prelude * replace "putStrLn" and "getLine" with our modified versions * repeatedly ask for Haskell expressions and evaluate until the user enters empty string. -} main :: IO () main = do -- start a new interactive session using the path specified above session <- GHC.newSession $ Just path -- initialize the default packages dflags1 <- GHC.getSessionDynFlags session (dflags2, _) <- Packages.initPackages dflags1 GHC.setSessionDynFlags session dflags2{GHC.hscTarget=GHC.HscInterpreted} -- now the order of the statements is important: -- 1) load our modified prelude functions target <- GHC.guessTarget "MyPrelude.hs" Nothing GHC.addTarget session target -- this would unload the standard prelude if it had already been loaded GHC.load session GHC.LoadAllTargets -- 2) load the standard prelude let preludeModule = GHC.mkModule (PackageConfig.stringToPackageId "base") (GHC.mkModuleName "Prelude") GHC.setContext session [] [preludeModule] -- 3) replace "putStrLn" and "getLine" with our modified versions replaceFunctions session -- enter a query-and-response loop nextStmt session where nextStmt session = do -- ask for a Haskell expression to evaluate -- print lines beginning with "***" so they can be distinguished from output coming from the GHC api putStr "* Enter statement: " >> hFlush stdout stmt <- getLine -- if the user entered an empty query ... if stmt == "" -- ... then quit the program then putStrLn "* Bye!" else do -- ... otherwise run the given statement result <- GHC.runStmt session stmt GHC.SingleStep -- display the result case result of GHC.RunOk names -> putStrLn $ "* Ok: " ++ showNames names -- see below GHC.RunFailed -> putStrLn "* Failed" GHC.RunException e -> putStrLn $ "* Exception: " ++ show e GHC.RunBreak _ _ _ -> putStrLn "* Break." -- and ask for an expression again nextStmt session -- If GHC.runStmt returns GHC.RunOk names then names is a list -- of the names of all variables that were bound during evaluation. -- This function somehow manages to pretty-print such a list. showNames :: [GHC.Name] -> String showNames = Outputable.showSDoc . Outputable.ppr -- replaces "putStrLn" and "getLine" with our modified versions replaceFunctions :: GHC.Session -> IO () replaceFunctions session = do mustWork "let putStrLn = MyPrelude.myPutStrLn" session GHC.SingleStep mustWork "let getLine = MyPrelude.myGetLine" session GHC.SingleStep return () -- simply use "let" to rebind "putStrLn" and "getLine" -- (obviously our prelude has only been imported with qualified names) where -- mustWork either runs the given statement successfully or triggers an error mustWork :: String -> GHC.Session -> GHC.SingleStep -> IO GHC.RunResult mustWork stmt sssn = GHC.runStmt sssn stmt -- if (liftM isOk) result then return () else error "replaceFunctions failed." -- is the given result of type "RunOk"? isOk :: GHC.RunResult -> Bool isOk (GHC.RunOk _) = True isOk _ = False