diff options
author | nat-goodspeed <nat@lindenlab.com> | 2024-02-15 11:47:16 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-15 11:47:16 -0500 |
commit | de72b1d573897c24e307f63b071fd5f02ce851c1 (patch) | |
tree | 270b33a014a7b138f4ab35b2176038309ed4f0f7 | |
parent | 9b1796df73f2d4fa19517f390258f798c10bff1e (diff) | |
parent | d583fb8badd8060c0f74bcb6e99bb6e7d08a67d0 (diff) |
Merge pull request #798 from secondlife/helpcmd
Add help() function to Lua "builtins."
-rw-r--r-- | autobuild.xml | 28 | ||||
-rw-r--r-- | indra/llcommon/llstring.h | 39 | ||||
-rw-r--r-- | indra/llcommon/lua_function.cpp | 226 | ||||
-rw-r--r-- | indra/llcommon/lua_function.h | 22 | ||||
-rw-r--r-- | indra/llcommon/stringize.h | 6 | ||||
-rw-r--r-- | indra/llui/lltexteditor.cpp | 3 | ||||
-rw-r--r-- | indra/llui/lltexteditor.h | 14 | ||||
-rw-r--r-- | indra/newview/llfloaterluadebug.cpp | 8 | ||||
-rw-r--r-- | indra/newview/llluamanager.cpp | 29 |
9 files changed, 299 insertions, 76 deletions
diff --git a/autobuild.xml b/autobuild.xml index 1d6451e93d..03f559500c 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1669,11 +1669,11 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>c326445818c4e2e2177d5efde1dd59e7af0d6c9a</string> + <string>59bf3d96f9df4b6981c406abac5c46ae276f9b15</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-luau/releases/assets/150694132</string> + <string>https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-darwin64-9567db9.tar.zst</string> </map> <key>name</key> <string>darwin64</string> @@ -1685,24 +1685,38 @@ <key>creds</key> <string>github</string> <key>hash</key> - <string>f3bd40eb043f2f36636d847747db20d0168f42dc</string> + <string>43e2cc2e6e94299f89655435002864925b640e16</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-luau/releases/assets/150694133</string> + <string>https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-windows64-9567db9.tar.zst</string> </map> <key>name</key> <string>windows64</string> </map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>549516ada483ddf276183f66b0a7c3d01e4ef1aa</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-linux64-9567db9.tar.zst</string> + </map> + <key>name</key> + <string>linux64</string> + </map> </map> <key>license</key> <string>MIT</string> <key>license_file</key> - <string>LICENSES/lua.txt</string> + <string>LICENSES/luau.txt</string> <key>copyright</key> <string>Copyright (c) 2019-2023 Roblox Corporation, Copyright (c) 1994–2019 Lua.org, PUC-Rio.</string> <key>version</key> - <string>0.609</string> + <string>0.609-9567db9</string> <key>name</key> <string>luau</string> <key>description</key> @@ -2509,8 +2523,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>https://bitbucket.org/lindenlab/3p-tracy</string> <key>source_type</key> <string>git</string> - <key>version</key> - <string>v0.8.1.578241</string> </map> <key>tut</key> <map> diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index b43093fbfc..dbd60bc9c7 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -516,9 +516,36 @@ struct ll_convert_impl TO operator()(const FROM& in) const; }; -// Use a function template to get the nice ll_convert<TO>(from_value) API. +/** + * somefunction(ll_convert(data)) + * target = ll_convert(data) + * totype otherfunc(const fromtype& data) + * { + * // ... + * return ll_convert(data); + * } + * all infer both the FROM type and the TO type. + */ +template <typename FROM> +class ll_convert +{ +private: + const FROM& mRef; + +public: + ll_convert(const FROM& ref): mRef(ref) {} + + template <typename TO> + inline operator TO() const + { + return ll_convert_impl<TO, FROM>()(mRef); + } +}; + +// When the TO type must be explicit, use a function template to get +// ll_convert_to<TO>(from_value) API. template<typename TO, typename FROM> -TO ll_convert(const FROM& in) +TO ll_convert_to(const FROM& in) { return ll_convert_impl<TO, FROM>()(in); } @@ -574,8 +601,8 @@ inline size_t ll_convert_length<char> (const char* zstr) { return std::strl // and longname(const string&, len) so calls written pre-ll_convert() will // work. Most of these overloads will be unified once we turn on C++17 and can // use std::string_view. -// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*) -// and ll_convert<OUTSTR>(const string&) will work. +// It also uses aliasmacro to ensure that both ll_convert(const char*) +// and ll_convert(const string&) will work. #define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ inline auto longname(const INSTR& in, size_t len) \ @@ -807,7 +834,7 @@ LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in template<typename STRING> STRING windows_message(unsigned long error) { - return ll_convert<STRING>(windows_message<std::wstring>(error)); + return ll_convert(windows_message<std::wstring>(error)); } /// There's only one real implementation @@ -1780,7 +1807,7 @@ auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> boost::optional<s if (found) { // return populated boost::optional - return { ll_convert<string_type>(*found) }; + return { ll_convert_to<string_type>(*found) }; } else { diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 07e0c1fac2..13210fe5a6 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -21,10 +21,14 @@ // external library headers // other Linden headers #include "hexdump.h" +#include "lleventcoro.h" #include "llsd.h" #include "llsdutil.h" #include "lualistener.h" +/***************************************************************************** +* luau namespace +*****************************************************************************/ namespace { // can't specify free function free() as a unique_ptr deleter @@ -53,6 +57,9 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te return lua_pcall(L, 0, LUA_MULTRET, 0); } +/***************************************************************************** +* Lua <=> C++ conversions +*****************************************************************************/ std::string lua_tostdstring(lua_State* L, int index) { size_t len; @@ -414,6 +421,9 @@ void lua_pushllsd(lua_State* L, const LLSD& data) } } +/***************************************************************************** +* LuaState class +*****************************************************************************/ LuaState::LuaState(script_finished_fn cb): mCallback(cb), mState(luaL_newstate()) @@ -426,25 +436,15 @@ LuaState::LuaState(script_finished_fn cb): LuaState::~LuaState() { - // Did somebody call listen_events() on this LuaState? + // Did somebody call obtainListener() on this LuaState? // That is, is there a LuaListener key in its registry? - auto keytype{ lua_getfield(mState, LUA_REGISTRYINDEX, "event.listener") }; - if (keytype == LUA_TNUMBER) + auto listener{ getListener() }; + if (listener) { - // We do have a LuaListener. Retrieve it. - int isint; - auto listener{ LuaListener::getInstance(lua_tointegerx(mState, -1, &isint)) }; - // pop the int "event.listener" key - lua_pop(mState, 1); // if we got a LuaListener instance, destroy it - // (if (! isint), lua_tointegerx() returned 0, but key 0 might - // validly designate someone ELSE's LuaListener) - if (isint && listener) - { - auto lptr{ listener.get() }; - listener.reset(); - delete lptr; - } + auto lptr{ listener.get() }; + listener.reset(); + delete lptr; } lua_close(mState); @@ -499,7 +499,48 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& return result; } +LuaListener::ptr_t LuaState::getListener(lua_State* L) +{ + // have to use one more stack slot + luaL_checkstack(L, 1, nullptr); + LuaListener::ptr_t listener; + // Does this lua_State already have a LuaListener stored in the registry? + auto keytype{ lua_getfield(L, LUA_REGISTRYINDEX, "event.listener") }; + llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER); + if (keytype == LUA_TNUMBER) + { + // We do already have a LuaListener. Retrieve it. + int isint; + listener = LuaListener::getInstance(lua_tointegerx(L, -1, &isint)); + // Nobody should have destroyed this LuaListener instance! + llassert(isint && listener); + } + // pop the int "event.listener" key + lua_pop(L, 1); + return listener; +} +LuaListener::ptr_t LuaState::obtainListener(lua_State* L) +{ + auto listener{ getListener(L) }; + if (! listener) + { + // have to use one more stack slot + luaL_checkstack(L, 1, nullptr); + // instantiate a new LuaListener, binding the L state -- but use a + // no-op deleter: we do NOT want this ptr_t to manage the lifespan of + // this new LuaListener! + listener.reset(new LuaListener(L), [](LuaListener*){}); + // set its key in the field where we'll look for it later + lua_pushinteger(L, listener->getKey()); + lua_setfield(L, LUA_REGISTRYINDEX, "event.listener"); + } + return listener; +} + +/***************************************************************************** +* LuaPopper class +*****************************************************************************/ LuaPopper::~LuaPopper() { if (mCount) @@ -508,15 +549,21 @@ LuaPopper::~LuaPopper() } } +/***************************************************************************** +* LuaFunction class +*****************************************************************************/ LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, const std::string_view& helptext) { - getRegistry().emplace(name, Registry::mapped_type{ function, helptext }); + const auto& [registry, lookup] = getState(); + registry.emplace(name, Registry::mapped_type{ function, helptext }); + lookup.emplace(function, name); } void LuaFunction::init(lua_State* L) { - for (const auto& [name, pair]: getRegistry()) + const auto& [registry, lookup] = getRState(); + for (const auto& [name, pair]: registry) { const auto& [funcptr, helptext] = pair; lua_register(L, name.c_str(), funcptr); @@ -527,19 +574,148 @@ lua_CFunction LuaFunction::get(const std::string& key) { // use find() instead of subscripting to avoid creating an entry for // unknown key - const auto& registry{ getRegistry() }; + const auto& [registry, lookup] = getState(); auto found{ registry.find(key) }; return (found == registry.end())? nullptr : found->second.first; } -LuaFunction::Registry& LuaFunction::getRegistry() +std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState() { - // use a function-local static to ensure it's initialized + // use function-local statics to ensure they're initialized static Registry registry; - return registry; + static Lookup lookup; + return { registry, lookup }; +} + +/***************************************************************************** +* help() +*****************************************************************************/ +lua_function(help, + "help(): list viewer's Lua functions\n" + "help(function): show help string for specific function") +{ + auto& luapump{ LLEventPumps::instance().obtain("lua output") }; + const auto& [registry, lookup]{ LuaFunction::getRState() }; + if (! lua_gettop(L)) + { + // no arguments passed: list all lua_functions + for (const auto& [name, pair] : registry) + { + const auto& [fptr, helptext] = pair; + luapump.post(helptext); + } + } + else + { + // arguments passed: list each of the specified lua_functions + for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx) + { + std::string arg{ stringize("<unknown ", lua_typename(L, lua_type(L, idx)), ">") }; + if (lua_type(L, idx) == LUA_TSTRING) + { + arg = lua_tostdstring(L, idx); + } + else if (lua_type(L, idx) == LUA_TFUNCTION) + { + // Caller passed the actual function instead of its string + // name. A Lua function is an anonymous callable object; it + // has a name only by assigment. You can't ask Lua for a + // function's name, which is why our constructor maintains a + // reverse Lookup map. + auto function{ lua_tocfunction(L, idx) }; + if (auto found = lookup.find(function); found != lookup.end()) + { + // okay, pass found name to lookup below + arg = found->second; + } + } + + if (auto found = registry.find(arg); found != registry.end()) + { + luapump.post(found->second.second); + } + else + { + luapump.post(arg + ": NOT FOUND"); + } + } + // pop all arguments + lua_settop(L, 0); + } + return 0; // void return } +/***************************************************************************** +* leaphelp() +*****************************************************************************/ +lua_function( + leaphelp, + "leaphelp(): list viewer's LEAP APIs\n" + "leaphelp(api): show help for specific api string name") +{ + LLSD request; + int top{ lua_gettop(L) }; + if (top) + { + request = llsd::map("op", "getAPI", "api", lua_tostdstring(L, 1)); + } + else + { + request = llsd::map("op", "getAPIs"); + } + // pop all args + lua_settop(L, 0); + + auto& outpump{ LLEventPumps::instance().obtain("lua output") }; + auto listener{ LuaState::obtainListener(L) }; + LLEventStream replyPump("leaphelp", true); + // ask the LuaListener's LeapListener and suspend calling coroutine until reply + auto reply{ llcoro::postAndSuspend(request, listener->getCommandName(), replyPump, "reply") }; + reply.erase("reqid"); + + if (auto error = reply["error"]; error.isString()) + { + outpump.post(error.asString()); + return 0; + } + + if (top) + { + // caller wants a specific API + outpump.post(stringize(reply["name"].asString(), ":\n", reply["desc"].asString())); + for (const auto& opmap : llsd::inArray(reply["ops"])) + { + std::ostringstream reqstr; + auto req{ opmap["required"] }; + if (req.isArray()) + { + const char* sep = " (requires "; + for (const auto& [reqkey, reqval] : llsd::inMap(req)) + { + reqstr << sep << reqkey; + sep = ", "; + } + reqstr << ")"; + } + outpump.post(stringize("---- ", reply["key"].asString(), " == '", + opmap["name"].asString(), "'", reqstr.str(), ":\n", + opmap["desc"].asString())); + } + } + else + { + // caller wants a list of APIs + for (const auto& [name, data] : llsd::inMap(reply)) + { + outpump.post(stringize("==== ", name, ":\n", data["desc"].asString())); + } + } + return 0; // void return +} +/***************************************************************************** +* lua_what +*****************************************************************************/ std::ostream& operator<<(std::ostream& out, const lua_what& self) { switch (lua_type(self.L, self.index)) @@ -595,6 +771,9 @@ std::ostream& operator<<(std::ostream& out, const lua_what& self) return out; } +/***************************************************************************** +* lua_stack +*****************************************************************************/ std::ostream& operator<<(std::ostream& out, const lua_stack& self) { const char* sep = "stack: ["; @@ -607,6 +786,9 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self) return out; } +/***************************************************************************** +* DebugExit +*****************************************************************************/ DebugExit::~DebugExit() { LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL; diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c23bf533ba..7973a769be 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,8 +17,11 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include <memory> // std::shared_ptr #include <utility> // std::pair +class LuaListener; + #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -83,6 +86,17 @@ public: operator lua_State*() const { return mState; } + // Return LuaListener for this LuaState if we already have one, else empty + // shared_ptr. + std::shared_ptr<LuaListener> getListener() { return getListener(mState); } + // Find or create LuaListener for this LuaState, returning its ptr_t. + std::shared_ptr<LuaListener> obtainListener() { return obtainListener(mState); } + // Return LuaListener for passed lua_State if we already have one, else + // empty shared_ptr. + static std::shared_ptr<LuaListener> getListener(lua_State* L); + // Find or create LuaListener for passed lua_State, returning its ptr_t. + static std::shared_ptr<LuaListener> obtainListener(lua_State* L); + private: script_finished_fn mCallback; lua_State* mState; @@ -131,9 +145,13 @@ public: static lua_CFunction get(const std::string& key); -private: +protected: using Registry = std::map<std::string, std::pair<lua_CFunction, std::string>>; - static Registry& getRegistry(); + using Lookup = std::map<lua_CFunction, std::string>; + static std::pair<const Registry&, const Lookup&> getRState() { return getState(); } + +private: + static std::pair<Registry&, Lookup&> getState(); }; /** diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index c0b13135f9..63d44a7272 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -88,13 +88,13 @@ struct gstringize_impl }; // partially specialize for a single STRING argument - -// note that ll_convert<T>(T) already handles the trivial case +// note that ll_convert_to<T>(T) already handles the trivial case template <typename OUTCHAR, typename INCHAR> struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>> { auto operator()(const std::basic_string<INCHAR>& arg) { - return ll_convert<std::basic_string<OUTCHAR>>(arg); + return ll_convert_to<std::basic_string<OUTCHAR>>(arg); } }; @@ -105,7 +105,7 @@ struct gstringize_impl<OUTCHAR, INCHAR*> { auto operator()(const INCHAR* arg) { - return ll_convert<std::basic_string<OUTCHAR>>(arg); + return ll_convert_to<std::basic_string<OUTCHAR>>(arg); } }; diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3d2a426913..ccd04f83e7 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1504,7 +1504,8 @@ void LLTextEditor::cleanStringForPaste(LLWString & clean_string) } -void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string) +template <> +void LLTextEditor::pasteTextWithLinebreaks<LLWString>(const LLWString & clean_string) { std::basic_string<llwchar>::size_type start = 0; std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start); diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index ec2b9a4b7d..01b5ce5fbd 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -34,6 +34,7 @@ #include "llstyle.h" #include "lleditmenuhandler.h" #include "llviewborder.h" // for params +#include "llstring.h" #include "lltextbase.h" #include "lltextvalidate.h" @@ -294,10 +295,19 @@ private: // // Methods // - void pasteHelper(bool is_primary); + void pasteHelper(bool is_primary); void cleanStringForPaste(LLWString & clean_string); - void pasteTextWithLinebreaks(LLWString & clean_string); +public: + template <typename STRINGTYPE> + void pasteTextWithLinebreaks(const STRINGTYPE& clean_string) + { + pasteTextWithLinebreaks<LLWString>(ll_convert(clean_string)); + } + template <> + void pasteTextWithLinebreaks<LLWString>(const LLWString & clean_string); + +private: void onKeyStroke(); // Concrete TextCmd sub-classes used by the LLTextEditor base class diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp index 7d56d8e618..3ebe40f80b 100644 --- a/indra/newview/llfloaterluadebug.cpp +++ b/indra/newview/llfloaterluadebug.cpp @@ -57,7 +57,7 @@ BOOL LLFloaterLUADebug::postBuild() .listen("LLFloaterLUADebug", [mResultOutput=mResultOutput](const LLSD& data) { - mResultOutput->insertText(data.asString()); + mResultOutput->pasteTextWithLinebreaks(data.asString()); mResultOutput->addLineBreakChar(true); return false; }); @@ -122,13 +122,13 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result) { // error: show error message mResultOutput->insertText("*** "); - mResultOutput->insertText(result.asString()); + mResultOutput->pasteTextWithLinebreaks(result.asString()); return; } if (count == 1) { // single result - mResultOutput->insertText(stringize(result)); + mResultOutput->pasteTextWithLinebreaks(stringize(result)); return; } // 0 or multiple results @@ -136,7 +136,7 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result) for (const auto& item : llsd::inArray(result)) { mResultOutput->insertText(sep); - mResultOutput->insertText(stringize(item)); + mResultOutput->pasteTextWithLinebreaks(stringize(item)); sep = ", "; } } diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index d58ee5ca5f..c6a900aa81 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -197,34 +197,7 @@ lua_function(listen_events, // back to the main thread from a coroutine thread? lua_State* mainthread{ L }; - luaL_checkstack(mainthread, 1, nullptr); - LuaListener::ptr_t listener; - // Does the main thread already have a LuaListener stored in the registry? - // That is, has this Lua chunk already called listen_events()? - auto keytype{ lua_getfield(mainthread, LUA_REGISTRYINDEX, "event.listener") }; - llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER); - if (keytype == LUA_TNUMBER) - { - // We do already have a LuaListener. Retrieve it. - int isint; - listener = LuaListener::getInstance(lua_tointegerx(mainthread, -1, &isint)); - // pop the int "event.listener" key - lua_pop(mainthread, 1); - // Nobody should have destroyed this LuaListener instance! - llassert(isint && listener); - } - else - { - // pop the nil "event.listener" key - lua_pop(mainthread, 1); - // instantiate a new LuaListener, binding the mainthread state -- but - // use a no-op deleter: we do NOT want to delete this new LuaListener - // on return from listen_events()! - listener.reset(new LuaListener(mainthread), [](LuaListener*){}); - // set its key in the field where we'll look for it later - lua_pushinteger(mainthread, listener->getKey()); - lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.listener"); - } + auto listener{ LuaState::obtainListener(mainthread) }; // Now that we've found or created our LuaListener, store the passed Lua // function as the callback. Beware: our caller passed the function on L's |