├── .gitignore ├── CHANGELOG.txt ├── CREDITS.txt ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── Makefile ├── README.md ├── TODO.txt ├── lib ├── rufus-lua.rb └── rufus │ ├── lua.rb │ └── lua │ ├── error.rb │ ├── lib.rb │ ├── objects.rb │ ├── state.rb │ ├── utils.rb │ └── version.rb ├── rufus-lua.gemspec ├── spec ├── coroutines_spec.rb ├── error_handler_spec.rb ├── eval_spec.rb ├── functions_spec.rb ├── gc_spec.rb ├── lib_spec.rb ├── rfunctions_spec.rb ├── spec.rb ├── spec_base.rb ├── state_spec.rb ├── string_spec.rb ├── tables_spec.rb └── utils_spec.rb └── test ├── bm0.rb ├── bm_fiber.rb ├── bm_gc_stop.rb ├── gc0.rb └── t.rb /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.swp 3 | .vimrc 4 | .viminfo 5 | .vimgrep 6 | 7 | pkg/ 8 | 9 | .ruby-version 10 | #.rspec 11 | 12 | .errors 13 | .rspec.out 14 | .bxsinfo.yaml 15 | .bxsenvs.yaml 16 | 17 | .todo.md 18 | 19 | Gemfile.lock 20 | 21 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 2 | = rufus-lua CHANGELOG.txt 3 | 4 | 5 | == rufus-lua - 1.1.5 released 2019/10/19 6 | 7 | - trust LUA_LIB or FFI.ffi_libs to find 'liblua5.1' else 8 | glob only /usr/lib/*/liblua5.1.*so* (Andri Möll) 9 | 10 | 11 | == rufus-lua - 1.1.4 released 2018/03/15 12 | 13 | - Replace deprecated Fixnum by Integer, gh-40 14 | 15 | 16 | == rufus-lua - 1.1.3 released 2016/08/21 17 | 18 | - respect non-ascii eval input sources (Andri Möll) 19 | 20 | 21 | == rufus-lua - 1.1.2 released 2014/09/13 22 | 23 | - properly turn Ruby nils into Lua nils (Christopher Saunders) 24 | - get rid of Lib.strlen (as suggested by Stas Ukolov) 25 | - let Ruby callbacks accept Lua functions as arguments (Gian Perrone) 26 | - add #set_error_handler (based on an initial request by Nathanael Jones) 27 | - fix unstack issue when returning table (https://github.com/lywaterman) 28 | - use "{filename}:#{lineno}" as eval chunk name (suggested by Nathanael Jones) 29 | - wrap Ruby callbacks in CallbackState (Nathanael Jones) 30 | - accept Ruby symbols containing \0 character (Nathanael Jones) 31 | 32 | 33 | == rufus-lua - 1.1.1 released 2014/07/01 34 | 35 | - move from bacon to rspec 36 | - remove double VERSION (Nathanael Jones - https://github.com/nathanaeljones) 37 | - load only one Lua lib (Thanks Nathanael) 38 | - add Rufus::Lua::Lib.path to learn which Lua lib got loaded (idea Nathanael) 39 | - set `ffi_lib_flags(:lazy, :global)` before loading Lua (thanks Nathanael) 40 | - fix panic error when creating new Rufus::Lua::State with specific 41 | list of lib to load (fix by Matthew Nielsen - https://github.com/xunker) 42 | - read back Lua strings containing \0 bytes (thanks Nathanael Jones) 43 | - let Ruby strings containing \0 bytes go to the Lua side freely (thanks NJ) 44 | - enhanced Rufus::Lua::LuaError by adding filename and lineno - based on 45 | a suggestion by Nathanael Jones) 46 | - enhanced Rufus::Lua::State#eval by adding optional 'filename' and 'lineno' 47 | arguments (like Kernel.eval) (hint by Nathanael Jones) 48 | 49 | 50 | == rufus-lua - 1.1.0 released 2009/09/30 51 | 52 | - todo : Ruby symbols are passed to Lua as strings (Scott) 53 | - todo : implemented State#function :to_ruby option 54 | - todo : implemented Table#to_ruby 55 | - todo : added 'pure' option to Table#to_a 56 | - issue : added State@callbacks array to preserve callback from GC (Scott) 57 | - issue : passing tables to ruby functions wrecked arg list. Fixed. 58 | - todo : added way to load only a given set of libs when State.new (Scott) 59 | - todo : honouring #to_lua when calling Lua functions from Ruby (Scott) 60 | - todo : search for dylib in /usr/lib as well (Scott) 61 | - issue #5 : Ruby function more tolerant with block arity. 62 | - issue #4 : Ruby function inverses order of its parameters. Fixed. (Scott) 63 | - todo : state['a'] = [ 1, 2, 3 ] now possible 64 | - issue #2 : tables returned from Ruby functions were indexed at 0. (Scott) 65 | - issue #3 : passing false from Ruby to Lua twists it to true. (Scott) 66 | - issue #1 : Ruby function return array not OK. Fixed. (Thanks Scott Persinger) 67 | 68 | 69 | == rufus-lua - 1.0.0 released 2009/03/27 70 | 71 | - automatically creates lib table when defined Ruby function (callback) 72 | - implemented #[]=, #objlen and #size for table 73 | - fixed bug when pushing floats on the stack (float --> double) 74 | - added State#function for defining Ruby functions (callbacks) available via Lua 75 | 76 | 77 | == rufus-lua - 0.1.0 released 2009/03/16 78 | 79 | - initial release 80 | 81 | -------------------------------------------------------------------------------- /CREDITS.txt: -------------------------------------------------------------------------------- 1 | 2 | = CREDITS.txt rufus-lua gem 3 | 4 | 5 | == authors 6 | 7 | John Mettraux http://jmettraux.lambda.io 8 | Alain Hoang http://blogs.law.harvard.edu/hoanga/ 9 | Scott Persinger https://github.com/scottpersinger 10 | 11 | 12 | == contributors 13 | 14 | Ashie Takuro https://github.com/ashie 15 | Andri Möll https://github.com/moll 16 | Christopher Saunders https://github.com/csaunders 17 | Gian Perrone https://github.com/quidrex 18 | Matthew Nielsen https://github.com/xunker 19 | Nathanael Jones https://github.com/nathanaeljones 20 | 21 | 22 | == feedback 23 | 24 | Chad Simmons https://github.com/polpak, gh-40 25 | Stas Ukolov https://github.com/ukoloff 26 | 27 | 28 | == inspiration 29 | 30 | http://rubyluabridge.rubyforge.org/ 31 | 32 | 33 | == finally 34 | 35 | many thanks to the authors of ruby-ffi, especially Wayne Meissner, 36 | 37 | http://wiki.github.com/ffi/ffi 38 | 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | 2 | source 'https://rubygems.org' 3 | 4 | gemspec 5 | 6 | 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rufus-lua (1.1.5) 5 | ffi (~> 1.9) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | diff-lcs (1.2.5) 11 | ffi (1.9.25) 12 | rspec (3.0.0) 13 | rspec-core (~> 3.0.0) 14 | rspec-expectations (~> 3.0.0) 15 | rspec-mocks (~> 3.0.0) 16 | rspec-core (3.0.1) 17 | rspec-support (~> 3.0.0) 18 | rspec-expectations (3.0.1) 19 | diff-lcs (>= 1.2.0, < 2.0) 20 | rspec-support (~> 3.0.0) 21 | rspec-mocks (3.0.1) 22 | rspec-support (~> 3.0.0) 23 | rspec-support (3.0.0) 24 | 25 | PLATFORMS 26 | ruby 27 | 28 | DEPENDENCIES 29 | rspec (>= 2.13.0) 30 | rufus-lua! 31 | 32 | BUNDLED WITH 33 | 1.16.2 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2009-2020, John Mettraux, Alain Hoang. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | 23 | Made in Japan 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | NAME = \ 3 | $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.name") 4 | VERSION = \ 5 | $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.version") 6 | 7 | 8 | gemspec_validate: 9 | @echo "---" 10 | ruby -e "s = eval(File.read(Dir['*.gemspec'].first)); p s.validate" 11 | @echo "---" 12 | 13 | name: gemspec_validate 14 | @echo "$(NAME) $(VERSION)" 15 | 16 | build: gemspec_validate 17 | gem build $(NAME).gemspec 18 | mkdir -p pkg 19 | mv $(NAME)-$(VERSION).gem pkg/ 20 | 21 | push: build 22 | gem push pkg/$(NAME)-$(VERSION).gem 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # rufus-lua 3 | 4 | [![Gem Version](https://badge.fury.io/rb/rufus-lua.svg)](http://badge.fury.io/rb/rufus-lua) 5 | 6 | Lua embedded in Ruby, via Ruby FFI. 7 | 8 | (Lua 5.1.x only, [no luajit](https://github.com/jmettraux/rufus-lua/issues/37) out of the box). 9 | 10 | 11 | ## Lua 12 | 13 | http://www.lua.org/about.html says : 14 | 15 | 16 | > Lua is a powerful, fast, lightweight, embeddable scripting language. 17 | > 18 | > Lua combines simple procedural syntax with powerful data description 19 | > constructs based on associative arrays and extensible semantics. Lua is 20 | > dynamically typed, runs by interpreting bytecode for a register-based 21 | > virtual machine, and has automatic memory management with incremental 22 | > garbage collection, making it ideal for configuration, scripting, and 23 | > rapid prototyping. 24 | 25 | http://www.lua.org/ 26 | 27 | 28 | ## other Ruby and Lua bridges / connectors 29 | 30 | * https://github.com/glejeune/ruby-lua by Gregoire Lejeune 31 | * http://rubyluabridge.rubyforge.org/ by Evan Wies 32 | * https://github.com/whitequark/rlua by Peter Zotov 33 | 34 | 35 | ## getting Lua on your system 36 | 37 | ### Debian GNU/Linux 38 | 39 | On Debian GNU/Linux, I do 40 | 41 | ``` 42 | sudo apt-get install liblua5.1-0 43 | ``` 44 | 45 | If your system's package manager doesn't have some version (5.1.x) of Lua around, jump to [compiling liblua.dylib](#compiling-libluadylib-osx) below or look at the [LuaBinaries solution](#lua-binaries-gnulinux) for GNU/Linux. 46 | 47 | Rufus-lua will look for library in a [list of know places](https://github.com/jmettraux/rufus-lua/blob/9ddf26cde9f4a73115032504ad7f7eb688849b73/lib/rufus/lua/lib.rb#L38-L50). 48 | 49 | If it doesn't find the Lua dynamic library or if it picks the wrong one, it's OK to set the `LUA_LIB` environment variable. For example: 50 | 51 | ```bash 52 | LUA_LIB=~/mystuff/lualib.5.1.4.so ruby myluacode.rb 53 | ``` 54 | or 55 | ```bash 56 | export LUA_LIB=~/mystuff/lualib.5.1.4.so 57 | # ... 58 | ruby myluacode.rb 59 | ``` 60 | 61 | ### OSX 62 | 63 | As of 2018-10-19, this [Homebrew](https://brew.sh/) command will install Lua and its .dylib on OSX: 64 | 65 | ```bash 66 | brew install lua@5.1 67 | ``` 68 | 69 | ### Windows 70 | 71 | On Windows try using [rufus-lua-win](https://github.com/ukoloff/rufus-lua-win) gem. 72 | 73 | ## using rufus-lua 74 | 75 | ``` 76 | gem install rufus-lua 77 | ``` 78 | 79 | or add to your Gemfile: 80 | 81 | ```ruby 82 | gem 'rufus-lua' 83 | ``` 84 | 85 | then 86 | 87 | ```ruby 88 | require 'rufus-lua' 89 | 90 | s = Rufus::Lua::State.new 91 | 92 | puts s.eval("return table.concat({ 'hello', 'from', 'Lua' }, ' ')") 93 | # 94 | # => "Hello from Lua" 95 | 96 | s.close 97 | ``` 98 | 99 | 100 | ### binding Ruby code as Lua functions 101 | 102 | ```ruby 103 | require 'rufus-lua' 104 | 105 | s = Rufus::Lua::State.new 106 | 107 | s.function 'key_up' do |table| 108 | table.inject({}) do |h, (k, v)| 109 | h[k.to_s.upcase] = v 110 | h 111 | end 112 | end 113 | 114 | p s.eval(%{ 115 | local table = { CoW = 2, pigs = 3, DUCKS = 'none' } 116 | return key_up(table) -- calling Ruby from Lua... 117 | }).to_h 118 | # => { 'COW' => 2.0, 'DUCKS => 'none', 'PIGS' => 3.0 } 119 | 120 | s.close 121 | ``` 122 | 123 | 124 | It's OK to bind a function inside of a table (library): 125 | 126 | ```ruby 127 | require 'rufus-lua' 128 | 129 | s = Rufus::Lua::State.new 130 | 131 | s.eval("rubies = {}") 132 | s.function 'rubies.add' do |x, y| 133 | x + y 134 | end 135 | 136 | p s.eval("return rubies.add(1, 2)") 137 | # => 3.0 138 | 139 | s.close 140 | ``` 141 | 142 | 143 | You can omit the table definition (only 1 level allowed here though): 144 | 145 | ```ruby 146 | require 'rufus-lua' 147 | 148 | s = Rufus::Lua::State.new 149 | 150 | s.function 'rubies.add' do |x, y| 151 | x + y 152 | end 153 | 154 | p s.eval("return rubies.add(1, 2)") 155 | # => 3.0 156 | 157 | s.close 158 | ``` 159 | 160 | 161 | The specs contain more examples: 162 | 163 | https://github.com/jmettraux/rufus-lua/tree/master/spec/ 164 | 165 | 166 | ### eval(code[, binding[, filename[, lineno ]]]) 167 | 168 | The examples so far have shown `Rufus::Lua::State#eval` being used with a single argument, a piece of code. 169 | 170 | But this rufus-lua eval mimics the Ruby `eval` and lets one specify binding, filename and lineno. 171 | 172 | (TODO) Binding hasn't yet been implemented. It'll probaby be with `setfenv` but nothing sure yet. Stick a `nil` to it for now. 173 | 174 | The string of Lua code may come from wild places, it may help to flag it with an arbitrary filename and a lineno. 175 | 176 | ```ruby 177 | require 'rufus-lua' 178 | 179 | lua = Rufus::Lua::State.new 180 | 181 | lua.eval('print("hello")', nil, 'myluastuff/hello.lua', 77) 182 | ``` 183 | 184 | ### set_error_handler 185 | 186 | `set_error_handler` gives a little bit of control on how error messages are prepared when errors occur in the Lua interpreter. 187 | 188 | Here are set of examples for each of the possible error handler kind: Lua code, Ruby block, `:traceback`. 189 | 190 | ```ruby 191 | require 'rufus-lua' 192 | 193 | lua = Rufus::Lua::State.new 194 | 195 | # 196 | # no error handler 197 | 198 | begin 199 | lua.eval('error("ouch!")') 200 | rescue => e 201 | puts(e) 202 | end 203 | # --> eval:pcall : '[string "line"]:1: ouch!' (2 LUA_ERRRUN) 204 | 205 | # 206 | # Lua error handler 207 | 208 | lua.set_error_handler(%{ 209 | function (msg) 210 | return 'something went wrong: ' .. string.gmatch(msg, ": (.+)$")() 211 | end 212 | }) 213 | 214 | begin 215 | lua.eval('error("ouch!")') 216 | rescue => e 217 | puts(e) 218 | end 219 | # --> eval:pcall : 'something went wrong: ouch!' (2 LUA_ERRRUN) 220 | 221 | # 222 | # Ruby block error handler 223 | 224 | lua.set_error_handler do |msg| 225 | ([ msg.split.last ] * 3).join(' ') 226 | end 227 | 228 | begin 229 | lua.eval('error("ouch!")') 230 | rescue => e 231 | puts(e) 232 | end 233 | # --> eval:pcall : 'ouch! ouch! ouch!' (2 LUA_ERRRUN) 234 | 235 | # 236 | # prepackaged :traceback handler 237 | 238 | lua.set_error_handler(:traceback) 239 | 240 | begin 241 | lua.eval('error("ouch!")') 242 | rescue => e 243 | puts(e) 244 | end 245 | # --> 246 | # eval:pcall : '[string "line"]:1: ouch! 247 | # stack traceback: 248 | # [C]: in function 'error' 249 | # [string "line"]:1: in main chunk' (2 LUA_ERRRUN) 250 | 251 | # 252 | # unset the error handler 253 | 254 | lua.set_error_handler(nil) 255 | 256 | begin 257 | lua.eval('error("ouch!")') 258 | rescue => e 259 | puts(e) 260 | end 261 | # --> eval:pcall : '[string "line"]:1: ouch!' (2 LUA_ERRRUN) 262 | # (back to default) 263 | ``` 264 | 265 | 266 | ## compiling liblua.dylib (OSX) 267 | 268 | original instructions by Adrian Perez at: 269 | 270 | http://lua-users.org/lists/lua-l/2006-09/msg00894.html 271 | 272 | get the source at: 273 | 274 | http://www.lua.org/ftp/lua-5.1.4.tar.gz 275 | 276 | then 277 | 278 | ``` 279 | tar xzvf lua-5.1.4.tar.gz 280 | cd lua-5.1.4 281 | ``` 282 | 283 | Modify the file `src/Makefile` as per http://lua-users.org/lists/lua-l/2006-09/msg00894.html 284 | 285 | It's mostly about adding that rule to the `src/Makefile`: 286 | ```make 287 | liblua51.dylib: $(CORE_O) $(LIB_O) 288 | $(CC) -dynamiclib -o $@ $^ $(LIBS) 289 | ``` 290 | 291 | Here's how to build the library file and deploy it: 292 | ``` 293 | make 294 | make macosx 295 | make -C src liblua51.dylib 296 | sudo cp src/liblua51.dylib /usr/local/lib/ 297 | 298 | sudo make macosx install 299 | ``` 300 | 301 | I tend to copy the lib with 302 | 303 | ``` 304 | sudo cp src/liblua.dylib /usr/local/lib/liblua.5.1.4.dylib 305 | 306 | # instead of 307 | #sudo cp src/liblua.dylib /usr/local/lib/ 308 | ``` 309 | 310 | ## lua binaries (GNU/Linux) 311 | 312 | Hat tip to [Micka33](https://github.com/Micka33) for pointing to [LuaBinaries](http://luabinaries.sourceforge.net/download.html) in [issue #34](https://github.com/jmettraux/rufus-lua/issues/34) 313 | 314 | 315 | ## tested with 316 | 317 | ruby 1.9.1p0, jruby 1.2.0 318 | jruby 1.1.6 has an issue with errors raised inside of Ruby functions (callbacks) 319 | 320 | ruby-ffi 0.4.0 and 0.5.0 321 | 322 | I run the specs with 323 | 324 | ``` 325 | bundle install # first time only 326 | bundle exec rspec 327 | ``` 328 | 329 | 330 | ## dependencies 331 | 332 | the ruby gem 'ffi' 333 | 334 | 335 | ## issue tracker 336 | 337 | http://github.com/jmettraux/rufus-lua/issues 338 | 339 | 340 | ## source 341 | 342 | http://github.com/jmettraux/rufus-lua 343 | 344 | ``` 345 | git clone git://github.com/jmettraux/rufus-lua.git 346 | ``` 347 | 348 | 349 | ## authors and credits 350 | 351 | see [CREDITS.txt](CREDITS.txt) 352 | 353 | 354 | ## license 355 | 356 | MIT 357 | 358 | Lua itself is licensed under the MIT license as well : 359 | 360 | http://www.lua.org/license.html 361 | 362 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | 2 | [o] spec table.free() 3 | [o] function.call() 4 | [o] Table#[] 5 | [o] Table + Enumerable (not necessarily) 6 | 7 | [x] State#stack_ready() maybe : NO ! 8 | 9 | [o] better protection for State methods (into lib ?) 10 | 11 | [o] fix coroutine & co / memoization (make it better later) 12 | [o] fib.lua, use local, bench ! (2 times faster almost) 13 | 14 | [o] add GC control methods (Alain) 15 | [X] Add method to disable GC 16 | [X] Add method to enable GC 17 | [ ] Look at parameters in Lua GC that can be tweaked 18 | [ ] Add method to tune GC parameters 19 | [ ] Test whether GC is truly independent for each state 20 | 21 | [x] ruby callbacks (functions) 22 | [ ] ability to bind ruby functions (callbacks) inside of tables (modules) 23 | 24 | [ ] state['var'] = value 25 | [ ] state['var'] = hash 26 | 27 | [ ] state.load_and_eval(filepath) 28 | 29 | -------------------------------------------------------------------------------- /lib/rufus-lua.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'rufus/lua' 3 | 4 | -------------------------------------------------------------------------------- /lib/rufus/lua.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'rufus/lua/version' 3 | 4 | require 'rufus/lua/lib' 5 | 6 | require 'rufus/lua/error' 7 | require 'rufus/lua/state' 8 | require 'rufus/lua/utils' 9 | require 'rufus/lua/objects' 10 | 11 | -------------------------------------------------------------------------------- /lib/rufus/lua/error.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rufus::Lua 3 | 4 | # error codes from 0 to 5 5 | # 6 | LUA_ERRS = %w[ 0 LUA_YIELD LUA_ERRRUN LUA_ERRSYNTAX LUA_ERRMEM LUA_ERRERR ] 7 | 8 | # 9 | # An error class for rufus-lua. 10 | # 11 | class LuaError < RuntimeError 12 | 13 | attr_reader :kind, :errcode, :msg 14 | attr_reader :bndng, :filename, :lineno 15 | 16 | attr_reader :original_backtrace 17 | 18 | def initialize(kind, errcode, msg, bndng, filename, lineno) 19 | 20 | super("#{kind} : '#{msg}' (#{errcode} #{LUA_ERRS[errcode]})") 21 | 22 | @kind = kind 23 | @errcode = errcode 24 | @msg = msg 25 | 26 | @bndng = bndng 27 | @filename = filename 28 | @lineno = lineno 29 | end 30 | 31 | def filename 32 | 33 | return @filename if @filename 34 | 35 | m = CALLER_REX.match(backtrace.first || '') 36 | return m ? m[1] : nil 37 | end 38 | 39 | def lineno 40 | 41 | return @lineno if @lineno 42 | 43 | m = CALLER_REX.match(backtrace.first || '') 44 | return m ? m[2].to_i : -1 45 | end 46 | 47 | def set_backtrace(trace) 48 | 49 | @original_backtrace = trace 50 | 51 | trace = 52 | trace.select { |line| 53 | m = CALLER_REX.match(line) 54 | ( ! m) || File.dirname(m[1]) != DIR 55 | } 56 | 57 | trace.insert(0, "#{@filename}:#{@lineno}:") if @filename 58 | 59 | super(trace) 60 | end 61 | 62 | CALLER_REX = /^(.+):(\d+):/ 63 | DIR = File.dirname(__FILE__) 64 | end 65 | end 66 | 67 | -------------------------------------------------------------------------------- /lib/rufus/lua/lib.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'ffi' 3 | 4 | 5 | module Rufus 6 | module Lua 7 | 8 | module Lib 9 | extend FFI::Library 10 | 11 | # 12 | # locate the dynamic library 13 | 14 | ffi_lib_flags(:lazy, :global) 15 | 16 | begin 17 | 18 | # first attempt 19 | 20 | ffi_lib(ENV['LUA_LIB'] || 'liblua5.1') 21 | 22 | rescue LoadError => le0 23 | 24 | paths = 25 | %w[ /usr/lib/*/liblua5.1.*so* ] 26 | .inject([]) { |a, e| a.concat(Dir.glob(e)) } 27 | 28 | begin 29 | 30 | # second attempt 31 | 32 | ffi_lib(paths) 33 | 34 | rescue LoadError => le1 35 | 36 | fail RuntimeError.new( 37 | "Didn't find the Lua dynamic library (liblua5.1.*) on your system. " + 38 | "Set LUA_LIB in your environment if have that library or " + 39 | "go to https://github.com/jmettraux/rufus-lua to learn how to " + 40 | "get it.") 41 | end 42 | end 43 | 44 | # Rufus::Lua::Lib.path returns the path to the library used. 45 | # 46 | def self.path 47 | 48 | f = ffi_libraries.first 49 | 50 | f ? f.name : nil 51 | end 52 | 53 | # 54 | # attach functions 55 | 56 | attach_function :lua_close, [ :pointer ], :void 57 | 58 | attach_function :luaL_openlibs, [ :pointer ], :void 59 | 60 | attach_function :lua_call, [ :pointer, :int, :int ], :void 61 | %w[ base package string table math io os debug ].each do |libname| 62 | attach_function "luaopen_#{libname}", [ :pointer ], :void 63 | end 64 | 65 | attach_function :lua_pcall, [ :pointer, :int, :int, :int ], :int 66 | #attach_function :lua_resume, [ :pointer, :int ], :int 67 | 68 | attach_function :lua_toboolean, [ :pointer, :int ], :int 69 | attach_function :lua_tonumber, [ :pointer, :int ], :double 70 | attach_function :lua_tolstring, [ :pointer, :int, :pointer ], :pointer 71 | 72 | attach_function :lua_type, [ :pointer, :int ], :int 73 | attach_function :lua_typename, [ :pointer, :int ], :string 74 | 75 | attach_function :lua_gettop, [ :pointer ], :int 76 | attach_function :lua_settop, [ :pointer, :int ], :void 77 | 78 | attach_function :lua_objlen, [ :pointer, :int ], :int 79 | attach_function :lua_getfield, [ :pointer, :int, :string ], :pointer 80 | attach_function :lua_gettable, [ :pointer, :int ], :void 81 | 82 | attach_function :lua_createtable, [ :pointer, :int, :int ], :void 83 | #attach_function :lua_newtable, [ :pointer ], :void 84 | attach_function :lua_settable, [ :pointer, :int ], :void 85 | 86 | attach_function :lua_next, [ :pointer, :int ], :int 87 | 88 | attach_function :lua_pushnil, [ :pointer ], :pointer 89 | attach_function :lua_pushboolean, [ :pointer, :int ], :pointer 90 | attach_function :lua_pushinteger, [ :pointer, :int ], :pointer 91 | attach_function :lua_pushnumber, [ :pointer, :double ], :pointer 92 | attach_function :lua_pushstring, [ :pointer, :string ], :pointer 93 | attach_function :lua_pushlstring, [ :pointer, :pointer, :int ], :pointer 94 | 95 | attach_function :lua_remove, [ :pointer, :int ], :void 96 | # removes the value at the given stack index, shifting down all elts above 97 | 98 | #attach_function :lua_pushvalue, [ :pointer, :int ], :void 99 | # pushes a copy of the value at the given index to the top of the stack 100 | #attach_function :lua_insert, [ :pointer, :int ], :void 101 | # moves the top elt to the given index, shifting up all elts above 102 | #attach_function :lua_replace, [ :pointer, :int ], :void 103 | # pops the top elt and override the elt at given index with it 104 | 105 | attach_function :lua_rawgeti, [ :pointer, :int, :int ], :void 106 | 107 | attach_function :luaL_newstate, [], :pointer 108 | attach_function :luaL_loadbuffer, [ :pointer, :string, :int, :string ], :int 109 | attach_function :luaL_ref, [ :pointer, :int ], :int 110 | attach_function :luaL_unref, [ :pointer, :int, :int ], :void 111 | 112 | attach_function :lua_gc, [ :pointer, :int, :int ], :int 113 | 114 | callback :cfunction, [ :pointer ], :int 115 | attach_function :lua_pushcclosure, [ :pointer, :cfunction, :int ], :void 116 | attach_function :lua_setfield, [ :pointer, :int, :string ], :void 117 | end 118 | end 119 | end 120 | 121 | -------------------------------------------------------------------------------- /lib/rufus/lua/objects.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rufus::Lua 3 | 4 | # 5 | # The parent class for Table, Function and Coroutine. Simply holds 6 | # a reference to the object in the Lua registry. 7 | # 8 | class Ref 9 | include StateMixin 10 | 11 | # The reference in the Lua registry. 12 | # (You shouldn't care about this value) 13 | # 14 | attr_reader :ref 15 | 16 | def initialize(pointer) 17 | 18 | @pointer = pointer 19 | @ref = Lib.luaL_ref(@pointer, LUA_REGISTRYINDEX) 20 | # this pops the object out of the stack ! 21 | @error_handler = 0 22 | end 23 | 24 | # Frees the reference to this object 25 | # (Problably a good idea if you want Lua's GC to get rid of it later). 26 | # 27 | def free 28 | 29 | Lib.luaL_unref(@pointer, LUA_REGISTRYINDEX, @ref) 30 | @ref = nil 31 | end 32 | 33 | protected 34 | 35 | # Brings the referenced object on top of the stack (will probably 36 | # then take part in a method call). 37 | # 38 | def load_onto_stack 39 | 40 | raise RuntimeError.new( 41 | "#{self.class} got freed, cannot re-access it directly" 42 | ) unless @ref 43 | 44 | stack_load_ref(@ref) 45 | end 46 | end 47 | 48 | # 49 | # A Lua function. 50 | # 51 | # require 'rufus/lua' 52 | # 53 | # s = Rufus::Lua::State.new 54 | # 55 | # f = s.eval(%{ 56 | # return function (x) 57 | # return 2 * x 58 | # end 59 | # }) 60 | # 61 | # f.call(2) # => 4.0 62 | # 63 | class Function < Ref 64 | 65 | # Calls the Lua function. 66 | # 67 | def call(*args) 68 | 69 | bottom = stack_top 70 | 71 | load_onto_stack 72 | # load function on stack 73 | 74 | args.each { |arg| stack_push(arg) } 75 | # push arguments on stack 76 | 77 | pcall(bottom, args.length, nil, nil, nil) 78 | end 79 | end 80 | 81 | # 82 | # (coming soon) 83 | # 84 | class Coroutine < Ref 85 | 86 | # Resumes the coroutine 87 | # 88 | def resume(*args) 89 | 90 | bottom = stack_top 91 | 92 | fetch_library_method('coroutine.resume').load_onto_stack 93 | 94 | load_onto_stack 95 | args.each { |arg| stack_push(arg) } 96 | 97 | pcall(bottom, args.length + 1, nil, nil, nil) 98 | end 99 | 100 | # Returns the string status of the coroutine : 101 | # suspended/running/dead/normal 102 | # 103 | def status 104 | 105 | bottom = stack_top 106 | 107 | fetch_library_method('coroutine.status').load_onto_stack 108 | load_onto_stack 109 | 110 | pcall(bottom, 1, nil, nil, nil) 111 | end 112 | end 113 | 114 | # 115 | # A Lua table. 116 | # 117 | # For now, the only thing you can do with it is cast it into a Hash or 118 | # an Array (will raise an exception if casting to an Array is not possible). 119 | # 120 | # Note that direct manipulation of the Lua table (inside Lua) is not possible 121 | # (as of now). 122 | # 123 | class Table < Ref 124 | include Enumerable 125 | 126 | # The classical 'each'. 127 | # 128 | # Note it cheats by first turning the table into a Ruby Hash and calling 129 | # the each of that Hash instance (this way, the stack isn't involved 130 | # in the iteration). 131 | # 132 | def each 133 | 134 | return unless block_given? 135 | self.to_h.each { |k, v| yield(k, v) } 136 | end 137 | 138 | # Returns the array of keys of this Table. 139 | # 140 | def keys 141 | 142 | self.to_h.keys 143 | end 144 | 145 | # Returns the array of values in this Table. 146 | # 147 | def values 148 | 149 | self.to_h.values 150 | end 151 | 152 | # Returns the value behind the key, or else nil. 153 | # 154 | def [](k) 155 | 156 | load_onto_stack # table 157 | stack_push(k) # key 158 | Lib.lua_gettable(@pointer, -2) # fetch val for key at top and table at -2 159 | stack_pop 160 | end 161 | 162 | # Sets a value in the table 163 | # 164 | # TODO : have something for adding in the array part... 165 | # 166 | def []=(k, v) 167 | 168 | load_onto_stack 169 | 170 | stack_push(k) 171 | stack_push(v) 172 | Lib.lua_settable(@pointer, -3) 173 | 174 | v 175 | end 176 | 177 | # Returns the size of the table, corresponds to the Lua '#' operator. 178 | # 179 | # Will thus return 0 if the table doesn't hold any value in its 180 | # 'array' part. 181 | # 182 | def objlen 183 | 184 | load_onto_stack 185 | Lib.lua_objlen(@pointer, -1) 186 | end 187 | 188 | # Returns the real size of the table (number of entries + number of elements 189 | # in array side) 190 | # 191 | def size 192 | 193 | self.to_h.size 194 | end 195 | alias :length :size 196 | 197 | # Returns a Ruby Hash instance representing this Lua table. 198 | # 199 | def to_h 200 | 201 | load_onto_stack 202 | 203 | table_pos = stack_top 204 | 205 | Lib.lua_pushnil(@pointer) 206 | 207 | h = {} 208 | 209 | while Lib.lua_next(@pointer, table_pos) != 0 do 210 | 211 | value = stack_fetch(-1) 212 | value.load_onto_stack if value.is_a?(Ref) 213 | 214 | key = stack_fetch(-2) 215 | key.load_onto_stack if key.is_a?(Ref) 216 | 217 | stack_unstack # leave key on top 218 | 219 | h[key] = value 220 | end 221 | 222 | h 223 | end 224 | 225 | # Returns a Ruby Array instance representing this Lua table. 226 | # 227 | # Will raise an error if the 'rendering' is not possible. 228 | # 229 | # s = Rufus::Lua::State.new 230 | # 231 | # @s.eval("return { a = 'A', b = 'B', c = 3 }").to_a 232 | # # => error ! 233 | # 234 | # @s.eval("return { 1, 2 }").to_a 235 | # # => [ 1.0, 2.0 ] 236 | # 237 | # @s.eval("return {}").to_a 238 | # # => [] 239 | # 240 | # @s.eval("return { 1, 2, car = 'benz' }").to_a 241 | # # => error ! 242 | # 243 | # == to_a(false) 244 | # 245 | # Setting the optional argument 'pure' to false will manage any table : 246 | # 247 | # s = Rufus::Lua::State.new 248 | # 249 | # @s.eval("return { a = 'A', b = 'B', c = 3 }").to_a(false) 250 | # # => [["a", "A"], ["b", "B"], ["c", 3.0]] 251 | # 252 | # @s.eval("return { 1, 2 }").to_a(false) 253 | # # => [1.0, 2.0] 254 | # 255 | # @s.eval("return {}").to_a(false) 256 | # # => [] 257 | # 258 | # @s.eval("return { 1, 2, car = 'benz' }").to_a(false) 259 | # # => [1.0, 2.0, ["car", "benz"]] 260 | # 261 | def to_a(pure=true) 262 | 263 | h = self.to_h 264 | 265 | pure && h.keys.find { |k| not [ Float ].include?(k.class) } && 266 | fail('cannot turn hash into array, some keys are not numbers') 267 | 268 | a_keys = (1..objlen).to_a.collect { |k| k.to_f } 269 | keys = a_keys + (h.keys - a_keys) 270 | 271 | keys.inject([]) { |a, k| 272 | a << (a_keys.include?(k) ? h[k] : [ k, h[k] ]) 273 | a 274 | } 275 | end 276 | 277 | # Turns the Lua table into a Ruby array, or else into a Ruby Hash instance. 278 | # 279 | def to_ruby 280 | 281 | to_a rescue to_h 282 | end 283 | end 284 | end 285 | 286 | -------------------------------------------------------------------------------- /lib/rufus/lua/state.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rufus::Lua 3 | 4 | # 5 | # Rufus::Lua::Lib contains all the raw C API Lua methods. The methods 6 | # here are shared by all the rufus-lua classes that have to deal with 7 | # a Lua state. They are protected since they aren't meant to be called 8 | # directly. 9 | # 10 | # The entry point of rufus-lua is Rufus::Lua::State, look there. 11 | # 12 | module StateMixin 13 | 14 | LUA_GLOBALSINDEX = -10002 15 | LUA_ENVIRONINDEX = -10001 16 | LUA_REGISTRYINDEX = -10000 17 | LUA_NOREF = -2 18 | LUA_REFNIL = -1 19 | 20 | # Lua GC constants 21 | LUA_GCSTOP = 0 22 | LUA_GCRESTART = 1 23 | LUA_GCCOLLECT = 2 24 | LUA_GCCOUNT = 3 25 | LUA_GCCOUNTB = 4 26 | LUA_GCSTEP = 5 27 | LUA_GCSETPAUSE = 6 28 | LUA_GCSETSTEPMUL = 7 29 | 30 | TNONE = -1 31 | TNIL = 0 32 | TBOOLEAN = 1 33 | TLIGHTUSERDATA = 2 34 | TNUMBER = 3 35 | TSTRING = 4 36 | TTABLE = 5 37 | TFUNCTION = 6 38 | TUSERDATA = 7 39 | TTHREAD = 8 40 | 41 | SIMPLE_TYPES = [ TNIL, TBOOLEAN, TNUMBER, TSTRING ] 42 | 43 | LUA_MULTRET = -1 44 | 45 | protected 46 | 47 | # This method is used to fetch/cache references to library methods like 48 | # 'math.sin' or 'coroutine.resume'. 49 | # The caching is done at the Lua state level (ie, all Lua objects available 50 | # via the state share the cache. 51 | # 52 | # (Not sure yet about this yet) 53 | # 54 | def fetch_library_method(s) 55 | 56 | m = @pointer.__lib_method_cache[s] 57 | return m if m 58 | 59 | @pointer.__lib_method_cache[s] = 60 | loadstring_and_call("return #{s}", nil, nil, nil) 61 | end 62 | 63 | # This method holds the 'eval' mechanism. 64 | # 65 | def loadstring_and_call(s, bndng, filename, lineno) 66 | 67 | bottom = stack_top 68 | chunk = filename ? "#{filename}:#{lineno}" : 'line' 69 | 70 | err = Lib.luaL_loadbuffer(@pointer, s, s.bytesize, chunk) 71 | fail_if_error('eval:compile', err, bndng, filename, lineno) 72 | 73 | pcall(bottom, 0, bndng, filename, lineno) # arg_count is set to 0 74 | end 75 | 76 | # Returns a string representation of the state's stack. 77 | # 78 | def stack_to_s 79 | 80 | # warning : don't touch at stack[0] 81 | 82 | s = (1..stack_top).inject([]) { |a, i| 83 | 84 | type, tname = stack_type_at(i) 85 | 86 | val = 87 | if type == TSTRING 88 | "\"#{stack_fetch(i)}\"" 89 | elsif SIMPLE_TYPES.include?(type) 90 | stack_fetch(i).to_s 91 | elsif type == TTABLE 92 | "(# is #{Lib.lua_objlen(@pointer, i)})" 93 | else 94 | '' 95 | end 96 | 97 | a << "#{i} : #{tname} (#{type}) #{val}" 98 | a 99 | }.reverse.join("\n") 100 | 101 | s += "\n" if s.length > 0 102 | 103 | s 104 | end 105 | 106 | # Outputs the stack to the stdout 107 | # 108 | def print_stack(msg=nil) 109 | 110 | puts "\n=stack= #{msg ? "(#{msg})" : ""}" 111 | puts "top : #{stack_top}" 112 | print stack_to_s 113 | puts "= =" 114 | end 115 | 116 | # Returns the offset (int) of the top element of the stack. 117 | # 118 | def stack_top 119 | 120 | Lib.lua_gettop(@pointer) 121 | end 122 | 123 | # Returns a pair type (int) and type name (string) of the element on top 124 | # of the Lua state's stack. There is an optional pos paramter to peek 125 | # at other elements of the stack. 126 | # 127 | def stack_type_at(pos=-1) 128 | 129 | type = Lib.lua_type(@pointer, pos) 130 | tname = Lib.lua_typename(@pointer, type) 131 | 132 | [ type, tname ] 133 | end 134 | 135 | # Fetches the top value on the stack (or the one specified by the optional 136 | # pos parameter), but does not 'pop' it. 137 | # 138 | def stack_fetch(pos=-1) 139 | 140 | type, tname = stack_type_at(pos) 141 | 142 | case type 143 | 144 | when TNIL then nil 145 | 146 | when TSTRING then 147 | len = FFI::MemoryPointer.new(:size_t) 148 | ptr = Lib.lua_tolstring(@pointer, pos, len) 149 | ptr.read_string(len.read_long) 150 | 151 | when TBOOLEAN then (Lib.lua_toboolean(@pointer, pos) == 1) 152 | when TNUMBER then Lib.lua_tonumber(@pointer, pos) 153 | 154 | when TTABLE then Table.new(@pointer) 155 | # warning : this pops up the item from the stack ! 156 | 157 | when TFUNCTION then Function.new(@pointer) 158 | when TTHREAD then Coroutine.new(@pointer) 159 | 160 | else tname 161 | end 162 | end 163 | 164 | # Pops the top value of lua state's stack and returns it. 165 | # 166 | def stack_pop 167 | 168 | r = stack_fetch 169 | stack_unstack if r.class != Rufus::Lua::Table 170 | 171 | r 172 | end 173 | 174 | # Makes sure the stack loses its top element (but doesn't return it). 175 | # 176 | def stack_unstack 177 | 178 | new_top = stack_top - 1 179 | new_top = 0 if new_top < 0 180 | # 181 | # there are no safeguard in Lua, setting top to -2 work well 182 | # when the stack is crowded, but it has bad side effects when the 183 | # stack is empty... Now safeguarding by ourselves. 184 | 185 | Lib.lua_settop(@pointer, new_top) 186 | end 187 | 188 | # Given a Ruby instance, will attempt to push it on the Lua stack. 189 | # 190 | def stack_push(o) 191 | 192 | return stack_push(o.to_lua) if o.respond_to?(:to_lua) 193 | 194 | case o 195 | 196 | when NilClass then Lib.lua_pushnil(@pointer) 197 | 198 | when TrueClass then Lib.lua_pushboolean(@pointer, 1) 199 | when FalseClass then Lib.lua_pushboolean(@pointer, 0) 200 | 201 | when Integer then Lib.lua_pushinteger(@pointer, o) 202 | when Float then Lib.lua_pushnumber(@pointer, o) 203 | 204 | when String then Lib.lua_pushlstring(@pointer, o, o.bytesize) 205 | when Symbol then Lib.lua_pushlstring(@pointer, o.to_s, o.to_s.bytesize) 206 | 207 | when Hash then stack_push_hash(o) 208 | when Array then stack_push_array(o) 209 | 210 | else raise( 211 | ArgumentError.new( 212 | "don't know how to pass Ruby instance of #{o.class} to Lua")) 213 | end 214 | end 215 | 216 | # Pushes a hash on top of the Lua stack. 217 | # 218 | def stack_push_hash(h) 219 | 220 | Lib.lua_createtable(@pointer, 0, h.size) 221 | # since we already know the size of the table... 222 | 223 | h.each do |k, v| 224 | stack_push(k) 225 | stack_push(v) 226 | Lib.lua_settable(@pointer, -3) 227 | end 228 | end 229 | 230 | # Pushes an array on top of the Lua stack. 231 | # 232 | def stack_push_array(a) 233 | 234 | Lib.lua_createtable(@pointer, a.size, 0) 235 | # since we already know the size of the table... 236 | 237 | a.each_with_index do |e, i| 238 | stack_push(i + 1) 239 | stack_push(e) 240 | Lib.lua_settable(@pointer, -3) 241 | end 242 | end 243 | 244 | # Loads a Lua global value on top of the stack 245 | # 246 | def stack_load_global(name) 247 | 248 | Lib.lua_getfield(@pointer, LUA_GLOBALSINDEX, name) 249 | end 250 | 251 | # Loads a field of the table currently on the top of the stack 252 | # 253 | def stack_load_field(name) 254 | 255 | Lib.lua_getfield(@pointer, -1, name) 256 | end 257 | 258 | # Loads a path (for example "debug.traceback") on top of the stack. 259 | # 260 | def stack_load_path(path) 261 | 262 | ps = path.split('.') 263 | 264 | stack_load_global(ps.shift) 265 | 266 | while ps.first 267 | stack_load_field(ps.shift) 268 | Lib.lua_remove(@pointer, -2) 269 | end 270 | end 271 | 272 | # Loads the Lua object registered with the given ref on top of the stack 273 | # 274 | def stack_load_ref(ref) 275 | 276 | Lib.lua_rawgeti(@pointer, LUA_REGISTRYINDEX, @ref) 277 | end 278 | 279 | # Returns the result of a function call or a coroutine.resume(). 280 | # 281 | def return_result(stack_bottom) 282 | 283 | count = stack_top - stack_bottom 284 | 285 | return nil if count == 0 286 | return stack_pop if count == 1 287 | 288 | (1..count).collect { |pos| stack_pop }.reverse 289 | end 290 | 291 | # Assumes the Lua stack is loaded with a ref to a method and arg_count 292 | # arguments (on top of the method), will then call that Lua method and 293 | # return a result. 294 | # 295 | # Will raise an error in case of failure. 296 | # 297 | def pcall(stack_bottom, arg_count, bndng, filename, lineno) 298 | 299 | #err = Lib.lua_pcall(@pointer, 0, 1, 0) 300 | # When there's only 1 return value. 301 | # Use LUA_MULTRET (-1) the rest of the time 302 | 303 | err = Lib.lua_pcall(@pointer, arg_count, LUA_MULTRET, @error_handler) 304 | fail_if_error('eval:pcall', err, bndng, filename, lineno) 305 | 306 | return_result(stack_bottom) 307 | end 308 | 309 | #-- 310 | # Resumes a coroutine (that has been placed, under its arguments, 311 | # on top of the stack). 312 | # 313 | #def do_resume(stack_bottom, arg_count) 314 | # err = Lib.lua_resume(@pointer, arg_count) 315 | # fail_if_error('eval:resume', err, nil, nil, nil) 316 | # return_result(stack_bottom) 317 | #end 318 | #++ 319 | 320 | # This method will raise an error with err > 0, else it will immediately 321 | # return. 322 | # 323 | def fail_if_error(kind, err, bndng, filename, lineno) 324 | 325 | return if err < 1 326 | 327 | s = Lib.lua_tolstring(@pointer, -1, nil).read_string 328 | Lib.lua_settop(@pointer, -2) 329 | 330 | fail LuaError.new(kind, err, s, bndng, filename, lineno) 331 | end 332 | 333 | # Given the name of a Lua global variable, will return its value (or nil 334 | # if there is nothing bound under that name). 335 | # 336 | def get_global(name) 337 | 338 | stack_load_global(name) 339 | stack_pop 340 | end 341 | end 342 | 343 | class CallbackState 344 | include StateMixin 345 | 346 | def initialize(pointer) 347 | 348 | @pointer = pointer 349 | @callbacks = [] 350 | @error_handler = 0 351 | end 352 | end 353 | 354 | # 355 | # A Lua state, wraps a Lua runtime. 356 | # 357 | # require 'rufus/lua' 358 | # s = Rufus::Lua::State.new 359 | # s.eval "a = 1 + 2" 360 | # 361 | # p s['a'] # => 3.0 362 | # 363 | class State 364 | include StateMixin 365 | 366 | # Instantiates a Lua state (runtime). 367 | # 368 | # Accepts an 'include_libs' optional arg. When set to true (the default, 369 | # all the base Lua libs are loaded in the runtime. 370 | # 371 | # This optional arg can be set to false, when no libs should be present, or 372 | # to an array of libs to load in order to prepare the state. 373 | # 374 | # The list may include 'base', 'package', 'table', 'string', 'math', 'io', 375 | # 'os' and 'debug'. 376 | # 377 | def initialize(include_libs=true) 378 | 379 | @pointer = Lib.luaL_newstate 380 | 381 | open_libraries(include_libs) 382 | 383 | # 384 | # preparing library methods cache 385 | 386 | class << @pointer 387 | attr_reader :__lib_method_cache 388 | end 389 | @pointer.instance_variable_set(:@__lib_method_cache, {}) 390 | 391 | # 392 | # an array for preserving callback (Ruby functions) from Ruby 393 | # garbage collection (Scott). 394 | 395 | @callbacks = [] 396 | 397 | @error_handler = 0 398 | end 399 | 400 | def set_error_handler(lua_code=nil, &block) 401 | 402 | if lua_code == nil && block 403 | 404 | function('_ruby_error_handler', &block) 405 | stack_load_path('_ruby_error_handler') 406 | @error_handler = stack_top 407 | 408 | elsif lua_code == nil 409 | 410 | Lib.lua_remove(@pointer, @error_handler) if @error_handler > 0 411 | @error_handler = 0 412 | 413 | elsif lua_code == :traceback 414 | 415 | stack_load_path('debug.traceback') 416 | @error_handler = stack_top 417 | 418 | else 419 | 420 | lua_code = lua_code.strip 421 | lua_code = 'return ' + lua_code unless lua_code.match(/^return\s+/) 422 | 423 | r = self.eval(lua_code) 424 | r.send(:load_onto_stack) 425 | @error_handler = stack_top 426 | end 427 | end 428 | 429 | # Evaluates a piece (string) of Lua code within the state. 430 | # 431 | def eval(s, bndng=nil, filename=nil, lineno=nil) 432 | 433 | loadstring_and_call(s, bndng, filename, lineno) 434 | end 435 | 436 | # Returns a value set at the 'global' level in the state. 437 | # 438 | # state.eval('a = 1 + 2') 439 | # puts state['a'] # => "3.0" 440 | # 441 | def [](k) 442 | 443 | k.index('.') ? self.eval("return #{k}") : get_global(k) 444 | end 445 | 446 | # Allows for setting a Lua varible immediately. 447 | # 448 | # state['var'] = [ 1, 2, 3 ] 449 | # puts state['var'].to_a[0] # => 1 450 | # 451 | def []=(k, v) 452 | 453 | #puts; puts("#{k} = #{Rufus::Lua.to_lua_s(v)}") 454 | self.eval("#{k} = #{Rufus::Lua.to_lua_s(v)}") 455 | end 456 | 457 | # Binds a Ruby function (callback) in the top environment of Lua 458 | # 459 | # require 'rufus/lua' 460 | # 461 | # s = Rufus::Lua::State.new 462 | # 463 | # s.function 'key_up' do |table| 464 | # table.inject({}) do |h, (k, v)| 465 | # h[k.to_s.upcase] = v 466 | # end 467 | # end 468 | # 469 | # p s.eval(%{ 470 | # local table = {} 471 | # table['CoW'] = 2 472 | # table['pigs'] = 3 473 | # table['DUCKS'] = 'none' 474 | # return key_up(table) 475 | # }).to_h 476 | # # => { 'COW' => 2.0, 'DUCKS => 'none', 'PIGS' => 3.0 } 477 | # 478 | # s.close 479 | # 480 | # == :to_ruby => true 481 | # 482 | # Without this option set to true, Lua tables passed to the wrapped 483 | # Ruby code are instances of Rufus::Lua::Table. With this option set, 484 | # rufus-lua will call #to_ruby on any parameter that responds to it 485 | # (And Rufus::Lua::Table does). 486 | # 487 | # s = Rufus::Lua::State.new 488 | # 489 | # s.function 'is_array', :to_ruby => true do |table| 490 | # table.is_a?(Array) 491 | # end 492 | # 493 | # s.eval(return is_array({ 1, 2 })) 494 | # # => true 495 | # s.eval(return is_array({ 'a' = 'b' })) 496 | # # => false 497 | # 498 | def function(name, opts={}, &block) 499 | 500 | fail 'please pass a block for the body of the function' unless block 501 | 502 | to_ruby = opts[:to_ruby] 503 | 504 | callback = 505 | Proc.new do |state| 506 | 507 | s = CallbackState.new(state) 508 | args = [] 509 | 510 | loop do 511 | 512 | break if s.stack_top == 0 # never touch stack[0] !! 513 | 514 | arg = s.stack_fetch 515 | 516 | args.unshift(arg) 517 | 518 | s.stack_unstack unless args.first.is_a?(Rufus::Lua::Ref) 519 | end 520 | 521 | while args.size < block.arity 522 | args << nil 523 | end 524 | 525 | args = args.collect { |a| a.respond_to?(:to_ruby) ? a.to_ruby : a } \ 526 | if to_ruby 527 | 528 | s.stack_push(block.call(*args)) 529 | 530 | 1 531 | end 532 | 533 | @callbacks << callback 534 | # preserving the callback from garbage collection 535 | 536 | name = name.to_s 537 | 538 | name, index = 539 | if ri = name.rindex('.') 540 | # 541 | # bind in the given table 542 | 543 | table_name = name[0..ri-1] 544 | 545 | t = self.eval("return #{table_name}") rescue nil 546 | 547 | raise ArgumentError.new( 548 | "won't create automatically nested tables" 549 | ) if (not t) and table_name.index('.') 550 | 551 | t = self.eval("#{table_name} = {}; return #{table_name}") \ 552 | unless t 553 | 554 | t.send(:load_onto_stack) 555 | 556 | [ name[ri+1..-1], -2 ] 557 | 558 | else 559 | # 560 | # bind function at the global level 561 | 562 | [ name, LUA_GLOBALSINDEX ] 563 | end 564 | 565 | Lib.lua_pushcclosure(@pointer, callback, 0) 566 | Lib.lua_setfield(@pointer, index, name) 567 | end 568 | 569 | # Closes the state. 570 | # 571 | # It's probably a good idea (mem leaks) to close a Lua state once you're 572 | # done with it. 573 | # 574 | def close 575 | 576 | fail 'State already closed' unless @pointer 577 | Lib.lua_close(@pointer) 578 | @pointer = nil 579 | end 580 | 581 | # Returns current amount of memory in KB in use by Lua 582 | # 583 | def gc_count 584 | 585 | fail 'State got closed, cannot proceed' unless @pointer 586 | Lib.lua_gc(@pointer, LUA_GCCOUNT, 0) 587 | end 588 | 589 | # Runs garbage collection 590 | # 591 | def gc_collect! 592 | 593 | fail 'State got closed, cannot proceed' unless @pointer 594 | Lib.lua_gc(@pointer, LUA_GCCOLLECT, 0) 595 | end 596 | 597 | # Stop garbage collection for this state 598 | # 599 | def gc_stop 600 | 601 | fail 'State got closed, cannot proceed' unless @pointer 602 | Lib.lua_gc(@pointer, LUA_GCSTOP, 0) 603 | end 604 | 605 | # Restart garbage collection for this state 606 | # 607 | def gc_resume 608 | 609 | fail 'State got closed, cannot proceed' unless @pointer 610 | Lib.lua_gc(@pointer, LUA_GCRESTART, 0) 611 | end 612 | 613 | # #open_library(libname) - load a lua library via lua_call(). 614 | # 615 | # This is needed because is the Lua 5.1 Reference Manual Section 5 616 | # (http://www.lua.org/manual/5.1/manual.html#5) it says: 617 | # 618 | # "The luaopen_* functions (to open libraries) cannot be called 619 | # directly, like a regular C function. They must be called through 620 | # Lua, like a Lua function." 621 | # 622 | # "..you must call them like any other Lua C function, e.g., by using 623 | # lua_call." 624 | # 625 | # (by Matthew Nielsen - https://github.com/xunker) 626 | # 627 | def open_library(libname) 628 | 629 | Lib.lua_pushcclosure( 630 | @pointer, lambda { |ptr| Lib.send("luaopen_#{libname}", @pointer) }, 0) 631 | Lib.lua_pushstring( 632 | @pointer, (libname.to_s == "base" ? "" : libname.to_s)) 633 | Lib.lua_call( 634 | @pointer, 1, 0) 635 | end 636 | 637 | def open_libraries(libs) 638 | 639 | if libs == true 640 | Lib.luaL_openlibs(@pointer) 641 | elsif libs.is_a?(Array) 642 | libs.each { |l| open_library(l) } 643 | end 644 | end 645 | end 646 | end 647 | 648 | -------------------------------------------------------------------------------- /lib/rufus/lua/utils.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rufus::Lua 3 | 4 | #-- 5 | # always make sure that the methods here are usable without the need 6 | # to load liblua.dylib 7 | #++ 8 | 9 | # Turns a Ruby instance into a Lua parseable string representation. 10 | # 11 | # Will raise an ArgumentError as soon as something else than a simple 12 | # Ruby type (or Hash/Array) is passed. 13 | # 14 | # Rufus::Lua.to_lua_s({ 'a' => 'A', 'b' => 2}) 15 | # # 16 | # # => '{ "a": "A", "b": 2 }' 17 | # 18 | def self.to_lua_s(o) 19 | 20 | case o 21 | 22 | when String then o.inspect 23 | when Integer then o.to_s 24 | when Float then o.to_s 25 | when TrueClass then o.to_s 26 | when FalseClass then o.to_s 27 | when NilClass then 'nil' 28 | 29 | when Hash then to_lua_table_s(o) 30 | when Array then to_lua_table_s(o) 31 | 32 | else fail( 33 | ArgumentError.new( 34 | "don't how to turning into a Lua string representation "+ 35 | "Ruby instances of class '#{o.class}'")) 36 | end 37 | end 38 | 39 | # Turns a Ruby Array or Hash instance into a Lua parseable string 40 | # representation. 41 | # 42 | def self.to_lua_table_s(o) 43 | 44 | s = if o.is_a?(Array) 45 | o.collect { |e| to_lua_s(e) } 46 | else 47 | o.collect { |k, v| "[#{to_lua_s(k)}] = #{to_lua_s(v)}" } 48 | end 49 | 50 | "{ #{s.join(', ')} }" 51 | end 52 | end 53 | 54 | -------------------------------------------------------------------------------- /lib/rufus/lua/version.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rufus 3 | module Lua 4 | 5 | VERSION = '1.1.5' 6 | end 7 | end 8 | 9 | -------------------------------------------------------------------------------- /rufus-lua.gemspec: -------------------------------------------------------------------------------- 1 | 2 | Gem::Specification.new do |s| 3 | 4 | s.name = 'rufus-lua' 5 | 6 | s.version = File.read( 7 | File.expand_path('../lib/rufus/lua/version.rb', __FILE__) 8 | ).match(/ VERSION *= *['"]([^'"]+)/)[1] 9 | 10 | s.platform = Gem::Platform::RUBY 11 | s.authors = [ 'John Mettraux', 'Alain Hoang', 'Scott Persinger' ] 12 | s.email = [ 'jmettraux@gmail.com' ] 13 | s.homepage = 'https://github.com/jmettraux/rufus-lua' 14 | s.license = 'MIT' 15 | s.summary = 'ruby-ffi based bridge from Ruby to Lua' 16 | 17 | s.description = %{ 18 | ruby-ffi based bridge from Ruby to Lua 19 | }.strip 20 | 21 | #s.files = `git ls-files`.split("\n") 22 | s.files = Dir[ 23 | 'Rakefile', 24 | 'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb', 25 | '*.gemspec', '*.txt', '*.rdoc', '*.md' 26 | ] 27 | 28 | s.add_dependency 'ffi', '~> 1.9' 29 | 30 | s.add_development_dependency 'rspec', '>= 2.13.0' 31 | 32 | s.require_path = 'lib' 33 | end 34 | 35 | -------------------------------------------------------------------------------- /spec/coroutines_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Sat Mar 14 23:51:42 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::State do 12 | 13 | describe 'a Lua coroutine' do 14 | 15 | before do 16 | @s = Rufus::Lua::State.new 17 | end 18 | after do 19 | @s.close 20 | end 21 | 22 | it 'can be returned to Ruby' do 23 | 24 | expect(@s.eval( 25 | 'return coroutine.create(function (x) end)' 26 | ).class).to eq(Rufus::Lua::Coroutine) 27 | end 28 | 29 | it 'has a status visible from Ruby' do 30 | 31 | co = @s.eval( 32 | 'return coroutine.create(function (x) end)' 33 | ) 34 | expect(co.status).to eq('suspended') 35 | end 36 | 37 | it 'can be resumed from Ruby' do 38 | 39 | @s.eval(%{ 40 | co = coroutine.create(function (x) 41 | while true do 42 | coroutine.yield(x) 43 | end 44 | end) 45 | }) 46 | expect(@s['co'].resume(7)).to eq [ true, 7.0 ] 47 | expect(@s['co'].resume()).to eq [ true, 7.0 ] 48 | end 49 | 50 | it 'can be resumed from Ruby (and is not averse to \0 bytes)' do 51 | 52 | @s.eval(%{ 53 | co = coroutine.create(function (x) 54 | while true do 55 | coroutine.yield(x) 56 | end 57 | end) 58 | }) 59 | s = [ 0, 64, 0, 0, 65, 0 ].pack('c*') 60 | expect(@s['co'].resume(s)).to eq [ true, s ] 61 | expect(@s['co'].resume()).to eq [ true, s ] 62 | end 63 | 64 | # compressed version of the spec proposed by Nathanael Jones 65 | # in https://github.com/nathanaeljones/rufus-lua/commit/179184aS 66 | # 67 | # for https://github.com/jmettraux/rufus-lua/issues/19 68 | # 69 | it 'yields the right value' do 70 | 71 | @s.function :host_function do 72 | 'success' 73 | end 74 | r = @s.eval(%{ 75 | function routine() 76 | coroutine.yield(host_function()) 77 | end 78 | co = coroutine.create(routine) 79 | a, b = coroutine.resume(co) 80 | return { a, b } 81 | }).to_ruby 82 | 83 | expect(r).to eq([ true, 'success' ]) 84 | end 85 | 86 | it 'executes a ruby function within a coroutine' do 87 | 88 | run_count = 0 89 | 90 | @s.function :host_function do 91 | run_count += 1 92 | end 93 | r = @s.eval(%{ 94 | function routine() 95 | host_function() 96 | coroutine.yield() 97 | host_function() 98 | coroutine.yield() 99 | end 100 | co = coroutine.create(routine) 101 | a, b = coroutine.resume(co) 102 | a, b = coroutine.resume(co) 103 | return { a, b } 104 | }).to_ruby 105 | 106 | expect(r).to eq([ true]) 107 | expect(run_count).to eq(2) 108 | end 109 | 110 | it 'executes a ruby function (with arguments) within a coroutine' do 111 | 112 | run_count = 0 113 | last_arguments = nil 114 | 115 | @s.function :host_function, { :to_ruby => true } do |*args| 116 | run_count += 1 117 | last_arguments = args 118 | 0 119 | end 120 | r = @s.eval(%{ 121 | function routine() 122 | host_function("hi") 123 | coroutine.yield() 124 | end 125 | co = coroutine.create(routine) 126 | a, b = coroutine.resume(co) 127 | return { a, b } 128 | }).to_ruby 129 | 130 | expect(r).to eq([ true ]) 131 | expect(run_count).to eq(1) 132 | expect(last_arguments).to eq([ "hi" ]) 133 | end 134 | end 135 | end 136 | 137 | -------------------------------------------------------------------------------- /spec/error_handler_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Tue Jul 22 21:07:10 JST 2014 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe 'State and error handler' do 12 | 13 | before do 14 | @s = Rufus::Lua::State.new 15 | end 16 | after do 17 | @s.close 18 | end 19 | 20 | describe '#set_error_handler(lua_code)' do 21 | 22 | it 'registers a function as error handler' do 23 | 24 | le = nil 25 | 26 | @s.set_error_handler(%{ 27 | function (e) 28 | return e .. '\\n' .. debug.traceback() 29 | end 30 | }) 31 | 32 | @s.eval(%{ 33 | function f () 34 | error("in f") 35 | end 36 | }, nil, 'mystuff.lua', 77) 37 | begin 38 | @s.eval('f()', nil, 'mymain.lua', 88) 39 | rescue Rufus::Lua::LuaError => le 40 | end 41 | 42 | expect(le.message).to eq(%{ 43 | eval:pcall : '[string "mystuff.lua:77"]:3: in f 44 | stack traceback: 45 | [string "line"]:2: in function <[string "line"]:1> 46 | [C]: in function 'error' 47 | [string "mystuff.lua:77"]:3: in function 'f' 48 | [string "mymain.lua:88"]:1: in main chunk' (2 LUA_ERRRUN) 49 | }.strip) 50 | 51 | expect(@s.send(:stack_top)).to eq(1) 52 | end 53 | 54 | it 'sets the error handler in a permanent way' do 55 | 56 | le = nil 57 | 58 | @s.set_error_handler(%{ 59 | function (e) 60 | return 'something went wrong: ' .. string.gmatch(e, ": (.+)$")() 61 | end 62 | }) 63 | 64 | begin 65 | @s.eval('error("a")') 66 | rescue Rufus::Lua::LuaError => le 67 | end 68 | 69 | expect(le.msg).to eq('something went wrong: a') 70 | 71 | begin 72 | @s.eval('error("b")') 73 | rescue Rufus::Lua::LuaError => le 74 | end 75 | 76 | expect(le.msg).to eq('something went wrong: b') 77 | end 78 | 79 | context 'CallbackState' do 80 | 81 | # Setting an error handler on the calling state or even directly 82 | # on the CallbackState doesn't have any effect. 83 | # Any error handler seems bypassed. 84 | 85 | it 'has no effect' do 86 | 87 | e = nil 88 | 89 | @s.set_error_handler(%{ 90 | function (e) return 'bad: ' .. string.gmatch(e, ": (.+)$")() end 91 | }) 92 | f = @s.function(:do_fail) { fail('in style') } 93 | begin 94 | @s.eval('do_fail()') 95 | rescue Exception => e 96 | end 97 | 98 | expect(e.class).to eq(RuntimeError) 99 | end 100 | end 101 | end 102 | 103 | describe '#set_error_handler(:traceback)' do 104 | 105 | it 'sets a vanilla debug.traceback() error handler' do 106 | 107 | le = nil 108 | 109 | @s.set_error_handler(:traceback) 110 | 111 | @s.eval(%{ 112 | function f () 113 | error("in f") 114 | end 115 | }, nil, 'mystuff.lua', 77) 116 | begin 117 | @s.eval('f()', nil, 'mymain.lua', 88) 118 | rescue Rufus::Lua::LuaError => le 119 | end 120 | 121 | expect(le.message).to eq(%{ 122 | eval:pcall : '[string "mystuff.lua:77"]:3: in f 123 | stack traceback: 124 | [C]: in function 'error' 125 | [string "mystuff.lua:77"]:3: in function 'f' 126 | [string "mymain.lua:88"]:1: in main chunk' (2 LUA_ERRRUN) 127 | }.strip) 128 | end 129 | end 130 | 131 | describe '#set_error_handler(:backtrace)' do 132 | 133 | it 'sets a special handler that provides a merged Ruby then Lua backtrace' 134 | # does it make any sense? 135 | end 136 | 137 | describe '#set_error_handler(&ruby_block)' do 138 | 139 | it 'sets a Ruby callback as handler' do 140 | 141 | le = nil 142 | 143 | @s.set_error_handler do |msg| 144 | ([ msg.split.last ] * 3).join(' ') 145 | end 146 | 147 | begin 148 | @s.eval('error("tora")') 149 | rescue Rufus::Lua::LuaError => le 150 | end 151 | 152 | expect(le.msg).to eq('tora tora tora') 153 | end 154 | end 155 | 156 | describe '#set_error_handler(nil)' do 157 | 158 | it 'unsets the current error handler' do 159 | 160 | le = nil 161 | 162 | # set 163 | 164 | @s.set_error_handler(%{ 165 | function (e) 166 | return 'something went wrong: ' .. string.gmatch(e, ": (.+)$")() 167 | end 168 | }) 169 | 170 | begin 171 | @s.eval('error("a")') 172 | rescue Rufus::Lua::LuaError => le 173 | end 174 | 175 | expect(le.msg).to eq('something went wrong: a') 176 | 177 | # unset 178 | 179 | @s.set_error_handler(nil) 180 | 181 | begin 182 | @s.eval('error("b")') 183 | rescue Rufus::Lua::LuaError => le 184 | end 185 | 186 | expect(le.msg).to eq('[string "line"]:1: b') 187 | expect(@s.send(:stack_top)).to eq(0) 188 | end 189 | end 190 | 191 | describe '#set_error_handler(x)' do 192 | 193 | it 'pops the previous handler from the stack' do 194 | 195 | le = nil 196 | 197 | expect(@s.send(:stack_top)).to eq(0) 198 | 199 | @s.set_error_handler(%{ function (e) return "a" end }) 200 | #@s.set_error_handler(:traceback) 201 | #@s.set_error_handler do |msg| return msg * 2; end 202 | 203 | #@s.send(:print_stack) 204 | 205 | expect(@s.send(:stack_top)).to eq(1) 206 | 207 | @s.set_error_handler(%{ function (e) return "b" end }) 208 | 209 | #@s.send(:print_stack) 210 | 211 | expect(@s.send(:stack_top)).to eq(1) 212 | 213 | begin 214 | @s.eval('error("Z")') 215 | rescue Rufus::Lua::LuaError => le 216 | end 217 | 218 | expect(le.msg).to eq('b') 219 | end 220 | end 221 | end 222 | 223 | -------------------------------------------------------------------------------- /spec/eval_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 4 | # Specifying rufus-lua 5 | # 6 | # Wed Mar 11 17:09:17 JST 2009 7 | # 8 | 9 | require 'spec_base' 10 | 11 | 12 | describe Rufus::Lua::State do 13 | 14 | before do 15 | @s = Rufus::Lua::State.new 16 | end 17 | after do 18 | @s.close 19 | end 20 | 21 | describe '#[]' do 22 | 23 | it 'returns nil for an unknown value/binding' do 24 | 25 | expect(@s['unknown']).to eq nil 26 | end 27 | end 28 | 29 | describe '#eval' do 30 | 31 | it 'evals true' do 32 | 33 | @s.eval('a = true') 34 | expect(@s['a']).to eq true 35 | end 36 | 37 | it 'evals false' do 38 | 39 | @s.eval('a = false') 40 | expect(@s['a']).to eq false 41 | end 42 | 43 | it 'evals strings' do 44 | 45 | @s.eval('a = "black adder"') 46 | expect(@s['a']).to eq 'black adder' 47 | end 48 | 49 | it 'evals additions' do 50 | 51 | @s.eval('a = 1 + 1') 52 | expect(@s['a']).to eq 2.0 53 | end 54 | 55 | it 'evals nested lookups' do 56 | 57 | @s.eval('a = { b = { c = 0 } }') 58 | @s.eval('_ = a.b.c') 59 | expect(@s['_']).to eq 0 60 | end 61 | 62 | #it 'returns the global environment' do 63 | # @s['_G']).to eq {} 64 | #end 65 | 66 | it 'returns numbers' do 67 | 68 | expect(@s.eval('return 7')).to eq 7.0 69 | end 70 | 71 | it 'returns multiple values' do 72 | 73 | expect(@s.eval('return 1, 2')).to eq [ 1.0, 2.0 ] 74 | end 75 | 76 | it 'returns multiple values (tables included)' do 77 | 78 | r = @s.eval('return 1, 2, {}') 79 | 80 | expect(r.class).to eq Array 81 | expect(r[0, 2]).to eq [ 1.0, 2.0 ] 82 | expect(r[2].class).to eq Rufus::Lua::Table 83 | expect(r[2].size).to eq 0 84 | end 85 | 86 | it 'returns multiple values (tables included) (take 2)' do 87 | 88 | r = @s.eval('return { "hello", "world" }, 2, { 3 }') 89 | 90 | expect(r[0].class).to eq Rufus::Lua::Table 91 | expect(r[0][1]).to eq 'hello' 92 | expect(r[0][2]).to eq 'world' 93 | expect(r[1]).to eq 2.0 94 | expect(r[2].class).to eq Rufus::Lua::Table 95 | expect(r[2][1]).to eq 3.0 96 | end 97 | 98 | it 'returns false' do 99 | 100 | expect(@s.eval('return false')).to eq false 101 | end 102 | 103 | it 'returns true' do 104 | 105 | expect(@s.eval('return true')).to eq true 106 | end 107 | 108 | it 'returns tables' do 109 | 110 | r = @s.eval('return { "hello", "world", 2 }') 111 | 112 | expect(r.class).to eq Rufus::Lua::Table 113 | expect(r[0]).to eq nil 114 | expect(r[1]).to eq 'hello' 115 | expect(r[2]).to eq 'world' 116 | expect(r[3]).to eq 2.0 117 | end 118 | 119 | it 'accepts a binding optional argument' 120 | 121 | it 'accepts a filename and a lineno optional arguments' do 122 | 123 | le = nil 124 | begin 125 | @s.eval('error(77)', nil, '/nada/virtual.lua', 63) 126 | rescue Rufus::Lua::LuaError => le 127 | end 128 | 129 | expect(le.kind).to eq('eval:pcall') 130 | expect(le.msg).to eq('[string "/nada/virtual.lua:63"]:1: 77') 131 | expect(le.errcode).to eq(2) 132 | 133 | expect(le.filename).to eq('/nada/virtual.lua') 134 | expect(le.lineno).to eq(63) 135 | 136 | expect(le.original_backtrace.first).to match(/\/lua\/state\.rb:/) 137 | expect(le.backtrace.first).to eq('/nada/virtual.lua:63:') 138 | end 139 | 140 | context 'and errors' do 141 | 142 | it 'makes the file and line available' do 143 | 144 | le = nil 145 | begin 146 | @s.eval('error(77)') 147 | rescue Rufus::Lua::LuaError => le 148 | end 149 | 150 | expect(le.kind).to eq('eval:pcall') 151 | expect(le.msg).to eq('[string "line"]:1: 77') 152 | expect(le.errcode).to eq(2) 153 | 154 | expect(le.message).to eq( 155 | "eval:pcall : '[string \"line\"]:1: 77' (2 LUA_ERRRUN)") 156 | 157 | expect(le.filename).to eq(__FILE__) 158 | expect(le.lineno).to eq(__LINE__ - 12) 159 | 160 | expect(le.original_backtrace.first).to match(/\/lua\/state\.rb:/) 161 | expect(le.backtrace.first).to match(/\/eval_spec\.rb:/) 162 | end 163 | end 164 | 165 | context 'and unicode input' do 166 | 167 | it 'does not truncate the input' do 168 | 169 | @s.eval('a = "ほりもり"') 170 | 171 | a = @s['a'] 172 | expect(a.encoding.to_s).to eq('ASCII-8BIT') 173 | 174 | a.force_encoding('UTF-8') 175 | expect(a).to eq('ほりもり') 176 | end 177 | end 178 | end 179 | end 180 | 181 | -------------------------------------------------------------------------------- /spec/functions_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Wed Mar 11 17:09:17 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::State do 12 | 13 | describe 'Lua functions' do 14 | 15 | before do 16 | @s = Rufus::Lua::State.new 17 | end 18 | after do 19 | @s.close 20 | end 21 | 22 | it 'are returned as Rufus::Lua::Function instances' do 23 | 24 | expect(@s.eval('return function () end').class).to eq Rufus::Lua::Function 25 | end 26 | 27 | it 'are callable from Ruby' do 28 | 29 | f = @s.eval(%{ 30 | f = function () 31 | return 77 32 | end 33 | return f 34 | }) 35 | 36 | expect(f.call()).to eq 77.0 37 | end 38 | 39 | it 'are callable even when they return multiple values' do 40 | 41 | f = @s.eval(%{ 42 | f = function () 43 | return 77, 44 44 | end 45 | return f 46 | }) 47 | 48 | expect(f.call()).to eq [ 77.0, 44.0 ] 49 | end 50 | 51 | it 'are callable with arguments' do 52 | 53 | f = @s.eval(%{ 54 | f = function (x) 55 | return x * x 56 | end 57 | return f 58 | }) 59 | 60 | expect(f.call(2)).to eq 4.0 61 | end 62 | 63 | it 'are callable with boolean arguments' do 64 | 65 | f = @s.eval(%{ 66 | f = function (x) 67 | return x 68 | end 69 | return f 70 | }) 71 | 72 | expect(f.call(true)).to eq true 73 | expect(f.call(false)).to eq false 74 | end 75 | 76 | it 'are callable with array arguments' do 77 | 78 | f = @s.eval(%{ 79 | f = function (x) 80 | return x 81 | end 82 | return f 83 | }) 84 | 85 | expect(f.call(%w[ one two three ]).to_a).to eq %w[ one two three ] 86 | end 87 | 88 | it 'are callable with multiple arguments' do 89 | 90 | f = @s.eval(%{ 91 | f = function (x, y) 92 | return x + y 93 | end 94 | return f 95 | }) 96 | 97 | expect(f.call(1, 2)).to eq 3.0 98 | end 99 | 100 | it 'are called with #to_lua\'ed Ruby arguments' do 101 | 102 | f = @s.eval(%{ 103 | f = function (x) 104 | return x 105 | end 106 | return f 107 | }) 108 | 109 | t = Time.now 110 | 111 | def t.to_lua 112 | "lua:#{to_s}" 113 | end 114 | 115 | expect(f.call(t)).to eq t.to_lua 116 | end 117 | end 118 | end 119 | 120 | -------------------------------------------------------------------------------- /spec/gc_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Mon Mar 16 23:07:49 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::State do 12 | 13 | before do 14 | @s = Rufus::Lua::State.new 15 | end 16 | after do 17 | @s.close 18 | end 19 | 20 | describe '#gc_collect!' do 21 | 22 | it 'raises when called on a closed Rufus::Lua::State instance' do 23 | 24 | s = Rufus::Lua::State.new 25 | s.close 26 | expect(lambda { s.gc_collect! }).to raise_error(RuntimeError) 27 | end 28 | end 29 | 30 | describe '#gc_count' do 31 | 32 | it 'returns an indication about the Lua interpreter memory usage' do 33 | 34 | before_usage = @s.gc_count 35 | @s.eval("return table.concat({ 'hello', 'from', 'Lua' }, ' ')") 36 | after_usage = @s.gc_count 37 | 38 | expect(after_usage).to be > before_usage 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Thu Jun 19 20:29:06 JST 2014 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::Lib do 12 | 13 | describe '.path' do 14 | 15 | it 'returns the Lua lib being used' do 16 | 17 | expect(Rufus::Lua::Lib.path).to match(/liblua/) 18 | end 19 | end 20 | end 21 | 22 | -------------------------------------------------------------------------------- /spec/rfunctions_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Wed Mar 18 17:53:06 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::State do 12 | 13 | before do 14 | @s = Rufus::Lua::State.new 15 | end 16 | after do 17 | @s.close 18 | end 19 | 20 | describe '#function' do 21 | 22 | it 'raises when no block is given' do 23 | 24 | expect(lambda { 25 | @s.function 'no_block' 26 | }).to raise_error(RuntimeError) 27 | end 28 | 29 | it 'works without arguments' do 30 | 31 | blinked = false 32 | 33 | @s.function 'blink' do 34 | blinked = true 35 | end 36 | 37 | @s.eval('blink()') 38 | 39 | expect(blinked).to eq true 40 | end 41 | 42 | it 'works with arguments' do 43 | 44 | message = nil 45 | 46 | @s.function :greet do |msg| 47 | message = msg 48 | end 49 | 50 | @s.eval("greet('obandegozaimasu')") 51 | 52 | expect(message).to eq 'obandegozaimasu' 53 | end 54 | 55 | it 'works with function arguments' do 56 | 57 | message = nil 58 | 59 | @s.function :exec do |fun| 60 | message = fun.call() 61 | end 62 | 63 | @s.eval("exec(function() return 'foobar' end)") 64 | 65 | expect(message).to eq 'foobar' 66 | end 67 | 68 | it 'binds functions inside of Lua tables' do 69 | 70 | @s.eval('lib = {}') 71 | @s.function 'lib.myfunc' do |x| 72 | x + 2 73 | end 74 | 75 | expect(@s.eval("return lib.myfunc(3)")).to eq 5.0 76 | end 77 | 78 | it 'creates the top Lua table if not present' do 79 | 80 | @s.function 'lib.myfunc' do |x| 81 | x + 2 82 | end 83 | 84 | expect(@s.eval("return lib.myfunc(3)")).to eq 5.0 85 | end 86 | 87 | it 'only creates the top table (not intermediary tables)' do 88 | 89 | expect(lambda { 90 | @s.function('lib.toto.myfunc') { |x| x + 2 } 91 | }).to raise_error(ArgumentError) 92 | end 93 | end 94 | 95 | context 'calling a Ruby function from Lua' do 96 | 97 | it 'may return a single value' do 98 | 99 | @s.function :greet do 100 | 'hello !' 101 | end 102 | 103 | expect(@s.eval('return greet()')).to eq 'hello !' 104 | end 105 | 106 | it 'may return multiple values' do 107 | 108 | @s.function :compute do 109 | [ 'a', true, 1 ] 110 | end 111 | 112 | expect(@s.eval('return compute()').to_a).to eq [ 'a', true, 1.0 ] 113 | end 114 | 115 | it 'may return tables' do 116 | 117 | @s.function :compute do 118 | { 'a' => 'alpha', 'z' => 'zebra' } 119 | end 120 | 121 | expect(@s.eval('return compute()').to_h).to eq( 122 | { 'a' => 'alpha', 'z' => 'zebra' }) 123 | end 124 | 125 | it 'may return tables (with nested arrays)' do 126 | 127 | @s.function :compute do 128 | { 'a' => 'alpha', 'z' => [ 1, 2, 3 ] } 129 | end 130 | 131 | expect(@s.eval('return compute()').to_h['z'].to_a).to eq [ 1.0, 2.0, 3.0 ] 132 | end 133 | 134 | it 'accepts hashes as arguments' do 135 | 136 | @s.function :to_json do |h| 137 | "{" + h.collect { |k, v| "#{k}:\"#{v}\"" }.join(",") + "}" 138 | end 139 | 140 | expect(@s.eval( 141 | "return to_json({ a = 'ALPHA', b = 'BRAVO' })" 142 | )).to eq( 143 | '{a:"ALPHA",b:"BRAVO"}' 144 | ) 145 | end 146 | 147 | it 'accepts arrays as arguments' do 148 | 149 | @s.function :do_join do |a| 150 | a.to_a.join(', ') 151 | end 152 | 153 | expect(@s.eval( 154 | "return do_join({ 'alice', 'bob', 'charly' })" 155 | )).to eq( 156 | 'alice, bob, charly' 157 | ) 158 | end 159 | 160 | it 'counts the animals correctly' do 161 | 162 | @s.function 'key_up' do |table| 163 | table.inject({}) do |h, (k, v)| 164 | h[k.to_s.upcase] = v; h 165 | end 166 | end 167 | 168 | expect(@s.eval(%{ 169 | local table = {} 170 | table['CoW'] = 2 171 | table['pigs'] = 3 172 | table['DUCKS'] = 'none' 173 | return key_up(table) 174 | }).to_h).to eq( 175 | { 'COW' => 2.0, 'DUCKS' => 'none', 'PIGS' => 3.0 } 176 | ) 177 | end 178 | 179 | it 'returns Ruby arrays as Lua tables' do 180 | 181 | @s.function :get_data do |msg| 182 | %w[ one two three ] 183 | end 184 | 185 | @s.eval('data = get_data()') 186 | 187 | expect(@s['data'].to_a).to eq %w[ one two three ] 188 | expect(@s.eval('return type(data)')).to eq('table') 189 | end 190 | 191 | it 'return properly indexed Lua tables' do 192 | 193 | @s.function :get_data do |msg| 194 | %w[ one two three ] 195 | end 196 | 197 | @s.eval('data = get_data()') 198 | 199 | expect(@s.eval('return data[0]')).to eq nil 200 | expect(@s.eval('return data[1]')).to eq 'one' 201 | end 202 | 203 | it 'accepts more than 1 arg and order the args correctly' do 204 | 205 | @s.function 'myfunc' do |a, b, c| 206 | "#{a}_#{b}_#{c}" 207 | end 208 | 209 | expect(@s.eval("return myfunc(1, 2, 3)")).to eq '1.0_2.0_3.0' 210 | end 211 | 212 | it 'accepts optional arguments' do 213 | 214 | @s.function 'myfunc' do |a, b, c| 215 | "#{a}_#{b}_#{c}" 216 | end 217 | 218 | expect(@s.eval("return myfunc(1)")).to eq '1.0__' 219 | end 220 | 221 | it 'is ok when there are too many args' do 222 | 223 | @s.function 'myfunc' do |a, b| 224 | "#{a}_#{b}" 225 | end 226 | 227 | expect(@s.eval("return myfunc(1, 2, 3)")).to eq '1.0_2.0' 228 | end 229 | 230 | it 'passes Float arguments correctly' do 231 | 232 | @s.function 'myfunc' do |a| 233 | "#{a.class} #{a}" 234 | end 235 | 236 | expect(@s.eval("return myfunc(3.14)")).to eq 'Float 3.14' 237 | end 238 | 239 | it 'preserves arguments' do 240 | 241 | @s.function 'check_types' do |t, s, f, h, a| 242 | 243 | #p [ t, s, f, h, a ] 244 | 245 | (t.is_a?(TrueClass) && 246 | s.is_a?(String) && 247 | f.is_a?(Float) && 248 | h.is_a?(Rufus::Lua::Table) && 249 | a.is_a?(Rufus::Lua::Table)) 250 | end 251 | 252 | expect(@s.eval( 253 | "return check_types(true, 'foobar', 3.13, {a='ay',b='bee'}, {'one','two','three'})" 254 | )).to eq true 255 | end 256 | 257 | it 'honours to_ruby=true' do 258 | 259 | @s.function 'check_types', :to_ruby => true do |t, s, f, h, a| 260 | 261 | #p [ t, s, f, h, a ] 262 | 263 | (t.is_a?(TrueClass) && 264 | s.is_a?(String) && 265 | f.is_a?(Float) && 266 | h.is_a?(Hash) && 267 | a.is_a?(Array)) 268 | end 269 | 270 | expect(@s.eval( 271 | "return check_types(true, 'foobar', 3.13, {a='ay',b='bee'}, {'one','two','three'})" 272 | )).to eq true 273 | end 274 | 275 | it 'protects callbacks from GC' do 276 | 277 | @s.function 'myfunc' do |a| 278 | end 279 | 280 | expect(@s.instance_variable_get(:@callbacks).size).to eq 1 281 | end 282 | end 283 | 284 | context 'Ruby functions and exceptions' do 285 | 286 | it 'raises exceptions (Ruby -> Lua -> Ruby and back)' do 287 | 288 | @s.function :do_fail do 289 | raise 'fail!' 290 | end 291 | 292 | expect { 293 | @s.eval('return do_fail()') 294 | }.to raise_error(RuntimeError) 295 | end 296 | 297 | it 'raises exceptions (Ruby -> Lua -> Ruby and back)' do 298 | 299 | @s.function :do_fail do 300 | raise 'fail!' 301 | end 302 | 303 | expect { 304 | @s.eval('do_fail()') 305 | }.to raise_error(RuntimeError) 306 | end 307 | 308 | it 'raises exceptions (gh-42)' do 309 | 310 | @s.function :no_fail do 311 | 1 312 | end 313 | @s.function :do_fail do 314 | raise 'fail!' 315 | end 316 | 317 | expect { 318 | @s.eval('do_fail(); no_fail()') 319 | }.to raise_error(RuntimeError) 320 | end 321 | end 322 | end 323 | 324 | -------------------------------------------------------------------------------- /spec/spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # specifying rufus-lua 4 | # 5 | # Wed Mar 11 16:09:11 JST 2009 6 | # 7 | 8 | Dir["#{File.dirname(__FILE__)}/*_spec.rb"].each { |path| load(path) } 9 | 10 | -------------------------------------------------------------------------------- /spec/spec_base.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Wed Mar 11 16:09:31 JST 2009 6 | # 7 | 8 | require 'fileutils' 9 | require 'rufus-lua' 10 | 11 | -------------------------------------------------------------------------------- /spec/state_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Mon Mar 16 23:38:00 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::State do 12 | 13 | describe '#new' do 14 | 15 | it 'loads all libs by default' do 16 | 17 | @s = Rufus::Lua::State.new 18 | expect(@s.eval('return os;')).not_to be nil 19 | @s.close 20 | end 21 | 22 | it 'loads no libs when told so' do 23 | 24 | @s = Rufus::Lua::State.new(false) 25 | expect(@s.eval('return os;')).to be nil 26 | @s.close 27 | end 28 | 29 | it 'loads only specific libs when told so' do 30 | 31 | @s = Rufus::Lua::State.new([ :os, :math ]) 32 | expect(@s.eval('return io;')).to be nil 33 | expect(@s.eval('return os;')).not_to be nil 34 | @s.close 35 | end 36 | end 37 | 38 | describe '#close' do 39 | 40 | it 'does not crash when closing an already closed State' do 41 | 42 | @s = Rufus::Lua::State.new 43 | @s.close 44 | 45 | expect(lambda { @s.close }).to raise_error(RuntimeError) 46 | end 47 | end 48 | 49 | describe '#[]' do 50 | 51 | before do 52 | @s = Rufus::Lua::State.new 53 | end 54 | after do 55 | @s.close 56 | end 57 | 58 | it 'return nils for unbound variables' do 59 | 60 | expect(@s['a']).to be nil 61 | end 62 | 63 | it 'accepts setting values directly' do 64 | 65 | @s['a'] = 1 66 | @s['a'] == 1 67 | end 68 | 69 | it 'accepts setting array values directly' do 70 | 71 | @s['a'] = [ true, false, %w[ alpha bravo charly ] ] 72 | 73 | expect(@s['a'].to_a[0]).to be true 74 | expect(@s['a'].to_a[2].to_a).to eq %w[ alpha bravo charly ] 75 | end 76 | 77 | it 'accepts setting hash values directly' do 78 | 79 | @s['a'] = { 'a' => 'alpha', 'b' => 'bravo' } 80 | 81 | expect(@s['a'].to_h).to eq({ 'a' => 'alpha', 'b' => 'bravo' }) 82 | end 83 | end 84 | 85 | context 'gh-6 panic: unprotected error' do 86 | 87 | it 'does not happen when loading io alone' do 88 | 89 | state = Rufus::Lua::State.new(%w[ io ]) 90 | 91 | expect(state.eval('return io;')).not_to be nil 92 | expect(state.eval('return math;')).to be nil 93 | state.close 94 | end 95 | 96 | it 'does not happen' do 97 | 98 | state = Rufus::Lua::State.new(%w[ base table string math package ]) 99 | 100 | expect(state.eval('return table;')).not_to be nil 101 | expect(state.eval('return math;')).not_to be nil 102 | state.close 103 | end 104 | end 105 | end 106 | 107 | -------------------------------------------------------------------------------- /spec/string_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Sat Jun 21 11:04:02 JST 2014 6 | # 7 | 8 | require 'spec_base' 9 | 10 | # adapted from https://github.com/jmettraux/rufus-lua/issues/14 11 | 12 | 13 | describe 'lua strings' do 14 | 15 | context 'and \0 bytes' do 16 | 17 | before :each do 18 | @s = Rufus::Lua::State.new 19 | end 20 | after :each do 21 | @s.close 22 | end 23 | 24 | it 'are not truncated when returned to Ruby' do 25 | 26 | s = @s.eval('return string.char(1, 0, 0, 2, 0, 0)') 27 | 28 | expect(s.bytes.to_a).to eq([ 1, 0, 0, 2, 0, 0 ]) 29 | end 30 | 31 | it 'are not truncated when passed from Ruby to Lua and back' do 32 | 33 | s = [ 65, 66, 0, 67, 0, 0, 68, 0 ].pack('c*') 34 | 35 | f = @s.eval(%{ 36 | f = function(s) 37 | return { s = s, l = string.len(s) } 38 | end 39 | return f 40 | }) 41 | 42 | expect(f.call(s).to_h).to eq({ 's' => s, 'l' => 8.0 }) 43 | end 44 | 45 | it 'accept symbols containing the \0 character' do 46 | 47 | s = [ 65, 66, 0, 67, 0, 0, 68, 0 ].pack('c*').to_sym 48 | 49 | f = @s.eval(%{ 50 | f = function(s) 51 | return { s = s, l = string.len(s) } 52 | end 53 | return f 54 | }) 55 | 56 | expect(f.call(s).to_h).to eq({ 's' => s.to_s, 'l' => 8.0 }) 57 | end 58 | end 59 | end 60 | 61 | -------------------------------------------------------------------------------- /spec/tables_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Fri Mar 13 23:42:29 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua::State do 12 | 13 | context 'tables' do 14 | 15 | before do 16 | @s = Rufus::Lua::State.new 17 | end 18 | after do 19 | @s.close 20 | end 21 | 22 | it 'finds a hash' do 23 | 24 | @s.eval('h = { a = "b", c = 2, 4 }') 25 | 26 | expect(@s['h'].to_h).to eq({ 'a' => 'b', 'c' => 2.0, 1.0 => 4.0 }) 27 | end 28 | 29 | it 'turns a hash into an array' do 30 | 31 | @s.eval('a = { "a", "b", "c" }') 32 | 33 | expect(@s['a'].to_h).to eq({ 1.0 => 'a', 2.0 => 'b', 3.0 => 'c' }) 34 | expect(@s['a'].to_a).to eq %w{ a b c } 35 | end 36 | 37 | it 'does nested lookups (2)' do 38 | 39 | @s.eval('a = { b = { c = 0 } }') 40 | 41 | expect(@s['a.b.c']).to eq 0 42 | end 43 | 44 | it 'returns Lua tables' do 45 | 46 | expect(@s.eval('return {}').class).to eq Rufus::Lua::Table 47 | end 48 | 49 | it 'turns Lua tables into Ruby hashes' do 50 | 51 | expect(@s.eval('return {}').to_h).to eq({}) 52 | end 53 | 54 | it 'can free Lua tables' do 55 | 56 | t = @s.eval('t = {}; return t') 57 | t.free 58 | 59 | expect(t.ref).to eq nil 60 | expect(lambda { t.to_h }).to raise_error(RuntimeError) 61 | end 62 | 63 | it 'indexes tables' do 64 | 65 | t = @s.eval("return { a = 'A' }") 66 | 67 | expect(t['a']).to eq 'A' 68 | expect(t['b']).to eq nil 69 | end 70 | 71 | it 'iterates over Lua tables' do 72 | 73 | #t = @s.eval("return { a = 'A', b = 'B', c = 3, d = 3.1 }") 74 | t = @s.eval("return { a = 'A', b = 'B', c = 3 }") 75 | 76 | expect(t.values.sort_by { |v| v.to_s }).to eq [ 3.0, 'A', 'B' ] 77 | expect(t.keys.sort).to eq [ 'a', 'b', 'c' ] 78 | end 79 | 80 | it 'provides keys and values for tables' do 81 | 82 | t = @s.eval("return { a = 'A', b = 'B', c = 3 }") 83 | 84 | expect(t.collect { |k, v| v }.size).to eq 3 85 | end 86 | 87 | it 'provides the size of a table' do 88 | 89 | expect(@s.eval("return { a = 'A', b = 'B', c = 3 }").objlen).to eq 0.0 90 | expect(@s.eval("return { 1, 2 }").objlen).to eq 2 91 | 92 | expect(@s.eval("return { a = 'A', b = 'B', c = 3 }").size).to eq 3 93 | expect(@s.eval("return { a = 'A', b = 'B', c = 3 }").length).to eq 3 94 | expect(@s.eval("return { 1, 2 }").size).to eq 2 95 | expect(@s.eval("return { 1, 2 }").length).to eq 2 96 | end 97 | 98 | it 'lets setting values in Lua tables from Ruby' do 99 | 100 | t = @s.eval("return { a = 'A', b = 'B', c = 3 }") 101 | t['b'] = 4 102 | 103 | expect(t['b']).to eq 4.0 104 | end 105 | 106 | it 'indexes tables properly' do 107 | 108 | @s.eval("t = { 'a', 'b', 'c' }") 109 | 110 | expect(@s.eval("return t[0]")).to eq nil 111 | expect(@s.eval("return t[1]")).to eq 'a' 112 | expect(@s.eval("return t[3]")).to eq 'c' 113 | expect(@s.eval("return t[4]")).to eq nil 114 | end 115 | 116 | it 'replies to to_a(false) (pure = false)' do 117 | 118 | expect(@s.eval( 119 | "return { a = 'A', b = 'B', c = 3 }" 120 | ).to_a(false).sort).to eq( 121 | [ [ "a", "A" ], [ "b", "B" ], [ "c", 3.0 ] ] 122 | ) 123 | expect(@s.eval( 124 | "return { 1, 2 }" 125 | ).to_a(false)).to eq( 126 | [ 1.0, 2.0 ] 127 | ) 128 | expect(@s.eval( 129 | "return {}" 130 | ).to_a(false)).to eq( 131 | [] 132 | ) 133 | expect(@s.eval( 134 | "return { 1, 2, car = 'benz' }" 135 | ).to_a(false)).to eq( 136 | [ 1.0, 2.0, ["car", "benz"] ] 137 | ) 138 | end 139 | 140 | it 'tries hard to honour #to_ruby' do 141 | 142 | expect(@s.eval( 143 | "return { a = 'A', b = 'B', c = 3 }" 144 | ).to_ruby).to eq( 145 | { "a" => "A", "b" => "B", "c" => 3.0 } 146 | ) 147 | expect(@s.eval( 148 | "return { 1, 2 }" 149 | ).to_ruby).to eq( 150 | [ 1.0, 2.0 ] 151 | ) 152 | expect(@s.eval( 153 | "return {}" 154 | ).to_ruby).to eq( 155 | [] 156 | ) 157 | expect(@s.eval( 158 | "return { 1, 2, car = 'benz' }" 159 | ).to_ruby).to eq( 160 | { 1.0 => 1.0, "car" => "benz", 2.0 => 2.0 } 161 | ) 162 | end 163 | end 164 | end 165 | 166 | -------------------------------------------------------------------------------- /spec/utils_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Specifying rufus-lua 4 | # 5 | # Sat Mar 14 11:39:15 JST 2009 6 | # 7 | 8 | require 'spec_base' 9 | 10 | 11 | describe Rufus::Lua do 12 | 13 | context '(utils)' do 14 | 15 | it 'turns Ruby arrays into Lua string representations' do 16 | 17 | expect(Rufus::Lua.to_lua_s( 18 | %w{ a b c } 19 | )).to eq( 20 | '{ "a", "b", "c" }' 21 | ) 22 | 23 | # TODO : ["a"] is probably better... 24 | end 25 | 26 | it 'turns Ruby hashes into Lua string representations' do 27 | 28 | expect(Rufus::Lua.to_lua_s( 29 | { 'a' => 'A', 'b' => 2} 30 | )).to eq( 31 | '{ ["a"] = "A", ["b"] = 2 }' 32 | ) 33 | end 34 | 35 | it 'turns Ruby NilClass into Lua nil representation' do 36 | 37 | expect(Rufus::Lua.to_lua_s( 38 | {'a' => nil} 39 | )).to eq( 40 | '{ ["a"] = nil }' 41 | ) 42 | end 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /test/bm0.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # benchmarking rufus-lua 4 | # 5 | # Thu Mar 12 15:40:26 JST 2009 6 | # 7 | 8 | $:.unshift('lib') 9 | 10 | require 'benchmark' 11 | 12 | require 'rubygems' 13 | require 'rufus/lua' 14 | 15 | 16 | Benchmark.benchmark(' ' * 31 + Benchmark::Tms::CAPTION, 31) do |b| 17 | 18 | b.report('ruby') do 19 | 1_000_000.times { |i| i } 20 | end 21 | 22 | s = Rufus::Lua::State.new 23 | b.report('lua') do 24 | s.eval('for i = 1, 1000000 do end') 25 | end 26 | s.close 27 | end 28 | 29 | # jmettraux@sanma ~/rufus/rufus-lua (master) $ ruby test/bm0.rb 30 | # user system total real 31 | # ruby 0.220000 0.000000 0.220000 ( 0.217909) 32 | # lua 0.010000 0.000000 0.010000 ( 0.013667) 33 | # jmettraux@sanma ~/rufus/rufus-lua (master) $ ruby19 test/bm0.rb 34 | # user system total real 35 | # ruby 0.120000 0.010000 0.130000 ( 0.123396) 36 | # lua 0.010000 0.000000 0.010000 ( 0.013869) 37 | # jmettraux@sanma ~/rufus/rufus-lua (master) $ ruby19 test/bm0.rb 38 | # user system total real 39 | # ruby 0.110000 0.000000 0.110000 ( 0.125229) 40 | # lua 0.020000 0.000000 0.020000 ( 0.012828) 41 | 42 | -------------------------------------------------------------------------------- /test/bm_fiber.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # benchmarking rufus-lua 4 | # 5 | # Thu Mar 12 15:40:26 JST 2009 6 | # 7 | 8 | $:.unshift('lib') 9 | 10 | require 'benchmark' 11 | 12 | require 'rubygems' 13 | require 'rufus/lua' 14 | 15 | RUBYFIBS = Fiber.new do 16 | n1 = n2 = 1 17 | loop do 18 | Fiber.yield n1 19 | n1, n2 = n2, n1+n2 20 | end 21 | end 22 | #20.times { print RUBYFIBS.resume, ' ' } 23 | 24 | s = %{ 25 | co = coroutine.create(function () 26 | local n1 = 1; local n2 = 1 27 | while true do 28 | coroutine.yield(n1) 29 | n1, n2 = n2, n1+n2 30 | end 31 | end) 32 | return co 33 | } 34 | 35 | LUA = Rufus::Lua::State.new 36 | LUAFIBS = LUA.eval(s) 37 | #20.times { print LUAFIBS.resume, ' ' } 38 | 39 | N = 10_000 40 | Benchmark.benchmark(' ' * 31 + Benchmark::Tms::CAPTION, 31) do |b| 41 | b.report('ruby') do 42 | N.times { RUBYFIBS.resume } 43 | end 44 | b.report('lua via ruby') do 45 | N.times { LUAFIBS.resume } 46 | end 47 | b.report('lua') do 48 | LUA.eval("for i = 0, #{N} do coroutine.resume(co) end") 49 | end 50 | end 51 | 52 | LUA.close 53 | 54 | 55 | # $ ruby19 test/bm_fiber.rb 56 | # user system total real 57 | # ruby 0.050000 0.010000 0.060000 ( 0.054605) 58 | # lua via ruby 0.180000 0.000000 0.180000 ( 0.189010) 59 | # lua 0.010000 0.000000 0.010000 ( 0.005543) 60 | # 61 | # $ ruby19 test/bm_fiber.rb 62 | # user system total real 63 | # ruby 0.050000 0.000000 0.050000 ( 0.051531) 64 | # lua via ruby 0.180000 0.010000 0.190000 ( 0.194944) 65 | # lua 0.010000 0.000000 0.010000 ( 0.006325) 66 | # 67 | # $ ruby19 test/bm_fiber.rb 68 | # user system total real 69 | # ruby 0.050000 0.010000 0.060000 ( 0.052032) 70 | # lua via ruby 0.180000 0.000000 0.180000 ( 0.195411) 71 | # lua 0.010000 0.000000 0.010000 ( 0.006394) 72 | # 73 | # $ ruby19 test/bm_fiber.rb 74 | # user system total real 75 | # ruby 0.050000 0.010000 0.060000 ( 0.054892) 76 | # lua via ruby 0.180000 0.000000 0.180000 ( 0.267880) 77 | # lua 0.000000 0.000000 0.000000 ( 0.005865) 78 | 79 | -------------------------------------------------------------------------------- /test/bm_gc_stop.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # benchmarking rufus-lua 4 | # 5 | # Tue Mar 17 15:02:27 JST 2009 6 | # 7 | 8 | $:.unshift('lib') 9 | 10 | require 'benchmark' 11 | 12 | require 'rubygems' 13 | require 'rufus/lua' 14 | 15 | code = %{ 16 | for i = 1, 10000 do 17 | local a = {} 18 | for j = 1, 2000 do 19 | a[#a] = j 20 | end 21 | end 22 | } 23 | 24 | Benchmark.benchmark(' ' * 31 + Benchmark::Tms::CAPTION, 31) do |b| 25 | 26 | l = Rufus::Lua::State.new 27 | 28 | b.report('lua (GC on)') do 29 | l.eval(code) 30 | end 31 | 32 | l.close 33 | 34 | l = Rufus::Lua::State.new 35 | l.gc_stop 36 | 37 | b.report('lua (GC off)') do 38 | l.eval(code) 39 | end 40 | 41 | l.close 42 | end 43 | 44 | -------------------------------------------------------------------------------- /test/gc0.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Test garbage collection API from rufus-lua 3 | # 4 | 5 | $:.unshift('lib') 6 | 7 | require 'rubygems' 8 | require 'rufus/lua' 9 | 10 | 11 | puts "Creating a new state..." 12 | s = Rufus::Lua::State.new 13 | puts " #{s.gc_count} KB in use by Lua interpreter" 14 | puts " Calling into Lua..." 15 | puts s.eval("return table.concat({ ' hello', 'from', 'Lua' }, ' ')") 16 | puts " #{s.gc_count} KB in use by Lua interpreter" 17 | puts "Performing forced garbage collection..." 18 | s.gc_collect! 19 | puts " #{s.gc_count} KB in use by Lua interpreter" 20 | -------------------------------------------------------------------------------- /test/t.rb: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # sandbox experiment file with rufus-lua 4 | # 5 | # Thu Mar 12 15:54:30 JST 2009 6 | # 7 | 8 | $:.unshift('lib') 9 | 10 | require 'rubygems' 11 | require 'rufus/lua' 12 | 13 | 14 | s = Rufus::Lua::State.new 15 | 16 | puts s.eval("return table.concat({ 'hello', 'from', 'Lua' }, ' ')") 17 | 18 | s.close 19 | 20 | --------------------------------------------------------------------------------