{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
module Foreign.Lua.Call
( HaskellFunction (..)
, toHsFnPrecursor
, toHsFnPrecursorWithStartIndex
, applyParameter
, returnResult
, Parameter (..)
, FunctionResult (..)
, FunctionResults
, (<#>)
, (=#>)
, (#?)
, FunctionDoc (..)
, ParameterDoc (..)
, FunctionResultDoc (..)
, render
, pushHaskellFunction
, parameter
, optionalParameter
, functionResult
) where
import Control.Monad.Except
import Data.Text (Text)
import Foreign.Lua.Core as Lua
import Foreign.Lua.Core.Types (liftLua)
import Foreign.Lua.Peek
import Foreign.Lua.Push
import Foreign.Lua.Raw.Call (hslua_pushhsfunction)
import qualified Data.Text as T
type LuaExcept a = ExceptT PeekError Lua a
data FunctionResult a
= FunctionResult
{ fnResultPusher :: Pusher a
, fnResultDoc :: FunctionResultDoc
}
type FunctionResults a = [FunctionResult a]
data Parameter a = Parameter
{ parameterPeeker :: Peeker a
, parameterDoc :: ParameterDoc
}
data HaskellFunction = HaskellFunction
{ callFunction :: Lua NumResults
, functionDoc :: Maybe FunctionDoc
}
data FunctionDoc = FunctionDoc
{ functionDescription :: Text
, parameterDocs :: [ParameterDoc]
, functionResultDocs :: [FunctionResultDoc]
}
deriving (Eq, Ord, Show)
data ParameterDoc = ParameterDoc
{ parameterName :: Text
, parameterType :: Text
, parameterDescription :: Text
, parameterIsOptional :: Bool
}
deriving (Eq, Ord, Show)
data FunctionResultDoc = FunctionResultDoc
{ functionResultType :: Text
, functionResultDescription :: Text
}
deriving (Eq, Ord, Show)
data HsFnPrecursor a = HsFnPrecursor
{ hsFnPrecursorAction :: LuaExcept a
, hsFnMaxParameterIdx :: StackIndex
, hsFnParameterDocs :: [ParameterDoc]
}
deriving (Functor)
toHsFnPrecursor :: a -> HsFnPrecursor a
toHsFnPrecursor = toHsFnPrecursorWithStartIndex (StackIndex 0)
toHsFnPrecursorWithStartIndex :: StackIndex -> a -> HsFnPrecursor a
toHsFnPrecursorWithStartIndex idx f = HsFnPrecursor
{ hsFnPrecursorAction = return f
, hsFnMaxParameterIdx = idx
, hsFnParameterDocs = mempty
}
applyParameter :: HsFnPrecursor (a -> b)
-> Parameter a
-> HsFnPrecursor b
applyParameter bldr param = do
let action = hsFnPrecursorAction bldr
let i = hsFnMaxParameterIdx bldr + 1
let context = "retrieving function argument " <>
(parameterName . parameterDoc) param
let nextAction f = withExceptT (pushMsg context) $ do
x <- ExceptT $ parameterPeeker param i
return $ f x
HsFnPrecursor
{ hsFnPrecursorAction = action >>= nextAction
, hsFnMaxParameterIdx = i
, hsFnParameterDocs = parameterDoc param : hsFnParameterDocs bldr
}
returnResults :: HsFnPrecursor a
-> FunctionResults a
-> HaskellFunction
returnResults bldr fnResults = HaskellFunction
{ callFunction = do
hsResult <- runExceptT $ hsFnPrecursorAction bldr
case hsResult of
Left err -> do
pushString $ formatPeekError err
Lua.error
Right x -> do
forM_ fnResults $ \(FunctionResult push _) -> push x
return $ NumResults (fromIntegral $ length fnResults)
, functionDoc = Just $ FunctionDoc
{ functionDescription = ""
, parameterDocs = reverse $ hsFnParameterDocs bldr
, functionResultDocs = map fnResultDoc fnResults
}
}
returnResult :: HsFnPrecursor a
-> FunctionResult a
-> HaskellFunction
returnResult bldr = returnResults bldr . (:[])
updateFunctionDescription :: HaskellFunction -> Text -> HaskellFunction
updateFunctionDescription fn desc =
case functionDoc fn of
Nothing -> fn
Just fnDoc ->
fn { functionDoc = Just $ fnDoc { functionDescription = desc} }
infixl 8 <#>, =#>, #?
(<#>) :: HsFnPrecursor (a -> b)
-> Parameter a
-> HsFnPrecursor b
(<#>) = applyParameter
(=#>) :: HsFnPrecursor a
-> FunctionResults a
-> HaskellFunction
(=#>) = returnResults
(#?) :: HaskellFunction -> Text -> HaskellFunction
(#?) = updateFunctionDescription
render :: FunctionDoc -> Text
render (FunctionDoc desc paramDocs resultDoc) =
(if T.null desc then "" else desc <> "\n\n") <>
renderParamDocs paramDocs <>
case resultDoc of
[] -> ""
rd -> "\nReturns:\n\n" <> T.intercalate "\n" (map renderResultDoc rd)
renderParamDocs :: [ParameterDoc] -> Text
renderParamDocs pds = "Parameters:\n\n" <>
T.intercalate "\n" (map renderParamDoc pds)
renderParamDoc :: ParameterDoc -> Text
renderParamDoc pd = mconcat
[ parameterName pd
, "\n: "
, parameterDescription pd
, " (", parameterType pd, ")\n"
]
renderResultDoc :: FunctionResultDoc -> Text
renderResultDoc rd = mconcat
[ " - "
, functionResultDescription rd
, " (", functionResultType rd, ")\n"
]
pushHaskellFunction :: HaskellFunction -> Lua ()
pushHaskellFunction fn = do
errConv <- Lua.errorConversion
let hsFn = flip (runWithConverter errConv) $ callFunction fn
liftLua $ \l -> hslua_pushhsfunction l hsFn
parameter :: Peeker a
-> Text
-> Text
-> Text
-> Parameter a
parameter peeker type_ name desc = Parameter
{ parameterPeeker = peeker
, parameterDoc = ParameterDoc
{ parameterName = name
, parameterDescription = desc
, parameterType = type_
, parameterIsOptional = False
}
}
optionalParameter :: Peeker a
-> Text
-> Text
-> Text
-> Parameter (Maybe a)
optionalParameter peeker type_ name desc = Parameter
{ parameterPeeker = optional peeker
, parameterDoc = ParameterDoc
{ parameterName = name
, parameterDescription = desc
, parameterType = type_
, parameterIsOptional = True
}
}
functionResult :: Pusher a
-> Text
-> Text
-> FunctionResults a
functionResult pusher type_ desc = (:[]) $ FunctionResult
{ fnResultPusher = pusher
, fnResultDoc = FunctionResultDoc
{ functionResultType = type_
, functionResultDescription = desc
}
}