Files
Ian Denhardt 33f1126948 Add SomeServer base class to new API.
Analogue of Server in the old API (the name for which is taken by the
type constraint alias in Export).
2021-08-05 18:07:18 -04:00

156 lines
4.9 KiB
Haskell

{-# 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
}