2017-07-31 68 views
2

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

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

require "behaviortrees" 

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

无需特地带来RootSequenceLeaf明确纳入范围或必须限定名称,如behaviortrees.Sequence

总之,我试图使定义文件尽可能干净,没有任何多余的线混乱树定义。

+1

一个require'的'点的是,它隔离呼叫者的请求的模块。如果你愿意,你可以从返回的表中复制任意数量的成员。此外,“调试”模块包含破坏许多正常规则的功能。 – Deduplicator

+1

你可以提供你想要达到的更多细节吗? – lhf

+0

如果您通常使用这些函数,那么最好手动将每个函数本地化,因此至少所有编译器都可以对它们进行优化。 – Hydro

回答

3

我可以在模块中像setfenv(1, dslEnv),让我来访问这些函数像全局变量?

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

不同的方法

在Lua require是可插拔的。您可以将一个函数(称为搜索器)添加到package.loaders阵列中,require将在尝试加载模块时调用它。假设您的所有DSL文件都有.bt后缀,而不是通常的.lua。然后,您将使用正常Lua搜索器的重新实现,其差别是您要寻找.bt文件而不是.lua文件,并且您可以拨打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一次,然后你可以require您从.bt文件自定义环境DSL文件的地方在那里你干脆把.lua文件作为好。而且你甚至不需要你的DSL文件中的require("behaviortrees")。例如: -

文件xxx.bt

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

文件main.lua

#!/usr/bin/lua5.1 
require("behaviortrees") -- loads the Lua module above and adds to package.loaders 
print(require("xxx")) -- loads xxx.bt (but an xxx Lua module would still take precedence) 
+0

因此,在DSL上编写的用户程序('xxx.bt')必须是'require()'-ed而不是像通常的程序一样执行('dofile()'-ed)? –

+0

@EgorSkriptunoff:是的,'dofile'不起作用。但我很少使用它。无论是DSL文件属于我的程序,在这种情况下,我使用'require',或者它是用户提供的,在这种情况下,我使用'loadfile' +'pcall'。在后一种情况下,插入'setfenv'是很简单的。 – siffiejoe

+0

为什么不使用直接的解决方案:重新定义'dofile'?在内部它会做同样的事情:检查文件扩展名'loadfile' +'setfenv' +'pcall',但是这比使用'require'来实现这种意想不到的目的更加自然(加载非模块)。 –

2

至少在Lua 5.2中,_ENV是确定环境表的本地。你可以改变任何函数的环境,基本上是块。

_ENV = behaviortrees; 

另一种方式是自动每个字段复制:

do 
    _ENV = _ENV or _G; 

    for k, v in next, behaviortrees do 
     _ENV[k] = v; 
    end 
end 

但是它可能是更有效的手动本地各领域从behaviortrees

+1

关于你在圆括号中的评论,块是一个函数。 – lhf

+0

@lhf谢谢,我已经澄清了。 – Hydro

+1

@ Hand1Cloud我不知道你可以改变一个做结束块的环境!这很酷。不幸的是,我使用Lua 5.1,它不使用_ENV表。我没有在我的文章中明确指出(有一个参考setfenv,但这很容易错过),对此感到遗憾。 +1仍然用于教我新的东西! –

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

与全局混合的有趣方法+1 – Hydro

相关问题