capnp-0.12.0.0: examples/lib/Examples/Rpc/CalculatorServer.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
module Examples.Rpc.CalculatorServer (main) where
import Prelude hiding (subtract)
import Data.Int
import Control.Concurrent.STM (STM, atomically)
import Control.Monad (when)
import Data.Function ((&))
import Data.Functor ((<&>))
import Network.Simple.TCP (serve)
import Supervisors (Supervisor)
import qualified Data.Vector as V
import Capnp.New
( Client
, Pipeline
, SomeServer
, callP
, def
, defaultLimit
, export
, getField
, handleParsed
, waitPipeline
)
import Capnp.Rpc
(ConnConfig(..), handleConn, socketTransport, throwFailed, toClient)
import Capnp.Gen.Calculator.New
newtype LitValue = LitValue Double
instance SomeServer LitValue
instance Value'server_ LitValue where
value'read (LitValue val) = handleParsed $ \_ ->
pure Value'read'results { value = val }
newtype OpFunc = OpFunc (Double -> Double -> Double)
instance SomeServer OpFunc
instance Function'server_ OpFunc where
function'call (OpFunc op) = handleParsed $ \Function'call'params{params} -> do
when (V.length params /= 2) $
throwFailed "Wrong number of parameters."
pure Function'call'results
{ value = (params V.! 0) `op` (params V.! 1) }
data ExprFunc = ExprFunc
{ paramCount :: !Int32
, body :: Parsed Expression
}
instance SomeServer ExprFunc
instance Function'server_ ExprFunc where
function'call ExprFunc{..} =
handleParsed $ \Function'call'params{params} -> do
when (fromIntegral (V.length params) /= paramCount) $
throwFailed "Wrong number of parameters."
eval params body
>>= waitResult
<&> Function'call'results
data MyCalc = MyCalc
{ add :: Client Function
, subtract :: Client Function
, multiply :: Client Function
, divide :: Client Function
, sup :: Supervisor
}
instance SomeServer MyCalc
instance Calculator'server_ MyCalc where
calculator'evaluate MyCalc{sup} =
handleParsed $ \Calculator'evaluate'params{expression} -> do
eval V.empty expression
>>= waitResult
>>= export @Value sup . LitValue
<&> Calculator'evaluate'results
calculator'getOperator MyCalc{..} =
handleParsed $ \Calculator'getOperator'params{op} ->
Calculator'getOperator'results <$> case op of
Operator'add -> pure add
Operator'subtract -> pure subtract
Operator'multiply -> pure multiply
Operator'divide -> pure divide
Operator'unknown' _ ->
throwFailed "Unknown operator"
calculator'defFunction MyCalc{sup} =
handleParsed $ \Calculator'defFunction'params{..} ->
Calculator'defFunction'results <$>
atomically (export @Function sup ExprFunc{..})
newCalculator :: Supervisor -> STM (Client Calculator)
newCalculator sup = do
add <- export @Function sup $ OpFunc (+)
subtract <- export @Function sup $ OpFunc (-)
multiply <- export @Function sup $ OpFunc (*)
divide <- export @Function sup $ OpFunc (/)
export @Calculator sup MyCalc{..}
data EvalResult
= Immediate Double
| CallResult (Pipeline Function'call'results)
| ReadResult (Pipeline Value'read'results)
waitResult :: EvalResult -> IO Double
waitResult (Immediate v) = pure v
waitResult (CallResult p) = getField #value <$> waitPipeline p
waitResult (ReadResult p) = getField #value <$> waitPipeline p
eval :: V.Vector Double -> Parsed Expression -> IO EvalResult
eval outerParams (Expression exp) = go outerParams exp where
go _ (Expression'literal lit) =
pure $ Immediate lit
go _ (Expression'previousResult val) = do
val
& callP #read def
<&> ReadResult
go args (Expression'parameter idx)
| fromIntegral idx >= V.length args =
throwFailed "Parameter index out of bounds"
| otherwise =
pure $ Immediate $ args V.! fromIntegral idx
go outerParams (Expression'call Expression'call'{function, params=innerParams}) = do
argPipelines <- traverse (eval outerParams) innerParams
argValues <- traverse waitResult argPipelines
function
& callP #call Function'call'params { params = argValues }
<&> CallResult
go _ (Expression'unknown' _) =
throwFailed "Unknown expression type"
main :: IO ()
main = serve "localhost" "4000" $ \(sock, _addr) ->
handleConn (socketTransport sock defaultLimit) def
{ getBootstrap = fmap (Just . toClient) . newCalculator
, debugMode = True
}