diff --git a/docs/source/api/api-top.rst b/docs/source/api/api-top.rst index d2951ea0..9443ef85 100644 --- a/docs/source/api/api-top.rst +++ b/docs/source/api/api-top.rst @@ -26,6 +26,7 @@ Browse the various function and classes :doc:`Sol<../index>` utilizes to make yo optional state table + metatable_key this_state thread tie diff --git a/docs/source/api/metatable_key.rst b/docs/source/api/metatable_key.rst new file mode 100644 index 00000000..901e66cc --- /dev/null +++ b/docs/source/api/metatable_key.rst @@ -0,0 +1,45 @@ +metatable_key +============= +A key for setting and getting an object's metatable +--------------------------------------------------- + +.. code-block:: cpp + + struct metatable_key_t {}; + const metatable_key_t metatable_key; + +You can use this in conjunction with :doc:`sol::table` to set/get a metatable. Lua metatables are powerful ways to override default behavior of objects for various kinds of operators, among other things. Here is an entirely complete example, showing getting and working with a :doc:`usertype`'s metatable defined by Sol: + +.. code-block:: cpp + :caption: messing with metatables + :linenos: + + #include + + int main () { + + struct bark { + int operator()(int x) { + return x; + } + }; + + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_usertype("bark", + sol::meta_function::call_function, &bark::operator() + ); + + bark b; + lua.set("b", &b); + + sol::table b_as_table = lua["b"]; + sol::table b_metatable = b_as_table[sol::metatable_key]; + sol::function b_call = b_metatable["__call"]; + sol::function b_as_function = lua["b"]; + + int result1 = b_as_function(1); + int result2 = b_call(b, 1); + // result1 == result2 == 1 + } \ No newline at end of file diff --git a/docs/source/api/object.rst b/docs/source/api/object.rst index bdcc829e..9c8f6cf5 100644 --- a/docs/source/api/object.rst +++ b/docs/source/api/object.rst @@ -49,4 +49,23 @@ non-members bool operator!=(const object& lhs, const nil_t&); bool operator!=(const nil_t&, const object& rhs); -These allow a person to compare an ``sol::object`` against :ref:`nil`, which essentially checks if an object references a non-nil value, like so: \ No newline at end of file +These allow a person to compare an ``sol::object`` against :ref:`nil`, which essentially checks if an object references a non-nil value, like so: + +.. code-block:: cpp + + if (myobj == sol::nil) { + // doesn't have anything... + } + +Use this to check objects. + +.. code-block:: cpp + :caption: function: make object + :name: make-object + + template + object make_object(lua_State* L, T&& value); + template + object make_object(lua_State* L, Args&&... args); + +Makes an object out of the value. It pushes it onto the stack, then pops it into the returned ``sol::object``. \ No newline at end of file diff --git a/docs/source/api/reference.rst b/docs/source/api/reference.rst index f79d529e..3bd31386 100644 --- a/docs/source/api/reference.rst +++ b/docs/source/api/reference.rst @@ -43,7 +43,7 @@ The value of the reference in the registry. bool valid () const noexcept; explicit operator bool () const noexcept; -These functions check if the reference at ``T`` is valid: that is, if it is not :doc:`nil` and if it is not non-existing (doesn't refer to anything, including nil) reference. The explicit operator bool allows you to use it in the context of an ``if ( my_obj )`` context. +These functions check if the reference at ``T`` is valid: that is, if it is not :ref:`nil` and if it is not non-existing (doesn't refer to anything, including nil) reference. The explicit operator bool allows you to use it in the context of an ``if ( my_obj )`` context. .. code-block:: cpp :caption: function: retrieves the type @@ -57,4 +57,19 @@ Gets the :doc:`sol::type` of the reference; that is, the Lua reference. lua_State* lua_state() const noexcept; -Gets the ``lua_State*`` this reference exists in. \ No newline at end of file +Gets the ``lua_State*`` this reference exists in. + + +non-members +----------- + +.. code-block:: cpp + :caption: functions: reference comparators + + bool operator==(const reference&, const reference&); + bool operator!=(const reference&, const reference&); + +Compares two references using the Lua API's `lua_compare`_ for equality. + + +.. _lua_compare: https://www.lua.org/manual/5.3/manual.html#lua_compare \ No newline at end of file diff --git a/docs/source/api/userdata.rst b/docs/source/api/userdata.rst index a4ee7673..6bc64791 100644 --- a/docs/source/api/userdata.rst +++ b/docs/source/api/userdata.rst @@ -1,5 +1,5 @@ userdata -=========== +======== reference to a userdata ----------------------- diff --git a/docs/source/api/usertype.rst b/docs/source/api/usertype.rst index b654b357..17b53d3f 100644 --- a/docs/source/api/usertype.rst +++ b/docs/source/api/usertype.rst @@ -149,6 +149,10 @@ The constructor of usertype takes a variable number of arguments. It takes an ev * ``"{name}", sol::initializers( func1, func2, ... )`` - Creates initializers that, given one or more functions, provides an overloaded lua function for creating a the specified type. + The function must have the argument signature ``func( T*, Arguments... )`` or ``func( T&, Arguments... )``, where the pointer or reference will point to a place of allocated memory that has an uninitialized ``T``. Note that lua controls the memory. +* ``"{name}", sol::no_constructor`` + - Specifically tells Sol not to create a `.new()` if one is not specified and the type is default-constructible. +* ``sol::call_constructor, {any constructor type}`` + - Specifies a function that makes the call turn into ``{usertype-name}( ... constructor arguments ... )``. This is compatible with luabind syntax. .. _destructor: diff --git a/sol/function_types_allocator.hpp b/sol/function_types_allocator.hpp index e9470985..5d463700 100644 --- a/sol/function_types_allocator.hpp +++ b/sol/function_types_allocator.hpp @@ -60,7 +60,7 @@ inline int construct(Match&& matchfx, lua_State* L, int fxarity, int start) { template inline int construct(lua_State* L) { static const auto& meta = usertype_traits::metatable; - call_syntax syntax = stack::get_call_syntax(L, meta); + call_syntax syntax = stack::get_call_syntax(L, meta, 1); int argcount = lua_gettop(L) - static_cast(syntax); T** pointerpointer = reinterpret_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); diff --git a/sol/object.hpp b/sol/object.hpp index 5b73fa30..471f4c53 100644 --- a/sol/object.hpp +++ b/sol/object.hpp @@ -70,6 +70,22 @@ public: } }; +template +object make_object(lua_State* L, T&& value) { + int backpedal = stack::push(L, std::forward(value)); + object r = stack::get(L, -backpedal); + lua_pop(L, backpedal); + return r; +} + +template +object make_object(lua_State* L, Args&&... args) { + int backpedal = stack::push(L, std::forward(args)...); + object r = stack::get(L, -backpedal); + lua_pop(L, backpedal); + return r; +} + inline bool operator==(const object& lhs, const nil_t&) { return !lhs.valid(); } diff --git a/sol/raii.hpp b/sol/raii.hpp index c498cc15..15d967f4 100644 --- a/sol/raii.hpp +++ b/sol/raii.hpp @@ -62,6 +62,12 @@ using constructors = constructor_list; const auto default_constructor = constructors>{}; +struct no_construction {}; +const auto no_constructor = no_construction{}; + +struct call_construction {}; +const auto call_constructor = call_construction{}; + template struct constructor_wrapper { std::tuple set; diff --git a/sol/reference.hpp b/sol/reference.hpp index 226464ac..2c14cc0b 100644 --- a/sol/reference.hpp +++ b/sol/reference.hpp @@ -144,6 +144,16 @@ public: return L; } }; + +inline bool operator== (const reference& l, const reference& r) { + auto ppl = stack::push_pop(l); + auto ppr = stack::push_pop(r); + return lua_compare(l.lua_state(), -1, -2, LUA_OPEQ) == 0; +} + +inline bool operator!= (const reference& l, const reference& r) { + return !operator==(l, r); +} } // sol #endif // SOL_REFERENCE_HPP diff --git a/sol/stack.hpp b/sol/stack.hpp index 821e3fe5..4c0efbed 100644 --- a/sol/stack.hpp +++ b/sol/stack.hpp @@ -93,7 +93,7 @@ inline void call(types, types ta, std::index_sequence tai, inline void remove( lua_State* L, int index, int count ) { if ( count < 1 ) return; - int top = lua_gettop( L ); + int top = lua_gettop(L); if ( index == -1 || top == index ) { // Slice them right off the top lua_pop( L, static_cast(count) ); @@ -104,11 +104,11 @@ inline void remove( lua_State* L, int index, int count ) { // Probably slower, maybe, haven't benchmarked, // but necessary if ( index < 0 ) { - index = lua_gettop( L ) + (index + 1); + index = lua_gettop(L) + (index + 1); } int last = index + count; for ( int i = index; i < last; ++i ) { - lua_remove( L, i ); + lua_remove(L, i); } } @@ -160,9 +160,9 @@ inline int call_into_lua(types, types ta, lua_State* L, i return push_reference(L, std::forward(r)); } -inline call_syntax get_call_syntax(lua_State* L, const std::string& key) { +inline call_syntax get_call_syntax(lua_State* L, const std::string& key, int index = -2) { luaL_getmetatable(L, key.c_str()); - if (lua_compare(L, -1, -2, LUA_OPEQ) == 1) { + if (lua_compare(L, -1, index, LUA_OPEQ) == 1) { lua_pop(L, 1); return call_syntax::colon; } diff --git a/sol/stack_field.hpp b/sol/stack_field.hpp index de761fe8..ed9c4d08 100644 --- a/sol/stack_field.hpp +++ b/sol/stack_field.hpp @@ -41,7 +41,8 @@ struct field_getter { template struct field_getter { void get(lua_State* L, metatable_key_t, int tableindex = -1) { - lua_getmetatable( L, tableindex ); + if (lua_getmetatable(L, tableindex) == 0) + push(L, nil); } }; @@ -166,7 +167,7 @@ template struct field_setter, b, C> { template void apply(std::index_sequence, lua_State* L, Key&& keys, Value&& value, int tableindex) { - I > 0 ? + I < 1 ? set_field(L, detail::forward_get(keys), std::forward(value)) : set_field(L, detail::forward_get(keys), std::forward(value), tableindex); } diff --git a/sol/stack_reference.hpp b/sol/stack_reference.hpp index 38cb1bff..a4f96e83 100644 --- a/sol/stack_reference.hpp +++ b/sol/stack_reference.hpp @@ -1,73 +1,81 @@ -// The MIT License (MIT) - -// Copyright (c) 2013-2016 Rapptz, ThePhD and contributors - -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -#ifndef SOL_STACK_REFERENCE_HPP -#define SOL_STACK_REFERENCE_HPP - -namespace sol { -class stack_reference { -private: - lua_State* L = nullptr; - int index = 0; - -protected: - int registry_index () const noexcept { - return LUA_NOREF; - } - -public: - stack_reference() noexcept = default; - stack_reference(lua_State* L, int i) noexcept : L(L), index(lua_absindex(L, i)) {} - stack_reference(stack_reference&& o) noexcept = default; - stack_reference& operator=(stack_reference&&) noexcept = default; - stack_reference(const stack_reference&) noexcept = default; - stack_reference& operator=(const stack_reference&) noexcept = default; - - int push() const noexcept { - lua_pushvalue(L, index); - return 1; - } - - void pop(int n = 1) const noexcept { - lua_pop(lua_state( ), n); - } - - int stack_index () const noexcept { - return index; - } - - type get_type() const noexcept { - int result = lua_type(L, index); - return static_cast(result); - } - - lua_State* lua_state() const noexcept { - return L; - } - - bool valid () const noexcept { - type t = get_type(); - return t != type::nil && t != type::none; - } -}; -} // sol - -#endif // SOL_STACK_REFERENCE_HPP +// The MIT License (MIT) + +// Copyright (c) 2013-2016 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_STACK_REFERENCE_HPP +#define SOL_STACK_REFERENCE_HPP + +namespace sol { +class stack_reference { +private: + lua_State* L = nullptr; + int index = 0; + +protected: + int registry_index () const noexcept { + return LUA_NOREF; + } + +public: + stack_reference() noexcept = default; + stack_reference(lua_State* L, int i) noexcept : L(L), index(lua_absindex(L, i)) {} + stack_reference(stack_reference&& o) noexcept = default; + stack_reference& operator=(stack_reference&&) noexcept = default; + stack_reference(const stack_reference&) noexcept = default; + stack_reference& operator=(const stack_reference&) noexcept = default; + + int push() const noexcept { + lua_pushvalue(L, index); + return 1; + } + + void pop(int n = 1) const noexcept { + lua_pop(lua_state( ), n); + } + + int stack_index () const noexcept { + return index; + } + + type get_type() const noexcept { + int result = lua_type(L, index); + return static_cast(result); + } + + lua_State* lua_state() const noexcept { + return L; + } + + bool valid () const noexcept { + type t = get_type(); + return t != type::nil && t != type::none; + } +}; + +inline bool operator== (const stack_reference& l, const stack_reference& r) { + return lua_compare(l.lua_state(), l.stack_index(), l.stack_index(), LUA_OPEQ) == 0; +} + +inline bool operator!= (const stack_reference& l, const stack_reference& r) { + return !operator==(l, r); +} +} // sol + +#endif // SOL_STACK_REFERENCE_HPP diff --git a/sol/usertype.hpp b/sol/usertype.hpp index 8a55b6db..d1a0a307 100644 --- a/sol/usertype.hpp +++ b/sol/usertype.hpp @@ -48,6 +48,9 @@ struct is_constructor> : std::true_type {}; template struct is_constructor> : std::true_type {}; +template <> +struct is_constructor : std::true_type {}; + template using has_constructor = meta::Or>...>; @@ -179,6 +182,7 @@ private: function_detail::base_function* indexfunc; function_detail::base_function* newindexfunc; function_map_t indexwrapper, newindexwrapper; + const char* constructfuncname; lua_CFunction constructfunc; const char* destructfuncname; lua_CFunction destructfunc; @@ -263,12 +267,16 @@ private: return make_functor_function(std::is_base_of(), name, std::forward(func)); } + template + void build_function(std::string funcname, no_construction) {} + template void build_function(std::string funcname, constructors) { functionnames.push_back(std::move(funcname)); std::string& name = functionnames.back(); // Insert bubble to keep with compile-time argument count (simpler and cheaper to do) functions.push_back(nullptr); + constructfuncname = name.c_str(); constructfunc = function_detail::construct; metafunctiontable.push_back({ name.c_str(), constructfunc }); } @@ -283,27 +291,28 @@ private: auto metamethodfind = std::find(meta_function_names.begin(), meta_function_names.end(), name); if (metamethodfind != meta_function_names.end()) { - metafunctiontable.push_back({ name.c_str(), function_detail::usertype_call }); - meta_function metafunction = static_cast(metamethodfind - meta_function_names.begin()); - switch (metafunction) { - case meta_function::garbage_collect: - destructfuncname = name.c_str(); - destructfunc = function_detail::usertype_call; - return; - case meta_function::index: - indexfunc = functions.back().get(); - needsindexfunction = true; - break; - case meta_function::new_index: - newindexfunc = functions.back().get(); - break; - case meta_function::construct: - constructfunc = function_detail::usertype_call; - break; + metafunctiontable.push_back({ name.c_str(), function_detail::usertype_call }); + meta_function metafunction = static_cast(metamethodfind - meta_function_names.begin()); + switch (metafunction) { + case meta_function::garbage_collect: + destructfuncname = name.c_str(); + destructfunc = function_detail::usertype_call; + return; + case meta_function::index: + indexfunc = functions.back().get(); + needsindexfunction = true; + break; + case meta_function::new_index: + newindexfunc = functions.back().get(); + break; + case meta_function::construct: + constructfuncname = name.c_str(); + constructfunc = function_detail::usertype_call; + break; default: break; - } - return; + } + return; } functiontable.push_back({ name.c_str(), direct }); } @@ -396,6 +405,11 @@ private: build_function_tables(funcname, std::forward(func), std::forward(args)...); } + template + void build_function_tables(call_construction, Fx&& func, Args&&... args) { + build_function_tables("__call", std::forward(func), std::forward(args)...); + } + template void build_function_tables(base_classes_tag, bases, Args&&... args) { build_function_tables(std::forward(args)...); @@ -447,8 +461,8 @@ private: } template - usertype(usertype_detail::verified_tag, Args&&... args) : indexfunc(nullptr), newindexfunc(nullptr), constructfunc(nullptr), - destructfunc(nullptr), functiongcfunc(nullptr), needsindexfunction(false), baseclasscheck(nullptr), baseclasscast(nullptr) { + usertype(usertype_detail::verified_tag, Args&&... args) : indexfunc(nullptr), newindexfunc(nullptr), constructfuncname(""), constructfunc(nullptr), + destructfuncname(""), destructfunc(nullptr), functiongcfunc(nullptr), needsindexfunction(false), baseclasscheck(nullptr), baseclasscast(nullptr) { static_assert((sizeof...(Args) % 2) == 0, "Incorrect argument count to usertype creation: not in pairs. Might be missing name, function/property/variable, comma"); functionnames.reserve(sizeof...(args)+3); functiontable.reserve(sizeof...(args)+3); @@ -480,8 +494,15 @@ public: usertype_detail::push_metatable, usertype_detail::stage::uniquemeta>(L, needsindexfunction, functions, functiontable, metafunctiontable, baseclasscheck, baseclasscast); lua_pop(L, 1); // but leave the regular T table on last - // so it can be linked to a type for usage with `.new(...)` or `:new(...)` + // so it can be linked to a key for usage with `.new(...)` or `:new(...)` usertype_detail::push_metatable(L, needsindexfunction, functions, functiontable, metafunctiontable, baseclasscheck, baseclasscast); + // be sure to link the construction function to allow for people to do the whole lua_bind thing + if (constructfunc != nullptr && constructfuncname != nullptr && std::find(meta_function_names.cbegin(), meta_function_names.cend(), constructfuncname) != meta_function_names.cend()) { + lua_createtable(L, 0, 0); + sol::stack_table mt(L, -1); + mt[constructfuncname] = constructfunc; + lua_setmetatable(L, -2); + } // Make sure to drop a table in the global namespace to properly destroy the pushed functions // at some later point in life usertype_detail::set_global_deleter(L, functiongcfunc, functions); diff --git a/test_tables.cpp b/test_tables.cpp index f8990102..a2edc527 100644 --- a/test_tables.cpp +++ b/test_tables.cpp @@ -272,6 +272,8 @@ TEST_CASE("tables/create-local-named", "Check if creating a table is kosher") { sol::table testtable = lua.create_table("testtable", 0, 0, "Woof", "Bark", 1, 2, 3, 4); sol::object testobj = lua["testtable"]; REQUIRE(testobj.is()); + REQUIRE((testobj == testtable)); + REQUIRE_FALSE((testobj != testtable)); REQUIRE((testtable["Woof"] == std::string("Bark"))); REQUIRE((testtable[1] == 2)); REQUIRE((testtable[3] == 4)); diff --git a/tests.cpp b/tests.cpp index 4b019dbb..d7dab814 100644 --- a/tests.cpp +++ b/tests.cpp @@ -186,6 +186,13 @@ bool something() { return true; } +struct thing { + int v = 100; + + thing() {} + thing(int x) : v(x) {} +}; + TEST_CASE("table/traversal", "ensure that we can chain requests and tunnel down into a value if we desire") { sol::state lua; @@ -935,8 +942,8 @@ TEST_CASE("usertype/readonly-and-static-functions", "Check if static functions c sol::meta_function::call_function, &bark::operator() ); - lua.script("assert(bark.oh_boy('woo') == 3)"); - lua.script("bark.oh_boy()"); + REQUIRE_NOTHROW(lua.script("assert(bark.oh_boy('woo') == 3)")); + REQUIRE_NOTHROW(lua.script("bark.oh_boy()")); bark b; lua.set("b", &b); @@ -1088,3 +1095,46 @@ TEST_CASE("utilities/this_state", "Ensure this_state argument can be gotten anyw REQUIRE(a == 625); REQUIRE(la == 625); } + +TEST_CASE("usertype/call_constructor", "make sure lua types can be constructed with function call constructors") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_usertype("thing", + "v", &thing::v + , sol::call_constructor, sol::constructors, sol::types>() + ); + + lua.script(R"( +t = thing(256) +)"); + + thing& y = lua["t"]; + std::cout << y.v << std::endl; + REQUIRE(y.v == 256); +} + +TEST_CASE("usertype/blank_constructor", "make sure lua types cannot be constructed if a blank / empty constructor is provided") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_usertype("thing", + "v", &thing::v + , sol::call_constructor, sol::constructors<>() + ); + + REQUIRE_THROWS(lua.script("t = thing(256)")); +} + + +TEST_CASE("usertype/no_constructor", "make sure lua types cannot be constructed if a blank / empty constructor is provided") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_usertype("thing", + "v", &thing::v + , sol::call_constructor, sol::no_constructor + ); + + REQUIRE_THROWS(lua.script("t = thing.new()")); +}