Hatena::ブログ(Diary)

Ko-Taのバ・ー・ルのようなもの

2010-08-30

テーブルのシリアライズとか

オブジェクトの状態復帰機構をこさえてました。

結局の所は、テーブルのシリアライズが出来れば事足りそうですね。

テーブルを文字列にシリアライズすると色々と応用が出てきて便利なのでお薦めです。

アプリケーション側に複雑かつ長いデータを渡す際にとても便利ですし、アプリケーション側にテーブル文字列をリスト表示する機構を持っていれば、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型を使ってサーチするといった、結構めんどくせー事をしなければなりません。

toyatoya 2013/02/08 23:22 Multimedia Fusion 2というソフトのXLuaというエクステンションを使って、
テーブルシリアライズのコードを実行してみましたが、
文字列に改行や「"」が入ってると上手く動作しませんでした。

return string.format('"%s"',v);

return string.format("%q",v);
と変更すると上手く動作しました。