summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2024-02-06 14:49:45 -0500
committerNat Goodspeed <nat@lindenlab.com>2024-02-06 14:49:45 -0500
commitf664c2ea26fb63f162f3d988b6d00f1483be5d45 (patch)
treed02bd5cfad21c6016a71a9707ef975ff3c8a5cc0 /indra/newview
parenteb0c19b2746890eb4dbfd6eac045699d0e5842dd (diff)
Break out lua_function.h,.cpp and lualistener.h,.cpp.
The intention is to decentralize Luau entry points into our C++ code, permitting a given entry point to be added to the .cpp file that already deals with that class or functional area. Continuing to add every such entry point to llluamanager.cpp doesn't scale well. Extract LuaListener class from llluamanager.cpp to its own header and .cpp file. Extract from llluamanager into lua_function.h (and .cpp) declarations useful for adding a lua_function Luau entry point, e.g.: lua_register() lua_rawlen() lua_tostdstring() lua_pushstdstring() lua_tollsd() lua_pushllsd() LuaPopper lua_function() and LuaFunction class LuaState lua_what lua_stack DebugExit
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/llluamanager.cpp798
1 files changed, 6 insertions, 792 deletions
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
index 3e3ce45cb0..596d8cab56 100644
--- a/indra/newview/llluamanager.cpp
+++ b/indra/newview/llluamanager.cpp
@@ -28,14 +28,10 @@
#include "llviewerprecompiledheaders.h"
#include "llluamanager.h"
-#include "hexdump.h"
#include "llerror.h"
#include "lleventcoro.h"
-#include "lleventfilter.h"
-#include "llevents.h"
-#include "llinstancetracker.h"
-#include "llleaplistener.h"
-#include "lluuid.h"
+#include "lua_function.h"
+#include "lualistener.h"
#include "stringize.h"
// skip all these link dependencies for integration testing
@@ -55,230 +51,10 @@ extern LLUIListener sUIListener;
#include "luau/luaconf.h"
#include "luau/lualib.h"
-#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n)))
-#define lua_rawlen lua_objlen
-
-#include <algorithm>
-#include <cstdlib> // std::rand()
-#include <cstring> // std::memcpy()
-#include <map>
-#include <memory> // std::unique_ptr
#include <sstream>
#include <string_view>
#include <vector>
-std::string lua_tostdstring(lua_State* L, int index);
-void lua_pushstdstring(lua_State* L, const std::string& str);
-LLSD lua_tollsd(lua_State* L, int index);
-void lua_pushllsd(lua_State* L, const LLSD& data);
-
-/**
- * LuaListener is based on LLLeap. It serves an analogous function.
- *
- * Each LuaListener instance has an int key, generated randomly to
- * inconvenience malicious Lua scripts wanting to mess with others. The idea
- * is that a given lua_State stores in its Registry:
- * - "event.listener": the int key of the corresponding LuaListener, if any
- * - "event.function": the Lua function to be called with incoming events
- * The original thought was that LuaListener would itself store the Lua
- * function -- but surprisingly, there is no C/C++ type in the API that stores
- * a Lua function.
- *
- * (We considered storing in "event.listener" the LuaListener pointer itself
- * as a light userdata, but the problem would be if Lua code overwrote that.
- * We want to prevent any Lua script from crashing the viewer, intentionally
- * or otherwise. Safer to use a key lookup.)
- *
- * Like LLLeap, each LuaListener instance also has an associated
- * LLLeapListener to respond to LLEventPump management commands.
- */
-class LuaListener: public LLInstanceTracker<LuaListener, int>
-{
- using super = LLInstanceTracker<LuaListener, int>;
-public:
- LuaListener(lua_State* L):
- super(getUniqueKey()),
- mListener(
- new LLLeapListener(
- [L](LLEventPump& pump, const std::string& listener)
- { return connect(L, pump, listener); }))
- {
- mReplyConnection = connect(L, mReplyPump, "LuaListener");
- }
-
- LuaListener(const LuaListener&) = delete;
- LuaListener& operator=(const LuaListener&) = delete;
-
- ~LuaListener()
- {
- LL_DEBUGS("Lua") << "~LuaListener('" << mReplyPump.getName() << "')" << LL_ENDL;
- }
-
- std::string getReplyName() const { return mReplyPump.getName(); }
- std::string getCommandName() const { return mListener->getPumpName(); }
-
-private:
- static int getUniqueKey()
- {
- // Find a random key that does NOT already correspond to a LuaListener
- // instance. Passing a duplicate key to LLInstanceTracker would do Bad
- // Things.
- int key;
- do
- {
- key = std::rand();
- } while (LuaListener::getInstance(key));
- // This is theoretically racy, if we were instantiating new
- // LuaListeners on multiple threads. Don't.
- return key;
- }
-
- static LLBoundListener connect(lua_State* L, LLEventPump& pump, const std::string& listener)
- {
- return pump.listen(
- listener,
- [L, pumpname=pump.getName()](const LLSD& data)
- { return call_lua(L, pumpname, data); });
- }
-
- static bool call_lua(lua_State* L, const std::string& pump, const LLSD& data)
- {
- LL_INFOS("Lua") << "LuaListener::call_lua('" << pump << "', " << data << ")" << LL_ENDL;
- if (! lua_checkstack(L, 3))
- {
- LL_WARNS("Lua") << "Cannot extend Lua stack to call listen_events() callback"
- << LL_ENDL;
- return false;
- }
- // push the registered Lua callback function stored in our registry as
- // "event.function"
- lua_getfield(L, LUA_REGISTRYINDEX, "event.function");
- llassert(lua_isfunction(L, -1));
- // pass pump name
- lua_pushstdstring(L, pump);
- // then the data blob
- lua_pushllsd(L, data);
- // call the registered Lua listener function; allow it to return bool;
- // no message handler
- auto status = lua_pcall(L, 2, 1, 0);
- bool result{ false };
- if (status != LUA_OK)
- {
- LL_WARNS("Lua") << "Error in listen_events() callback: "
- << lua_tostdstring(L, -1) << LL_ENDL;
- }
- else
- {
- result = lua_toboolean(L, -1);
- }
- // discard either the error message or the bool return value
- lua_pop(L, 1);
- return result;
- }
-
-#ifndef LL_TEST
- LLEventStream mReplyPump{ LLUUID::generateNewID().asString() };
-#else
- LLEventLogProxyFor<LLEventStream> mReplyPump{ "luapump", false };
-#endif
- LLTempBoundListener mReplyConnection;
- std::unique_ptr<LLLeapListener> mListener;
-};
-
-/**
- * LuaPopper is an RAII struct whose role is to pop some number of entries
- * from the Lua stack if the calling function exits early.
- */
-struct LuaPopper
-{
- LuaPopper(lua_State* L, int count):
- mState(L),
- mCount(count)
- {}
-
- LuaPopper(const LuaPopper&) = delete;
- LuaPopper& operator=(const LuaPopper&) = delete;
-
- ~LuaPopper()
- {
- if (mCount)
- {
- lua_pop(mState, mCount);
- }
- }
-
- void disarm() { set(0); }
- void set(int count) { mCount = count; }
-
- lua_State* mState;
- int mCount;
-};
-
-/**
- * LuaFunction is a base class containing a static registry of its static
- * subclass call() methods. call() is NOT virtual: instead, each subclass
- * constructor passes a pointer to its distinct call() method to the base-
- * class constructor, along with a name by which to register that method.
- *
- * The init() method walks the registry and registers each such name with the
- * passed lua_State.
- */
-class LuaFunction
-{
-public:
- LuaFunction(const std::string_view& name, lua_CFunction function)
- {
- getRegistry().emplace(name, function);
- }
-
- static void init(lua_State* L)
- {
- for (const auto& pair: getRegistry())
- {
- lua_register(L, pair.first.c_str(), pair.second);
- }
- }
-
- static lua_CFunction get(const std::string& key)
- {
- // use find() instead of subscripting to avoid creating an entry for
- // unknown key
- const auto& registry{ getRegistry() };
- auto found{ registry.find(key) };
- return (found == registry.end())? nullptr : found->second;
- }
-
-private:
- using Registry = std::map<std::string, lua_CFunction>;
- static Registry& getRegistry()
- {
- // use a function-local static to ensure it's initialized
- static Registry registry;
- return registry;
- }
-};
-
-/**
- * lua_function(name) is a macro to facilitate defining C++ functions
- * available to Lua. It defines a subclass of LuaFunction and declares a
- * static instance of that subclass, thereby forcing the compiler to call its
- * constructor at module initialization time. The constructor passes the
- * stringized instance name to its LuaFunction base-class constructor, along
- * with a pointer to the static subclass call() method. It then emits the
- * call() method definition header, to be followed by a method body enclosed
- * in curly braces as usual.
- */
-#define lua_function(name) \
-static struct name##_ : public LuaFunction \
-{ \
- name##_(): LuaFunction(#name, &call) {} \
- static int call(lua_State* L); \
-} name; \
-int name##_::call(lua_State* L)
-// {
-// ... supply method body here, referencing 'L' ...
-// }
-
/*
// This function consumes ALL Lua stack arguments and returns concatenated
// message string
@@ -484,80 +260,6 @@ lua_function(await_event)
return 1;
}
-/**
- * RAII class to manage the lifespan of a lua_State
- */
-class LuaState
-{
-public:
- LuaState(const std::string_view& desc, LLLUAmanager::script_finished_fn cb):
- mDesc(desc),
- mCallback(cb),
- mState(luaL_newstate())
- {
- luaL_openlibs(mState);
- LuaFunction::init(mState);
- // Try to make print() write to our log.
- lua_register(mState, "print", LuaFunction::get("print_info"));
- }
-
- LuaState(const LuaState&) = delete;
- LuaState& operator=(const LuaState&) = delete;
-
- ~LuaState()
- {
- // Did somebody call listen_events() 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)
- {
- // 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;
- }
- }
-
- lua_close(mState);
-
- if (mCallback)
- {
- // mError potentially set by previous checkLua() call(s)
- mCallback(mError);
- }
- }
-
- bool checkLua(int r)
- {
- if (r != LUA_OK)
- {
- mError = lua_tostring(mState, -1);
- lua_pop(mState, 1);
-
- LL_WARNS() << mDesc << ": " << mError << LL_ENDL;
- return false;
- }
- return true;
- }
-
- operator lua_State*() const { return mState; }
-
-private:
- std::string mDesc;
- LLLUAmanager::script_finished_fn mCallback;
- lua_State* mState;
- std::string mError;
-};
-
void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn cb)
{
std::string desc{ stringize("runScriptFile('", filename, "')") };
@@ -580,16 +282,9 @@ void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn
if (in_file.is_open())
{
- std::string text((std::istreambuf_iterator<char>(in_file)), std::istreambuf_iterator<char>());
-
- size_t bytecodeSize = 0;
- char *bytecode = luau_compile(text.c_str(), text.length(), NULL, &bytecodeSize);
- L.checkLua(luau_load(L, desc.c_str(), bytecode, bytecodeSize, 0));
- free(bytecode);
-
- L.checkLua(lua_pcall(L, 0, 0, 0));
-
- in_file.close();
+ std::string text{std::istreambuf_iterator<char>(in_file),
+ std::istreambuf_iterator<char>()};
+ L.checkLua(lluau::dostring(L, desc, text));
}
else
{
@@ -613,12 +308,7 @@ void LLLUAmanager::runScriptLine(const std::string& cmd, script_finished_fn cb)
LLCoros::instance().launch(desc, [desc, cmd, cb]()
{
LuaState L(desc, cb);
-
- size_t bytecodeSize = 0;
- char *bytecode = luau_compile(cmd.c_str(), cmd.length(), NULL, &bytecodeSize);
- L.checkLua(luau_load(L, desc.c_str(), bytecode, bytecodeSize, 0));
- free(bytecode);
- L.checkLua(lua_pcall(L, 0, 0, 0));
+ L.checkLua(lluau::dostring(L, desc, cmd));
});
}
@@ -642,479 +332,3 @@ void LLLUAmanager::runScriptOnLogin()
runScriptFile(filename);
#endif // ! LL_TEST
}
-
-std::string lua_tostdstring(lua_State* L, int index)
-{
- size_t len;
- const char* strval{ lua_tolstring(L, index, &len) };
- return { strval, len };
-}
-
-void lua_pushstdstring(lua_State* L, const std::string& str)
-{
- luaL_checkstack(L, 1, nullptr);
- lua_pushlstring(L, str.c_str(), str.length());
-}
-
-// Usage: std::cout << lua_what(L, stackindex) << ...;
-// Reports on the Lua value found at the passed stackindex.
-// If cast to std::string, returns the corresponding string value.
-class lua_what
-{
-public:
- lua_what(lua_State* state, int idx):
- L(state),
- index(idx)
- {}
-
- friend std::ostream& operator<<(std::ostream& out, const lua_what& self)
- {
- switch (lua_type(self.L, self.index))
- {
- case LUA_TNONE:
- // distinguish acceptable but non-valid index
- out << "none";
- break;
-
- case LUA_TNIL:
- out << "nil";
- break;
-
- case LUA_TBOOLEAN:
- {
- auto oldflags { out.flags() };
- out << std::boolalpha << lua_toboolean(self.L, self.index);
- out.flags(oldflags);
- break;
- }
-
- case LUA_TNUMBER:
- out << lua_tonumber(self.L, self.index);
- break;
-
- case LUA_TSTRING:
- out << std::quoted(lua_tostdstring(self.L, self.index));
- break;
-
- case LUA_TUSERDATA:
- {
- const S32 maxlen = 20;
- S32 binlen{ lua_rawlen(self.L, self.index) };
- LLSD::Binary binary(std::min(maxlen, binlen));
- std::memcpy(binary.data(), lua_touserdata(self.L, self.index), binary.size());
- out << LL::hexdump(binary);
- if (binlen > maxlen)
- {
- out << "...(" << (binlen - maxlen) << " more)";
- }
- break;
- }
-
- case LUA_TLIGHTUSERDATA:
- out << lua_touserdata(self.L, self.index);
- break;
-
- default:
- // anything else, don't bother trying to report value, just type
- out << lua_typename(self.L, lua_type(self.L, self.index));
- break;
- }
- return out;
- }
-
- operator std::string() const { return stringize(*this); }
-
-private:
- lua_State* L;
- int index;
-};
-
-// Usage: std::cout << lua_stack(L) << ...;
-// Reports on the contents of the Lua stack.
-// If cast to std::string, returns the corresponding string value.
-class lua_stack
-{
-public:
- lua_stack(lua_State* state):
- L(state)
- {}
-
- friend std::ostream& operator<<(std::ostream& out, const lua_stack& self)
- {
- const char* sep = "stack: [";
- for (int index = 1; index <= lua_gettop(self.L); ++index)
- {
- out << sep << lua_what(self.L, index);
- sep = ", ";
- }
- out << ']';
- return out;
- }
-
- operator std::string() const { return stringize(*this); }
-
-private:
- lua_State* L;
-};
-
-// log exit from any block declaring an instance of DebugExit, regardless of
-// how control leaves that block
-struct DebugExit
-{
- DebugExit(const std::string& name): mName(name) {}
- DebugExit(const DebugExit&) = delete;
- DebugExit& operator=(const DebugExit&) = delete;
- ~DebugExit()
- {
- LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL;
- }
-
- std::string mName;
-};
-
-// By analogy with existing lua_tomumble() functions, return an LLSD object
-// corresponding to the Lua object at stack index 'index' in state L.
-// This function assumes that a Lua caller is fully aware that they're trying
-// to call a viewer function. In other words, the caller must specifically
-// construct Lua data convertible to LLSD.
-//
-// For proper error handling, we REQUIRE that the Lua runtime be compiled as
-// C++ so errors are raised as C++ exceptions rather than as longjmp() calls:
-// http://www.lua.org/manual/5.4/manual.html#4.4
-// "Internally, Lua uses the C longjmp facility to handle errors. (Lua will
-// use exceptions if you compile it as C++; search for LUAI_THROW in the
-// source code for details.)"
-// Some blocks within this function construct temporary C++ objects in the
-// expectation that these objects will be properly destroyed even if code
-// reached by that block raises a Lua error.
-LLSD lua_tollsd(lua_State* L, int index)
-{
- LL_DEBUGS("Lua") << "lua_tollsd(" << index << ") of " << lua_gettop(L) << " stack entries: "
- << lua_what(L, index) << LL_ENDL;
- DebugExit log_exit("lua_tollsd()");
- switch (lua_type(L, index))
- {
- case LUA_TNONE:
- // Should LUA_TNONE be an error instead of returning isUndefined()?
- case LUA_TNIL:
- return {};
-
- case LUA_TBOOLEAN:
- return bool(lua_toboolean(L, index));
-
- case LUA_TNUMBER:
- {
- // check if integer truncation leaves the number intact
- int isint;
- lua_Integer intval{ lua_tointegerx(L, index, &isint) };
- if (isint)
- {
- return LLSD::Integer(intval);
- }
- else
- {
- return lua_tonumber(L, index);
- }
- }
-
- case LUA_TSTRING:
- return lua_tostdstring(L, index);
-
- case LUA_TUSERDATA:
- {
- LLSD::Binary binary(lua_rawlen(L, index));
- std::memcpy(binary.data(), lua_touserdata(L, index), binary.size());
- return binary;
- }
-
- case LUA_TTABLE:
- {
- // A Lua table correctly constructed to convert to LLSD will have
- // either consecutive integer keys starting at 1, which we represent
- // as an LLSD array (with Lua key 1 at C++ index 0), or will have
- // all string keys.
- //
- // In the belief that Lua table traversal skips "holes," that is, it
- // doesn't report any key/value pair whose value is nil, we allow a
- // table with integer keys >= 1 but with "holes." This produces an
- // LLSD array with isUndefined() entries at unspecified keys. There
- // would be no other way for a Lua caller to construct an
- // isUndefined() LLSD array entry. However, to guard against crazy int
- // keys, we forbid gaps larger than a certain size: crazy int keys
- // could result in a crazy large contiguous LLSD array.
- //
- // Possible looseness could include:
- // - A mix of integer and string keys could produce an LLSD map in
- // which the integer keys are converted to string. (Key conversion
- // must be performed in C++, not Lua, to avoid confusing
- // lua_next().)
- // - However, since in Lua t[0] and t["0"] are distinct table entries,
- // do not consider converting numeric string keys to int to return
- // an LLSD array.
- // But until we get more experience with actual Lua scripts in
- // practice, let's say that any deviation is a Lua coding error.
- // An important property of the strict definition above is that most
- // conforming data blobs can make a round trip across the language
- // boundary and still compare equal. A non-conforming data blob would
- // lose that property.
- // Known exceptions to round trip identity:
- // - Empty LLSD map and empty LLSD array convert to empty Lua table.
- // But empty Lua table converts to isUndefined() LLSD object.
- // - LLSD::Real with integer value returns as LLSD::Integer.
- // - LLSD::UUID, LLSD::Date and LLSD::URI all convert to Lua string,
- // and so return as LLSD::String.
- // - Lua does not store any table key whose value is nil. An LLSD
- // array with isUndefined() entries produces a Lua table with
- // "holes" in the int key sequence; this converts back to an LLSD
- // array containing corresponding isUndefined() entries -- except
- // when one or more of the final entries isUndefined(). These are
- // simply dropped, producing a shorter LLSD array than the original.
- // - For the same reason, any keys in an LLSD map whose value
- // isUndefined() are simply discarded in the converted Lua table.
- // This converts back to an LLSD map lacking those keys.
- // - If it's important to preserve the original length of an LLSD
- // array whose final entries are undefined, or the full set of keys
- // for an LLSD map some of whose values are undefined, store an
- // LLSD::emptyArray() or emptyMap() instead. These will be
- // represented in Lua as empty table, which should convert back to
- // undefined LLSD. Naturally, though, those won't survive a second
- // round trip.
-
- // This is the most important of the luaL_checkstack() calls because a
- // deeply nested Lua structure will enter this case at each level, and
- // we'll need another 2 stack slots to traverse each nested table.
- luaL_checkstack(L, 2, nullptr);
- // BEFORE we push nil to initialize the lua_next() traversal, convert
- // 'index' to absolute! Our caller might have passed a relative index;
- // we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we
- // push nil, what we find at index -1 is nil, not the table!
- index = lua_absindex(L, index);
- LL_DEBUGS("Lua") << "checking for empty table" << LL_ENDL;
- lua_pushnil(L); // first key
- LL_DEBUGS("Lua") << lua_stack(L) << LL_ENDL;
- if (! lua_next(L, index))
- {
- // it's a table, but the table is empty -- no idea if it should be
- // modeled as empty array or empty map -- return isUndefined(),
- // which can be consumed as either
- LL_DEBUGS("Lua") << "empty table" << LL_ENDL;
- return {};
- }
- // key is at stack index -2, value at index -1
- // from here until lua_next() returns 0, have to lua_pop(2) if we
- // return early
- LuaPopper popper(L, 2);
- // Remember the type of the first key
- auto firstkeytype{ lua_type(L, -2) };
- LL_DEBUGS("Lua") << "table not empty, first key type " << lua_typename(L, firstkeytype)
- << LL_ENDL;
- switch (firstkeytype)
- {
- case LUA_TNUMBER:
- {
- // First Lua key is a number: try to convert table to LLSD array.
- // This is tricky because we don't know in advance the size of the
- // array. The Lua reference manual says that lua_rawlen() is the
- // same as the length operator '#'; but the length operator states
- // that it might stop at any "hole" in the subject table.
- // Moreover, the Lua next() function (and presumably lua_next())
- // traverses a table in unspecified order, even for numeric keys
- // (emphasized in the doc).
- // Make a preliminary pass over the whole table to validate and to
- // collect keys.
- std::vector<LLSD::Integer> keys;
- // Try to determine the length of the table. If the length
- // operator is truthful, avoid allocations while we grow the keys
- // vector. Even if it's not, we can still grow the vector, albeit
- // a little less efficiently.
- keys.reserve(lua_objlen(L, index));
- do
- {
- auto arraykeytype{ lua_type(L, -2) };
- switch (arraykeytype)
- {
- case LUA_TNUMBER:
- {
- int isint;
- lua_Integer intkey{ lua_tointegerx(L, -2, &isint) };
- if (! isint)
- {
- // key isn't an integer - this doesn't fit our LLSD
- // array constraints
- luaL_error(L, "Expected integer array key, got %f instead", lua_tonumber(L, -2));
- return 0;
- }
- if (intkey < 1)
- {
- luaL_error(L, "array key %d out of bounds", int(intkey));
- return 0;
- }
-
- keys.push_back(LLSD::Integer(intkey));
- break;
- }
-
- case LUA_TSTRING:
- // break out strings specially to report the value
- luaL_error(L, "Cannot convert string array key '%s' to LLSD", lua_tostring(L, -2));
- return 0;
-
- default:
- luaL_error(L, "Cannot convert %s array key to LLSD", lua_typename(L, arraykeytype));
- return 0;
- }
-
- // remove value, keep key for next iteration
- lua_pop(L, 1);
- } while (lua_next(L, index) != 0);
- popper.disarm();
- // Table keys are all integers: are they reasonable integers?
- // Arbitrary max: may bite us, but more likely to protect us
- size_t array_max{ 10000 };
- if (keys.size() > array_max)
- {
- luaL_error(L, "Conversion from Lua to LLSD array limited to %d entries", int(array_max));
- return 0;
- }
- // We know the smallest key is >= 1. Check the largest. We also
- // know the vector is NOT empty, else we wouldn't have gotten here.
- std::sort(keys.begin(), keys.end());
- LLSD::Integer highkey = *keys.rbegin();
- if ((highkey - LLSD::Integer(keys.size())) > 100)
- {
- // Looks like we've gone beyond intentional array gaps into
- // crazy key territory.
- luaL_error(L, "Gaps in Lua table too large for conversion to LLSD array");
- return 0;
- }
- LL_DEBUGS("Lua") << "collected " << keys.size() << " keys, max " << highkey << LL_ENDL;
- // right away expand the result array to the size we'll need
- LLSD result{ LLSD::emptyArray() };
- result[highkey - 1] = LLSD();
- // Traverse the table again, and this time populate result array.
- lua_pushnil(L); // first key
- while (lua_next(L, index))
- {
- // key at stack index -2, value at index -1
- // We've already validated lua_tointegerx() for each key.
- auto key{ lua_tointeger(L, -2) };
- LL_DEBUGS("Lua") << "key " << key << ':' << LL_ENDL;
- // Don't forget to subtract 1 from Lua key for LLSD subscript!
- result[LLSD::Integer(key) - 1] = lua_tollsd(L, -1);
- // remove value, keep key for next iteration
- lua_pop(L, 1);
- }
- return result;
- }
-
- case LUA_TSTRING:
- {
- // First Lua key is a string: try to convert table to LLSD map
- LLSD result{ LLSD::emptyMap() };
- do
- {
- auto mapkeytype{ lua_type(L, -2) };
- if (mapkeytype != LUA_TSTRING)
- {
- luaL_error(L, "Cannot convert %s map key to LLSD", lua_typename(L, mapkeytype));
- return 0;
- }
-
- auto key{ lua_tostdstring(L, -2) };
- LL_DEBUGS("Lua") << "map key " << std::quoted(key) << ':' << LL_ENDL;
- result[key] = lua_tollsd(L, -1);
- // remove value, keep key for next iteration
- lua_pop(L, 1);
- } while (lua_next(L, index) != 0);
- popper.disarm();
- return result;
- }
-
- default:
- // First Lua key isn't number or string: sorry
- luaL_error(L, "Cannot convert %s table key to LLSD", lua_typename(L, firstkeytype));
- return 0;
- }
- }
-
- default:
- // Other Lua entities (e.g. function, C function, light userdata,
- // thread, userdata) are not convertible to LLSD, indicating a coding
- // error in the caller.
- luaL_error(L, "Cannot convert type %s to LLSD", luaL_typename(L, index));
- return 0;
- }
-}
-
-// By analogy with existing lua_pushmumble() functions, push onto state L's
-// stack a Lua object corresponding to the passed LLSD object.
-void lua_pushllsd(lua_State* L, const LLSD& data)
-{
- // might need 2 slots for array or map
- luaL_checkstack(L, 2, nullptr);
- switch (data.type())
- {
- case LLSD::TypeUndefined:
- lua_pushnil(L);
- break;
-
- case LLSD::TypeBoolean:
- lua_pushboolean(L, data.asBoolean());
- break;
-
- case LLSD::TypeInteger:
- lua_pushinteger(L, data.asInteger());
- break;
-
- case LLSD::TypeReal:
- lua_pushnumber(L, data.asReal());
- break;
-
- case LLSD::TypeBinary:
- {
- auto binary{ data.asBinary() };
- std::memcpy(lua_newuserdata(L, binary.size()),
- binary.data(), binary.size());
- break;
- }
-
- case LLSD::TypeMap:
- {
- // push a new table with space for our non-array keys
- lua_createtable(L, 0, data.size());
- for (const auto& pair: llsd::inMap(data))
- {
- // push value -- so now table is at -2, value at -1
- lua_pushllsd(L, pair.second);
- // pop value, assign to table[key]
- lua_setfield(L, -2, pair.first.c_str());
- }
- break;
- }
-
- case LLSD::TypeArray:
- {
- // push a new table with space for array entries
- lua_createtable(L, data.size(), 0);
- lua_Integer key{ 0 };
- for (const auto& item: llsd::inArray(data))
- {
- // push new array value: table at -2, value at -1
- lua_pushllsd(L, item);
- // pop value, assign table[key] = value
- lua_rawseti(L, -2, ++key);
- }
- break;
- }
-
- case LLSD::TypeString:
- case LLSD::TypeUUID:
- case LLSD::TypeDate:
- case LLSD::TypeURI:
- default:
- {
- lua_pushstdstring(L, data.asString());
- break;
- }
- }
-}