IHaskell/Main.hs

188 lines
6.8 KiB
Haskell
Raw Normal View History

2013-10-20 13:22:56 -07:00
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Description : Argument parsing and basic messaging loop, using Haskell
-- Chans to communicate with the ZeroMQ sockets.
module Main where
2013-10-12 22:31:47 -07:00
import ClassyPrelude hiding (liftIO)
import Control.Concurrent.Chan
import Data.Aeson
2013-10-27 19:08:41 -07:00
import Text.Printf
import qualified Data.Map as Map
import IHaskell.Types
2013-08-26 14:47:27 -07:00
import IHaskell.ZeroMQ
import qualified IHaskell.Message.UUID as UUID
2013-10-10 22:52:32 -07:00
import IHaskell.Eval.Evaluate
import qualified Data.ByteString.Char8 as Chars
2013-10-20 20:16:34 -07:00
import IHaskell.IPython
import IHaskell.Completion (makeCompletions)
import GHC
import Exception (ghandle, gcatch)
import Outputable (showSDoc, ppr)
2013-09-28 21:29:54 -07:00
data KernelState = KernelState
{ getExecutionCounter :: Int
}
main :: IO ()
main = do
2013-10-27 19:08:41 -07:00
(major, minor, patch) <- ipythonVersion
when (major /= 1) $ do
printf "Expecting IPython version 1.*, found version %d.%d.%d.\n" major minor patch
error "Incorrect ipython --version."
2013-10-20 20:16:34 -07:00
args <- map unpack <$> getArgs
case args of
-- Create the "haskell" profile.
["setup"] -> setupIPythonProfile "haskell"
2013-10-20 20:16:34 -07:00
-- Run the ipython <cmd> --profile haskell <args> command.
2013-10-21 13:49:21 -07:00
"notebook":ipythonArgs -> runIHaskell "haskell" "notebook" ipythonArgs
"console":ipythonArgs -> runIHaskell "haskell" "console" ipythonArgs
2013-10-20 20:16:34 -07:00
-- Read the profile JSON file from the argument list.
["kernel", profileSrc] -> kernel profileSrc
-- Bad arguments.
[] -> putStrLn "Provide command to run ('setup', 'kernel <profile-file.json>', \
\'notebook [args]', 'console [args]')."
2013-10-20 20:16:34 -07:00
cmd:_ -> putStrLn $ "Unknown command: " ++ pack cmd
-- | Run the IHaskell language kernel.
kernel :: String -- ^ Filename of profile JSON file.
-> IO ()
kernel profileSrc = do
2013-08-26 14:47:27 -07:00
-- Parse the profile file.
2013-10-20 20:16:34 -07:00
Just profile <- liftM decode . readFile . fpFromText $ pack profileSrc
2013-08-26 14:47:27 -07:00
-- Serve on all sockets and ports defined in the profile.
interface <- serveProfile profile
2013-08-26 14:47:27 -07:00
state <- initialKernelState
2013-08-26 14:47:27 -07:00
-- Receive and reply to all messages on the shell socket.
2013-10-12 22:31:47 -07:00
interpret $ forever $ do
2013-08-26 14:47:27 -07:00
-- Read the request from the request channel.
2013-10-12 22:31:47 -07:00
request <- liftIO $ readChan $ shellRequestChannel interface
2013-08-26 14:47:27 -07:00
-- Create a header for the reply.
2013-10-20 12:22:27 -07:00
replyHeader <- createReplyHeader (header request)
2013-08-26 14:47:27 -07:00
-- Create the reply, possibly modifying kernel state.
2013-10-12 22:31:47 -07:00
oldState <- liftIO $ takeMVar state
(newState, reply) <- replyTo interface request replyHeader oldState
liftIO $ putMVar state newState
2013-08-26 14:47:27 -07:00
-- Write the reply to the reply channel.
2013-10-12 22:31:47 -07:00
liftIO $ writeChan (shellReplyChannel interface) reply
2013-08-26 14:47:27 -07:00
-- Initial kernel state.
initialKernelState :: IO (MVar KernelState)
2013-10-12 22:31:47 -07:00
initialKernelState =
2013-10-10 22:52:32 -07:00
newMVar KernelState {
2013-10-12 22:31:47 -07:00
getExecutionCounter = 1
2013-09-28 21:29:54 -07:00
}
2013-10-10 22:52:32 -07:00
-- | Duplicate a message header, giving it a new UUID and message type.
2013-10-12 22:31:47 -07:00
dupHeader :: MessageHeader -> MessageType -> Interpreter MessageHeader
2013-10-10 22:52:32 -07:00
dupHeader header messageType = do
2013-10-12 22:31:47 -07:00
uuid <- liftIO UUID.random
2013-10-10 22:52:32 -07:00
return header { messageId = uuid, msgType = messageType }
2013-08-26 14:47:27 -07:00
-- | Create a new message header, given a parent message header.
2013-10-20 12:22:27 -07:00
createReplyHeader :: MessageHeader -> Interpreter MessageHeader
2013-08-26 14:47:27 -07:00
createReplyHeader parent = do
-- Generate a new message UUID.
2013-10-20 12:22:27 -07:00
newMessageId <- liftIO UUID.random
2013-08-26 14:47:27 -07:00
return MessageHeader {
identifiers = identifiers parent,
parentHeader = Just parent,
metadata = Map.fromList [],
messageId = newMessageId,
sessionId = sessionId parent,
username = username parent,
msgType = replyType $ msgType parent
}
2013-10-20 12:22:27 -07:00
-- | Compute a reply to a message.
2013-10-12 22:31:47 -07:00
replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> Interpreter (KernelState, Message)
2013-10-20 12:22:27 -07:00
-- Reply to kernel info requests with a kernel info reply. No computation
-- needs to be done, as a kernel info reply is a static object (all info is
-- hard coded into the representation of that message type).
2013-08-26 14:47:27 -07:00
replyTo _ KernelInfoRequest{} replyHeader state = return (state, KernelInfoReply { header = replyHeader })
2013-08-26 16:14:48 -07:00
2013-10-20 12:22:27 -07:00
-- Reply to an execution request. The reply itself does not require
-- computation, but this causes messages to be sent to the IOPub socket
-- with the output of the code in the execution request.
2013-10-10 22:52:32 -07:00
replyTo interface ExecuteRequest{ getCode = code } replyHeader state = do
let execCount = getExecutionCounter state
2013-10-20 12:22:27 -07:00
-- Convenience function to send a message to the IOPub socket.
2013-10-12 22:31:47 -07:00
send msg = liftIO $ writeChan (iopubChannel interface) msg
2013-10-07 21:32:51 -07:00
2013-10-20 12:22:27 -07:00
-- Notify the frontend that the kernel is busy computing.
-- All the headers are copies of the reply header with a different
-- message type, because this preserves the session ID, parent header,
-- and other important information.
2013-10-10 22:52:32 -07:00
busyHeader <- dupHeader replyHeader StatusMessage
send $ PublishStatus busyHeader Busy
2013-10-20 12:22:27 -07:00
-- Get display data outputs of evaluating the code.
2013-10-12 22:31:47 -07:00
outputs <- evaluate $ Chars.unpack code
2013-10-10 22:52:32 -07:00
2013-10-20 12:22:27 -07:00
-- Find all the plain text outputs.
-- Send plain text output via an output message, because we are just
-- publishing output and not some representation of data.
2013-10-10 22:52:32 -07:00
let isPlain (Display mime _) = mime == PlainText
case find isPlain outputs of
Just (Display PlainText text) -> do
outHeader <- dupHeader replyHeader OutputMessage
send $ PublishOutput outHeader text execCount
Nothing -> return ()
2013-10-20 12:22:27 -07:00
-- Send all the non-plain-text representations of data to the frontend.
2013-10-10 22:52:32 -07:00
displayHeader <- dupHeader replyHeader DisplayDataMessage
send $ PublishDisplayData displayHeader "haskell" $ filter (not . isPlain) outputs
2013-10-20 12:22:27 -07:00
-- Notify the frontend that we're done computing.
idleHeader <- dupHeader replyHeader StatusMessage
send $ PublishStatus idleHeader Idle
-- Increment the execution counter in the kernel state.
2013-10-10 22:52:32 -07:00
let newState = state { getExecutionCounter = execCount + 1 }
2013-09-28 21:29:54 -07:00
return (newState, ExecuteReply {
2013-08-27 09:46:31 -07:00
header = replyHeader,
2013-10-10 22:52:32 -07:00
executionCounter = execCount,
status = Ok
2013-08-27 09:46:31 -07:00
})
replyTo _ creq@CompleteRequest{} replyHeader state = trace (show creq) $ do
cr <- makeCompletions replyHeader creq
return (state, cr)
replyTo _ ObjectInfoRequest{objectName=oname} replyHeader state = do
dflags <- getSessionDynFlags
maybeDocs <- flip gcatch (\(e::SomeException) -> return Nothing) $ do
result <- exprType . Chars.unpack $ oname
let docs = (showSDoc dflags) . ppr $ result
return (Just docs)
let docs = maybe "" id maybeDocs
let reply = ObjectInfoReply {
header = replyHeader,
objectName = oname,
objectFound = if isNothing maybeDocs then False else True,
objectTypeString = Chars.pack docs,
objectDocString = Chars.pack docs
}
return (state, reply)