2013-10-20 13:22:56 -07:00
|
|
|
{-# LANGUAGE NoImplicitPrelude #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2013-11-04 21:31:27 -05:00
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2013-10-31 10:51:11 -04:00
|
|
|
-- | 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)
|
2013-10-08 19:54:05 -07:00
|
|
|
import Control.Concurrent.Chan
|
2013-08-26 00:18:30 -07:00
|
|
|
import Data.Aeson
|
2013-10-27 19:08:41 -07:00
|
|
|
import Text.Printf
|
2013-11-09 17:01:18 -08:00
|
|
|
import System.Exit (exitSuccess)
|
2013-12-13 11:15:57 -08:00
|
|
|
import System.Directory
|
2013-08-26 00:18:30 -07:00
|
|
|
|
|
|
|
import qualified Data.Map as Map
|
|
|
|
|
|
|
|
import IHaskell.Types
|
2013-08-26 14:47:27 -07:00
|
|
|
import IHaskell.ZeroMQ
|
2013-10-08 19:54:05 -07:00
|
|
|
import qualified IHaskell.Message.UUID as UUID
|
2013-10-10 22:52:32 -07:00
|
|
|
import IHaskell.Eval.Evaluate
|
2013-12-17 21:47:59 -08:00
|
|
|
import IHaskell.Eval.Completion (complete)
|
2013-11-10 18:31:55 -08:00
|
|
|
import IHaskell.Eval.Info
|
2013-10-10 22:52:32 -07:00
|
|
|
import qualified Data.ByteString.Char8 as Chars
|
2013-10-20 20:16:34 -07:00
|
|
|
import IHaskell.IPython
|
2013-08-26 00:18:30 -07:00
|
|
|
|
2013-11-04 21:31:27 -05:00
|
|
|
import GHC
|
|
|
|
import Outputable (showSDoc, ppr)
|
|
|
|
|
2013-09-28 21:29:54 -07:00
|
|
|
data KernelState = KernelState
|
|
|
|
{ getExecutionCounter :: Int
|
|
|
|
}
|
2013-08-26 00:18:30 -07:00
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
main = do
|
2013-10-27 19:08:41 -07:00
|
|
|
(major, minor, patch) <- ipythonVersion
|
2013-11-09 17:01:18 -08:00
|
|
|
when (major < 1) $ do
|
|
|
|
void $ printf "Expecting IPython version 1.*, found version %d.%d.%d.\n" major minor patch
|
2013-10-27 19:08:41 -07:00
|
|
|
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-08-26 00:18:30 -07:00
|
|
|
|
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.
|
2013-11-09 17:01:18 -08:00
|
|
|
[] -> 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-12-13 11:15:57 -08:00
|
|
|
-- Switch to a temporary directory so that any files we create aren't
|
|
|
|
-- visible. On Unix, this is usually /tmp. If there is no temporary
|
|
|
|
-- directory available, just stay in the current one and ignore the
|
|
|
|
-- raised exception.
|
|
|
|
try (getTemporaryDirectory >>= setCurrentDirectory) :: IO (Either SomeException ())
|
|
|
|
|
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 00:18:30 -07:00
|
|
|
|
2013-08-26 14:47:27 -07:00
|
|
|
-- Serve on all sockets and ports defined in the profile.
|
|
|
|
interface <- serveProfile profile
|
2013-08-26 00:18:30 -07:00
|
|
|
|
2013-08-26 14:47:27 -07:00
|
|
|
state <- initialKernelState
|
2013-08-26 00:18:30 -07:00
|
|
|
|
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 00:18:30 -07:00
|
|
|
|
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 00:18:30 -07:00
|
|
|
|
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 00:18:30 -07:00
|
|
|
|
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 00:18:30 -07:00
|
|
|
|
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-08-26 00:18:30 -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 00:18:30 -07:00
|
|
|
|
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-08-26 00:18:30 -07:00
|
|
|
|
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-11-09 17:01:18 -08:00
|
|
|
-- Reply to a shutdown request by exiting the main thread.
|
2013-11-09 17:05:18 -08:00
|
|
|
-- Before shutdown, reply to the request to let the frontend know shutdown
|
|
|
|
-- is happening.
|
|
|
|
replyTo interface ShutdownRequest{restartPending = restartPending} replyHeader _ = liftIO $ do
|
|
|
|
writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader restartPending
|
|
|
|
exitSuccess
|
2013-11-09 17:01:18 -08: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-12-11 18:10:19 -08:00
|
|
|
-- Construct a function for publishing output as this is going.
|
|
|
|
let publish :: [DisplayData] -> Interpreter ()
|
|
|
|
publish outputs = do
|
|
|
|
header <- dupHeader replyHeader DisplayDataMessage
|
|
|
|
send $ PublishDisplayData header "haskell" outputs
|
|
|
|
|
2013-12-12 00:03:20 -08:00
|
|
|
-- Run code and publish to the frontend as we go.
|
2013-12-11 18:10:19 -08:00
|
|
|
evaluate execCount (Chars.unpack code) publish
|
|
|
|
|
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
|
|
|
})
|
2013-10-23 13:26:33 -04:00
|
|
|
|
|
|
|
|
2013-12-17 21:47:59 -08:00
|
|
|
replyTo _ req@CompleteRequest{} replyHeader state = do
|
|
|
|
(matchedText, completions) <- complete (Chars.unpack $ getCodeLine req) (getCursorPos req)
|
|
|
|
|
|
|
|
let reply = CompleteReply replyHeader (map Chars.pack completions) (Chars.pack matchedText) (getCodeLine req) True
|
|
|
|
return (state, reply)
|
2013-10-23 13:26:33 -04:00
|
|
|
|
2013-11-07 07:44:22 -05:00
|
|
|
-- | Reply to the object_info_request message. Given an object name, return
|
|
|
|
-- | the associated type calculated by GHC.
|
2013-11-04 21:31:27 -05:00
|
|
|
replyTo _ ObjectInfoRequest{objectName=oname} replyHeader state = do
|
2013-11-10 18:31:55 -08:00
|
|
|
docs <- info $ Chars.unpack oname
|
2013-11-04 21:31:27 -05:00
|
|
|
let reply = ObjectInfoReply {
|
|
|
|
header = replyHeader,
|
|
|
|
objectName = oname,
|
2013-11-10 18:31:55 -08:00
|
|
|
objectFound = docs == "",
|
2013-11-04 21:31:27 -05:00
|
|
|
objectTypeString = Chars.pack docs,
|
|
|
|
objectDocString = Chars.pack docs
|
|
|
|
}
|
|
|
|
return (state, reply)
|
|
|
|
|
|
|
|
|
2013-10-23 13:26:33 -04:00
|
|
|
|