1. ホーム
  2. スクリプト・コラム
  3. ルア

Luaにおけるグローバル環境、パッケージ、モジュール構成のパース処理

2022-01-06 04:04:15

モジュールとはライブラリのことで、パッケージとはモジュールの集合体のことで、Luaでモジュールを要求し、テーブルを表すグローバル変数を取得することで読み込むことができる。この記事では、まず環境に関する実用的なテクニックを紹介し、次にモジュールの参照方法と基本的な書き方を説明します。

1. 環境について
Luaは環境テーブルをグローバル変数_Gに保持し、アクセス・設定することができます。他の変数に格納されている名前のグローバル変数を操作したい場合や、実行時に計算して取得する必要がある場合は、value = _G[varname]で動的な名前のグローバル変数を取得することができます。

環境(quot;environment)の大きな問題の1つは、それがグローバルであり、それに対するいかなる変更もプログラムのすべての部分に影響を与えるということです。Lua 5 では、各関数がグローバル変数を見つけるために環境のサブセットを持つことができ、関数の環境を setfenv で変更できます。最初の引数は現在の関数の 1、現在の関数を呼び出す関数の 2 (以降同様)、次の引数は新しい環境テーブルとなります。

a = 1
setfenv(1, {})
print(a) -- will report an error, print is a nil. this is because once the environment is changed, all global accesses will use the new table


上記の問題を回避するには、setfenv(1, {_G = _G})で元の環境を保存し、_G.printで参照すればよいでしょう。新しい環境を組み立てるもう一つの方法は、継承を利用することです。次のコードでは、新しい環境は元の環境からprintとaを継承し、すべての代入は新しいテーブルで行われます。

a = 1
local newgt = {}
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)
print(a)



2. モジュールとパッケージ
2.1 モジュールの呼び出し

モジュールmodのfooメソッドを呼び出すには、require関数でロードします。

require "mod"
mod.foo()
-- or
local m = require "mod"
m.foo()


require関数の動作。(require が使用するパス検索ストラテジーと混同しないように)
package.loadedテーブルでモジュールがロードされているかどうかをチェックします。
=> ロードされた場合、対応する値を返します(モジュールは一度だけロードされることを参照してください)。
=> ロードされていない場合、package.preloadで渡されたモジュール名を照会してみる。
===> 関数を見つけ、その関数をモジュールローダーとして使用します。
===> 見つからない場合は、LuaファイルやCライブラリからモジュールを読み込んでみてください。
=====> Luaファイルを探し、loadfileでファイルを読み込む。
=====Cライブラリを探し、loadlib経由でファイルをロードする。

2.2 環境を利用する

次のコードは、複雑なモジュールを作成するために環境を使用する方法を示しています。

-- Module settings
local modname = "complex"
local M = {}
_G[modname] = M
package.loaded[modname] = M

-- declare everything the module needs from the outside world
local _G = _G -- retains references to the old environment, and needs to be used like _G.print when used
local io = io

-- the environment will change after running this line
setfenv(1, M)

function new(r, i) return {r=r, i=i} end

function add(c1, c2)
  return new(c1.r + c2.r, c1.i + c2.i)
end



こうすることで、関数addを宣言するときにcomplex.addとなり、同じモジュール内の他の関数の呼び出しに接頭辞が不要になる。

2.3 モジュール関数

Lua 5.1では、上記の環境定義のための関数群をカプセル化したmoduleという新しい関数が用意されています。モジュールを書き始めるとき、直前のセットアップコードを直接 module("modname", package.seeall) に置き換えることができます。モジュールファイルの最初にこの呼び出しをすることで、後続のすべてのコードでモジュール名や外部名を修飾する必要がなくなり、同様にモジュールテーブルを返す必要もなくなります。

2.4 サブモジュールとパッケージ

Luaは階層を持つモジュール名をサポートしており、名前の階層をドットで区切ります。例えば、mod.subという名前のモジュールは、modのサブモジュールです。パッケージは完全なモジュールツリーであり、Luaにおけるヘアスタイルの単位となります。サブモジュールのファイルを探すとき、requireはドットをディレクトリの区切りとして探すので、require "a.b"を呼ぶと、.NETを開こうとすることに注意してください。/a/b.lua, /usr/local/lua/a/b.lua, /usr/local/lua/a/b/init.lua といった具合です。このロード方法を用いると、パッケージの全モジュールを1つのディレクトリにまとめることができます。

2.5 カスタム方法でのLuaモジュールのロード
Lua 5.1より、Luaにはモジュール管理ライブラリが標準装備されました。そのため、モジュールの読み込みは全てrequireで行います。requireの設計はかなり拡張性があり、定義されたいくつかのローダーから新しいモジュールを1つずつロードしようとします。システムライブラリには、ロードされたモジュール、Luaモジュール、C拡張のための4つのローダー実装が用意されています(C拡張のロードには2つのローダー実装が使用されます)。これらのローダーは、CFunctionsとしてrequire環境のテーブルに配置されています。

luaモジュールのロード方法を変更したい場合は、ローダーを置き換えるか、新しいローダーを追加すればよいのです。

loadlib.c にある loader_Lua 関数の実装を独自に作成するだけです。例えば、私たちのプロジェクトでは、カスタムフォーマットされたパケットから暗号化された Lua コードファイルをロードできるようにします。そして、(lua_getfenvを使用して)require環境を取得するCコードを数行記述し、"loaders"テーブルを取得して新しいカスタムローダーをインデックス2へ挿入します。

コードの詳細は省きますが、ll_requireの実装(loadlib.c内)を読むとわかりやすいと思います。Luaの優れた設計のおかげで、解析から実装まで2時間かからずに終わりました :D ネットワークに接続されたストリームからLuaモジュールをロードしたり、http/ftpプロトコルでダウンロードする場合でも、うまくいくはずです。