Rua: Ruby Lua Library

http://rua.rubyforge.org/
http://rubyforge.org/projects/rua/
http://storehouse.sakura.ne.jp/viewvc/viewvc.cgi/rua/rua.tar.gz?root=svn&view=tar
extconf.rbでコンパイルが通って、動作確認もできたので正式公開。
近日中にRubyForgeに移す予定。

これは何?

Rubyから組み込み用言語Luaを使うための拡張ライブラリです。
Luaのライブラリはすでにいくつかあるんですが

  • Lua 5.1に対応したライブラリが欲しかった(RAAに登録されているものは対応していないみたい…)
  • win32用のバイナリが欲しかった

ということで作成しました。

インストール

gem
~$ gem install rua
Select which gem to install for your platform (i386-mswin32)
 1. rua 0.1.0 (mswin32)
 2. rua 0.1.0 (ruby)
 3. Skip this gem
 4. Cancel installation
>
unix

Lua5.1の開発用パッケージをインストールして、以下のようにrua.soを作成します。

~$ ruby extconf.rb --with-lua-include=/usr/include/lua5.1 --with-lua-lib=/usr/lib
checking for lua.h... yes
checking for lualib.h... yes
checking for lauxlib.h... yes
checking for main() in -llua5.1... yes
creating Makefile
~$ make
cc -I. -I. -I/usr/lib/ruby/1.8/i486-linux -I. -DHAVE_LUA_H -DHAVE_LUALIB_H -DHAVE_LAUXLIB_H -I/usr/i
nclude/lua5.1  -fPIC -fno-strict-aliasing -O2  -fPIC  -c rua.c
cc -shared -rdynamic -Wl,-export-dynamic   -L"/usr/lib" -o rua.so rua.o  -lruby1.8 -llua5.1  -lpthre
ad -ldl -lcrypt -lm   -lc

とりあえず動かす

とりあえず動かすだけならこんな感じです。

require 'rua'

rua = Rua.new
rua.openlibs(:all) # ライブラリの読み込み

rua.foo = 65535  # 大域変数の書き込み
rua.func = lambda do |x|
  puts x
end

p rua.eval(<<EOS)  # 文字列の評価
  print('hello Rua!')
  print(foo)
  func(100)
  f = function()
    print('f() called.')
  end
  return 100
EOS

f = rua.f        # 大域変数の読み込み
f.call

基本的にopenlibsメソッドでライブラリを開かないと、出来ることは少ないです。
標準ライブラリはここにあるとおり。
「rua.openlibs(:all)」ですべてのライブラリを開きます。
個別にライブラリを開く場合は「rua.openlibs(:base, :io, :string)」と必要なだけ渡します。


evalメソッドで文字列を評価しますが、評価する文字列で明示的にreturnをしないと、戻り値はnilになります。

オブジェクトの変換

オブジェクトの変換は次のようになります。

  • RubyLua
    • nil/true/false→nil/true/false
    • Numeric→数値
    • String→文字列
    • Hash/Array→テーブル
    • Proc/Method→関数
    • RuaFunc/RuaThread→元に戻る
    • その他→テーブル
  • LuaRuby
    • nil/true/false→nil/true/false
    • 数値→Float/Integer
    • 文字列→String
    • テーブル→Hash
    • 関数→RuaFunc
    • スレッド→RuaThread
    • その他→nil

Arayは1オリジンの数列をキーとしたテーブルになります。

require 'rua'

rua = Rua.new
rua.openlibs(:all)

rua.ary = ['foo', 'bar', 'zoo']

rua.eval(<<EOS) #=> 「1:foo、2:bar、3:zoo」と表示
  for i, v in ipairs(ary) do
    print(i .. ':' .. v)
  end
EOS


また、Rubyのその他のオブジェクトは、定義されているメソッド名をキーに持つテーブルになります。

require 'rua'

rua = Rua.new
rua.openlibs(:all)

rua.Time = Time

rua.eval(<<EOS)
  time = Time.new()  -- 大域変数Timeのテーブルにnewというキーで関数が保持されている
  print(time.to_s()) -- > Sat Nov 10 23:02:01 +0900 2007
EOS


Luaで定義した関数・RubyからLuaに渡した関数は、Ruby側でRuaFuncインスタンスに変換されます。
使い方はProcとほとんど変わりませんが、例外を投げることがありません

例外の扱いについて

Luaは例外機構を持たない言語です。
なので、RubyからLuaに定義した関数で例外が投げられても、処理を続行します。Ruby側に例外は返ってきません。
※0.3.1からRuaErrorで実行を中止するようにしました。処理を続行する場合は Rua#abort_by_error に false をセットしてください。
Luaの実行中に発生した例外を補足するには、エラーハンドラを使います。

require 'rua'

rua = Rua.new
rua.abort_by_error = false
rua.error_handler = lambda {|e|
  p e
} # ハンドラを設定
rua.openlibs(:all)

rua[:f] = lambda do
  raise 'xxxxx'
end

rua.eval(<<EOS)
  f() -- 例外が発生するが処理は続行
  print(100)
EOS

セキュアモード

Lua側からRubyへメタプログラミングされるのを防ぐため、デフォルトではセキュアモードが有効になっています。
セキュアモードでは以下のような動作になります。

  • Lua側へModule・Classクラスインスタンスを渡せない。
    • 渡すと例外が投げられるか、fatalでとまりますnilに変換されます。
  • 以下のメソッドが変換されない
    • __id__
    • __send__
    • ancestors
    • autoload
    • autoload?
    • class
    • class_eval
    • class_variable_defined?
    • class_variables
    • const_defined?
    • const_get
    • const_missing
    • const_set
    • constants
    • extend
    • freeze
    • id
    • include?
    • included_modules
    • instance_eval
    • instance_method
    • instance_methods
    • instance_variable_defined?
    • instance_variable_get
    • instance_variable_set
    • instance_variables
    • method
    • method_defined?
    • method_missing
    • methods
    • module_eval
    • private_class_method
    • private_instance_methods
    • private_method_defined?
    • private_methods
    • protected_instance_methods
    • protected_method_defined?
    • protected_methods
    • public_class_method
    • public_instance_methods
    • public_method_defined?
    • public_methods
    • respond_to?
    • send
    • singleton_methods
    • taint
    • to_ary
    • to_hash
    • to_int
    • to_str
    • type
    • untaint

セキュアモードを無効にするには「rua.secure = false」としてください。

コルーチン

require 'rua'

co = rua.eval(<<-EOS)
  function foo (a)
    print('foo', a)
    return coroutine.yield(2 * a)
  end

  return coroutine.create(function (a, b)
    print('co-body', a, b)
    local r = foo(a + 1)
    print('co-body', r)
    local r, s = coroutine.yield(a + b, a - b)
    print('co-body', r, s)
    return b, 'end'
  end)
EOS

p co.resume(1, 10)
p co.resume('r')
p co.resume('x', 'y')

今後の課題

  • スレッドまわりはまったく忘れていたので、これから実装します。。。
  • RuaFuncインスタンスを再度Luaに渡すと、テーブルになっちゃうので直す。
  • セキュアモードを実装中です