1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 09:35:28 +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; 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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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