{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
#if !MIN_VERSION_base(4,8,0)
{-# LANGUAGE OverlappingInstances #-}
#endif
module Foreign.Lua.Types.FromLuaStack
( FromLuaStack (..)
, Result
, peekEither
, pairsFromTable
, toList
) where
import Data.ByteString (ByteString)
import Data.Map (Map, fromList)
import Data.Monoid ((<>))
import Foreign.Lua.Api
import Foreign.Lua.Types.Lua
import Foreign.Lua.Types.Error
import Foreign.Ptr (Ptr)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.ByteString.Lazy as BL
type Result a = Either String a
typeChecked :: String
-> (StackIndex -> Lua Bool)
-> (StackIndex -> Lua a)
-> StackIndex
-> Lua a
typeChecked expectedType test peekfn n = do
v <- test n
if v
then peekfn n
else do
actual <- ltype n >>= typename
throwLuaError $ "Expected a " <> expectedType <> " but got a " <> actual
class FromLuaStack a where
peek :: StackIndex -> Lua a
peekEither :: FromLuaStack a => StackIndex -> Lua (Either String a)
peekEither idx = catchLuaError (return <$> peek idx) (return . Left . show)
instance FromLuaStack () where
peek = typeChecked "nil" isnil (const $ return ())
instance FromLuaStack LuaInteger where
peek = typeChecked "number" isnumber tointeger
instance FromLuaStack LuaNumber where
peek = typeChecked "number" isnumber tonumber
instance FromLuaStack ByteString where
peek = typeChecked "string" isstring tostring
instance FromLuaStack Bool where
peek = typeChecked "boolean" isboolean toboolean
instance FromLuaStack CFunction where
peek = typeChecked "C function" iscfunction tocfunction
instance FromLuaStack (Ptr a) where
peek = typeChecked "user data" isuserdata touserdata
instance FromLuaStack LuaState where
peek = typeChecked "LuaState (i.e., a thread)" isthread tothread
instance FromLuaStack T.Text where
peek = fmap T.decodeUtf8 . peek
instance FromLuaStack BL.ByteString where
peek = fmap BL.fromStrict . peek
#if MIN_VERSION_base(4,8,0)
instance {-# OVERLAPS #-} FromLuaStack [Char] where
#else
instance FromLuaStack String where
#endif
peek = fmap T.unpack . peek
instance FromLuaStack a => FromLuaStack [a] where
peek = typeChecked "table" istable toList
instance (Ord a, FromLuaStack a, FromLuaStack b) => FromLuaStack (Map a b) where
peek idx = fromList <$> pairsFromTable idx
toList :: FromLuaStack a => StackIndex -> Lua [a]
toList n = resetStackOnError ("Could not read list: " ++) $
go . enumFromTo 1 =<< rawlen n
where
go [] = return []
go (i : is) = do
ret <- rawgeti n i *> peek (-1) <* pop 1
(ret:) <$> go is
pairsFromTable :: (FromLuaStack a, FromLuaStack b) => StackIndex -> Lua [(a, b)]
pairsFromTable idx =
resetStackOnError ("Could not read key-value pairs: " ++) $ do
pushnil
remainingPairs
where
remainingPairs = do
res <- nextPair (if idx < 0 then idx - 1 else idx)
case res of
Nothing -> return []
Just a -> (a:) <$> remainingPairs
nextPair :: (FromLuaStack a, FromLuaStack b)
=> StackIndex -> Lua (Maybe (a, b))
nextPair idx = do
hasNext <- next idx
if hasNext
then do
v <- peek (-1)
k <- peek (-2)
pop 1
return (Just (k, v))
else return Nothing
resetStackOnError :: (String -> String) -> Lua a -> Lua a
resetStackOnError modifier op = do
oldTop <- gettop
op `catchLuaError` \(LuaException msg) -> do
settop oldTop
throwLuaError (modifier msg)
instance (FromLuaStack a, FromLuaStack b) => FromLuaStack (a, b) where
peek idx = do
a <- rawgeti idx 1 *> peek (-1) <* pop 1
b <- rawgeti idx 2 *> peek (-1) <* pop 1
return (a, b)
instance (FromLuaStack a, FromLuaStack b, FromLuaStack c) =>
FromLuaStack (a, b, c)
where
peek idx = do
pushvalue idx
a <- getTableIndex 1
b <- getTableIndex 2
c <- getTableIndex 3
pop 4
return (a, b, c)
instance (FromLuaStack a, FromLuaStack b, FromLuaStack c, FromLuaStack d) =>
FromLuaStack (a, b, c, d)
where
peek idx = do
pushvalue idx
a <- getTableIndex 1
b <- getTableIndex 2
c <- getTableIndex 3
d <- getTableIndex 4
pop 5
return (a, b, c, d)
instance (FromLuaStack a, FromLuaStack b, FromLuaStack c,
FromLuaStack d, FromLuaStack e) =>
FromLuaStack (a, b, c, d, e)
where
peek idx = do
pushvalue idx
a <- getTableIndex 1
b <- getTableIndex 2
c <- getTableIndex 3
d <- getTableIndex 4
e <- getTableIndex 5
pop 6
return (a, b, c, d, e)
instance (FromLuaStack a, FromLuaStack b, FromLuaStack c,
FromLuaStack d, FromLuaStack e, FromLuaStack f) =>
FromLuaStack (a, b, c, d, e, f)
where
peek idx = do
pushvalue idx
a <- getTableIndex 1
b <- getTableIndex 2
c <- getTableIndex 3
d <- getTableIndex 4
e <- getTableIndex 5
f <- getTableIndex 6
pop 7
return (a, b, c, d, e, f)
instance (FromLuaStack a, FromLuaStack b, FromLuaStack c, FromLuaStack d,
FromLuaStack e, FromLuaStack f, FromLuaStack g) =>
FromLuaStack (a, b, c, d, e, f, g)
where
peek idx = do
pushvalue idx
a <- getTableIndex 1
b <- getTableIndex 2
c <- getTableIndex 3
d <- getTableIndex 4
e <- getTableIndex 5
f <- getTableIndex 6
g <- getTableIndex 7
pop 8
return (a, b, c, d, e, f, g)
instance (FromLuaStack a, FromLuaStack b, FromLuaStack c, FromLuaStack d,
FromLuaStack e, FromLuaStack f, FromLuaStack g, FromLuaStack h) =>
FromLuaStack (a, b, c, d, e, f, g, h)
where
peek idx = do
pushvalue idx
a <- getTableIndex 1
b <- getTableIndex 2
c <- getTableIndex 3
d <- getTableIndex 4
e <- getTableIndex 5
f <- getTableIndex 6
g <- getTableIndex 7
h <- getTableIndex 8
pop 9
return (a, b, c, d, e, f, g, h)
getTableIndex :: FromLuaStack b => Int -> Lua b
getTableIndex key = rawgeti (StackIndex (- (fromIntegral key))) key *> peek (-1)