class TransparentLua

Constants

SUPPORTED_SIMPLE_DATATYPES
VERSION

Attributes

logger[R]
sandbox[R]
state[R]

Public Class Methods

new(sandbox, options = {}) click to toggle source

@param [Object] sandbox The object which will be made visible to the lua script @param [Hash] options @option options [Lua::State] :state (Lua::State.new) a lua state to use @option options [Boolean] :leak_globals When true, all locals from the lua scope are set in the sandbox.

The sandbox must store the values itself or an error will be raised.
When false the locals are not reflected in the sandbox
# File lib/transparent_lua.rb, line 24
def initialize(sandbox, options = {})
  @sandbox                 = sandbox
  @state                   = options.fetch(:state) { Lua::State.new }
  @logger                  = options.fetch(:logger) { Logger.new('/dev/null') }
  leak_locals              = options.fetch(:leak_globals) { false }
  setup(leak_locals)
end

Public Instance Methods

call(script, script_name = nil) click to toggle source

@param [String] script a lua script @param [String] script_name the name of the lua script (see Lua::State.__eval) @return [Object] the return value of the lua script

# File lib/transparent_lua.rb, line 35
def call(script, script_name = nil)
  v = state.__eval(script, script_name)
  lua2rb(v)
end

Private Instance Methods

can_require_module?(modname) click to toggle source
# File lib/transparent_lua.rb, line 65
def can_require_module?(modname)
  return false unless sandbox.respond_to? :can_require_module?

  sandbox.can_require_module? modname
end
delegation_table(object = nil, hash) click to toggle source
# File lib/transparent_lua.rb, line 131
def delegation_table(object = nil, hash)
  tab                = Lua::Table.new(@state)
  tab.__rb_object_id = -> { object.__id__.to_f } if object
  tab.__metatable    = hash
  tab
end
get_method(object, method_name) click to toggle source
# File lib/transparent_lua.rb, line 111
def get_method(object, method_name)
  method_name = get_ruby_method_name(method_name)
  object.method(method_name.to_sym)
rescue NameError
  fail NoMethodError, "#{object}##{method_name.to_s} is not a method (but might be a valid message which is not supported)"
end
get_ruby_method_name(lua_method_name) click to toggle source

@param [Symbol] lua_method_name @return [Symbol] ruby method name

# File lib/transparent_lua.rb, line 162
def get_ruby_method_name(lua_method_name)
  lua_method_name = String(lua_method_name)
  case lua_method_name
  when /^is_(.*)$/, /^has_(.*)$/
    return :"#{$1}?"
  else
    return lua_method_name.to_sym
  end
end
getter_table(object) click to toggle source
# File lib/transparent_lua.rb, line 78
def getter_table(object)
  return object if is_supported_simple_datatype? object

  metatable = {
    '__index'    => index_table(object),
    '__newindex' => newindex_table(object),
  }
  delegation_table(object, metatable)
end
has_rb_object_id?(o) click to toggle source
# File lib/transparent_lua.rb, line 153
def has_rb_object_id?(o)
  o.__rb_object_id
  true
rescue NoMethodError
  false
end
index_table(object) click to toggle source
# File lib/transparent_lua.rb, line 94
def index_table(object)
  ->(t, k, *newindex_args) do
    method = get_method(object, k)
    k = get_ruby_method_name(k)
    logger.debug { "Dispatching method #{method}(#{method.parameters})" }

    case method
    when ->(m) { m.arity == 0 }
      logger.debug { "Creating a getter table for #{method}" }
      getter_table(object.public_send(k.to_sym, *newindex_args))
    else
      logger.debug { "Creating a method table for #{method}" }
      method_table(method)
    end
  end
end
is_supported_simple_datatype?(object) click to toggle source
# File lib/transparent_lua.rb, line 172
def is_supported_simple_datatype?(object)
  return true if SUPPORTED_SIMPLE_DATATYPES.include? object.class
  return true if Array === object && object.all? { |i| is_supported_simple_datatype?(i) }
  return true if Hash === object && object.keys.all? { |k| is_supported_simple_datatype?(k) } && object.values.all? { |v| is_supported_simple_datatype?(v) }
  false
end
lua2rb(v) click to toggle source
# File lib/transparent_lua.rb, line 138
def lua2rb(v)
  case v
  when ->(t) { has_rb_object_id?(t) }
    ObjectSpace._id2ref(Integer(v.__rb_object_id))
  when ->(t) { Lua::Table === t && t.to_hash.keys.all? { |k| k.is_a? Numeric } }
    v.to_hash.values.collect { |v| lua2rb(v) }
  when Lua::Table
    v.to_hash.each_with_object({}) { |(k, v), h| h[lua2rb(k)] = lua2rb(v) }
  when Float
    (Integer(v) == v) ? Integer(v) : v
  else
    v
  end
end
method_table(method) click to toggle source

@param [Method] method

# File lib/transparent_lua.rb, line 119
def method_table(method)
  delegation_table(
    '__call' => ->(t, *args) do
      converted_args = args.collect do |arg|
        lua2rb(arg)
      end

      getter_table(method.call(*converted_args))
    end
  )
end
newindex_table(object) click to toggle source
# File lib/transparent_lua.rb, line 88
def newindex_table(object)
  ->(t, k, v) do
    getter_table(object.public_send(:"#{k}=", lua2rb(v)))
  end
end
require_module(modname) click to toggle source
# File lib/transparent_lua.rb, line 71
def require_module(modname)
  fail NoMethodError,
       "#{sandbox} must respond to #require_module because it responds to #can_require_module?" unless sandbox.respond_to? :require_module

  String(sandbox.require_module(modname))
end
setup(leak_globals = false) click to toggle source
# File lib/transparent_lua.rb, line 41
def setup(leak_globals = false)
  state.__load_stdlib :all

  global_metatable               = {
    '__index' => index_table(sandbox)
  }
  global_metatable['__newindex'] = newindex_table(sandbox) if leak_globals

  state._G.__metatable = global_metatable

  state.package.loaders    = Lua::Table.new(state)
  state.package.loaders[1] = ->(modname) do
    return "\n\tno module '#{modname}' available in sandbox" unless can_require_module? modname

    loader = ->(modname) do
      source = require_module(modname)
      state.__eval(source, "=#{modname}")
    end

    state.package.loaded[modname] = loader
    loader
  end
end