きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

速習Lua

Luaとは

Luaとは、軽量スクリプト言語です。主にC/C++等に組み込んで使われます。組み込んだ方のアプリケーションをホストといいます。

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を除く全てのデータが使える。テーブルは文字列で指定した場合、ソースコード上で.を付けて呼び出すこともできる。

エラー処理

Luaは構文エラーや実行時エラーをホスト側に返します。標準関数errorで明示的にエラーを起こすこともできます。

メタテーブル

全てのデータには、キーとして表立って列挙されないが実際に持つことのできるデータがあります。そのデータのリストをメタテーブルと呼びます。標準関数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

またfor-in文では、オブジェクトのイテレータ関数を実行して、関数がnilを返すまで繰り返されます。

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. ^
  2. not # - (単項)
  3. * / %
  4. + -
  5. ..
  6. < > <= >= ~= ==
  7. and
  8. or

となります。

テーブルコンストラクタ

テーブルを作成するには、{ }を用います。{ }の中には,または;で区切ってテーブルの要素を指定できます。特に何も指定せずに要素を列挙すると、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);