2017-07-31 8 views
2

requireをLuaファイルに呼び出す方法はありますか?モジュールはそれを呼び出すファイルの環境を設定していますか?たとえば、関数RootSequenceを定義したDSL(ドメイン固有の言語)をテーブルに定義している場合、グローバル変数のような関数にアクセスできるモジュールにはsetfenv(1, dslEnv)のようなものがありますか?Luaのrequireを使用して呼び出し元ファイルの環境を設定できますか?

私心の中ではこれが私の定義ファイルは、この(またはできるだけそれだけ近く)のように見える方法で行動ツリーDSLで使用している目標:

require "behaviortrees" 

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

特にRootを持参することなく、 、Sequence、およびLeafを明示的に適用するか、behaviortrees.Sequenceなどの名前を修飾する必要があります。要するに、定義ファイルを可能な限りきれいにするために、無関係な線がツリー定義を乱雑にしないようにしようとしています。

+1

一つは、発信者から要求されたモジュールを隔離することです。必要に応じて、返されたテーブルから任意の数のメンバーをコピーできます。また、 'debug'モジュールには、通常の規則の多くを破る関数が含まれています。 – Deduplicator

+1

達成しようとしていることの詳細を教えてください。 – lhf

+0

これらの関数を一般的に使用する場合は、それぞれを手動でローカルにする方がよいので、少なくともすべてのコンパイラで最適化することができます。 – Hydro

答えて

3

は、私は私がそれらにアクセスすることを可能にするモジュールでsetfenv(1, dslEnv)のようなものを持つことができますグローバル変数のような関数?

もちろん可能です。 setfenvコールで1の代わりに正しいスタックレベルを把握するだけで済みます。通常は、スタックに関数が見つかるまで、debug.getinfo呼び出しのループを使用してスタックを歩き、次のメインチャンクを見つけるまで関数を移動します(関数内でrequireを呼び出した場合)。これは、setfenvで使用するスタックレベルです。しかし、私はLuaの中

requireはプラグ可能です...

異なるアプローチを提案することができます。 package.loaders配列に関数(サーチャー)を追加すると、requireはモジュールをロードしようとしたときに呼び出されます。すべてのDSLファイルに通常の.luaの代わりに.btの接尾辞があるとします。次に、通常のLuaサーチャーの再実装を使用して、.luaファイルの代わりに.btファイルを探して、そしてloadfileによってreturnsetfenvを呼び出すという違いを見つけてください。このような何か:

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:require` `のポイントの

#!/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')は通常のプログラム(' dofile() '-ed)として実行されるのではなく' require() 'でなければなりませんか? –

+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あなたはdo end chunkの環境を変えることができるか分からなかった!それはいいね。残念ながら、私は_ENVテーブルを使用しないLua 5.1を使用しています。私は自分の投稿に明示的に指定していませんでした(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

関連する問題