有没有办法在Lua文件中调用require
,并让模块设置调用它的文件的环境?例如,如果我有一个DSL(特定于域的语言)来定义表中定义的函数Root
和Sequence
,我可以在模块中使用setfenv(1, dslEnv)
之类的东西来允许我访问像全局变量这样的函数吗?
我想到的目标是使用这个行为树DSL的方式使我的定义文件看起来像这样(或尽可能接近它):
require "behaviortrees"
return Root {
Sequence {
Leaf "leafname",
Leaf "leafname"
}
}
无需明确地将Root
,Sequence
和Leaf
纳入范围,或者必须限定behaviortrees.Sequence
等名称。
简而言之,我正在努力使定义文件尽可能干净,没有任何多余的线条使树的定义混乱。
答案 0 :(得分:3)
setfenv(1, dslEnv)
的内容来访问全局变量等函数吗?当然可以。您只需要确定要使用的正确堆栈级别,而不是1
调用中的setfenv
。通常,您使用带有debug.getinfo
调用的循环向上走,然后在堆栈上找到require
函数,然后再移动一些直到找到下一个主要块(仅在有人在函数中调用require
。这是您必须与setfenv
一起使用的堆栈级别。但我可以建议......
require
是可插拔的。您可以向package.loaders
数组添加一个函数(称为搜索器),require
在尝试加载模块时会调用它。我们假设您的所有DSL文件都有.bt
后缀,而不是通常的.lua
。然后,您将使用普通Lua搜索器的重新实现,以及您查找.bt
个文件而不是.lua
个文件的差异,并且您需要调用{{ 1 {}在由setfenv
编辑的return
函数上。像这样:
loadfile
如果你把它放在一个模块中并且local function Root( x ) return x end
local function Sequence( x ) return x end
local function Leaf( x ) return x end
local delim = package.config:match( "^(.-)\n" ):gsub( "%%", "%%%%" )
local function searchpath( name, path )
local pname = name:gsub( "%.", delim ):gsub( "%%", "%%%%" )
local msg = {}
for subpath in path:gmatch( "[^;]+" ) do
local fpath = subpath:gsub( "%?", pname ):gsub("%.lua$", ".bt") -- replace suffix
local f = io.open( fpath, "r" )
if f then
f:close()
return fpath
end
msg[ #msg+1 ] = "\n\tno file '"..fpath.."'"
end
return nil, table.concat( msg )
end
local function bt_searcher( modname )
assert( type( modname ) == "string" )
local filename, msg = searchpath( modname, package.path )
if not filename then
return msg
end
local env = { -- create custom environment
Root = Root,
Sequence = Sequence,
Leaf = Leaf,
}
local mod, msg = loadfile( filename )
if not mod then
error( "error loading module '"..modname.."' from file '"..filename..
"':\n\t"..msg, 0 )
end
setfenv( mod, env ) -- set custom environment
return mod, filename
end
table.insert( package.loaders, bt_searcher )
一次从你的主程序中,那么你可以require
你的DSL文件使用来自你require
文件的自定义环境您的.bt
文件也是如此。你甚至不需要DSL文件中的.lua
。 E.g:
档案require("behaviortrees")
:
xxx.bt
档案return Root {
Sequence {
Leaf "leafname",
Leaf "leafname"
}
}
:
main.lua
答案 1 :(得分:2)
至少在Lua 5.2中,delete PullToRefresh;
是确定环境表的本地。您可以更改任何功能的环境,基本上是块。
_ENV
另一种方法是自动复制每个字段:
_ENV = behaviortrees;
但是,从do
_ENV = _ENV or _G;
for k, v in next, behaviortrees do
_ENV[k] = v;
end
end
手动本地化每个字段可能更有效。
答案 2 :(得分:2)
模块“behaviortrees.lua”
local behaviortrees = {
-- insert your code for these functions
Root = function(...) ... end,
Sequence = function(...) ... end,
Leaf = function(...) ... end,
}
-- Now set the environment of the caller. Two ways are available:
-- If you want to make DSL environment isolated from Lua globals
-- (for example, "require" and "print" functions will not be available
-- after executing require "behaviortrees")
setfenv(3, behaviortrees)
-- or
-- If you want to preserve all globals for DSL
setfenv(3, setmetatable(behaviortrees, {__index = getfenv(3)}))
主要Lua计划:
require "behaviortrees"
return Root {
Sequence {
Leaf "leafname",
Leaf "leafname"
}
}