LuaでTOMLのパースプログラムを書く
今日はlua言語でTOMLフォーマットを読み込んで見ます。
luaってあまり聞かない言語ですが、使ってみるとすごい便利だったりします。
なにが良いかって、文字列が非常に扱いやすいこと、そして軽量。
C言語ライクにプログラムが書ける謎のスクリプト言語って感じです。
基本的なところは以下のサイトで勉強しました。
http://starcode.web.fc2.com
TOMLとは?
JSONのように設定ファイルを記述するフォーマットです。 まだ仕様が固まりきっていないみたいなのですが、人間が読みやすく、かつ、記述しやすいように作られています。 最近はやりのGolangでもよく使われていて、GoBGPなどで設定ファイルとして採用しています。
例えば、以下のように記述すると、
[hoge] a = "HOGE" [hoge.foo] b = 1 [bar] [[bar.barbar]] c = 2 [[bar.barbar]] d = 3
JSONてきにはこんな感じになります。
{"hoge":{"a" : "HOGE"}, {"foo" : {"b": 1 } } } {"bar" : { "barbar" : [ { "c" = 2 }, { "d" = 3 } ] } }
二重括弧でくくるだけで配列のような構造ができるので、とても記述しやすいし、いちいち括弧の数を数えなくても良いので、とても読みやすく書きやすいです。
Luaで文字列操作
Luaでの文字列操作は、正規表現を使うことが多くなります。
逆に言えば、正規表現で指定した文字列を簡単に取り出したり、配列に入れたりができるので、使いやすいものです。
よく使う、3つくらいを紹介します。
line = string.gmatch(str,"[^\n]+")
これは、文字列中に含まれる改行コードとその前の文字列をを全て抜き取ってline配列に入れています。
(一行ずづ抜き出しています。)
x = string.split("hello world", /\s+/)
これは、文字列をスペースに区切って配列に入れます。
x = string.sub("ABCDE", 2, 4)
これはABCDE→ BCD に削ります。
Luaの配列みたいなの
LuaではPythonでいうリストのような形で、Tableという型が使われる。 これが面白く、
hoge = {} hoge["foo"] = 2 print(hoge.foo) --[[ 結果 2 ]]--
こんな感じで、入れ子のTableにした場合、要素をドットでつなげることで参照できる。上記プログラム中の"foo"の部分を何も指定しなかった場合、1からインクリメントされる数字が勝手に入ってくるのでご注意。
この辺0からじゃなくて1からってのが、Luaが気持ち悪いって言われる所以なのかもしれません。
確かに気持ち悪いです。
LuaでTOMLを読み込み
ここまでくるとなんとなく、TOMLの読み込み方がわかってきました。
TOMLは行毎に意味を持つので、行毎に読み込んで、構造化し、LuaのTableに入れてるプログラムを書きます。
[Global] [Global.GlobalConfig] As = 1 HoldTime = 180 RouterId = "172.17.0.1" [Neighbors] [[Neighbors.NeighborList]] NeighborAddress = "10.0.1.2" [[Neighbors.NeighborList]] NeighborAddress = "10.0.1.3"
上記のようなTOMLファイル(BGPの設定ファイルっぽいもの)を読み込むようなプログラムを作って見ます。
function split(line,delim) local ret = {} local pat = "[^" .. delim .. "]+" local i = 1 for group in string.gmatch(line, pat) do ret[i] = group i = i + 1 end return ret end function loadstr(str) InLabel = {} InLabelArray = {} ret = {} Label = ret ArrayTable = 0 count = 0 for line in string.gmatch(str,"[^\n]+") do line = string.format( "%s", line:match( "^%s*(.-)%s*$" )) if string.sub(line,1,1) == "[" then if string.sub(line,1,2) == "[[" then ArrayTable = 1 line = string.sub(line,3,#line -2) if InLabelArray[line] == nil then InLabelArray[line] = true count = 0 end else line = string.sub(line,2,#line -1) ArrayTable = 0 end Label = ret arrays = split(line,".") for i, value in pairs(arrays) do if Label[value] ~= nil then --if in the value if i == table.maxn(arrays) then if InLabel[value] ~= nil then InLabel[value] = nil else print("double") end end else if i ~= table.maxn(arrays) then InLabel[value] = true end Label[value] = {} end Label = Label[value] end if ArrayTable == 1 then count = count + 1 Label[count] = {} end elseif line:match("=") ~= nil then formula = split(line,"=") formula[1] = string.format( "%s", formula[1]:match( "^%s*(.-)%s*$" )) formula[2] = string.format( "%s", formula[2]:match( "^%s*(.-)%s*$" )) if ArrayTable == 1 then Label[count][formula[1]]=formula[2] else Label[formula[1]]=formula[2] end end end return ret end
以下のように呼び出すことができます。
このプログラムではパラメータを全て文字列で呼び出していますが、このあたりはまだ改変する必要があります。
file = io.open("bgp.conf", "r") string = file:read("*all") file:close() test = loadstr(string) print(test.Global.GlobalConfig.As) print(test.Neighbors.NeighborList[1].NeighborAddress) print(test.Neighbors.NeighborList[2].NeighborAddress) --[[ 実行結果 1 "10.0.1.2" "10.0.1.3" ]]--