2010年2月2日火曜日

Lua基礎文法最速マスター

このブログ記事をはてなブックマークに追加

最近、基礎文法最速マスターというプログラミング言語の解説が流行ってるようなので、便乗してみた。個人的にはC++やPythonの方が慣れ親しんでいるのだが、自分でも勉強できるように普段使っていない言語を書いてみることにした。以前にここのブログで言及した言語、Processing、Erlang、Lua、PowerShellなどの中でもErlangとLuaに興味があったので、比較的書きやすいLuaを選んでみた。

何故Luaなのか? Wikipediaによると、Luaはブラジル・リオデジャネイロのカトリカ大学で生まれた手続き型言語だ。高速な動作、高い移植性、組み込みの容易さが特徴だ。また、ホストプログラムへの組み込みが容易であることもあって、コンピュータゲームなどで利用されている。有名どころでは、PlayStation HomeWorld of Warcraftなどがある。また、小飼弾氏もなんてめんこい言語と述べているように評価が高い。

さて、早速Lua基礎文法最速マスターを書いてみる。上述のように自分にとっては勉強を兼ねて書いているので、間違いや記述漏れなどがあるかもしれない。コメントなどで指摘していただけるとありがたい。また、C言語の連携など、足りない部分については随時追加していく予定だ。

追記(2010/2/9): 「C言語とLuaの連携」を追加した。



1. 基礎

対話環境

Luaは対話環境として実行できます。

$ lua Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio > print("Hello world") Hello, world > print(1 + 2) 3 > print(type("Hello world")) string

print関数の代わりに = を利用することもできます。

> = "Hello world" Hello world > = 1 + 2 3 > = type("Hello world") string

print関数

print("Hello world") print(1 + 2)

コメント

ハイフン2つ(--)から行末までがコメントになります。

-- コメント.

複数行コメントには--[[ ~ ]]を使います。また、最後の]]を--]]としておけば、先頭の--[[を---[[と変更することでコメントを外すことができます。

--[[ これは, コメントです. --]] ---[[ print("これは,") print("コメントではありません.") --]]

変数の宣言と代入

大域変数には宣言はありません。局所変数にはlocalを使います。

a = 1 a = "hello" local a = 3 local a = "world"

Luaでは多重代入することができます。

a, b, c = 1, 2, 3

多重代入を利用してスワップが可能です。

a, b = b, a -- aとbの値を入れ替える.

スクリプトの実行

コマンドラインから以下のように実行することができます。

lua test.lua

またコマンドライン引数はテーブルargに保持されます。

--[[ arg[-1]: "lua" arg[0]: "test.lua" arg[1]: "arg1" arg[2]: "arg2" --]] lua test.lua arg1 arg2

2. 数値

数値の表現

実数と整数の区別はありません。以下の変数b, c, dは同じ数値になります。

a = 1 b = 1.234 c = 0.1234e1 d = 12.34e-1

各種演算

num = 5 + 2 -- 7 num = 5 - 2 -- 3 num = 5 * 2 -- 10 num = 5 / 2 -- 2.5 num = 5 % 2 -- 1 (剰余) num = 5 ^ 2 -- 25 (累乗)

インクリメントとデクリメント

Luaには++演算子や+=演算子がないので以下のように記述します。

i = i + 1 i = i - 1

3. 文字列

文字列表現

文字列はシングルクォート(')かダブルクォート(")で囲みます。2重の角括弧([[~]])でも文字列となり、エスケープシーケンスなどもそのまま表示できます。

str = 'abc' str = "abc" str = [[abc]] str = 'ab"cd"ef' -- ab"cd"ef str = "ab'cd'ef" -- ab'cd'ef str = "abc\ndef" -- abcに改行が入り、次の行にdefが出力される str = [[abc\def]] -- abc\defが1行で出力される

string.format関数を利用してC言語のprintf関数のような書式設定ができます。

string.format("%d, %6.3f", 10, 12.34567) -- 10, 12.346 ("%d, %6.3f"):format(10, 12.34567)

"%q"は、安全に読み出せる文字列に書式化します。

--[[ "test\ \"test\"\ test" --]] string.format("%q", 'test\n"test"\ntest') ("%q"):format('test\n"test"\ntest')

文字列操作

分割については標準ライブラリにないので自前でsplit関数を用意します。

-- 結合. a = "aaa" .. "bbb" -- "aaabbb" a = table.concat({ "aaa", "bbb", "ccc" }, ",") -- "aaa,bbb,ccc" -- 分割. function split(str, del) p, nrep = str:gsub("%s*"..del.."%s*", "") return { str:match((("%s*(.-)%s*"..del.."%s*"):rep(nrep).."(.*)")) } end split("aaa, bbb, ccc", ",") -- { "aaa", "bbb", "ccc" } -- 長さ. length = #"abc" -- 3 -- 切り出し. substr = string.sub("abcd", 0, 2) -- ab substr = ("abcd"):sub(0, 2) -- 検索. result = string.find("abcd", "cd") -- 見つかった場合はその位置(この場合は3)、見つからなかった場合はnilが返る. result = ("abcd"):find("cd")

4. 配列

配列変数の宣言と代入

-- 配列の宣言 array = {} -- 配列への代入 array = { 1, 2, 3 }

配列の要素の参照と代入

Luaでは配列の添え字(インデックス)は通常1から始まります。

-- 要素の参照. array[1] -- 配列arrayの先頭. array[2] -- 要素の代入. array[1] = 1 array[2] = 2

配列の個数

array_num = #array array_num = table.getn(array)

配列の操作

array = { 1, 2, 3 } -- 先頭を取り出す. first = table.remove(array, 1) -- firstは 1, arrayは { 2, 3 } -- 先頭に追加. table.insert(array, 1, 5) -- arrayは { 5, 2, 3 } -- 末尾を取り出す. last = table.remove(array) -- lastは 3, arrayは { 5, 2 } -- 末尾に追加. table.insert(array, 9) -- arrayは { 5, 2, 9 }

5. テーブル

Luaのテーブルは連想配列になっていて、nilを除く数値や文字列を添え字とすることができます。上述の配列についても基本的にはテーブルとなります。また、テーブルを入れ子にしたり、関数を保持させることもできます。

テーブルの宣言と代入

t = { 1, 2, 3 } t = { name = "lua", length = 3, sense = "moon" } t = { "students", name = { "taro", "hanako", "kenta" }, num = 3 } t = { func1 = function() return "ok" end, func2 = function(a, b) local c = a * b return c end }

テーブルの要素の参照と代入

-- 要素の参照. t[1] t["name"] t.name -- 文字列を添え字にする場合はピリオド(.)を使用できる. t.func2(2, 3) -- テーブルに保持した関数を呼び出す. -- 要素の代入. t[1] = 5 t["name"] = "lua" t.name = "lua"

6. 制御文

if 文

if 条件 then -- 条件が真. end

if ~ else文

if 条件 then -- 条件が真. else -- 条件が偽. end

if ~ elseif 文

if 条件1 then -- 条件1が真. elseif 条件2 then -- 条件1が偽、条件2が真. end

while文

while 条件 do -- 条件が真の間、処理される. end

repeat ~ until文

repeat -- 条件が偽の間、処理される. until 条件

for文

for i = 1, 10 do print(i) -- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10を出力. end for i = 1, 10, 2 do print(i) -- 1, 3, 5, 7, 9を出力. end

汎用forループとipairs/pairs関数を使って配列/テーブルを出力できます。

for i, value in ipairs({ 10, 20, 30 }) do --[[ 1 10 2 20 3 30 と表示される. --]] print(i, value) end data = { a = 5, b = 6, c = 7 } for key, value in pairs(data) do --[[ a 5 c 7 b 6 と表示される. --]] print(key, value) end

比較演算子

比較演算子では偽の時にfalse、真の時にtrueを返します。

num1 == num2 -- num1はnum2と等しい. num1 ~= num2 -- num1はnum2と等しくない. num1 < num2 -- num1はnum2より小さい. num1 > num2 -- num1はnum2より大きい. num1 <= num2 -- num1はnum2以下. num1 >= num2 -- num1はnum2以上.

7. 関数

Luaでは関数を作るにはfunction(...) ~ endを使います。また、関数内に関数を作ることができます。

function sum(num1, num2) local total = num1 + num2 return total end sum = function(num1, num2) local total = num1 + num2 return total end

8. ファイル入出力

Lua独自のファイル入出力として、io.input, io.output関数があります。初期状態で標準入力、標準出力に割り当てられています。

io.input("input.txt") -- 入力をinput.txtから読み込む. io.output("output.txt") -- 出力をoutput.txtに書き出す. while line = io.read() do print(line) -- io.write(line .."\n")と同じ. end io.input() -- 入力を標準入力にする. io.output() -- 出力を標準出力にする.

C言語のようなファイル入出力にはio.open関数を使います。

--[[ r: 読み込み, w:書き込み, a:追記, r+, w+, a+ fh: ファイルハンドル, msg: エラーメッセージ. --]] fh, msg = io.open("input.txt", "r") if fh then data = fh:read("*a") -- ファイル全体を読み込む. print(data) else print(msg) end

9. C言語とLuaの連携

LuaではC言語と連携を取るためのAPIが充実しているので、C言語からLuaを利用することも、LuaからC言語を利用することも簡単です。基本的な流れとして、スタックに積んだデータをやりとりすることになります。

以下にサンプルコードを示しておきます。LuaからC言語を利用する場合も、C言語からLuaを利用する場合も、C言語のソースコードをコンパイルしたファイルから実行されます。

ヘッダファイルやライブラリのパスを通した上で、Visual Studioでは、

cl func.c lua51.lib

gccでは、

gcc func.c -llua -lm

でコンパイルできます。

func.lua

print(func1(1.5)) -- C言語で作成した関数func1を呼び出す. data = { test = "lua" } print(func2(data)) -- C言語で作成した関数func2を呼び出す.

func.c

#include #include "lua.h" #include "lualib.h" #include "lauxlib.h" /* 引数で渡された数値を半分と倍にして返す */ int func1(lua_State *L) { /* 最初のスタックから数値を読み出す */ double d = lua_tonumber(L, 1); lua_pushnumber(L, d / 2.0); /* 半分にしてスタックに載せる */ lua_pushnumber(L, d * 2.0); /* 倍にしてスタックに載せる */ return 2; /* 戻り値のスタック数 */ } /* 引数で渡されたテーブル内のtestフィールドの文字列を返す */ int func2(lua_State *L) { /* スタックをクリアする */ lua_settop(L, 1); /* テーブル内のフィールド"test"をスタックの最初に置く */ lua_getfield(L, 1, "test"); /* スタックの2番目(つまりフィールド"test"のデータ)の */ /* 文字列を読み出してスタックに置く*/ lua_pushstring(L, lua_tostring(L, 2)); return 1; /* 戻り値のスタック数 */ } int main() { /* Luaを使えるようにする */ lua_State *L = luaL_newstate(); /* Luaの標準関数を利用可能にする */ luaL_openlibs(L); /* C言語で作成した関数をLua上で呼び出せるようにする */ lua_register(L, "func1", func1); lua_register(L, "func2", func2); /* Luaソースファイル"func.lua"を読み出して実行する */ /* func.lua内でfunc1, func2を利用している */ if (luaL_loadfile(L, "func.lua") || lua_pcall(L, 0, 0, 0)) { fprintf(stderr, "error\n"); return -1; } /* luaL_dostringを利用してC言語からLuaを使用する */ luaL_dostring(L, "print(3.5)"); /* スタックを利用してC言語からLuaを利用する */ lua_getglobal(L, "print"); /* print関数をスタックに置く */ lua_pushstring(L, "string"); /* 文字列"string"をスタックに置く*/ lua_pcall(L, 1, 0, 0); /* 引数1個、戻り値なし、エラー関数なしで実行 */ lua_close(L); return 0; }

出力結果:

0.75 3 lua 3.5 string

上記の方法だと、LuaからC言語の関数を呼び出す場合でもC言語からLuaのコードを呼ばなくてはならず、コンパイルが必要になります。そこで、以下にC言語による関数をWindowsのDLLファイルおよびUnixの共有ライブラリファイルのモジュールとして呼び出す方法を書いておきます。関数の内容は上記のコードと同じで、func1とfunc2を定義しています。

Visual Studioでのコンパイル方法は以下の通りです。

cl cmodule.c lua51.lib /LD

gccでのコンパイル方法は以下のようになります。

gcc cmodule.c -llua -shared -o cmodule.so

実行は、通常のLuaスクリプトと同じです。

lua func_mod.lua

func_mod.lua

require("cmodule") -- cmodule.dll/cmodule.soを読み込む. print(cmodule.func1(1.5)) -- C言語で作成した関数func1を呼び出す. data = { test = "lua" } print(cmodule.func2(data)) -- C言語で作成した関数func2を呼び出す.

cmodule.c

#include <stdio.h> #include "lua.h" #include "lauxlib.h" /* 引数で渡された数値を半分と倍にして返す */ static int func1(lua_State *L) { /* 最初のスタックから数値を読み出す */ double d = lua_tonumber(L, 1); lua_pushnumber(L, d / 2.0); /* 半分にしてスタックに載せる */ lua_pushnumber(L, d * 2.0); /* 倍にしてスタックに載せる */ return 2; /* 戻り値のスタック数 */ } /* 引数で渡されたテーブル内のtestフィールドの文字列を返す */ static int func2(lua_State *L) { /* スタックをクリアする */ lua_settop(L, 1); /* テーブル内のフィールド"test"をスタックの最初に置く */ lua_getfield(L, 1, "test"); /* スタックの2番目(つまりフィールド"test"のデータ)の */ /* 文字列を読み出してスタックに置く*/ lua_pushstring(L, lua_tostring(L, 2)); return 1; /* 戻り値のスタック数 */ } /* モジュールに登録するために関数のテーブルを定義 */ static luaL_Reg cmodule[] = { { "func1", func1 }, { "func2", func2 }, { NULL, NULL } /* 最後は NULL, NULL とする */ }; /* DLL/共有ライブラリにエクスポートする関数 luaopen_(モジュール名) */ /* Unixでは__declspec(dllexport)は不要 */ __declspec(dllexport) int luaopen_cmodule(lua_State *L) { luaL_register(L, "cmodule", cmodule); /* cmoduleモジュール */ return 1; }

出力結果:

0.75 3 lua

知っておいた方がよい文法

真偽値

falseとnil以外はすべて真になります。よって、0や""や空のテーブルなどもすべて真です。

論理演算子

a = 10 b = 30 c = a < 20 or b < 20 -- true d = a < 20 and b < 20 -- false a = 10 or 20 -- a = 10 a = 10 and 20 -- a = 20 a = nil or 10 -- a = 10 a = nil and 10 -- a = nil

初期化されていない変数はすべてnilが入っていることを利用して、orを使ってデフォルト値を設定することができます。

a = a or 10 -- aに何も代入されていなければ10が代入される.

外部Luaファイルの読み込み

外部Luaファイルの実行やテーブルなどのデータを読み込む目的で利用できます。

dofile("file.lua")

文字列をプログラムとして実行

Perlなどのeval機能として、loadstring関数があります。

f = loadstring("i = i + 1") i = 0 f() f() f() print(i) -- 3と表示される.

データの型を文字列で返す

type(1) -- "number" type("abc") -- "string" type({ 1, 2, 3 }) -- "table" type(true) -- "boolean" type(print) -- "function"

数値と文字列の変換

tonumber("123.5") -- 123.5 tostring(123.5) -- "123.5"

参考サイト


他の基礎文法最速マスター


4 コメント:

dan5 さんのコメント...

table.insert(array, 5, 1)のところは
table.insert(array, 1, 5)の誤りではないでしょうか。

nox さんのコメント...

dan5さん、こんにちは。
ご指摘の通り、table.insert(array, 5, 1)は誤りです。
table.insert(array, 1, 5)に修正しておきました。
ありがとうございました。

匿名 さんのコメント...

repeat - untilのくだりですが、処理を繰り返すのは、条件が偽の場合ではないですか?

nox さんのコメント...

ご指摘ありがとうございます。
修正しておきました。