1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-18 13:12:50 +00:00

Merge branch 'lua_pairs' into 'master'

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

See merge request OpenMW/openmw!1628
This commit is contained in:
Evil Eye 2022-02-06 22:10:21 +00:00
commit 7a7a95407a
7 changed files with 57 additions and 33 deletions

View File

@ -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();

View File

@ -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>>
{

View File

@ -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);
}

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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