Proper support of `pairs` and `ipairs` in Lua; fix bug in `makeReadOnly`.

C++20
Petr Mikheev 3 years ago committed by Evil Eye
parent becffef142
commit a294adcdaf

@ -49,7 +49,7 @@ namespace MWLua
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create); sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 16; api["API_REVISION"] = 17;
api["quit"] = [lua]() api["quit"] = [lua]()
{ {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();

@ -72,7 +72,7 @@ namespace MWLua
else else
throw std::runtime_error("Index out of range"); throw std::runtime_error("Index out of range");
}; };
listT["ipairs"] = [registry](const ListT& list) listT[sol::meta_function::ipairs] = [registry](const ListT& list)
{ {
auto iter = [registry](const ListT& l, int64_t i) -> sol::optional<std::tuple<int64_t, ObjectT>> auto iter = [registry](const ListT& l, int64_t i) -> sol::optional<std::tuple<int64_t, ObjectT>>
{ {

@ -39,6 +39,7 @@ return {
-- should throw an error -- should throw an error
incorrectRequire = function() require('counter') end, incorrectRequire = function() require('counter') end,
modifySystemLib = function() math.sin = 5 end, modifySystemLib = function() math.sin = 5 end,
modifySystemLib2 = function() math.__index.sin = 5 end,
rawsetSystemLib = function() rawset(math, 'sin', 5) end, rawsetSystemLib = function() rawset(math, 'sin', 5) end,
callLoadstring = function() loadstring('print(1)') end, callLoadstring = function() loadstring('print(1)') end,
setSqr = function() require('sqrlib').sqr = math.sin end, setSqr = function() require('sqrlib').sqr = math.sin end,
@ -119,6 +120,7 @@ return {
// but read-only object can not be modified even with rawset // but read-only object can not be modified even with rawset
EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)"); EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)");
EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value"); EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value");
EXPECT_ERROR(LuaUtil::call(script["modifySystemLib2"]), "a nil value");
EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script); EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script);
} }

@ -58,20 +58,41 @@ namespace LuaUtil
mLua["math"]["randomseed"] = []{}; mLua["math"]["randomseed"] = []{};
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
mLua.script(R"(printToLog = function(name, ...) mLua["cmetatable"] = [](const sol::table& v) -> sol::object { return v[sol::metatable_key]; };
local msg = name mLua.script(R"(
for _, v in ipairs({...}) do local _pairs = pairs
msg = msg .. '\t' .. tostring(v) local _ipairs = ipairs
local _tostring = tostring
local _write = writeToLog
local printToLog = function(name, ...)
local msg = name
for _, v in _ipairs({...}) do
msg = msg .. '\t' .. _tostring(v)
end
return _write(msg)
end end
return writeToLog(msg) printGen = function(name) return function(...) return printToLog(name, ...) end end
end)");
mLua.script("printGen = function(name) return function(...) return printToLog(name, ...) end end"); local _cmeta = cmetatable
function pairsForReadOnly(v) return _pairs(_cmeta(v).__index) end
function ipairsForReadOnly(v) return _ipairs(_cmeta(v).__index) end
)");
// Some fixes for compatibility between different Lua versions // Some fixes for compatibility between different Lua versions
if (mLua["unpack"] == sol::nil) if (mLua["unpack"] == sol::nil)
mLua["unpack"] = mLua["table"]["unpack"]; mLua["unpack"] = mLua["table"]["unpack"];
else if (mLua["table"]["unpack"] == sol::nil) else if (mLua["table"]["unpack"] == sol::nil)
mLua["table"]["unpack"] = mLua["unpack"]; mLua["table"]["unpack"] = mLua["unpack"];
if (LUA_VERSION_NUM <= 501)
{
mLua.script(R"(
local _pairs = pairs
local _ipairs = ipairs
local _cmeta = cmetatable
pairs = function(v) return ((_cmeta(v) or v).__pairs or _pairs)(v) end
ipairs = function(v) return ((_cmeta(v) or v).__ipairs or _ipairs)(v) end
)");
}
mSandboxEnv = sol::table(mLua, sol::create); mSandboxEnv = sol::table(mLua, sol::create);
mSandboxEnv["_VERSION"] = mLua["_VERSION"]; mSandboxEnv["_VERSION"] = mLua["_VERSION"];
@ -106,24 +127,22 @@ namespace LuaUtil
if (table.is<sol::userdata>()) if (table.is<sol::userdata>())
return table; // it is already userdata, no sense to wrap it again return table; // it is already userdata, no sense to wrap it again
lua_State* lua = table.lua_state(); lua_State* luaState = table.lua_state();
table[sol::meta_function::index] = table; sol::state_view lua(luaState);
lua_newuserdata(lua, 0); sol::table meta(lua, sol::create);
sol::stack::push(lua, std::move(table)); meta["__index"] = table;
lua_setmetatable(lua, -2); meta["__pairs"] = lua["pairsForReadOnly"];
return sol::stack::pop<sol::table>(lua); meta["__ipairs"] = lua["ipairsForReadOnly"];
lua_newuserdata(luaState, 0);
sol::stack::push(luaState, meta);
lua_setmetatable(luaState, -2);
return sol::stack::pop<sol::table>(luaState);
} }
sol::table getMutableFromReadOnly(const sol::userdata& ro) sol::table getMutableFromReadOnly(const sol::userdata& ro)
{ {
lua_State* lua = ro.lua_state(); return ro[sol::metatable_key].get<sol::table>()["__index"];
sol::stack::push(lua, ro);
int ok = lua_getmetatable(lua, -1);
assert(ok);
(void)ok;
sol::table res = sol::stack::pop<sol::table>(lua);
lua_pop(lua, 1);
return res;
} }
void LuaState::addCommonPackage(std::string packageName, sol::object package) void LuaState::addCommonPackage(std::string packageName, sol::object package)

@ -439,10 +439,7 @@ namespace LuaUtil
mEventHandlers.clear(); mEventHandlers.clear();
mSimulationTimersQueue.clear(); mSimulationTimersQueue.clear();
mGameTimersQueue.clear(); mGameTimersQueue.clear();
mPublicInterfaces.clear(); mPublicInterfaces.clear();
// Assigned by LuaUtil::makeReadOnly, but `clear` removes it, so we need to assign it again.
mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces;
} }
ScriptsContainer::Script& ScriptsContainer::getScript(int scriptId) ScriptsContainer::Script& ScriptsContainer::getScript(int scriptId)

@ -4,7 +4,7 @@ Overview of Lua scripting
Language and sandboxing Language and sandboxing
======================= =======================
OpenMW supports scripts written in Lua 5.1. OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2.
There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT. There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT.
Here are starting points for learning Lua: Here are starting points for learning Lua:
@ -24,11 +24,22 @@ These libraries are loaded automatically and are always available.
Allowed `basic functions <https://www.lua.org/manual/5.1/manual.html#5.1>`__: Allowed `basic functions <https://www.lua.org/manual/5.1/manual.html#5.1>`__:
``assert``, ``error``, ``ipairs``, ``next``, ``pairs``, ``pcall``, ``print``, ``select``, ``tonumber``, ``tostring``, ``type``, ``unpack``, ``xpcall``, ``rawequal``, ``rawget``, ``rawset``, ``getmetatable``, ``setmetatable``. ``assert``, ``error``, ``ipairs``, ``next``, ``pairs``, ``pcall``, ``print``, ``select``, ``tonumber``, ``tostring``, ``type``, ``unpack``, ``xpcall``, ``rawequal``, ``rawget``, ``rawset``, ``getmetatable``, ``setmetatable``.
Supported Lua 5.2 features:
- ``goto`` and ``::labels::``;
- hex escapes ``\x3F`` and ``\*`` escape in strings;
- ``math.log(x [,base])``;
- ``string.rep(s, n [,sep])``;
- in ``string.format()``: ``%q`` is reversible, ``%s`` uses ``__tostring``, ``%a`` and ``%A`` are added;
- String matching pattern ``%g``;
- ``__pairs`` and ``__ipairs`` metamethods;
- Function ``table.unpack`` (alias to Lua 5.1 ``unpack``).
Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way: Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way:
1. If `library_name` is one of the standard libraries, then return the library. 1. If `library_name` is one of the standard libraries, then return the library.
2. If `library_name` is one of the built-in `API packages`_, then return the package. 2. If `library_name` is one of the built-in `API packages`_, then return the package.
3. Otherwise search for a Lua source file with such name in :ref:`data folders <Multiple data folders>`. For example ``require('my_lua_library.something')`` will try to open the file ``my_lua_library/something.lua``. 3. Otherwise search for a Lua source file with such name in :ref:`data folders <Multiple data folders>`. For example ``require('my_lua_library.something')`` will try to open one of the files ``my_lua_library/something.lua`` or ``my_lua_library/something/init.lua``.
Loading DLLs and precompiled Lua files is intentionally prohibited for compatibility and security reasons. Loading DLLs and precompiled Lua files is intentionally prohibited for compatibility and security reasons.

@ -293,11 +293,6 @@
-- @type ObjectList -- @type ObjectList
-- @extends #list<#GameObject> -- @extends #list<#GameObject>
-------------------------------------------------------------------------------
-- Create iterator.
-- @function [parent=#ObjectList] ipairs
-- @param self
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Filter list with a Query. -- Filter list with a Query.
-- @function [parent=#ObjectList] select -- @function [parent=#ObjectList] select

Loading…
Cancel
Save