RubyLuaBridge - a seamless bridge between Ruby and Lua
RubyLuaBridge lets you access Lua from Ruby. Eventually, support for accessing Ruby from Lua will be added. This documentation assumes basic knowledge of Ruby and Lua.
Relevant web pages:
Installation
RubyLuaBridge is a C extension that uses the actual Lua C library. It currently targets Lua 5.1 and Ruby 1.8. You must run:
% ruby extconf.rb --with-lua-include=/path/to/lua/include --with-lualib=lualibname % make % sudo make install
There are extconf helpers for various platforms. Run these instead of the ‘ruby extconf.rb’. Included are:
- build/extconf_osx.sh
- build/extconf_ubuntu.sh
The docs can be built with rake (currently needs jambis.rb installed):
% sudo rake install_jamis_template % rake
Basic Usage
First, you need to require the RubyLuaBridge library. The core object this library is the Lua::State, which wraps a Lua virtual machine. With this object, you can run Lua code, load Lua libraries, and access Lua objects.
require 'rubyluabridge' l = Lua::State.new # we're gonna do a lot with this guy
Once you have a Lua::State, you can run Lua code using the Lua::State.eval. This method will return whatever value is returned by the block. It will also raise an exception on errors.
l.eval "print('hello world')" # => nil
l.eval "function give_me_one() return 1 end" # => nil
l.eval("return give_me_one()") # => 1
Since Lua can return multiple values, Lua::State.eval_mult will return all values in an Array.
require 'pp'
pp l.eval_mult("return") # => []
pp l.eval_mult("return 1") # => [1.0]
pp l.eval_mult("return 1, 2, 3") # => [1.0, 2.0, 3.0]
Due to the dynamic natures of Ruby and Lua, you can easily query and set Lua values in a natural way. You can use square brackets [key], which is necessary if your key is not a string. Otherwise, string keys can also be accessed by invoking it like a method (see example below). Querying from the Lua::State will access the Lua global table.
l.eval <<LUA_END
n = 5 -- see how Ruby long strings
s = "hello" -- make it easy to embed Lua code?
a = { 1, 2, 3, 4 }
h = { a='x', b='y', }
LUA_END
l.n # => 5
l.s # => "hello"
l.a # => Lua::Table
l.a[1] # => 1
l.h # => Lua::Table
l.h['a'] # => "x"
l.h.a # => "x"
Similarly, you can use this style to set Lua variables. To make a new table, invoke new_table_at on the object, specifying where you want it. Setting a Ruby Arrays or Hashes to a Lua variable will create and assign a new table with a shallow copy of the Ruby container.
l.n = 5 # n = 5
l.new_table_at 't' # t = {}
l.t.u = 4 # t.u = 4
l.t['u'] = 4 # t.u = 4
l.a = [1,2] # t.a = { 1, 2 }
l.h = { 'a' => 'b' } # t.h = { a = 'b' }
When RubyLuaBridge has to marshal a table to Ruby, it return a Lua::Table. The Lua::Table implements various iterators that take blocks. There are two ways of looking at a Lua table. The first is as a hash, where it has keys of any type mapping to values of any type. The second is as an array, where it has integer keys from 1 to N, where N is the size of the array. The each_i* methods iterate the table as an array, whereas the each_* methods (without the i) iterate the table as a hash. In each case you can iterate of the keys, the values, or the pair [key, value]. Lua::Table#each is aliased to Lua::Table#each_pair and Lua::Table#each_index is aliased to Lua::Table#each_ikey.
l.eval( "array = { 100, 200, 300, 400 }" )
l.array.each_ikey { |k| print "#{k} " } # 1 2 3 4
l.array.each_ivalue { |v| print "#{v} " } # 100.0 200.0 300.0 400.0
l.array.each_ipair { |k,v| print "#{k},#{v} " } # 1,100.0 2,200.0 3,300.0 4,400.0
l.eval( "hsh = { a = 100, b = 200, [1] = 300, [5] = 400 }" )
l.hsh.each_key { |k| print "#{k} " } # a 1.0 5.0 b
l.hsh.each_value { |v| print "#{v} " } # 100.0 300.0 400.0 200.0
l.hsh.each_pair { |k,v| print "#{k},#{v} " } # a,100.0 1.0,300.0 5.0,400.0 b,200.0
l.hsh.each_ikey { |k| print "#{k} " } # 1
l.hsh.each_ivalue { |v| print "#{v} " } # 300.0
l.hsh.each_ipair { |k,v| print "#{k},#{v} " } # 1,300.0
You can also extract a Ruby Hash or Array that is a shallow-copy of the table.
l.eval "a = {1,2} ; h = { a=3, b=4 }"
l.a.to_array # => [1, 2]
l.a.to_hash # => { 1 => 1, 2 => 2 }
l.h.to_array # => []
l.a.to_hash # => { 'a' => 3, 'b' => 4 }
Note that Ruby Array’s start with index 0, whereas Lua array’s start with index 1.
Note for advanced Lua users: all of these accesses use the Lua object’s metamethods.
RubyLuaBridge has a comprehensive test suite (LuaInRuby_Test). Examine it to see various simple uses of this library.
Language Mismatchs
There are two sticky issues when trying to express ideas with RubyLuaBridge.
Lua uses a colon : syntax to define Lua’s "methods", functions that have an implicit extra parameter self. Ruby methods always have this self parameter implicitly. RubyLuaBridge’s will only pass the self parameter to a Lua function invocation if you end the method name with bang !. The bang was chosen because it vaguely resembles a colon : !. Note that in Lua, the colon is before the method name, but in RubyLuaBridge, the bang is after (but attached to) the method name.
Another issue is that it there is an ambiguity whether an index is meant to be a property or a function call. This happens when there are no arguments, since Ruby’s parentheses are optional. RubyLuaBridge always returns the object when there are no arguments, unless it’s name is suffixed with an underscore _. With the underscore, RubyLuaBridge will invoke the object like a function. The underscore can be used with multiple arguments as well. Awkwardly, it will do this parameter-less behavior even when paretheses are present, so don’t do that!
The one exception to the no argument behavior is if the object is a Lua function, in which case it is dispatched. To get a reference to a function, rather than invoking it, use [].
Examples of this notation, with their semantics shown in the more explicit Lua code:
l.eval <<END_OF_LUA
obj = {
foo = function (...)
end,
bar = "a"
}
END_OF_LUA
l.obj.foo # return obj.foo()
l.obj.foo() # return obj.foo()
l.obj.foo_ # return obj.foo()
l.obj.foo 2 # return obj.foo(2)
l.obj.foo_ 2 # return obj.foo(2)
l.obj.foo! # return obj:foo(obj)
l.obj.foo! 2 # return obj:foo(obj, 2)
l.obj['foo'] # return obj.foo
l.obj.bar # return obj.bar
l.obj.bar() # return obj.bar -- yuck!
l.obj.bar_ # error("object not callable")
l.obj.bar! # error("object not callable")
l.obj.bar(2) # error("object not callable")
l.obj['bar'] # return obj.bar
Design Philosophy
- simplest marshalling possible
- simplest API possible
- do more complicated things in Lua itself,
rather than exposing them!
For example, the coroutine library is not explicitly exposed because it can be handled all through eval and indexing: l.coroutine.create(f)
Ruby to Lua Type Mapping
| Ruby: | Lua |
| nil: | nil |
| None: | nil |
| True: | true |
| False: | false |
| Fixnum: | number |
| Bignum: | number |
| Float: | number |
| String: | string |
| Symbol: | string |
| Hash: | new Lua::Table clone |
| Array: | new Lua::Table clone |
| everything else: | lightuserdata |
Lua to Ruby Type Mapping
| Lua: | Ruby |
| none: | nil |
| nil: | nil |
| true: | True |
| false: | False |
| number: | Float |
| string: | String |
| table: | Lua::Table |
| lightuserdata: | Lua::RefObject |
| function: | Lua::RefObject |
| userdata: | Lua::RefObject |
| thread: | Lua::RefObject |
TODO
- package as gem
- stack trace in error callback
- how to get some external lua_State there?
- how to deal with .clone and .dup?
- accessing Ruby from Lua
Credits
The following persons have contributed to RubyLuaBridge:
- Evan Wies (evan a neomantra d net)
RubyLuaBridge is inspired by, but not derived from:
- Lunatic Python [labix.org/lunatic-python]
- ruby-lua [raa.ruby-lang.org/project/ruby-lua]
License
Licensed under the BSD License. It is free software, and may be redistributed under the terms specified in the accompanying LICENSE file, as follows:
Copyright © 2007, Evan Wies. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the neomantra nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Evan Wies ``AS IS’’ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Evan Wies BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Support
Download the latest sources from there. Please use the mailing list and issue tracking features as well.
I am particularly interested in problems you may have on various systems. I have only tested it in Ubuntu Dapper and MacOSX.