luaのメモリローダー

lua5.1も気がつけば5.3です。luaに関してはまだ若かりし5.1の頃の記事が残ってますが、良い加減ちゃんと書き直したいので1,2,3を飛ばしてloader周りから書き直したいと思います。

オンメモリ読み込み

組み込みで避けて通れないのがオンメモリ読み込みとその実装です。ファイルから読んじゃダメってやつです。デバッグは良いんですけどね。
luaの場合、スクリプト読み込みは「lua_load」を使って任意ストリーム(データを返す関数ポインタ)でロードできます。任意ポインタを関数callback時に渡せるので…
と思ったらもっと便利な「luaL_loadbuffer」があるのでこっち使えば良いですね。なんだよーもうloadでつくっちゃったよー。

  • luaL_loadbuffer

http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#luaL_loadbuffer
ちなみに、loadstringという手もあるのですが、これだとチャンク名が設定できない関係上デバッグの際に読み込みファイル名がゲットできないという致命傷がございますのでご注意くださいませ。なんせデバッグは重要ですので。

それrequireだとダメですよね

はいそうです。これだと内部requireのロードが拾えません。なので、まず読み込むファイル、そしてrequireのローダを乗っ取るの2弾構えが必要です。
本家マニュアルによりますと

  package.searchers
このテーブルの各エントリは 検索関数 です。 モジュールを検索するとき、 require はモジュール名 (require に指定した引数) を引数にしてこれらの各検索関数を昇順で呼びます。 検索関数は別の関数 (モジュールの ローダー) およびそのローダーに渡される追加の引数、 またはモジュールを見つけられなかった理由を説明する文字列 (または何も言うべきことがなければ nil) を返すことができます。

任侠沈没に言ってわからん。
要約すると、requireで指定した文字列を渡すので、それをチャンク(load)にして返してください。エラーならそのメッセージを返してくださいということです。
言ってるかな?…言ってるかも…。

function onmemoryloader(modulename)
  local filename = modulename..".lua"
  local data = custom.load(filename)
  local chank,msg = load(data,filename)
  if chank~=nil then
    return chank
  else
    return msg
  end
end

最小構成はこんな感じかな?こんな感じかも。
「custom.load()」は仮の組み込み関数で、ファイル名を指定するとその内容の(nilを含んだ)文字列を返す命令とします。仮です。
luaに置いては基本バイナリは文字列で扱います。でもnilがあるとそこで止まっちゃうので「lua_pushlstring」でnilを許容した文字列をluaに送り込みます。という点が特殊なので作成の際は注意してください。
話が脱線すると、このあたりのバイナリデータの取り扱いの仕様もあるので、luaの標準文字コードは5.3でutf8でほぼ決定なのかなと思います。無理にutf16にしなくてもね。文字列やりとりする際は変換が必要なんですが、utf16とutf8はかなり高速に相互変換出来るのでまぁいいかなーって思います。
で、これをloaderにすり替えます。

if _VERSION=="Lua 5.1" then
	package.preload[1]=onmemoryloader
end
if _VERSION=="Lua 5.2" or _VERSION=="Lua 5.3" then
	package.searchers[1]=onmemoryloader
end

一応バージョンによって名前が違うので、両対応しておきましょう。
とりあえず一番最初のサーチに入れないと、普通にlua標準ファイル読み込みしちゃうので、一番最初に入れます。
この場所が最良なのかどうか、と言う点は私はわかりませんので公式に聞いてください。

githubにおいておきます


https://github.com/Ko-Ta2142/lua_memoryload
まだgithub使い慣れてませんが適当に組んだものを置いておきます。zlibでお使いください。
使い方はこれをrequireすれば、それ以降のrequireで作用します。
最小限動作させるには「memoryload.onload」にロードしてバイナリ文字列を返す任意のイベント(function)をセットする必要があります。

弊害と利点

この方法ではDLLの読み込みは出来ません。やったら怒られました。なので、luasocketなどのモジュールと併用する際は気をつけてください。
また、moonscriptのようにloaderを乗っ取ってスクリプトluaに変換するもの(コンパイル済は大丈夫)とも衝突します。その場合は、デバッグとリリースでloaderの順序を変えたりして回避するのが一番簡単かとおもいます。
利点は簡単なマクロの実装などもありますが、まずはutf8-bomなどの文字コード対応が一番幸せになれるかなって思います。