|
1 | 1 | {-# LANGUAGE OverloadedStrings #-}
|
| 2 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 3 | +{-# LANGUAGE LambdaCase #-} |
2 | 4 | -- |Provide a protocol adapter/transport for JSON over stdio
|
3 | 5 |
|
4 | 6 | module Haskell.Ide.Engine.Transport.JsonStdio where
|
5 | 7 |
|
6 | 8 | import Control.Concurrent
|
| 9 | +import Control.Lens (view) |
7 | 10 | import Control.Logging
|
| 11 | +import Control.Monad.State.Strict |
8 | 12 | import qualified Data.Aeson as A
|
| 13 | +import qualified Data.Attoparsec.ByteString as AB |
| 14 | +import qualified Data.ByteString.Char8 as B |
| 15 | +import qualified Data.ByteString.Lazy as BL |
| 16 | +import Data.Char |
9 | 17 | import qualified Data.Map as Map
|
| 18 | +import qualified Data.Text as T |
10 | 19 | import Haskell.Ide.Engine.PluginDescriptor
|
11 | 20 | import Haskell.Ide.Engine.Types
|
12 |
| -import Pipes |
13 |
| -import qualified Pipes.Aeson as P |
14 |
| -import qualified Pipes.ByteString as P |
| 21 | +import qualified Pipes as P |
| 22 | +import qualified Pipes.Aeson as PAe |
| 23 | +import qualified Pipes.Attoparsec as PA |
| 24 | +import qualified Pipes.ByteString as PB |
15 | 25 | import qualified Pipes.Prelude as P
|
16 |
| -import qualified Data.ByteString.Char8 as B |
17 |
| -import qualified Data.ByteString.Lazy as BL |
18 |
| -import qualified Data.Text as T |
19 |
| -import Pipes.Parse |
20 | 26 | import System.IO
|
21 | 27 |
|
22 | 28 | -- TODO: Can pass in a handle, then it is general
|
23 | 29 | jsonStdioTransport :: Chan ChannelRequest -> IO ()
|
24 | 30 | jsonStdioTransport cin = do
|
25 | 31 | cout <- newChan :: IO (Chan ChannelResponse)
|
26 | 32 | hSetBuffering stdout NoBuffering
|
27 |
| - let |
28 |
| - loop cid stream = do |
29 |
| - debug "jsonStdioTransport:calling go" |
30 |
| - (req,stream') <- runStateT decodeMsg stream |
31 |
| - debug $ T.pack $ "jsonStdioTransport:got:" ++ show req |
32 |
| - case req of |
33 |
| - Just (Left err) -> do |
34 |
| - putStr $ show (HieError (A.String $ T.pack $ show err)) |
35 |
| - loop (cid + 1) stream' |
36 |
| - Just (Right r) -> do |
37 |
| - writeChan cin (wireToChannel cout cid r) |
38 |
| - rsp <- readChan cout |
39 |
| - BL.putStr $ A.encode (channelToWire rsp) |
40 |
| - loop (cid + 1) stream' |
41 |
| - Nothing -> do |
42 |
| - -- exit the loop |
43 |
| - putStr $ show (HieError (A.String $ T.pack $ "Got Nothing")) |
44 |
| - loop 1 P.stdin |
45 |
| - |
46 |
| -decodeMsg :: (Monad m) => Parser B.ByteString m (Maybe (Either P.DecodingError WireRequest)) |
47 |
| -decodeMsg = P.decode |
| 33 | + P.runEffect (parseFrames PB.stdin P.>-> parseToJsonPipe cin cout 1 P.>-> jsonConsumer) |
| 34 | + |
| 35 | +parseToJsonPipe |
| 36 | + :: Chan ChannelRequest |
| 37 | + -> Chan ChannelResponse |
| 38 | + -> Int |
| 39 | + -> P.Pipe (Either PAe.DecodingError WireRequest) A.Value IO () |
| 40 | +parseToJsonPipe cin cout cid = |
| 41 | + do parseRes <- P.await |
| 42 | + case parseRes of |
| 43 | + Left decodeErr -> |
| 44 | + do let rsp = |
| 45 | + CResp "" cid $ |
| 46 | + IdeResponseError |
| 47 | + (A.toJSON (HieError (A.String $ T.pack $ show decodeErr))) |
| 48 | + liftIO $ debug $ |
| 49 | + T.pack $ "jsonStdioTransport:parse error:" ++ show decodeErr |
| 50 | + P.yield $ A.toJSON $ channelToWire rsp |
| 51 | + Right req -> |
| 52 | + do liftIO $ writeChan cin (wireToChannel cout cid req) |
| 53 | + rsp <- liftIO $ readChan cout |
| 54 | + P.yield $ A.toJSON $ channelToWire rsp |
| 55 | + parseToJsonPipe cin |
| 56 | + cout |
| 57 | + (cid + 1) |
| 58 | + |
| 59 | +jsonConsumer :: P.Consumer A.Value IO () |
| 60 | +jsonConsumer = |
| 61 | + do val <- P.await |
| 62 | + liftIO $ BL.putStr (A.encode val) |
| 63 | + liftIO $ BL.putStr (BL.singleton $ fromIntegral (ord '\STX')) |
| 64 | + jsonConsumer |
| 65 | + |
| 66 | +parseFrames |
| 67 | + :: forall m |
| 68 | + . Monad m |
| 69 | + => P.Producer B.ByteString m () |
| 70 | + -> P.Producer (Either PAe.DecodingError WireRequest) m () |
| 71 | +parseFrames prod0 = do |
| 72 | + -- if there are no more bytes, we just return () |
| 73 | + (isEmpty, prod1) <- lift $ runStateT PB.isEndOfBytes prod0 |
| 74 | + if isEmpty then return () else go prod1 |
| 75 | + where |
| 76 | + terminatedJSON :: AB.Parser A.Value |
| 77 | + terminatedJSON = A.json' <* AB.endOfInput |
| 78 | + -- endOfInput: we want to be sure that the given |
| 79 | + -- parser consumes the entirety of the given input |
| 80 | + go :: P.Producer B.ByteString m () |
| 81 | + -> P.Producer (Either PAe.DecodingError WireRequest) m () |
| 82 | + go prod = do |
| 83 | + let splitProd :: P.Producer B.ByteString m (P.Producer B.ByteString m ()) |
| 84 | + splitProd = view (PB.break (== fromIntegral (ord '\STX'))) prod |
| 85 | + (maybeRet, leftoverProd) <- lift $ runStateT (PA.parse terminatedJSON) splitProd |
| 86 | + case maybeRet of |
| 87 | + Nothing -> return () |
| 88 | + Just ret -> do |
| 89 | + let wrappedRet :: Either PAe.DecodingError WireRequest |
| 90 | + wrappedRet = case ret of |
| 91 | + Left parseErr -> Left $ PAe.AttoparsecError parseErr |
| 92 | + Right a -> case A.fromJSON a of |
| 93 | + A.Error err -> Left $ PAe.FromJSONError err |
| 94 | + A.Success wireReq -> Right wireReq |
| 95 | + |
| 96 | + P.yield wrappedRet |
| 97 | + -- prod3 is guaranteed to be empty by the use of A8.endOfInput in ap1 |
| 98 | + newProd <- lift $ P.runEffect (leftoverProd P.>-> P.drain) |
| 99 | + -- recur into parseLines to parse the next line, drop the leading '\n' |
| 100 | + parseFrames (PB.drop (1::Int) newProd) |
48 | 101 |
|
49 | 102 | -- to help with type inference
|
50 |
| -printTest :: (MonadIO m) => Consumer' [Int] m r |
| 103 | +printTest :: (MonadIO m) => P.Consumer' [Int] m r |
51 | 104 | printTest = P.print
|
52 | 105 |
|
53 | 106 | -- ---------------------------------------------------------------------
|
|
0 commit comments