From fffa17d7e28f4a3147adf1f2ae2a73c4b0f7b945 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Wed, 12 Jun 2024 11:26:38 +0200 Subject: Add support for to-be-closed linda --- src/compat.cpp | 2 +- src/deep.cpp | 3 +- src/deep.h | 2 +- src/linda.cpp | 115 ++++++++++++++++++++++++++++++++++++++++++++------- src/lindafactory.cpp | 23 ++--------- 5 files changed, 107 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/compat.cpp b/src/compat.cpp index e1e7488..4076aa6 100644 --- a/src/compat.cpp +++ b/src/compat.cpp @@ -92,7 +92,7 @@ void* lua_newuserdatauv(lua_State* L_, size_t sz_, [[maybe_unused]] int nuvalue_ // ################################################################################################# // push on stack uservalue #n of full userdata at idx -int lua_getiuservalue(lua_State* L_, int idx_, int n_) +int lua_getiuservalue(lua_State* const L_, int const idx_, int const n_) { STACK_CHECK_START_REL(L_, 0); // full userdata can have only 1 uservalue before 5.4 diff --git a/src/deep.cpp b/src/deep.cpp index b6a9a22..249c497 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -295,7 +295,7 @@ void DeepFactory::PushDeepProxy(DestState const L_, DeepPrelude* const prelude_, * * Returns: 'proxy' userdata for accessing the deep data via 'DeepFactory::toDeep()' */ -int DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const +void DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const { STACK_GROW(L_, 1); STACK_CHECK_START_REL(L_, 0); @@ -322,7 +322,6 @@ int DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const DeepFactory::PushDeepProxy(L_, _prelude, nuv_, LookupMode::LaneBody, L_); // proxy STACK_CHECK(L_, 1); - return 1; } // ################################################################################################# diff --git a/src/deep.h b/src/deep.h index fb62276..bb86c20 100644 --- a/src/deep.h +++ b/src/deep.h @@ -68,7 +68,7 @@ class DeepFactory static void DeleteDeepObject(lua_State* L_, DeepPrelude* o_); [[nodiscard]] static DeepFactory* LookupFactory(lua_State* L_, int index_, LookupMode mode_); static void PushDeepProxy(DestState L_, DeepPrelude* o_, int nuv_, LookupMode mode_, lua_State* errL_); - [[nodiscard]] int pushDeepUserdata(DestState L_, int nuv_) const; + void pushDeepUserdata(DestState L_, int nuv_) const; [[nodiscard]] DeepPrelude* toDeep(lua_State* L_, int index_) const; }; diff --git a/src/linda.cpp b/src/linda.cpp index 1bd5d16..91138c5 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -282,6 +282,33 @@ LUAG_FUNC(linda_cancel) // ################################################################################################# +#if LUA_VERSION_NUM >= 504 +// linda:__close(err|nil) +static LUAG_FUNC(linda_close) +{ + [[maybe_unused]] Linda* const _linda{ ToLinda(L_, 1) }; // L_: linda err|nil + + // do we have a uservalue? it contains a callback + switch (lua_getiuservalue(L_, 1, 1)) { + case LUA_TTABLE: // callable table + case LUA_TUSERDATA: // callable userdata + case LUA_TFUNCTION: // L_: linda err|nil on_close() + lua_insert(L_, 1); // L_: on_close() linda err|nil + lua_call(L_, lua_gettop(L_) - 1, 0); // L_: + return 0; + + case LUA_TNONE: + case LUA_TNIL: + return 0; + + default: + raise_luaL_error(L_, "Invalid __close handler"); + } +} +#endif // LUA_VERSION_NUM >= 504 + +// ################################################################################################# + /* * string = linda:__concat( a, b) * @@ -811,6 +838,9 @@ LUAG_FUNC(linda_towatch) namespace { namespace local { static luaL_Reg const sLindaMT[] = { +#if LUA_VERSION_NUM >= 504 + { "__close", LG_linda_close }, +#endif // LUA_VERSION_NUM >= 504 { "__concat", LG_linda_concat }, { "__tostring", LG_linda_tostring }, #if HAVE_DECODA_SUPPORT() @@ -837,31 +867,88 @@ namespace { // ################################################################################################# /* - * ud = lanes.linda( [name[,group]]) + * ud = lanes.linda( [name[,group[,close_handler]]]) * * returns a linda object, or raises an error if creation failed */ LUAG_FUNC(linda) { + static constexpr int kLastArg{ LUA_VERSION_NUM >= 504 ? 3 : 2}; int const _top{ lua_gettop(L_) }; + luaL_argcheck(L_, _top <= kLastArg, _top, "too many arguments"); + int _closeHandlerIdx{}; + int _nameIdx{}; int _groupIdx{}; - luaL_argcheck(L_, _top <= 2, _top, "too many arguments"); - if (_top == 1) { - LuaType const _t{ luaG_type(L_, 1) }; - int const _nameIdx{ (_t == LuaType::STRING) ? 1 : 0 }; - _groupIdx = (_t == LuaType::NUMBER) ? 1 : 0; - luaL_argcheck(L_, _nameIdx || _groupIdx, 1, "wrong parameter (should be a string or a number)"); - } else if (_top == 2) { - luaL_checktype(L_, 1, LUA_TSTRING); - luaL_checktype(L_, 2, LUA_TNUMBER); - _groupIdx = 2; + for (int const _i : std::ranges::iota_view{ 1, _top + 1 }) { + switch (luaG_type(L_, _i)) { +#if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4 + case LuaType::FUNCTION: + luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler"); + _closeHandlerIdx = _i; + break; + + case LuaType::USERDATA: + case LuaType::TABLE: + luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler"); + luaL_argcheck(L_, luaL_getmetafield(L_, _i, "__call") != 0, _i, "__close handler is not callable"); + lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it + _closeHandlerIdx = _i; + break; +#endif // LUA_VERSION_NUM >= 504 + + case LuaType::STRING: + luaL_argcheck(L_, _nameIdx == 0, _i, "More than one name"); + _nameIdx = _i; + break; + + case LuaType::NUMBER: + luaL_argcheck(L_, _groupIdx == 0, _i, "More than one group"); + _groupIdx = _i; + break; + + default: + luaL_argcheck(L_, false, _i, "Bad argument type (should be a string, a number, or a callable type)"); + } } + int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() }; if (!_groupIdx) { - luaL_argcheck(L_, _nbKeepers < 2, 0, "there are multiple keepers, you must specify a group"); + luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios"); } else { int const _group{ static_cast(lua_tointeger(L_, _groupIdx)) }; - luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "group out of range"); + luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "Group out of range"); } - return LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0); + + // done with argument checking, let's proceed + if constexpr (LUA_VERSION_NUM >= 504) { + // make sure we have kMaxArgs arguments on the stack for processing, with name, group, and handler, in that order + lua_settop(L_, kLastArg); // L_: a b c + // If either index is 0, lua_settop() adjusted the stack with a nil in slot kLastArg + lua_pushvalue(L_, _closeHandlerIdx ? _closeHandlerIdx : kLastArg); // L_: a b c close_handler + lua_pushvalue(L_, _groupIdx ? _groupIdx : kLastArg); // L_: a b c close_handler group + lua_pushvalue(L_, _nameIdx ? _nameIdx : kLastArg); // L_: a b c close_handler group name + lua_replace(L_, 1); // L_: name b c close_handler group + lua_replace(L_, 2); // L_: name group c close_handler + lua_replace(L_, 3); // L_: name group close_handler + + // if we have a __close handler, we need a uservalue slot to store it + int const _nuv{ _closeHandlerIdx ? 1 : 0 }; + LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv); // L_: name group close_handler linda + if (_closeHandlerIdx != 0) { + lua_replace(L_, 2); // L_: name linda close_handler + lua_setiuservalue(L_, 2, 1); // L_: name linda + } + // depending on whether we have a handler or not, the stack is not in the same state at this point + // just make sure we have our Linda at the top + LUA_ASSERT(L_, ToLinda(L_, -1)); + return 1; + } else { // no to-be-closed support + // ensure we have name, group in that order on the stack + if (_nameIdx > _groupIdx) { + lua_insert(L_, 1); // L_: name group + } + LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0); // L_: name group linda + return 1; + } + } diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp index 8622c12..863f16e 100644 --- a/src/lindafactory.cpp +++ b/src/lindafactory.cpp @@ -105,26 +105,9 @@ std::string_view LindaFactory::moduleName() const DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const { - std::string_view _linda_name{}; - LindaGroup _linda_group{ 0 }; - // should have a string and/or a number of the stack as parameters (name and group) - switch (lua_gettop(L_)) { - default: // 0 - break; - - case 1: // 1 parameter, either a name or a group - if (luaG_type(L_, -1) == LuaType::STRING) { - _linda_name = luaG_tostring(L_, -1); - } else { - _linda_group = LindaGroup{ static_cast(lua_tointeger(L_, -1)) }; - } - break; - - case 2: // 2 parameters, a name and group, in that order - _linda_name = luaG_tostring(L_, -2); - _linda_group = LindaGroup{ static_cast(lua_tointeger(L_, -1)) }; - break; - } + // we always expect name and group at the bottom of the stack (either can be nil). any extra stuff we ignore and keep unmodified + std::string_view _linda_name{ luaG_tostring(L_, 1) }; + LindaGroup _linda_group{ static_cast(lua_tointeger(L_, 2)) }; // store in the linda the location of the script that created it if (_linda_name == "auto") { -- cgit v1.2.3-55-g6feb