Skip to content

Commit

Permalink
Add std::optional integration (vinniefalco#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanfrings authored May 14, 2021
1 parent 723dc5b commit 6c1d0a1
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 4 deletions.
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ project (LuaBridge)

include (CMakeDependentOption)

set (CMAKE_CXX_STANDARD 11)
option (LUABRIDGE_CXX17 "Use C++17 standard if supported by compiler" OFF)

if (LUABRIDGE_CXX17)
set (CMAKE_CXX_STANDARD 17)
else ()
set (CMAKE_CXX_STANDARD 11)
endif ()

set (CMAKE_CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)

cmake_dependent_option (LUABRIDGE_TESTING "Build tests" ON
"CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF)
Expand Down
6 changes: 3 additions & 3 deletions Manual.html
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ <h2>1.1 - <span id="s1.1">Design</span></h2>
<li>Global variables (variables must be wrapped in a named scope).
<li>Automatic conversion between STL container types and Lua tables
(conversion can be enabled for <code>std::list</code>, <code>std::vector</code>,
<code>std::map</code> or <code>std::unordered_map</code> by including
<code>List.h</code>, <code>Vector.h</code>, <code>Map</code> or
<code>UnorderedMap.h</code> respectively)
<code>std::map</code>, <code>std::unordered_map</code> or <code>std::optional</code>
by including <code>List.h</code>, <code>Vector.h</code>, <code>Map.h</code>,
<code>UnorderedMap.h</code> or <code>Optional.h</code> respectively)
<li>Inheriting Lua classes from C++ classes.
<li>Passing nil to a C++ function that expects a pointer or reference.
<li>Standard containers like <code>std::shared_ptr</code>.
Expand Down
46 changes: 46 additions & 0 deletions Source/LuaBridge/Optional.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// https://github.com/vinniefalco/LuaBridge
// Copyright 2021, Stefan Frings
// SPDX-License-Identifier: MIT

#pragma once

#include <LuaBridge/detail/Stack.h>

#include <optional>

namespace luabridge {

template<class T>
struct Stack<std::optional<T>>
{
static void push(lua_State* L, std::optional<T> const& optional)
{
if (optional)
{
Stack<T>::push(L, *optional);
}
else
{
lua_pushnil(L);
}
}

static std::optional<T> get(lua_State* L, int index)
{
if (lua_isnil(L, index))
{
lua_pop(L, 1);

return std::nullopt;
}

return Stack<T>::get(L, index);
}

static bool isInstance(lua_State* L, int index)
{
return lua_isnil(L, index) || Stack<T>::isInstance(L, index);
}
};

} // namespace luabridge
6 changes: 6 additions & 0 deletions Tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ set (LUABRIDGE_TEST_SOURCE_FILES
Source/VectorTests.cpp
)

if (LUABRIDGE_CXX17)
set (LUABRIDGE_TEST_SOURCE_FILES
${LUABRIDGE_TEST_SOURCE_FILES}
Source/OptionalTests.cpp)
endif ()

source_group ("Source" FILES ${LUABRIDGE_TEST_SOURCE_FILES})

set (LUABRIDGE_TEST_JUICE_FILES
Expand Down
209 changes: 209 additions & 0 deletions Tests/Source/OptionalTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// https://github.com/vinniefalco/LuaBridge
// Copyright 2021, Stefan Frings
// SPDX-License-Identifier: MIT

#include "TestBase.h"
#include "TestTypes.h"

#include "LuaBridge/Optional.h"

#include <optional>

template<class T>
struct OptionalTest : TestBase
{
};

TYPED_TEST_CASE_P(OptionalTest);

template<typename T>
std::string toLuaSrcString(T const& value)
{
return std::to_string(value);
}

template<>
std::string toLuaSrcString(bool const& value)
{
return value ? "true" : "false";
}

template<>
std::string toLuaSrcString(char const& value)
{
return "'" + std::string(&value, 1) + "'";
}

template<>
std::string toLuaSrcString(std::string const& value)
{
return "'" + value + "'";
}

template<typename T>
std::optional<T> optCast(luabridge::LuaRef const& ref)
{
// NOTE cast to std::optional: https://stackoverflow.com/a/45865802
return ref.cast<std::optional<T>>();
}

TYPED_TEST_P(OptionalTest, LuaRefPresent)
{
using Traits = TypeTraits<TypeParam>;

for (TypeParam const& value : Traits::values())
{
std::string const luaSrc = "result = " + toLuaSrcString(value);

SCOPED_TRACE(luaSrc);
this->runLua(luaSrc);

std::optional<TypeParam> const actual = optCast<TypeParam>(this->result());
ASSERT_TRUE(actual.has_value());
ASSERT_EQ(value, actual.value());
}
}

TYPED_TEST_P(OptionalTest, LuaRefNotPresent)
{
this->runLua("result = nil");

std::optional<TypeParam> const actual = optCast<TypeParam>(this->result());
ASSERT_FALSE(actual.has_value());
}

TYPED_TEST_P(OptionalTest, LuaRefIsInstancePresent)
{
using Traits = TypeTraits<TypeParam>;

for (TypeParam const& value : Traits::values())
{
std::string const luaSrc = "result = " + toLuaSrcString(value);

SCOPED_TRACE(luaSrc);
this->runLua(luaSrc);

luabridge::LuaRef const actualRef = this->result();
ASSERT_TRUE(actualRef.isInstance<std::optional<TypeParam>>());

// if isInstance returns true a cast without issues is possible
std::optional<TypeParam> const actual = optCast<TypeParam>(actualRef);
}
}

TYPED_TEST_P(OptionalTest, LuaRefIsInstancePresentWrongType)
{
this->runLua("function func() end; result = func");

luabridge::LuaRef const actualRef = this->result();
ASSERT_FALSE(actualRef.isInstance<std::optional<TypeParam>>());
}

TYPED_TEST_P(OptionalTest, LuaRefIsInstanceNotPresent)
{
this->runLua("result = nil");

luabridge::LuaRef const actualRef = this->result();
ASSERT_TRUE(actualRef.isInstance<std::optional<TypeParam>>());

// if isInstance returns true a cast without issues is possible
std::optional<TypeParam> const actual = optCast<TypeParam>(actualRef);
}

REGISTER_TYPED_TEST_CASE_P(OptionalTest,
LuaRefPresent,
LuaRefNotPresent,
LuaRefIsInstancePresent,
LuaRefIsInstancePresentWrongType,
LuaRefIsInstanceNotPresent);

INSTANTIATE_TYPED_TEST_CASE_P(OptionalTest, OptionalTest, TestTypes);

namespace {

struct Data
{
explicit Data(int i) : i(i) {}

int i;
};

bool operator==(Data const& lhs, Data const& rhs)
{
return lhs.i == rhs.i;
}

template<typename T>
bool operator==(std::optional<T> const& lhs, std::optional<T> const& rhs)
{
if (lhs.has_value() != rhs.has_value())
{
return false;
}

if (lhs.has_value())
{
assert(rhs.has_value());
return lhs.value() == rhs.value();
}

assert(!lhs.has_value());
assert(!rhs.has_value());
return true;
}

std::optional<Data> processValue(std::optional<Data> const& data)
{
return data;
}

std::optional<Data> processPointer(std::optional<const Data*> const& data)
{
std::optional<Data> result;
if (data)
{
result = **data;
}
return result;
}

} // namespace

struct OptionalTests : TestBase
{
};

template<typename T>
void testPassFromLua(OptionalTests const& fixture,
std::string const& functionName,
std::string const& valueString,
std::optional<T> const expected)
{
fixture.resetResult();

std::string const luaSrc = "result = " + functionName + "(" + valueString + ")";

SCOPED_TRACE(luaSrc);
fixture.runLua(luaSrc);

std::optional<T> const actual = optCast<T>(fixture.result());
ASSERT_EQ(expected, actual);
}

TEST_F(OptionalTests, PassFromLua)
{
luabridge::getGlobalNamespace(L)
.beginClass<Data>("Data")
.addConstructor<void (*)(int)>()
.endClass()
.addFunction("processValue", &processValue)
.addFunction("processPointer", &processPointer);

testPassFromLua<Data>(*this, "processValue", "Data(-1)", Data(-1));
testPassFromLua<Data>(*this, "processValue", "Data(2)", Data(2));
testPassFromLua<Data>(*this, "processValue", "nil", std::nullopt);

testPassFromLua<Data>(*this, "processPointer", "Data(-1)", Data(-1));
testPassFromLua<Data>(*this, "processPointer", "Data(2)", Data(2));
testPassFromLua<Data>(*this, "processPointer", "nil", std::nullopt);
}

0 comments on commit 6c1d0a1

Please sign in to comment.