ipython-kernel-0.10.1.0: src/IHaskell/IPython/Message/Parser.hs
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-name-shadowing #-}
-- | Description : Parsing messages received from IPython
--
-- This module is responsible for converting from low-level ByteStrings obtained from the 0MQ
-- sockets into Messages. The only exposed function is `parseMessage`, which should only be used in
-- the low-level 0MQ interface.
module IHaskell.IPython.Message.Parser (parseMessage) where
import Control.Applicative ((<$>), (<*>))
import Data.Aeson ((.:), (.:?), (.!=), decode, FromJSON, Result(..), Object, Value(..))
import Data.Aeson.Types (Parser, parse, parseEither)
import Data.ByteString hiding (unpack)
import qualified Data.ByteString.Lazy as Lazy
import Data.HashMap.Strict as HM
import Data.Maybe (fromMaybe)
import Data.Text (unpack)
import Debug.Trace
import IHaskell.IPython.Types
type LByteString = Lazy.ByteString
-- --- External interface ----- | Parse a message from its ByteString components into a Message.
parseMessage :: [ByteString] -- ^ The list of identifiers sent with the message.
-> ByteString -- ^ The header data.
-> ByteString -- ^ The parent header, which is just "{}" if there is no header.
-> ByteString -- ^ The metadata map, also "{}" for an empty map.
-> ByteString -- ^ The message content.
-> Message -- ^ A parsed message.
parseMessage idents headerData parentHeader metadata content =
let header = parseHeader idents headerData parentHeader metadata
messageType = mhMsgType header
messageWithoutHeader = parser messageType $ Lazy.fromStrict content
in messageWithoutHeader { header = header }
-- --- Module internals ----- | Parse a header from its ByteString components into a MessageHeader.
parseHeader :: [ByteString] -- ^ The list of identifiers.
-> ByteString -- ^ The header data.
-> ByteString -- ^ The parent header, or "{}" for Nothing.
-> ByteString -- ^ The metadata, or "{}" for an empty map.
-> MessageHeader -- The resulting message header.
parseHeader idents headerData parentHeader metadata =
MessageHeader idents parentResult metadataMap messageUUID sessionUUID username messageType
where
-- Decode the header data and the parent header data into JSON objects. If the parent header data is
-- absent, just have Nothing instead.
Just result = decode $ Lazy.fromStrict headerData :: Maybe Object
parentResult = if parentHeader == "{}"
then Nothing
else Just $ parseHeader idents parentHeader "{}" metadata
Success (messageType, username, messageUUID, sessionUUID) = flip parse result $ \obj -> do
messType <- obj .: "msg_type"
username <- obj .: "username"
message <- obj .: "msg_id"
session <- obj .: "session"
return (messType, username, message, session)
-- Get metadata as a simple map.
Just metadataMap = fmap Metadata $ decode $ Lazy.fromStrict metadata
noHeader :: MessageHeader
noHeader = error "No header created"
parser :: MessageType -- ^ The message type being parsed.
-> LByteString -> Message -- ^ The parser that converts the body into a message. This message
-- should have an undefined header.
parser KernelInfoRequestMessage = kernelInfoRequestParser
parser ExecuteInputMessage = executeInputParser
parser ExecuteRequestMessage = executeRequestParser
parser ExecuteReplyMessage = executeReplyParser
parser ExecuteErrorMessage = executeErrorParser
parser ExecuteResultMessage = executeResultParser
parser DisplayDataMessage = displayDataParser
parser IsCompleteRequestMessage = isCompleteRequestParser
parser CompleteRequestMessage = completeRequestParser
parser InspectRequestMessage = inspectRequestParser
parser ShutdownRequestMessage = shutdownRequestParser
parser InputReplyMessage = inputReplyParser
parser CommOpenMessage = commOpenParser
parser CommDataMessage = commDataParser
parser CommInfoRequestMessage = commInfoRequestParser
parser CommCloseMessage = commCloseParser
parser HistoryRequestMessage = historyRequestParser
parser StatusMessage = statusMessageParser
parser StreamMessage = streamMessageParser
parser InputMessage = inputMessageParser
parser OutputMessage = outputMessageParser
parser ClearOutputMessage = clearOutputMessageParser
parser other = error $ "Unknown message type " ++ show other
-- | Parse a kernel info request. A kernel info request has no auxiliary information, so ignore the
-- body.
kernelInfoRequestParser :: LByteString -> Message
kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader }
-- | Parse a comm info request. A comm info request has no auxiliary information, so ignore the
-- body.
commInfoRequestParser :: LByteString -> Message
commInfoRequestParser _ = CommInfoRequest { header = noHeader }
-- | Parse an execute_input response. Fields used are:
executeInputParser :: LByteString -> Message
executeInputParser = requestParser $ \obj -> do
code <- obj .: "code"
executionCount <- obj .: "execution_count"
return $ ExecuteInput noHeader code executionCount
-- | Parse an execute request. Fields used are:
-- 1. "code": the code to execute.
-- 2. "silent": whether to execute silently.
-- 3. "store_history": whether to include this in history.
-- 4. "allow_stdin": whether to allow reading from stdin for this code.
executeRequestParser :: LByteString -> Message
executeRequestParser content =
let parser obj = do
let getOrElse a k = (fromMaybe a) <$> obj .:? k
code <- obj .: "code"
silent <- getOrElse False "silent"
storeHistory <- getOrElse (not silent) "store_history"
allowStdin <- obj .: "allow_stdin"
return (code, silent, storeHistory, allowStdin)
Just decoded = decode content
Success (code, silent, storeHistory, allowStdin) = parse parser decoded
in ExecuteRequest
{ header = noHeader
, getCode = code
, getSilent = silent
, getAllowStdin = allowStdin
, getStoreHistory = storeHistory
, getUserVariables = []
, getUserExpressions = []
}
-- | Parse an execute reply
executeReplyParser :: LByteString -> Message
executeReplyParser = requestParser $ \obj -> do
status <- obj .: "status"
executionCount <- obj .: "execution_count"
return $ ExecuteReply noHeader status [] executionCount
-- | Parse an execute reply
executeErrorParser :: LByteString -> Message
executeErrorParser = requestParser $ \obj -> do
-- executionCount <- obj .: "execution_count"
traceback <- obj .: "traceback"
ename <- obj .: "ename"
evalue <- obj .: "evalue"
return $ ExecuteError noHeader traceback ename evalue
makeDisplayDatas :: Object -> [DisplayData]
makeDisplayDatas dataDict = [DisplayData (read $ unpack mimeType) content | (mimeType, String content) <- HM.toList
dataDict]
-- | Parse an execute result
executeResultParser :: LByteString -> Message
executeResultParser = requestParser $ \obj -> do
executionCount <- obj .: "execution_count"
dataDict :: Object <- obj .: "data"
let displayDatas = makeDisplayDatas dataDict
metadataDict <- obj .: "metadata"
return $ ExecuteResult noHeader displayDatas metadataDict executionCount
-- | Parse a display data message
displayDataParser :: LByteString -> Message
displayDataParser = requestParser $ \obj -> do
dataDict :: Object <- obj .: "data"
let displayDatas = makeDisplayDatas dataDict
return $ PublishDisplayData noHeader displayDatas Nothing
requestParser :: FromJSON a => (a -> Parser Message) -> LByteString -> Message
requestParser parser content =
case parseEither parser decoded of
Right parsed -> parsed
Left err -> trace ("Parse error: " ++ show err) SendNothing
where
Just decoded = decode content
historyRequestParser :: LByteString -> Message
historyRequestParser = requestParser $ \obj ->
HistoryRequest noHeader <$> obj .: "output" <*> obj .: "raw" <*> historyAccessType obj
where
-- TODO: Implement full history access type parsing from message spec
historyAccessType obj = do
accessTypeStr <- obj .: "hist_access_type"
return $
case accessTypeStr of
"range" -> HistoryRange
"tail" -> HistoryTail
"search" -> HistorySearch
str -> error $ "Unknown history access type: " ++ str
statusMessageParser :: LByteString -> Message
statusMessageParser = requestParser $ \obj -> do
execution_state <- obj .: "execution_state"
return $ PublishStatus noHeader execution_state
streamMessageParser :: LByteString -> Message
streamMessageParser = requestParser $ \obj -> do
streamType <- obj .: "name"
streamContent <- obj .: "text"
return $ PublishStream noHeader streamType streamContent
inputMessageParser :: LByteString -> Message
inputMessageParser = requestParser $ \obj -> do
code <- obj .: "code"
executionCount <- obj .: "execution_count"
return $ Input noHeader code executionCount
getDisplayDatas :: Maybe Object -> [DisplayData]
getDisplayDatas Nothing = []
getDisplayDatas (Just dataDict) = makeDisplayDatas dataDict
outputMessageParser :: LByteString -> Message
outputMessageParser = requestParser $ \obj -> do
-- Handle both "data" and "text" keys
maybeDataDict1 :: Maybe Object <- obj .:? "data"
let displayDatas1 = getDisplayDatas maybeDataDict1
maybeDataDict2 :: Maybe Object <- obj .:? "text"
let displayDatas2 = getDisplayDatas maybeDataDict2
executionCount <- obj .: "execution_count"
return $ Output noHeader (displayDatas1 ++ displayDatas2) executionCount
clearOutputMessageParser :: LByteString -> Message
clearOutputMessageParser = requestParser $ \obj -> do
wait <- obj .: "wait"
return $ ClearOutput noHeader wait
isCompleteRequestParser :: LByteString -> Message
isCompleteRequestParser = requestParser $ \obj -> do
code <- obj .: "code"
return $ IsCompleteRequest noHeader code
completeRequestParser :: LByteString -> Message
completeRequestParser = requestParser $ \obj -> do
code <- obj .: "code"
pos <- obj .: "cursor_pos"
return $ CompleteRequest noHeader code pos
inspectRequestParser :: LByteString -> Message
inspectRequestParser = requestParser $ \obj -> do
code <- obj .: "code"
pos <- obj .: "cursor_pos"
dlevel <- obj .: "detail_level"
return $ InspectRequest noHeader code pos dlevel
shutdownRequestParser :: LByteString -> Message
shutdownRequestParser = requestParser $ \obj -> do
code <- obj .: "restart"
return $ ShutdownRequest noHeader code
inputReplyParser :: LByteString -> Message
inputReplyParser = requestParser $ \obj -> do
value <- obj .: "value"
return $ InputReply noHeader value
commOpenParser :: LByteString -> Message
commOpenParser = requestParser $ \obj -> do
uuid <- obj .: "comm_id"
targetName <- obj .: "target_name"
targetModule <- obj .:? "target_module" .!= ""
value <- obj .: "data"
return $ CommOpen noHeader targetName targetModule uuid value
commDataParser :: LByteString -> Message
commDataParser = requestParser $ \obj -> do
uuid <- obj .: "comm_id"
value <- obj .: "data"
return $ CommData noHeader uuid value
commCloseParser :: LByteString -> Message
commCloseParser = requestParser $ \obj -> do
uuid <- obj .: "comm_id"
value <- obj .: "data"
return $ CommClose noHeader uuid value