テーブルのシリアライズとか
オブジェクトの状態復帰機構をこさえてました。
結局の所は、テーブルのシリアライズが出来れば事足りそうですね。
テーブルを文字列にシリアライズすると色々と応用が出てきて便利なのでお薦めです。
アプリケーション側に複雑かつ長いデータを渡す際にとても便利ですし、アプリケーション側にテーブル文字列をリスト表示する機構を持っていれば、RAD構築やデバッグ表示などにも大いに役立つことでしょう。
テーブルの保存と復元(シリアライズ)
というわけで、まずはテーブルのシリアライズです。
テーブル変数を文字列に出力、またその文字列から復元、を行うシリアライズコードです。
対応している型は、number,string,nilだけー。
function型やuserdata型はすっぱり諦めましょう。関数切り替え番号をnumber型で保持しておいて、呼び出す際に番号で分岐させ関数を呼ぶ、というので代用するのが落とし所ではないかと思います。
-- lua -- テーブルシリアライズ function value2str(v) local vt = type(v); if (vt=="nil")then return "nil"; end; if (vt=="number")then return string.format("%d",v); end; if (vt=="string")then return string.format('"%s"',v); end; if (vt=="boolean")then if (v==true)then return "true"; else return "false"; end; end; if (vt=="function")then return '"*function"'; end; if (vt=="thread")then return '"*thread"'; end; if (vt=="userdata")then return '"*userdata"'; end; return '"UnsupportFormat"'; end; function field2str(v) local vt = type(v); if (vt=="number")then return string.format("[%d]",v); end; if (vt=="string")then return string.format("%s",v); end; return 'UnknownField'; end; function serialize(t) local f,v,buf; -- テーブルじゃない場合 if not(type(t)=="table")then return value2str(t); end buf = ""; f,v = next(t,nil); while f do -- ,を付加する if (buf~="")then buf = buf .. ","; end; -- 値 if (type(v)=="table")then buf = buf .. field2str(f) .. "=" .. serialize(v); else buf = buf .. field2str(f) .. "=" .. value2str(v); end; -- 次の要素 f,v = next(t,f); end buf = "{" .. buf .. "}"; return buf; end; function deserialize(d) --文字列を関数実行させる return assert(loadstring("return " .. d .. ";"))(); --分解すると --function -- return {x=400,y=5...}; --end end;
書き方が古っぽくてすみません。「;」大好きですいません。
-- lua t = {x=300,y=20,b=false,t={[1]=30,[4]=300}}; t.y=math.random(0,20); print( serialize(t) ); -- out {y=4,x=300,t={[1]=30,[4]=300},b=false}
「serialize」(保存)を行うと、テーブル宣言コードそのものを文字列として返します。
なので、「deserialize」(復元)は、文字列をまんまコードとして実行して、テーブル変数をreturnで返すだけとなっております。
グローバル変数一覧出力
仮想マシン内のグローバル変数の一覧を出力したいとおもいます。デバッグの時にあると便利ですよね。
上記のテーブルシリアライズ関数を応用、使用します。
「_G」がグローバルテーブルなのですが、如何せん自分自身のLINKが入ってたり、モジュールが入っていたりしますので、それらは除外するコードが含まれています。
もし、自分でDLLを組み込んだ場合は、除外コードを追加する必要があるかも知れません。(やったことないのでまだわからんのですよー)
モジュールテーブルを読み込むと魔の無限循環を発生させますので、package.loadedからモジュールテーブル名の一覧を取得し、それに当てはまるテーブルを除外すればOKみたいっすね。
たぶんこれで、package.seeallのものを読み込んだりDLLを読み込ませても正しく吐けるかな?と思います。
-- lua function globalserialize() local f,v,buf,vt,ign; buf = ""; --対処外のテーブル名をpackeage.loadedから取得(つまりmoduleテーブルね) ign = {}; f,v = next(package.loaded,nil); while f do vt=type(v); if (vt=="table")then ign[f]=1; end; f,v = next(package.loaded,f); end; f,v = next(_G,nil); while f do vt=type(v); -- 変数名フィルタ if not((ign[f]==1)or(f=="_G"))then -- 型フィルタ if not(vt=="function")then if not(buf=="")then buf = buf .. ","; end; if (vt=="table")then buf = buf .. field2str(f) .. "=" .. serialize(v); else buf = buf .. field2str(f) .. "=" .. value2str(v); end; end; end; -- 次の要素 f,v = next(_G,f); end; buf = "{" .. buf .. "}"; return buf; end;
尚、これを_Gに対して復元などはしないようにね。
ローカル変数一覧表示
グローバルの次はローカルです。
ここで実行を止めて、ローカル内容を表示ってする場合に必要ですね。
-- lua function localserialize(l) local buf,i; -- 参照レベル if (l==nil)or(l==0)then l=2; end -- テーブルとして出力させようか buf = ""; i = 1; while(true)do local f,v = debug.getlocal(l,i); if (f==nil)then break; end local vt = type(v); if not(buf=="")then buf = buf .. ","; end; -- 値 if (vt=="table")then buf = buf .. field2str(f) .. "=" .. serialize(v); else buf = buf .. field2str(f) .. "=" .. value2str(v); end; i=i+1; end buf = "{" .. buf .. "}"; return buf; end
ローカルは結構ややこしくて、出力関数が呼ばれた位置から何レベル遡った場所のローカル内容を表示するのか?という引数を与えます。
「1」が自分、つまり表示関数のローカル空間なので、使用することはありません。
「2」が1つ上、つまり表示関数を呼んだ関数のローカル空間となります。
もし、表示関数をデバッグ関数に組み込んだ場合は、「表示→デバッグ→目的の関数」となるので、レベルは「3」になります。
nilとか0をぶち込むと、自動的に2(呼び出し元のローカル)が使用されます。
-- lua function () local x,y,obj; x = 2; y = 3; x = x + y; print( localserialize(2) ); end; -- out {x=5,y=3,obj=nil,(*temporary)="*function"}
現在位置の取得
おまけ。
ローカル変数表示が出来たので、ついでにその表示がソースコード上のどこなのかといった情報もあると便利なので、それを取得しましょう。
-- lua function localinfo(l) local info,buf; -- 参照レベル if (l==nil)or(l==0)then l=2; end return serialize(debug.getinfo(l)); end
引数はローカル変数の時と同じです。
-- lua function () local x,y,obj; x = 2; y = 3; x = x + y; print( localinfo(2) ); end; -- out {nups=0,what="Lua",func="*function",lastlinedefined=30,source="@test02.lua",currentline=27,namewhat="",linedefined=13,short_src="test02.lua"}
表示内容はソースファイルによって異なってくるので、この通りには出力されません。あしからず。
重要そうなのは、currentline(現在実行行)、linedefined(関数開始行)、short_src(読み込みファイル名/指定タグ名)ぐらいでしょうか。
関数名については、「name」というフィールド名で登録されるのですが、c側からpcallされた関数(一番古いスタック)については名前が取得されないようです。
この名前を取得するには、lua側で全関数の対応テーブルを作成し、funcに登録されているfunction型を使ってサーチするといった、結構めんどくせー事をしなければなりません。