Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit cefcceb

Browse files
committed
Expect json input separated by STX, fix #43
1 parent cbc4c1c commit cefcceb

File tree

2 files changed

+85
-31
lines changed

2 files changed

+85
-31
lines changed

haskell-ide-engine.cabal

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ library
2525
Haskell.Ide.Engine.Transport.JsonStdio
2626
Haskell.Ide.Engine.Types
2727
other-modules: Paths_haskell_ide_engine
28-
build-depends: base >= 4.7 && < 5
29-
, Cabal >= 1.22
28+
build-depends: Cabal >= 1.22
3029
, aeson
3130
, attoparsec
31+
, base >= 4.7 && < 5
3232
, bytestring
3333
, containers
3434
, directory
@@ -46,6 +46,7 @@ library
4646
, optparse-simple >= 0.0.3
4747
, pipes
4848
, pipes-aeson
49+
, pipes-attoparsec >= 0.5
4950
, pipes-bytestring
5051
, pipes-parse
5152
, servant-server

src/Haskell/Ide/Engine/Transport/JsonStdio.hs

+82-29
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,106 @@
11
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE ScopedTypeVariables #-}
3+
{-# LANGUAGE LambdaCase #-}
24
-- |Provide a protocol adapter/transport for JSON over stdio
35

46
module Haskell.Ide.Engine.Transport.JsonStdio where
57

68
import Control.Concurrent
9+
import Control.Lens (view)
710
import Control.Logging
11+
import Control.Monad.State.Strict
812
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
917
import qualified Data.Map as Map
18+
import qualified Data.Text as T
1019
import Haskell.Ide.Engine.PluginDescriptor
1120
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
1525
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
2026
import System.IO
2127

2228
-- TODO: Can pass in a handle, then it is general
2329
jsonStdioTransport :: Chan ChannelRequest -> IO ()
2430
jsonStdioTransport cin = do
2531
cout <- newChan :: IO (Chan ChannelResponse)
2632
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)
48101

49102
-- to help with type inference
50-
printTest :: (MonadIO m) => Consumer' [Int] m r
103+
printTest :: (MonadIO m) => P.Consumer' [Int] m r
51104
printTest = P.print
52105

53106
-- ---------------------------------------------------------------------

0 commit comments

Comments
 (0)