Skip to content

Commit

Permalink
Add function tonumberx and tointegerx
Browse files Browse the repository at this point in the history
Those functions are part of the 5.2 and 5.3 API and can help to avoid an
additional call to the API.
  • Loading branch information
tarleb committed Aug 12, 2017
1 parent 3962703 commit ab50806
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 18 deletions.
48 changes: 42 additions & 6 deletions src/Foreign/Lua/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ module Foreign.Lua.Api (
, toboolean
, tocfunction
, tointeger
, tointegerx
, tonumber
, tonumberx
, topointer
, tostring
, tothread
Expand Down Expand Up @@ -175,7 +177,7 @@ import Foreign.Lua.Api.RawBindings
import Foreign.Lua.Api.Types
import Foreign.Lua.Types.Error
import Foreign.Lua.Types.Lua
import Foreign.Marshal.Alloc
import Foreign.Marshal.Alloc (alloca)
import Foreign.Ptr

import qualified Data.ByteString as B
Expand Down Expand Up @@ -1045,23 +1047,57 @@ tocfunction n = liftLua $ \l -> lua_tocfunction l n
-- <https://www.lua.org/manual/5.3/manual.html#lua_tointeger lua_tointeger>.
tointeger :: StackIndex -> Lua LuaInteger
#if LUA_VERSION_NUMBER >= 502
tointeger n = liftLua $ \l -> lua_tointegerx l n 0
tointeger n = liftLua $ \l -> lua_tointegerx l n nullPtr
#else
tointeger n = liftLua $ \l -> lua_tointeger l n
#endif

-- | Like @'tointeger'@, but returns @Nothing@ if the conversion failed
tointegerx :: StackIndex -> Lua (Maybe LuaInteger)
#if LUA_VERSION_NUMBER >= 502
tointegerx n = liftLua $ \l -> alloca $ \bptr -> do
res <- lua_tointegerx l n bptr
isNum <- fromLuaBool <$> F.peek bptr
if isNum
then return $ Just res
else return $ Nothing
#else
tointegerx n = do
isNum <- isnumber n
if isNum
then Just <$> tointeger n
else return Nothing
#endif

-- | Converts the Lua value at the given index to the C type lua_Number. The Lua
-- value must be a number or a string convertible to a number; otherwise,
-- @tonumber@ returns 0.
--
-- See <https://www.lua.org/manual/5.3/manual.html#lua_tonumber lua_tonumber>.
tonumber :: StackIndex -> Lua LuaNumber
#if LUA_VERSION_NUMBER >= 502
tonumber n = liftLua $ \l -> lua_tonumberx l n 0
tonumber n = liftLua $ \l -> lua_tonumberx l n nullPtr
#else
tonumber n = liftLua $ \l -> lua_tonumber l n
#endif

-- | Like @'tonumber'@, but returns @Nothing@ if the conversion failed
tonumberx :: StackIndex -> Lua (Maybe LuaNumber)
#if LUA_VERSION_NUMBER >= 502
tonumberx n = liftLua $ \l -> alloca $ \bptr -> do
res <- lua_tonumberx l n bptr
isNum <- fromLuaBool <$> F.peek bptr
if isNum
then return $ Just res
else return $ Nothing
#else
tonumberx n = do
isNum <- isnumber n
if isNum
then Just <$> tonumber n
else return Nothing
#endif

-- | Converts the value at the given index to a generic C pointer (void*). The
-- value can be a userdata, a table, a thread, or a function; otherwise,
-- lua_topointer returns NULL. Different objects will give different pointers.
Expand All @@ -1077,9 +1113,9 @@ topointer n = liftLua $ \l -> lua_topointer l n
-- | See <https://www.lua.org/manual/5.3/manual.html#lua_tostring lua_tostring>.
tostring :: StackIndex -> Lua B.ByteString
tostring n = liftLua $ \l -> alloca $ \lenPtr -> do
cstr <- lua_tolstring l n lenPtr
cstrLen <- F.peek lenPtr
B.packCStringLen (cstr, fromIntegral cstrLen)
cstr <- lua_tolstring l n lenPtr
cstrLen <- F.peek lenPtr
B.packCStringLen (cstr, fromIntegral cstrLen)

-- | Converts the value at the given index to a Lua thread (represented as
-- lua_State*). This value must be a thread; otherwise, the function returns
Expand Down
4 changes: 2 additions & 2 deletions src/Foreign/Lua/Api/RawBindings.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ foreign import ccall unsafe "lua.h lua_tocfunction"
#if LUA_VERSION_NUMBER >= 502
-- | See <https://www.lua.org/manual/5.3/manual.html#lua_tointegerx lua_tointegerx>
foreign import ccall unsafe "lua.h lua_tointegerx"
lua_tointegerx :: LuaState -> StackIndex -> CInt -> IO LuaInteger
lua_tointegerx :: LuaState -> StackIndex -> Ptr LuaBool -> IO LuaInteger

-- | See <https://www.lua.org/manual/5.3/manual.html#lua_tonumberx lua_tonumberx>
foreign import ccall unsafe "lua.h lua_tonumberx"
lua_tonumberx :: LuaState -> StackIndex -> CInt -> IO LuaNumber
lua_tonumberx :: LuaState -> StackIndex -> Ptr LuaBool -> IO LuaNumber
#else
-- | See <https://www.lua.org/manual/5.1/manual.html#lua_tointeger lua_tointeger>
foreign import ccall unsafe "lua.h lua_tointeger"
Expand Down
4 changes: 3 additions & 1 deletion src/Foreign/Lua/Api/Types.hsc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import Prelude hiding (EQ, LT)
import Data.Int (#{type LUA_INTEGER})
import Foreign.C (CInt)
import Foreign.Ptr (FunPtr, Ptr)
import Foreign.Storable (Storable)

#include "lua.h"

Expand Down Expand Up @@ -94,7 +95,8 @@ type LuaNumber = #{type LUA_NUMBER}

-- | Boolean value returned by a Lua C API function. This is a @'CInt'@ and
-- interpreted as @'False'@ iff the value is @0@, @'True'@ otherwise.
newtype LuaBool = LuaBool CInt deriving Eq
newtype LuaBool = LuaBool CInt
deriving (Eq, Storable)

-- | Convert a @'LuaBool'@ to a Haskell @'Bool'@.
fromLuaBool :: LuaBool -> Bool
Expand Down
57 changes: 48 additions & 9 deletions test/Foreign/Lua/ApiTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,54 @@ tests = testGroup "Haskell version of the C API"
slen <- strlen (-1)
return $ rlen == olen && rlen == slen && rlen == 6

, luaTestCase "isfunction" $ do
pushLuaExpr "function () print \"hi!\" end"
isfunction (-1)

, luaTestCase "isnil" $ pushLuaExpr "nil" *> isnil (-1)
, luaTestCase "isnone" $ isnone 500 -- stack index 500 does not exist
, luaTestCase "isnoneornil" $ do
pushLuaExpr "nil"
(&&) <$> isnoneornil 500 <*> isnoneornil (-1)
, testGroup "Type checking"
[ luaTestCase "isfunction" $ do
pushLuaExpr "function () print \"hi!\" end"
isfunction (-1)

, luaTestCase "isnil" $ pushLuaExpr "nil" *> isnil (-1)

, luaTestCase "isnone" $ isnone 500 -- stack index 500 does not exist

, luaTestCase "isnoneornil" $ do
pushLuaExpr "nil"
(&&) <$> isnoneornil 500 <*> isnoneornil (-1)
]

, testGroup "getting values"
[ testCase "tointegerx returns numbers verbatim" . runLua $ do
pushLuaExpr "149"
res <- tointegerx (-1)
liftIO $ assertEqual "Not the correct number" (Just 149) res

, testCase "tointegerx accepts strings coercible to integers" . runLua $ do
pushLuaExpr "'451'"
res <- tointegerx (-1)
liftIO $ assertEqual "Not the correct number" (Just 451) res

, testCase "tointegerx returns Nothing for non-integer numbers" . runLua $ do
pushLuaExpr "4.5"
res <- tointegerx (-1)
liftIO $ assertEqual "Not the correct number" Nothing res

, testCase "tointegerx returns Nothing when given a boolean" . runLua $ do
pushLuaExpr "true"
liftIO . assertEqual "Not the correct number" Nothing =<< tointegerx (-1)

, testCase "tonumberx returns numbers verbatim" . runLua $ do
pushLuaExpr "14.9"
res <- tonumberx (-1)
liftIO $ assertEqual "Not the correct number" (Just 14.9) res

, testCase "tonumberx accepts strings as numbers" . runLua $ do
pushLuaExpr "'42.23'"
res <- tonumberx (-1)
liftIO $ assertEqual "Not the correct number" (Just 42.23) res

, testCase "tonumberx returns Nothing when given a boolean" . runLua $ do
pushLuaExpr "true"
liftIO . assertEqual "Not the correct number" Nothing =<< tonumberx (-1)
]

, luaTestCase "setting and getting a global works" $ do
pushLuaExpr "{'Moin', Hello = 'World'}"
Expand Down

0 comments on commit ab50806

Please sign in to comment.