我可以使用Lua的require来设置调用文件的环境吗?

时间:2017-07-31 21:11:49

标签: lua lua-5.1

有没有办法在Lua文件中调用require,并让模块设置调用它的文件的环境?例如,如果我有一个DSL(特定于域的语言)来定义表中定义的函数RootSequence,我可以在模块中使用setfenv(1, dslEnv)之类的东西来允许我访问像全局变量这样的函数吗?

我想到的目标是使用这个行为树DSL的方式使我的定义文件看起来像这样(或尽可能接近它):

require "behaviortrees"

return Root {
    Sequence {
        Leaf "leafname",
        Leaf "leafname"
    }
}

无需明确地将RootSequenceLeaf纳入范围,或者必须限定behaviortrees.Sequence等名称。

简而言之,我正在努力使定义文件尽可能干净,没有任何多余的线条使树的定义混乱。

3 个答案:

答案 0 :(得分:3)

我可以在模块中使用类似setfenv(1, dslEnv)的内容来访问全局变量等函数吗?

当然可以。您只需要确定要使用的正确堆栈级别,而不是1调用中的setfenv。通常,您使用带有debug.getinfo调用的循环向上走,然后在堆栈上找到require函数,然后再移动一些直到找到下一个主要块(仅在有人在函数中调用require。这是您必须与setfenv一起使用的堆栈级别。但我可以建议......

不同的方法

Lua中的

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"
   }
}