diff --git a/IHaskell.cabal b/IHaskell.cabal
index 30d6d482..8d480a1f 100644
--- a/IHaskell.cabal
+++ b/IHaskell.cabal
@@ -69,6 +69,12 @@ executable IHaskell
uuid ==1.2.*,
containers ==0.5.*,
process ==1.1.*,
- haskell-src-exts ==1.14.*
+ haskell-src-exts ==1.14.*,
+ ghc ==7.6.*,
+ ghc-paths ==0.1.*,
+ knob ==0.1.*,
+ directory ==1.2.*,
+ deepseq ==1.3.*
+
diff --git a/IHaskell/Eval/Evaluate.hs b/IHaskell/Eval/Evaluate.hs
index e6bb9887..643528f4 100644
--- a/IHaskell/Eval/Evaluate.hs
+++ b/IHaskell/Eval/Evaluate.hs
@@ -1,128 +1,163 @@
-- | This module exports all functions used for evaluation of IHaskell input.
module IHaskell.Eval.Evaluate (
- evaluate, Interpreter, makeInterpreter
+ interpret, evaluate, Interpreter, liftIO
) where
-import ClassyPrelude
+import ClassyPrelude hiding (liftIO, hGetContents)
import Prelude(putChar, tail, init)
-import System.Process
-import System.IO (hSetBuffering, BufferMode(..), hPutStr, hGetChar)
import Data.List.Utils
import Data.String.Utils
import Text.Printf
import Language.Haskell.Exts.Parser
import Language.Haskell.Exts.Pretty
-import Language.Haskell.Exts.Syntax
+import Language.Haskell.Exts.Syntax hiding (Name)
+
+import InteractiveEval
+import HscTypes
+import Name
+import GhcMonad (liftIO)
+import GHC hiding (Stmt)
+import GHC.Paths
+import Exception hiding (evaluate)
import IHaskell.Types
+write :: GhcMonad m => String -> m ()
+write x = liftIO $ hPutStrLn stderr x
+
type LineNumber = Int
type ColumnNumber = Int
+type Interpreter = Ghc
+data Command
+ = Directive String
+ | Import String
+ | Statement String
+ | ParseError LineNumber ColumnNumber String
+ deriving Show
-promptString :: String
-promptString = "+++GHCI_IHASKELL+++>"
+globalImports :: [String]
+globalImports =
+ [ "import Prelude"
+ , "import Control.Applicative ((<$>))"
+ , "import GHC.IO.Handle (hDuplicateTo, hDuplicate)"
+ , "import System.IO"
+ ]
-data Interpreter = Interpreter {
- inStream :: Handle,
- outStream :: Handle,
- errStream :: Handle,
- ghciHandle :: ProcessHandle
- }
+directiveChar :: Char
+directiveChar = ':'
-makeInterpreter :: IO Interpreter
-makeInterpreter = do
- let processSpec = (proc "ghci.sh" ["-ghci-script", "ihaskell.ghci"]) {
- std_in = CreatePipe,
- std_out = CreatePipe,
- std_err = CreatePipe
- }
- (Just input, Just output, Just errs, processHandle) <- createProcess processSpec
+interpret :: Interpreter a -> IO a
+interpret action = runGhc (Just libdir) $ do
+ -- Set the dynamic session flags
+ dflags <- getSessionDynFlags
+ setSessionDynFlags $ dflags { hscTarget = HscInterpreted, ghcLink = LinkInMemory }
- hSetBuffering input NoBuffering
- hSetBuffering output NoBuffering
- hSetBuffering errs NoBuffering
+ -- Import modules.
+ imports <- mapM parseImportDecl globalImports
+ setContext $ map IIDecl imports
- let interpreter = Interpreter {
- inStream = input,
- outStream = output,
- errStream = errs,
- ghciHandle = processHandle
- }
-
- initializeInterpreter interpreter
- return interpreter
-
-ghciSend :: Interpreter -> String -> IO ()
-ghciSend interpreter = hPutStr (inStream interpreter)
-
-ghciConsumePrompt :: Interpreter -> IO [String]
-ghciConsumePrompt interpreter = readChars []
- where readChars prev =
- if startswith (reverse promptString) prev
- then case lines prev of
- _ : rest -> return $ reverse $ map reverse rest
- [] -> error "No prompt present."
- else do
- nextChar <- hGetChar (outStream interpreter)
- when (nextChar == '\n') $ print $ reverse prev
- readChars (nextChar : prev)
-
-initializeInterpreter :: Interpreter -> IO ()
-initializeInterpreter = void . ghciConsumePrompt
+ -- Run the rest of the interpreter
+ action
-- | Evaluate some IPython input code.
-evaluate :: Interpreter -- ^ Handle to the interpreter context.
- -> String -- ^ Haskell code or other interpreter commands.
- -> IO [DisplayData] -- ^ All of the output.
-evaluate interpreter code =
- case strip code of
- "" -> return []
- strippedCode ->
- let codePieces = map makeCodePiece $ groupBy sameCodePiece $ lines strippedCode in
- concat <$> mapM (evalCodePiece interpreter) codePieces
+evaluate :: String -- ^ Haskell code or other interpreter commands.
+ -> Interpreter [DisplayData] -- ^ All of the output.
+evaluate code
+ | strip code == "" = return []
+ | otherwise = joinDisplays <$> mapM evalCommand (parseCommands $ strip code)
-data CodePiece = GhciDirectives String | HaskellStmts String
+joinDisplays :: [[DisplayData]] -> [DisplayData]
+joinDisplays displays =
+ let isPlain (Display mime _) = (mime == PlainText)
+ allDisplays = concat displays
+ plains = filter isPlain allDisplays
+ other = filter (not . isPlain) allDisplays
+ getText (Display PlainText text) = text
+ joinedPlains = Display PlainText $ concat $ map getText plains in
+ joinedPlains : other
-makeCodePiece :: [String] -> CodePiece
-makeCodePiece lines =
- if any isDirective lines
- then GhciDirectives $ unlines lines
- else HaskellStmts $ unlines lines
-
-isDirective :: String -> Bool
-isDirective line =
- let stripped = strip line in
- startswith ":" stripped || startswith "import" stripped
-
-sameCodePiece :: String -> String -> Bool
-sameCodePiece = (==) `on` isDirective
-
-evalCodePiece :: Interpreter -> CodePiece -> IO [DisplayData]
-evalCodePiece interpreter (HaskellStmts code) =
- case parseStmts code of
- Left (errLine, errCol, errMsg) -> return [Display MimeHtml $ makeError $ printf "error Error (line %d, column %d): %s" errLine errCol errMsg]
- Right statements -> do
- mapM_ (putStrLn . pack) $ map prettyPrint statements
- concat <$> mapM (getResponse interpreter . prettyPrint) (init statements)
-
-evalCodePiece interpreter (GhciDirectives directives) = do
- mapM_ (getResponse interpreter) $ lines directives
- return []
-
-stripDoBlock :: String -> String
-stripDoBlock =
- unlines . map dedent . init . lines
+parseCommands :: String -- ^ Code containing commands.
+ -> [Command] -- ^ Commands contained in code string.
+parseCommands code = concatMap makeCommands pieces
where
- dedent (' ':' ':rest) = rest
+ pieces = groupBy ((==) `on` isDirective) $ lines code
+ makeCommands lines
+ | any isDirective lines = map createDirective lines
+ | otherwise =
+ case parseStmts $ unlines lines of
+ Left (srcLine, srcColumn, errMsg) -> [ParseError srcLine srcColumn errMsg]
+ Right stmts -> map (Statement . prettyPrint) $ init stmts
+ isDirective line = startswith [directiveChar] stripped || startswith "import" stripped
+ where stripped = strip line
+ createDirective line =
+ case strip line of
+ ':':_ -> Directive $ strip line
+ _ -> Import $ strip line
-getResponse :: Interpreter -> String -> IO [DisplayData]
-getResponse interpreter code = do
- mapM_ (ghciSend interpreter . (++ "\n")) $ lines code
- inlines <- ghciConsumePrompt interpreter
- case inlines of
- [] -> return []
- _ -> return $ parseOutput $ unlines inlines
+evalCommand :: Command -> Interpreter [DisplayData]
+evalCommand (Import importStr) = do
+ write $ "Import: " ++ importStr
+ importDecl <- parseImportDecl importStr
+ context <- getContext
+ setContext $ IIDecl importDecl : context
+ return []
+
+evalCommand (Directive directive) = do
+ write $ "Directive: " ++ directive
+ return [Display MimeHtml $ printf "%s" directive]
+
+evalCommand (Statement stmt) = do
+ write $ "Statement: " ++ stmt
+ ghandle handler $ do
+
+ (printed, result) <- capturedStatement stmt
+ case result of
+ RunOk names -> --concat <$> mapM showName names
+ return [Display PlainText printed]
+ RunException exception -> do
+ write $ "RunException: " ++ show exception
+ return [Display MimeHtml $ makeError $ show exception]
+ RunBreak{} ->
+ error "Should not break."
+ where
+ handler :: SomeException -> Interpreter [DisplayData]
+ handler exception = do
+ write $ concat ["Break: ", show exception, "\nfrom statement:\n", stmt]
+ return [Display MimeHtml $ makeError $ show exception]
+
+evalCommand (ParseError line col err) =
+ return [Display MimeHtml $ makeError $ printf "error Error (line %d, column %d): %s" line col err]
+
+capturedStatement :: String -> Interpreter (String, RunResult)
+capturedStatement stmt =
+ let fileVariable = "ridiculous" :: String
+ fileName = ".capture" :: String
+ oldVariable = fileVariable ++ "'" :: String
+ initStmts :: [String]
+ initStmts = [
+ printf "%s <- openFile \"%s\" WriteMode" fileVariable fileName,
+ printf "%s <- hDuplicate stdout" oldVariable,
+ printf "hDuplicateTo %s stdout" fileVariable]
+ postStmts :: [String]
+ postStmts = [
+ "hFlush stdout",
+ printf "hDuplicateTo %s stdout" oldVariable,
+ printf "hClose %s" fileVariable]
+ goStmt s = runStmt s RunToCompletion in do
+
+ forM_ initStmts goStmt
+ result <- goStmt stmt
+ forM_ postStmts goStmt
+
+ printedOutput <- liftIO $ readFile $ fpFromString fileName
+ liftIO $ print printedOutput
+
+ return (printedOutput, result)
+
+showName :: Name -> Interpreter [DisplayData]
+showName _ =
+ return [Display PlainText "Hello!"]
parseStmts :: String -> Either (LineNumber, ColumnNumber, String) [Stmt]
parseStmts code =
@@ -136,13 +171,5 @@ parseStmts code =
indent = (" " ++)
returnStmt = "return ()"
-
-parseOutput :: String -> [DisplayData]
-parseOutput output
- | startswith "" output = [Display MimeHtml $ makeError output]
- | otherwise = [Display PlainText output]
-
makeError :: String -> String
-makeError output =
- let _ : rest = words output in
- printf "%s" $ unwords rest
+makeError = printf "%s"
diff --git a/Main.hs b/Main.hs
index 454914c4..2783486d 100644
--- a/Main.hs
+++ b/Main.hs
@@ -1,4 +1,4 @@
-import ClassyPrelude
+import ClassyPrelude hiding (liftIO)
import Control.Concurrent.Chan
import Data.Aeson
@@ -12,7 +12,6 @@ import qualified Data.ByteString.Char8 as Chars
data KernelState = KernelState
{ getExecutionCounter :: Int
- , getInterpreter :: Interpreter
}
main :: IO ()
@@ -29,33 +28,32 @@ main = do
state <- initialKernelState
-- Receive and reply to all messages on the shell socket.
- forever $ do
+ interpret $ forever $ do
-- Read the request from the request channel.
- request <- readChan $ shellRequestChannel interface
+ request <- liftIO $ readChan $ shellRequestChannel interface
-- Create a header for the reply.
- replyHeader <- createReplyHeader (header request)
+ replyHeader <- liftIO $ createReplyHeader (header request)
-- Create the reply, possibly modifying kernel state.
- reply <- modifyMVar state $ replyTo interface request replyHeader
+ oldState <- liftIO $ takeMVar state
+ (newState, reply) <- replyTo interface request replyHeader oldState
+ liftIO $ putMVar state newState
-- Write the reply to the reply channel.
- writeChan (shellReplyChannel interface) reply
+ liftIO $ writeChan (shellReplyChannel interface) reply
-- Initial kernel state.
initialKernelState :: IO (MVar KernelState)
-initialKernelState = do
- interpreter <- makeInterpreter
-
+initialKernelState =
newMVar KernelState {
- getExecutionCounter = 1,
- getInterpreter = interpreter
+ getExecutionCounter = 1
}
-- | Duplicate a message header, giving it a new UUID and message type.
-dupHeader :: MessageHeader -> MessageType -> IO MessageHeader
+dupHeader :: MessageHeader -> MessageType -> Interpreter MessageHeader
dupHeader header messageType = do
- uuid <- UUID.random
+ uuid <- liftIO UUID.random
return header { messageId = uuid, msgType = messageType }
@@ -75,13 +73,12 @@ createReplyHeader parent = do
msgType = replyType $ msgType parent
}
-replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> IO (KernelState, Message)
+replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> Interpreter (KernelState, Message)
replyTo _ KernelInfoRequest{} replyHeader state = return (state, KernelInfoReply { header = replyHeader })
replyTo interface ExecuteRequest{ getCode = code } replyHeader state = do
let execCount = getExecutionCounter state
- interpreter = getInterpreter state
- send = writeChan $ iopubChannel interface
+ send msg = liftIO $ writeChan (iopubChannel interface) msg
idleHeader <- dupHeader replyHeader StatusMessage
send $ PublishStatus idleHeader Idle
@@ -89,7 +86,7 @@ replyTo interface ExecuteRequest{ getCode = code } replyHeader state = do
busyHeader <- dupHeader replyHeader StatusMessage
send $ PublishStatus busyHeader Busy
- outputs <- evaluate interpreter $ Chars.unpack code
+ outputs <- evaluate $ Chars.unpack code
let isPlain (Display mime _) = mime == PlainText
case find isPlain outputs of