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;
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 16;
api["API_REVISION"] = 17;
api["quit"] = [lua]()
{
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();

@ -72,7 +72,7 @@ namespace MWLua
else
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>>
{

@ -39,6 +39,7 @@ return {
-- should throw an error
incorrectRequire = function() require('counter') end,
modifySystemLib = function() math.sin = 5 end,
modifySystemLib2 = function() math.__index.sin = 5 end,
rawsetSystemLib = function() rawset(math, 'sin', 5) end,
callLoadstring = function() loadstring('print(1)') 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
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["modifySystemLib2"]), "a nil value");
EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script);
}

@ -58,20 +58,41 @@ namespace LuaUtil
mLua["math"]["randomseed"] = []{};
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
mLua.script(R"(printToLog = function(name, ...)
local msg = name
for _, v in ipairs({...}) do
msg = msg .. '\t' .. tostring(v)
mLua["cmetatable"] = [](const sol::table& v) -> sol::object { return v[sol::metatable_key]; };
mLua.script(R"(
local _pairs = pairs
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
return writeToLog(msg)
end)");
mLua.script("printGen = function(name) return function(...) return printToLog(name, ...) end end");
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
if (mLua["unpack"] == sol::nil)
mLua["unpack"] = mLua["table"]["unpack"];
else if (mLua["table"]["unpack"] == sol::nil)
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["_VERSION"] = mLua["_VERSION"];
@ -106,24 +127,22 @@ namespace LuaUtil
if (table.is<sol::userdata>())
return table; // it is already userdata, no sense to wrap it again
lua_State* lua = table.lua_state();
table[sol::meta_function::index] = table;
lua_newuserdata(lua, 0);
sol::stack::push(lua, std::move(table));
lua_setmetatable(lua, -2);
return sol::stack::pop<sol::table>(lua);
lua_State* luaState = table.lua_state();
sol::state_view lua(luaState);
sol::table meta(lua, sol::create);
meta["__index"] = table;
meta["__pairs"] = lua["pairsForReadOnly"];
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)
{
lua_State* lua = ro.lua_state();
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;
return ro[sol::metatable_key].get<sol::table>()["__index"];
}
void LuaState::addCommonPackage(std::string packageName, sol::object package)

@ -439,10 +439,7 @@ namespace LuaUtil
mEventHandlers.clear();
mSimulationTimersQueue.clear();
mGameTimersQueue.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)

@ -4,7 +4,7 @@ Overview of Lua scripting
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.
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>`__:
``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:
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.
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.

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

Loading…
Cancel
Save