速習Lua
Luaの特徴
Hello, world.
print("Hello, world.");
Luaは標準関数としてprintを持っており、標準出力に出力します。引数として、メタテーブルに__tostringを持っているオブジェクトなら何でも渡せます。引数として複数渡せて、タブ文字で区切られます。
コメント
コメントは--以降行末までです。
文
1文は;までですが、;を省略することも可能です。この場合基本的に行末までが1文とされますが、次の行に跨がって実行できるような場合実行されることがあります。パーサのことを考えると;を入れた方が(コンピュータにとって判断しやすいため)高速に動くともいえます。
複数の文のまとまりをブロックと言います。ブロックは明示的に
do 文のリスト end
と定義できます。
Luaでは実行の単位としてチャンクと呼んでいます。Luaは再帰的にチャンクを実行しています。チャンクとは文法的にはブロックと等価です。チャンクはLuaによって仮想マシンのバイト列にコンパイルされ、それが実行されます。コンパイル済みのバイト列を実行することもできます。
Luaの型
Luaは動的型付け言語です。基本型としてnil, boolean, number, string, function, userdata, thread, and tableがあります。
- nil
- 他の全てではない型
- boolean
- 真偽値を表す。trueまたはfalseのどちらかである
- number
- IEEE 754(IEEE浮動小数点演算標準)に基づいた実数値を表す。
- string
- 8bitデータが連続したものであり、通常文字列を表すためにある
- function
- 一連の処理をまとめたもの
- userdata
- ホスト側で定義する型である。ホスト側メモリをLuaが管理するものとホスト側(Luaを使う側)が管理するものがある。
- thread
- Lua独自のスレッドシステムに使われる型。
- table
- 連想配列である。キーにはnilとNaNを除く全てのデータが使える。テーブルは文字列で指定した場合、ソースコード上で.を付けて呼び出すこともできる。
メタテーブル
全てのデータには、キーとして表立って列挙されないが実際に持つことのできるデータがあります。そのデータのリストをメタテーブルと呼びます。標準関数getmetatableで取得でき、setmetatableで追加できます。これについてはテーブルの節を参照して下さい。
メタテーブルのキーの内、特殊なキーがあります。それらは__で始まっている名前であり、関数が格納されます。例えば__addはop1 + op2を実行するときに__add(op1, op2)として呼び出されます。ベクトルの加算を実装した例が次のものです。
a = {}; b = {}; function plus(op1, op2) return { x = op1.x + op2.x, y = op1.y + op2.y }; end setmetatable(a, { __add = plus }); setmetatable(b, { __add = plus }); a.x = 10; a.y = 20; b.x = 1; b.y = 2; c = a + b; print(c.x .. ", " .. c.y); -- 11, 22と表示される
次のようなメタテーブルの特殊なキーが存在します。
- add(op1, op2) +
- sub(op1, op2) -
- mul(op1, op2) *
- div(op1, op2) /
- mod(op1, op2) %
- pow(op1, op2) ^
- unm(op) -
- concat(op1, op2) ..
- len(op) #
- eq(op1, op2) ==
- lt(op1, op2)
- le(op1, op2) <=
- index(table, key) table[key]またはtable.keyへアクセスしたとき
- newindex(table, key) table[key]またはtable.keyが生成されたとき
- call(func, 引数) 関数が呼び出されるとき
ガベージコレクション
Luaには自動的にメモリの確保・解放を行う機構があります。このガベージコレクションの機構は参照カウント方式よりも優れているとされるマーク・スイーブ方式を採っています。
ガベージコレクションによって解放されるときに実行される関数がメタテーブルの__gcによって定義されています。
コルーチン
関数の実行を途中で停止したり、そこから開始したりできます。以下の例では関数の異なる引数での呼び出しを交互に実行しています。これはマルチスレッドのように振る舞っていることを意味します。
function test(a) print("test", a); coroutine.yield(a); a = a + 1; return a; end co = coroutine.create(test); co2 = coroutine.create(test); print("main", coroutine.resume(co, 1)); -- test 1 print("main", coroutine.resume(co2, 10)); -- test 10 print("main", coroutine.resume(co)); -- test 2 print("main", coroutine.resume(co2)); -- test 11
文法一覧
変数
Luaにはグローバル変数、ローカル変数、テーブルフィールドの3種類の変数があります。ローカル変数を定義するにはlocalを用いて
local var;
とします。初期値を代入文によって与えなければnilが設定されます。また、多重代入が可能です。ローカル変数として定義しなければグローバル変数となります。
テーブルフィールドはtable[key]とアクセスします。特にkeyが文字列の時はtable.keyとしてもアクセスできます。
代入
Luaでは変数の定義に代入を用います。宣言する必要がありませんが、定義されていない変数を参照するとエラーとなります。
Luaでは多重代入が可能です。即ち、
a, b, c = 1, 2, 3;
のように複数の変数に代入することができます。代入する際には一度全ての式が評価されます。
i = 5; i, a[i] = i + 1, 10;
は、
i = 5; i, a[i] = 6, 10; -- i=6;の後にa[6]=10;
と動作することを意味します。
if, while, repeat
一般的な制御構文です。条件式は任意の値を返すことができますが、nil及びfalseは偽となり、それ以外は辛として扱われます。これは0や空文字列""が真として扱われることを意味します。
x = 2; if x == 1 then print("xは1です。"); elseif x == 2 then print("xは2です。"); elseif x == 3 then print("xは3です。"); else print("xは1, 2, 3以外です。"); end
-- 0から9まで表示 local i = 0; while i < 10 do print(i); i = i + 1; end
-- 0から10まで表示 local i = 0; repeat print(i); i = i + 1; until i > 10;
for文
for文は特殊な繰り返しをするのに用いられます。for var = e1, e2, e3 do 処理 endとして定義されます。varはfor文の中で用いられるローカル変数です。e1はvarの初期値、e2は最終値、e3は加算値です。e3は省略可能で、省略すると1が設定されます。
-- 0から10まで表示 for i = 0, 10 do print(i); end
break文
while, repeat, for文の途中でループを抜けるにはbreak文を実行します。
-- 0から5まで表示 for i = 0, 10 do print(i); if i == 5 then break; end end
return文
関数やチャンクが値を返すためにreturn文を用います。return文はチャンクの最後に実行しなければなりません。即ちendの直前で実行しなければなりません。
チャンクの中にチャンクがあり、その内部のチャンクでreturnされると、その外側のチャンクも処理を中断して値を返します。これは再帰的に適応されます。例えば次の例では関数funcを途中で中断して値1を返します。
function func() do do return 1; end end -- ここまで処理は来ない end print(func());
算術演算子
Luaには+, -, *, /, %, ^, 単項演算子-が存在します。特に%は剰余、^は累乗を表します。剰余は以下のように定義されています。
a % b == a - math.floor(a/b)*b
文字列と数値の型変換
文字列に対して算術演算が施されると、自動的に数値に変換されます。逆に、文字列が使われるべき場面で数値が使われると文字列に自動的に変換されます。
今までprint(100);などとして表示されたのはそのためです。
関係演算子
等価==, 不等価~=, より小さい<, より大きい>, 以下<=, 以上>=を判定する演算子が定義されています。これらの演算子は常にtrueまたはfalseを返します。
等価演算子には先ほどの文字列と数値の型変換が適用されません。従って、"0" == 0はfalseを返します。
print(1 == 2); -- false print(1 ~= 2); -- true print(1 < 2); -- true print(1 > 2); -- false
論理演算子
and演算子はe1 and e2として使われます。e1が真ならe2を評価し、e2が真ならe2を返し、e2が偽ならe1を返します。一方でe1が偽ならfalseを返します。
or演算子はe1 or e2として使われます。e1が偽ならe2を評価し、e2が真ならe2を返し、e2が偽ならfalseを返します。一方でe1が真ならe1を返します。
not演算子はnot eとして使われます。eが真ならfalseを返し、eが偽ならtrueを返します。
print(1 and 2); -- 2 print(1 or 2); -- 1 print(not 1); -- false
連結演算子
連結演算子..は文字列か数値を文字列としてつなぎ合わせます。
print(1 .. 2); -- 12 print("a" .. 2); -- a2 print("a" .. "b"); -- ab
長さ演算子
長さ演算子#は文字列のbyte数を返します。
a = "str"; print(#a);
文字列以外でもメタテーブルの__lenを変更することにより独自の長さを返す関数を定義できます。
また、テーブルに対して#を使うとシーケンスなテーブルに対してのみその要素数を返します。シーケンスであるとは、キーが1からnまで存在して、その間の途中のデータにnilを含まないようなテーブルを指します。キーが整数でないデータは関係ありません。
もちろんテーブルの__lenを変更することでこの動作を変えられます。
テーブルコンストラクタ
テーブルを作成するには、{ }を用います。{ }の中には,または;で区切ってテーブルの要素を指定できます。特に何も指定せずに要素を列挙すると、1から順番にキーが振られます。
local my_list = { "str", 100; true; false }; for key, value in pairs(my_list) do print(tostring(key) .. " = " .. tostring(value)); end
インデックスではなく、オブジェクトをキーとして設定できます。数値や文字列以外にもテーブルやユーザーデータなど何でも指定可能です。キーとしてオブジェクトを指定する場合[object] = valueの形を取ります。key = valueという形を取ると["key"] = valueと見なされます。
local table = {}; local my_list = { ["key"] = "str", key2 = 100, [true] = false, [table] = false }; for key, value in pairs(my_list) do print(tostring(key) .. " = " .. tostring(value)); end
この例ではキーの内、"key"と"key2"が文字列で、trueが真値、tableがテーブルです。各キーと値は初期化時に評価され、戻り値が使用されます。
関数の戻り値や可変長引数式...を使うと、その戻り値を使用してテーブルを初期化できます。この際、[key]=value形式ではなく単体のvalueを指定します。また、これはリストの一番最後にある場合のみ有効です。
function func(m, n) local list = {}; for i = 1, n do list[i] = i * m; end return unpack(list); end list = { func(2, 5) }; list2 = { func(2, 5), 100 }; -- 最後が100だから有効でない list3 = { [1] = func(2, 5) }; -- [key]=value形式は有効でない print("list:"); for i, v in ipairs(list) do print(i .. " = " .. v); end print("list2:"); for i, v in ipairs(list2) do print(i .. " = " .. v); end print("list3:"); for i, v in ipairs(list3) do print(i .. " = " .. v); end
実行結果は、
list: 1 = 2 2 = 4 3 = 6 4 = 8 5 = 10 list2: 1 = 2 2 = 100 list3: 1 = 2
です。
関数
関数の定義
function() 処理 end
とすることで関数を定義できます。
function 名前() 処理 end
は
名前 = function() 処理 end
のシンタックスシュガーです。
また、
local function 名前() 処理 end
は
local 名前; -- ここが重要 名前 = function() 処理 end
のシンタックスシュガーです。このlocalの宣言が先にされるのが重要で、次の例を見て下さい。
func = function(x) print("A"); end local func = function(x) print("B"); if x then func(false); -- Aが表示される end end func(true); -- local funcの方を呼び出し
一方でlocalで宣言すると、
func = function(x) print("A"); end local fucn; func = function(x) print("B"); if x then func(false); -- (自身が呼び出されて)Bが表示される end end func(true); -- local funcの方を呼び出し
これはローカル変数がグローバル変数よりも優先されるために起こります。
関数はreturn文によって値を返せます。値は複数返せます。
function func() return 1, 2, 3; end a, b, c = func(); print(a, b, c);
関数の途中で値を返すことはできますが、直接return文を書くことはできません。内部のブロックがreturnによって中断されると、関数も途中で中断できます。
function func(x) y = x + 1; if y > 2 then return y; end return x; end print(func(2));
明示的にブロックを作ってreturnすることもできます。
function func() -- 処理 do return 1; end -- 処理 end
可変長引数
function func(a, b, ...) vl = { ... }; print(a); - 1 print(b); - 2 print(vl[1]); - 3 print(vl[2]); - 4 print(vl[3]); - nil end func(1, 2, 3, 4);
引数の最後に...を指定することにより、残りの引数を全て...に格納できます。関数内では { ... }などのようにすることでテーブルかできます。もちろん、{ "a", ... }のように何らかの要素の後に追加することもできます。これはテーブルコンストラクタで述べたとおりです。
関数の呼び出し
関数を定義したら、関数名(引数リスト)として呼び出せます。関数名と()の間に空白文字があっても構いません。なお、引数リストは,で区切ります。
ところで、引数にテーブル1つを渡す場合、()を省略できます。
function func(list) print(list.x, list.y); end func({ x = 10, y = 20 }); func { x = 10, y = 20 };
引数に文字列1つを渡す場合、()を省略できます。または関数名文字列としても渡せます。
function func(str) print(str); end func("文字列"); func "文字列"; func[[文字列]];
テーブル:関数名(引数リスト)はテーブル.関数名(テーブル, 引数リスト)へのシンタックスシュガーです。これはテーブルのメンバに格納されている関数をメソッドのように実行するためにあります。
object = { func = function(self, x) print(x); end }; object:func(10);