Metalua を試してみる [Lua]
Metalua [1] は Lua を拡張してマクロ機能をつけたもの。ここでいうマクロは構文木をいじるほうのマクロで、OCaml に対する Camlp4 のような存在と思えばよい。
* インストールするまで
Linux では Lua が入った環境で普通に make すればいい…はずなのだが、玄箱に入れるところでまずつまづいた。
Metalua は独自に Lua のバイトコードを生成するようなのだが、Lua のバイトコードというのはエンディアン依存で、Metalua の出力するバイトコードはリトルエンディアンのみらしい。玄箱は PPC でビッグエンディアンだからうまく動かない。
これを解決するには Lua のほうでエンディアン対応してあげるしかなくて、Lua のメーリングリストにそういうパッチ [2] があった。これを適用したら玄箱でも動くようになった。
* Metalua 付属の構文拡張
Metalua では自分で構文拡張を作らない状態でも以下のような構文拡張がなされている。
匿名関数を書くのに function といちいち書かずに Ruby 風に書ける。
table.sort(grades, function(a, b) return a.grade < b.grade end) -- Lua
table.sort(grades, |a,b| a.grade < b.grade) --Metalua
バッククォート記号を使うことで任意の関数を中置演算子として使える。これは Haskell 風。
function plus (x, y)
return x + y
end
print (2 `plus` 3) --> 5
こうした構文拡張以外にも割といろいろあって、table モジュールに関数型言語風の高階関数が追加されてたりして地味に便利そう。
* クォーテーションとアンチクォーテーション
Metalua で構文拡張をするには代数データ型(これも Metalua 付属の拡張のひとつ)を駆使して手で構文木を書く方法とクォーテーションを使う方法がある。勿論手書きはしたくないので後者を取り上げる。
print("Hello World!") に相当する構文木はクォーテーションを使って以下のように書ける。+{...} が Camlp4 でいう <:xxx< ... >> に相当すると思えばよい。
+{expr: print("Hello World!") }
コロンの手前は式をクォートするときは expr、ステートメントをクォートするときは stat、複数の式の場合は block と書く。expr の場合にのみ省略できる。
アンチクォートさせたい場合は以下のように書く。この例だと print 関数の引数の位置に msg 変数の構文木が挿入される。-{...} が Camlp4 でいう $...$ に近い。
+{expr: print(-{msg}) }
-{...} はソース上に構文木を展開する使い方もできる。以下は Hello World 二つ分のプログラムにコンパイルされる。
-{ +{ print ("Hello World!") } }
print(-{ +{ "Hello World!" } })
実行結果と逆アセンブル結果は以下のとおり。
KURO-BOX% mlc b.lua Compiling b.lua... ...Wrote b.luac. KURO-BOX% lua b.luac Hello World! Hello World! KURO-BOX% luac -l b.luac main <?:0,42> (7 instructions, 28 bytes at 0x1002b820) 0+ params, 2 slots, 0 upvalues, 0 locals, 2 constants, 0 functions 1 [-] GETGLOBAL 0 -1 ; print 2 [-] LOADK 1 -2 ; "Hello World!" 3 [-] CALL 0 2 1 4 [2] GETGLOBAL 0 -1 ; print 5 [2] LOADK 1 -2 ; "Hello World!" 6 [-] CALL 0 2 1 7 [2] RETURN 0 1
動作としては -{...} はコンパイル時に実行され、その値が構文木とみなされるということのようだ。以下のような例だとコンパイル時に Hello World が出るけど、実行時には出ない。
-{stat: print("Hello World!") }
KURO-BOX% mlc c.lua Compiling c.lua... Hello World! ...Wrote c.luac. KURO-BOX% lua c.luac KURO-BOX%
構文木以外を返すとコンパイルできない。
-{stat: return 777}
KURO-BOX% mlc d.lua Compiling d.lua... lua: compiler/compile.lua:678: attempt to index local 'ast' (a number value)
* Metalua パーサ
Metalua では gg という汎用のパーサモジュールがあって、それを使って mlp という Metalua 構文解析用のパーサが作られているようだ。これは Camlp4 でいう Grammar モジュールと Pcaml モジュールの関係のようなものだと思う。
* 構文拡張を作る
コンパイル時に実行される -{...} の中で mlp モジュールを使ってパーサの動きを変えてやるというのが自分で構文拡張を作るときのやり方のようだ。本来はちゃんと詳しく仕様を理解すべきなんだけどとりあえずは見よう見まねで unless ステートメントを作成してみた。(単純なんだけどうまくいかないときのエラーが意味不明ですごい時間かかった)
-{block:
local function unless_builder (x)
local expr, block = unpack (x)
return +{stat:
if not(-{expr}) then
-{block}
end
}
end
mlp.lexer:add{"unless"}
mlp.stat:add{
"unless", mlp.expr, "then", mlp.block, "end",
builder = unless_builder
}
}
unless 1 == 1 then
print("not 1 == 1")
end
unless 1 == 2 then
print("not 1 == 2")
end
--> "not 1 == 2"
[1] http://metalua.luaforge.net/
[2] http://lua-users.org/lists/lua-l/2006-02/msg00507.html
コメント 0