是否有一种简单的方法来获取在Lua代码文件中定义的所有全局变量?

时间:2019-03-28 03:02:45

标签: lua global-variables

每个人都知道,Lua中的变量(如果未明确定义为“局部”)将是全局变量。有时这会引起问题,例如覆盖库函数,或者意外地为另一个具有相同名称的全局变量提供值。因此,如果有一种方法可以找到在单个Lua代码文件中定义的所有全局变量,那将非常有帮助。

但是,我没有找到有关这个看似受欢迎的问题的任何线索。我可以上网获得的最佳答案是使用_G在环境中打印所有全局变量,这并没有太大帮助。我目前正在使用Emmylua在Intellij Idea中对Lua进行编码,Emmylua是一种功能强大的工具,可以以特殊的样式显示全局变量,并且可以轻松地将全局变量追溯到其定义。但是当代码变得很长时,这也无济于事。

所以基本上,我只想获取在给定的Lua代码文件中定义的全局变量的列表。使用工具或出色的功能。如果可以简化事情,我们可以假定代码文件是一个模块。如果它可以进一步打印这些全局变量的定义位置,那就更好了。有人可以帮我吗?

2 个答案:

答案 0 :(得分:0)

卢阿(Lua)无法判断何时引入全局变量。

特别是值是一个函数,debug.getinfo可以通过告诉您定义函数的位置(通常但不总是将函数全局化的位置)来提供帮助。 / p>

您可以在引入全局信息时捕获所需的信息。这可以通过在全局表上使用__newindex方法设置一个元表来完成。引入新的全局变量时(而不是覆盖现有全局变量时),将调用此方法。在这种方法中,您可以使用debug.getinfo找出呼叫者的来源。还要注意,如果您的任何其他代码都试图在全局环境中使用元表,则必须对其进行良好的操作。 (它只能有一个元表。)

您还可以避免使用全局表。这样做的一种中间方法是覆盖环境。在Lua 5.2和Lua 5.3中,这是通过声明一个名为_ENV的本地表来完成的-对全局表的所有访问都将访问该表。 (实际上,全局访问默认始终使用_ENV,默认情况下_ENV_G。)通过为该_ENV提供一个将访问权限转发到{{ 1}}(或任何其他环境)。此处的区别在于,即使_G中存在绑定,__newindex仍将被调用,因此此方法可以检测替代。

使用_G,尽管本质上是作用域的本地变量(例如,每个文件都需要覆盖它)。这样的钩子也可以全局安装。如果使用_ENV函数手动加载模块(不太可能),则可以仅提供自定义load作为参数。如果您使用_ENV,则可以通过覆盖require中的Lua搜索程序来获得执行之前已保存文件的保留。这是package.searchers[2]调用的内置函数,用于在文件系统中查找文件并加载。返回值是require然后运行的已加载函数。因此,在加载之后但返回到require之前,您可以使用require覆盖默认的debug.setupvalue值(如果有)。

示例代码(仅经过轻微测试):

_ENV

请注意,此处的钩子仅适用于运行此代码后所需的文件,并且基本上仅适用于通过内置local global_info = {} local default_searcher2 = package.searchers[2] package.searchers[2] = function(...) local result = default_searcher2(...) local parent_environment = _G local my_env = setmetatable({}, { __index = parent_environment, __newindex = function(self, k, v) local new_info = debug.getinfo(2) -- keeping rich data like this could be a memory leak -- if some globals are assigned repeatedly, but that -- may still be okay in a debugging scenario local history = global_info[k] if history == nil then history = {} global_info[k] = history end table.insert(history, {info = new_info, value = v}) parent_environment[k] = v end, }) if type(result) == "function" then debug.setupvalue(result, 1, my_env) end return result end function gethistory(name) local history = global_info[name] if history == nil then print('"' .. name .. '" has never been defined...') else print('History for "' .. name .. '":') for _, record in ipairs(history) do print(record.info.short_src .. ": " .. record.info.currentline) end end end 包含的Lua文件(不适用于C库)。它不会在全局环境中设置一个元表,因此不会在全局环境中发生冲突,但是如果文件直接访问require(或例如在目录中访问_G而不是_G,则可以绕开它他们自己的_ENV表)。也可以考虑此类问题,但这可能是一个兔子洞,具体取决于您需要此补丁的“不可见”程度。

在Lua 5.1中,您拥有_ENV而不是_ENV,我相信它可以起到类似的作用。

还要注意,我概述的所有方法只能检测在运行时实际执行的全局访问。

答案 1 :(得分:0)

是的。本地与全局是一个具有约束力的问题,它主要在编译时确定。当然,设置变量是在编译时确定的。

Lua提供了luac编译器,该编译器将参数-l用于列表。

在Lua 5.1中,有一个操作码SETGLOBAL。列指示语句的行号,注释指示全局名称。

在5.2及更高版本中,存在操作码SETTABUP。列指示语句的行号,注释指示表和键的名称。 _ENV高值引用的表中有“全局变量”。

因此,您可以使用Lua提供的工具轻松找到设置全局变量的任何语句的行号。

BTW-在许多模块系统中,模块脚本不会设置任何全局变量。