sh data by wrapping it into a userdata object, using the object at the
-- top of the stack after performing the given operation as metatable.
pushAnyWithMetatable :: Lua ()       -- ^ operation to push the metatable
                     -> a            -- ^ object to push to Lua.
                     -> Lua ()
pushAnyWithMetatable mtOp x = do
  xPtr <- Lua.liftIO (StablePtr.newStablePtr x)
  udPtr <- Lua.newuserdata (Storable.sizeOf xPtr)
  Lua.liftIO $ Storable.poke (Ptr.castPtr udPtr) xPtr
  mtOp
  Lua.setmetatable (Lua.nthFromTop 2)
  return ()

-- | Push the metatable used to define the behavior of the given value in Lua.
-- The table will be created if it doesn't exist yet.
ensure
pushAnyWithMetatable :: Lua ()       -- ^ operation to push the metatable
                     -> a            -- ^ object to push to Lua.
                     -> Lua ()
pushAnyWithMetatable mtOp x = do
  xPtr <- Lua.liftIO (StablePtr.newStablePtr x)
  udPtr <- Lua.newuserdata (Storable.sizeOf xPtr)
  Lua.liftIO $ Storable.poke (Ptr.castPtr udPtr) xPtr
  mtOp
  Lua.setmetatable (Lua.nthFromTop 2)
  return ()

-- | Push the metatable used to define the behavior of the given value in Lua.
-- The table will be created if it doesn't exist yet.
pushAnyWithMetatable mtOp x = do
  xPtr <- Lua.liftIO (StablePtr.newStablePtr x)
  udPtr <- Lua.newuserdata (Storable.sizeOf xPtr)
  Lua.liftIO $ Storable.poke (Ptr.castPtr udPtr) xPtr
  mtOp
  Lua.setmetatable (Lua.nthFromTop 2)
  return ()

-- | Push the metatable used to define the behavior of the given value in Lua.
-- The table will be created if it doesn't exist yet.