Luaというスクリプト言語がある。非常にコンパクトかつ高速に動作し、C/C++とも親和性がよいらしい。
 ということで、Luaを初めてみる。


インストール

Linuxの場合はパッケージにあるので、aptやyumでインストールすればよいでしょう。
Linuxでソースからコンパイルする場合は、以下のようにする。

1)以下からソース用のtarballをダウンロードする。
  http://www.lua.org/ftp/

2)展開すると「INSTALL」というファイルがあるのでそれを一読する。

3) 「make linux」 を実行
 linuxのところには、対象のプラットホームを名をいれれば、それに応じてメイクできる。
 指定可能なものは以下のとおり。
$ make
Please do
   make PLATFORM
where PLATFORM is one of these:
   aix ansi bsd freebsd generic linux macosx mingw posix solaris
See INSTALL for complete instructions.
4) 「make install」 を実行するとインストール完了。
 なお、デフォルトは”/usr/local”配下にインストールされます。別のところがよければ、以下のようにします。
$ make install INSTALL_TOP=xxx    xxxにはインストール先のパスを指定  

Cygwin1.7.xへのインストール

Windowsでluaを使いたくなりCygwinにインストールしてみた。
  http://lua-users.org/wiki/BuildingLua
上記にWindowsでのインストール方法が書いてあった。Cygwin以外もあったが、コンパイル環境を整えるのが面倒なので、インストール済みのCygwinにインストールしてみた。
ソースtarballを展開したら、以下の手順でインストールします。

1) src/Makefile に以下を追加
cygwin:
 	$(MAKE) "LUA_A=cyglua-5.1.dll" "LUA_T=lua.exe" \
 	"AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \
 	"MYCFLAGS=-DLUA_USE_LINUX -DLUA_BUILD_AS_DLL" \
 	"MYLIBS=-lreadline -lhistory" "MYLDFLAGS=-s" lua.exe
2) Makefile の以下にcygwinを追加
 # Convenience platforms targets.
 PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris cygwin   <--これ
3) 「make cygwin」を実行
4) 「make install」を実行
5) cyglua-5.1.dll のコピー
 make installではcyglua-5.1.dllがパスのとおったところにインストールされない。
 src/cyglua-5.1.dll をパスのとおっている /usr/local/lib にコピーして、インストール完了。


参考Webサイト

  • 本家サイト
  • lua-users wiki(サンプルがたくさんある)
  • Lua5.1リファレンスマニュアル(日本語訳)
  • Lua文法最速マスター


LuaからC言語の関数を呼び出す

 LuaからCの関数を利用するには、いくつかの決まりがあります。

1)Cの関数は以下のインターフェースで定義する。Luaから利用可能な関数をグルー関数と呼ぶ(LuaとCを糊付けするため)。
typedef int (*lua_CFunction) (lua_State *L);

2)グルー関数をLuaの関数として呼び出せるようにするため、以下の"luaL_Reg"構造体にグルー関数のポインタとLuaの関数名の対応を定義し、"luaL_register"関数でレジストする。
typedef struct luaL_Reg {
     const char *name;
     lua_CFunction func;
} luaL_Reg;

3)luaL_register関数は以下のインターフェースの関数内に定義する。
int luaopen_<モジュール名>(lua_State *L)
 ※モジュール名のところをどうすべきかの説明が見つけられなかった。1)〜3)をすべて1つのCソースにして、その拡張子をとったものをモジュール名とすればOKだった。

4)Lua関数とグルー関数間の引数と戻り値の授受はLua独自のスタックを使用する。Lua関数に渡す引数は順に自動的にスタックに積まれ、グルー関数からはスタックのインデックス(第1引数がインデックス1、第2引数が2、・・・)で取得できる。グルー関数からの戻り値は、戻り値の型に応じた"lua_push*"関数によりスタックにpushすることで、Lua関数の戻り値として取得できる(複数pushすれば、複数の戻り値も取得できる)。

5)Luaがグルー関数の定義を探せるよう、グルー関数をダイナミックリンクライブラリにして、Luaスクリプトからrequire関数でリンクする。

以上を踏まえ、2つの数字を与えると掛け算の答えを出すmul関数と、2つの数字を与えるとその商と余りを出すdiv関数を例にして、そのグルー関数と、それを呼び出すためのLuaスクリプトを以下に示す。

a.C言語のグルー関数(calc.c)
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int mul(lua_State * L)
{
   int val1 = lua_tonumber(L, 1);   /* 第1引数を数字としてスタックから取り出す */
   int val2 = lua_tonumber(L, 2);   /* 第2引数を数字としてスタックから取り出す */
   lua_pushnumber(L, val1 * val2);  /* 積をスタックに積む->第1の戻り値 */
   return 1;                        /* グルー関数の戻り値の数1を返却 */
}

static int div(lua_State * L)
{
   int val1 = lua_tonumber(L, 1);   /* 第1引数を数字としてスタックから取り出す */
   int val2 = lua_tonumber(L, 2);   /* 第2引数を数字としてスタックから取り出す */
   lua_pushnumber(L, val1 / val2);  /* 商をスタックに積む->第1の戻り値 */
   lua_pushnumber(L, val1 % val2);  /* 余りをスタックに積む->第2の戻り値 */
   return 2;                        /* グルー関数の戻り値の数2を返却 */
}

/* lsleepモジュールに登録する関数のマッピング定義 */
static const struct luaL_reg calctable[] = {
  {"mul", mul},
  {"div", div},
  {NULL, NULL},		/* 最後はダミー行が必要 */
};

/* lsleepモジュール初期化関数 */
int luaopen_calc(lua_State * L)
{
  luaL_register(L, "calc", calctable);
  return 1;
}

b.Luaスクリプト(test.lua)
require("calc")		-- calc.soの動的リンク

x = 8
y = 3

seki = calc.mul( x, y ) 	-- calcはレジストしたモジュール名
syou, amari = calc.div( x, y )

print(x .. " * " .. y .. " = " .. seki )
print(x .. " / " .. y .. " = " .. syou .. " ・・・ " .. amari )

実行してみると、以下のとおり計算結果が得られる。
$ lua test.lua 
8 * 3 = 24
8 / 3 = 2 ・・・ 2


【参考】Linuxでのダイナミックリンクライブラリ作成
 以下のとおり、普通に作ればよい。Lua用のヘッダやライブラリをインストールしたパスを指定する必要がある。
gcc -shared -fPIC -o calc.so calc.c -L/opt/develop/lib -llua -I/opt/develop/include

 また、上記ではカレントにcalc.soを作成しているが、そのままだとLuaがcalc.soを見つけられないので、環境変数LUA_CPATHにカレントディレクトリを指定する必要がある。
export LUA_CPATH="./?.so"


CからLuaを実行する。

CからLuaを実行する形態としては以下のものがある。
1)Luaのスクリプト全体を実行する。
2)CソースかかれたLuaの命令を実行する。
3)Luaスクリプトに書かれた関数を実行する。
ーー>Comming Soon!!

バイナリダンプ(Hex dump)

指定されたデータを指定開始位置から指定終了位置までバイナリダンプ(ヘキサダンプ)する関数(hexdump)を作ってみた。
動作確認のため、コマンドライン引数で指定されたファイルをダンプするようにした。
以下のデータで実行すると、以下のように表示する。
$ cat test.dat
0123456789abcdef0--------------f
$ lua hexdump.lua test.dat
00000000 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66  0123456789abcdef
00000010 30 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 66  0--------------f
00000020 0A

$ lua hexdump.lua test.dat 10 15
00000009 39 61 62 63 64 65                 9abcde

ソース(hexdump.lua)は以下からどうぞ。
http://www35.atwiki.jp/futoyama?cmd=upload&act=open&pageid=74&file=hexdump.lua


インストールしたはずのモジュールが見つからない場合

LuaでXML-RPCをためしてみるため、Lua XML-RPCをインストールした。ためしにアーカイブの付属サンプルを動かそうとしたら、以下のエラーがでた。
$ lua server_xavante.lua 
lua: /mnt/work/develop/share/lua/5.1/xmlrpc/server.lua:11: module 'xmlrpc' not found:
	no field package.preload['xmlrpc']
	no file '/mnt/work/develop/share/lua/5.1/xmlrpc.lua'
	no file './xmlrpc.lua'
	no file '/mnt/work/develop/lib/lua/5.1/xmlrpc.so'
	no file './xmlrpc.so'
stack traceback:
	[C]: in function 'require'
	/mnt/work/develop/share/lua/5.1/xmlrpc/server.lua:11: in main chunk
	[C]: in function 'require'
	server_xavante.lua:6: in main chunk
	[C]: ?

Lua XML-RPCのソースは以下の3つだけであり、Luaソースのみで、Cなどのモジュールはない。インストールも環境変数LUA_PATHで指定されたところに「xmlrpc」ディレクトリを作成し、そこに3つのソースをコピーするだけ。
$ ls src
http.lua  init.lua  server.lua
しかしrequireコマンドは「xmlrpc.lua」を探そうとしてないのでエラーになっている。リファレンスマニュアル>の「package.path」の説明を見ると「init.lua」の文字が見つかった。私のLuaはデフォルトとは別のパスにをインストールしており、package.pathを環境変数LUA_PATHで設定している。エラーの原因は、package.pathにinit.luaがもれているためだった。標準のパスでインストールされた環境のpackage.pathを見て、漏れているものを追加して、無事起動できた。

■デフォルトのパスでインストールした環境
$ lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> print( package.path)
./?.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua;/usr/lib64/lua/5.1/?.lua;/usr/lib64/lua/5.1/?/init.lua
■変更内容
export LUA_PATH="./?.lua;/mnt/work/develop/share/lua/5.1/?.lua;/mnt/work/develop/share/lua/5.1/?/init.lua"


バイナリデータを文字列に変換してファイルに書きこむ

 file:write(value1, ...) を使うと、ファイルにデータを書き込める。
 リファレンスマニュアルによれば、引数(書きこむデータ)には文字列か数値を指定できるとあるので、数値を指定すればバイナリデータを書き込めるかと思いきや、数値を文字列にしたもの(1234は"1234")が書かれてしまう。
 file:writeのソース(liolib.cのg_write())を見ると、以下のとおりで、数値だとfprintf()を使い、文字列だとfwrite()を使っている。

if (lua_type(L, arg) == LUA_TNUMBER) {
   /* optimization: could be done exactly as for strings */
   status = status &&
       fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0;
}
else {
   size_t l;
   const char *s = luaL_checklstring(L, arg, &l);
   status = status && (fwrite(s, sizeof(char), l, f) == l);
}

Luaの文字列は `\0´ で表されるゼロを含み、いかなる8ビット値も含むことができるので、バイナリデータを文字列にすれば、バイナリデータをファイルに書くことができる。lua-users wikiにバイナリデータを文字列にするwite_format()が出ていた(以下に引用)。

-- Example:
--   write_format(true, "421", 0x12345678, 0x432931, 0x61) returns "xV4.1)a",
--     a 7 bytes string whose characters are in hex: 78 56 45 12 31 29 61
function write_format(little_endian, format, ...)
  local res = ''
  local values = {...}
  for i=1,#format do
    local size = tonumber(format:sub(i,i))
    local value = values[i]
    local str = ""
    for j=1,size do
      str = str .. string.char(value % 256)
      value = math.floor(value / 256)
    end
    if not little_endian then
      str = string.reverse(str)
    end
    res = res .. str
  end
  return res
end

これを使うと、以下のとおりバイナリデータをファイルにかける。

fh, msg = io.open( "testfile.bin", "wb" )
dat = write_format(true, "42",0x12345678, 0xabcd )
fh:write2( dat )
fh:flush()
fh:close()

$ hexdump -C testfile.bin
00000000 78 56 34 12 cd ab |xV4...|
00000006






名前:
コメント:


最終更新:2011年09月12日 00:36
添付ファイル