有没有办法在Lua文件中调用require,并让模块设置调用它的文件的环境?例如,如果我有一个DSL(特定于域的语言)来定义表中定义的函数Root和Sequence,那么我可以在模块中使用类似setfenv(1,dslEnv)的东西来访问那些像全局变量这样的函数吗?
我想到的目标是使用这个行为树DSL,使我的定义文件看起来像这样(或尽可能接近它):
require "behaviortrees" return Root { Sequence { Leaf "leafname",Leaf "leafname" } }
无需专门将Root,Sequence和Leaf明确地纳入范围,或者必须限定诸如behaviortrees.Sequence之类的名称.
简而言之,我正在尝试使定义文件尽可能干净,没有任何多余的线条使树的定义混乱.
解决方法
我可以在模块中使用类似setfenv(1,dslEnv)的东西来允许我访问全局变量等函数吗?
你当然可以.您只需要确定要使用的正确堆栈级别,而不是setfenv调用中的1.通常你会使用带有debug.getinfo调用的循环向上走,直到你在堆栈上找到require函数,然后再移动一些直到找到下一个主块(以防万一有人在函数中调用require) .这是你必须与setfenv一起使用的堆栈级别.但我可以建议……
不同的方法
Lua中的require是可插拔的.您可以向package.loaders数组添加一个函数(称为搜索器),require会在尝试加载模块时调用它.假设您的所有DSL文件都有.bt后缀而不是通常的.lua.然后,您将使用正常Lua搜索器的重新实现,以及您要查找.bt文件而不是.lua文件的差异,并且您将在loadfile返回的函数上调用setfenv.像这样的东西:
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 )
如果你把它放在一个模块中并且需要从主程序中获取一次,那么你就可以在.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)