diff --git a/spec/lua_state_spec.cr b/spec/lua_state_spec.cr index 1db840e..9fc1e12 100644 --- a/spec/lua_state_spec.cr +++ b/spec/lua_state_spec.cr @@ -245,13 +245,12 @@ describe Luajit::LuaState do end end - describe "Macros" do + describe "LuaBinding" do it "works" do Luajit.run do |state| - Luajit.generate_lua_binding(state, SpecHelper::Sprite) + Luajit::LuaBinding.generate_lua_binding(state, SpecHelper::Sprite) state.execute(<<-'LUA').ok?.should be_true - local Sprite = _G["SpecHelper::Sprite"] local sprite = Sprite.new() assert(sprite:x() == 1000) LUA @@ -260,11 +259,11 @@ describe Luajit::LuaState do it "works with different global" do Luajit.run do |state| - Luajit.generate_lua_binding(state, SpecHelper::Sprite2) + Luajit::LuaBinding.generate_lua_binding(state, SpecHelper::Sprite2) state.execute(<<-'LUA').ok?.should be_true local sprite = Sprite.new() - assert(sprite:x() == 1000) + assert(sprite:x() == 5000) LUA end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index fc9da8c..ef29882 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -9,50 +9,43 @@ module SpecHelper class Sprite include Luajit::LuaBinding - property x : Int32 - - @[LuaClass(name: "new")] - def self._new(state : LuaState) : Int32 + def_class_method "new" do |state| _self = new(1000) - box = Box(Sprite).box(_self) - state.track(box) - state.create_userdata(box, Luajit.metatable(SpecHelper::Sprite)) + Luajit::LuaBinding.setup_userdata(state, _self, self) 1 end - @[LuaInstance(name: "x")] - def self._x(state : LuaState) : Int32 - ud_ptr = state.get_userdata(1, Luajit.metatable(SpecHelper::Sprite)) - _self = Box(Sprite).unbox(ud_ptr) + def_instance_method "x" do |state| + _self = Luajit::LuaBinding.userdata_value(state, self) state.push(_self.x) 1 end + property x : Int32 + def initialize(@x) end end - @[Luajit::Config(global: "Sprite")] class Sprite2 include Luajit::LuaBinding - property x : Int32 + global_name "Sprite" - def_lua self.new do - _self = new(1000) - box = Box(Sprite2).box(_self) - __state.track(box) - __state.create_userdata(box, Luajit.metatable(SpecHelper::Sprite2)) + def_class_method "new" do |state| + _self = new(5000) + Luajit::LuaBinding.setup_userdata(state, _self, self) 1 end - def_lua x do - ud_ptr = __state.get_userdata(1, Luajit.metatable(SpecHelper::Sprite2)) - _self = Box(Sprite2).unbox(ud_ptr) - __state.push(_self.x) + def_instance_method "x" do |state| + _self = Luajit::LuaBinding.userdata_value(state, self) + state.push(_self.x) 1 end + property x : Int32 + def initialize(@x) end end diff --git a/src/luajit.cr b/src/luajit.cr index 74f0063..c9f5f95 100644 --- a/src/luajit.cr +++ b/src/luajit.cr @@ -6,8 +6,6 @@ require "./luajit/lua_any" require "./luajit/*" module Luajit - alias Config = LuaBinding::LuaConfig - # Same as `LuaState.create` def self.new : LuaState LuaState.create diff --git a/src/luajit/lua_binding.cr b/src/luajit/lua_binding.cr index bdb8f42..b9ca870 100644 --- a/src/luajit/lua_binding.cr +++ b/src/luajit/lua_binding.cr @@ -1,109 +1,110 @@ module Luajit module LuaBinding - alias LuaState = Luajit::LuaState + macro included + {% t = @type.stringify %} + {% if t.includes?("::") %} + @@__global = {{t.split("::")[-1]}} + @@__metatable = {{t.split("::")[-1]}} + {% else %} + @@__global = {{t}} + @@__metatable = {{t}} + {% end %} + @@__class_methods = {} of String => Luajit::LuaState::Function + @@__instance_methods = {} of String => Luajit::LuaState::Function - annotation LuaConfig - end + def self.global_name : String + @@__global + end - annotation LuaClass - end + def self.global_name(name : String) : Nil + @@__global = name + end - annotation LuaInstance - end + def self.metatable_name : String + @@__metatable + end - # Defines a lua method binding (short-hand) - # - # Same type signature as `LuaState::Function`, but `LuaState` is accessed - # via `__state` - # - # *method_name* starts with "self.": class method - # - # *method_name*: instance method - macro def_lua(method_name, &) - {% names = method_name.stringify.split(".") %} - {% if names[0] == "self" %} - @[Luajit::LuaBinding::LuaClass(name: {{names[1]}})] - {% else %} - @[Luajit::LuaBinding::LuaInstance(name: {{method_name.stringify}})] - {% end %} - def self.%method(__state : Luajit::LuaState) : Int32 - {{yield}} + def self.metatable_name(name : String) : Nil + @@__metatable = name end - end - end - macro global(raw_type) - {% type = raw_type.resolve %} - {% anno = type.annotation(Luajit::LuaBinding::LuaConfig) %} - {% if anno && anno[:global] %} - {{anno[:global]}} - {% else %} - {{type.stringify}} - {% end %} - end + # :nodoc: + def self.__class_methods : Hash(String, Luajit::LuaState::Function) + @@__class_methods + end - macro metatable(raw_type) - {% type = raw_type.resolve %} - {% anno = type.annotation(Luajit::LuaBinding::LuaConfig) %} - {% if anno && anno[:metatable] %} - {{anno[:metatable]}} - {% elsif anno && anno[:global] %} - {{anno[:global]}} - {% else %} - {{type.stringify}} - {% end %} - end + def self.def_class_method(name : String, &block : Luajit::LuaState::Function) + @@__class_methods[name] = block + end + + # :nodoc: + def self.__instance_methods : Hash(String, Luajit::LuaState::Function) + @@__instance_methods + end - # :nodoc: - macro bind_class_method(lua, raw_type, name) - {% type = raw_type.resolve %} - {{lua}}.push_fn_closure do |%state| - {{type}}.{{name.id}}(%state) + def self.def_instance_method(name : String, &block : Luajit::LuaState::Function) + @@__instance_methods[name] = block + end end - end - # :nodoc: - macro bind_instance_method(lua, raw_type, anno_name, name) - {% type = raw_type.resolve %} - {{lua}}.push_fn_closure do |%state| - {% if type.class? %} - {% if anno_name == "__gc" %} - %state.untrack(%state.get_userdata(-1, Luajit.metatable({{type}}))) - {% end %} + # Generates bindings for *type* + def self.generate_lua_binding(state : LuaState, type : T.class) : Nil forall T + {% unless T <= Luajit::LuaBinding %} + {% raise "'type' argument must include Luajit::LuaBinding" %} {% end %} - {{type}}.{{name.id}}(%state) - end - end - macro generate_lua_binding(lua, raw_type) - {% type = raw_type.resolve %} + state.new_table + class_index = state.size + state.push_value(class_index) + state.set_global(type.global_name) - {{lua}}.new_table - %class_index = {{lua}}.size - {{lua}}.push_value(%class_index) - {{lua}}.set_global(Luajit.global({{type}})) + state.new_metatable(type.metatable_name) + instance_index = state.size - {{lua}}.new_metatable(Luajit.metatable({{type}})) - %instance_index = {{lua}}.size + type.__class_methods.each do |method_name, func| + state.push(method_name) + state.push(func) + state.set_table(class_index) + end - {% class_type = type.class %} - {% for m in class_type.methods %} - {% anno = m.annotation(Luajit::LuaBinding::LuaClass) %} - {% if anno && anno[:name] %} - {{lua}}.push({{anno[:name]}}) - Luajit.bind_class_method({{lua}}, {{type}}, {{m.name}}) - {{lua}}.set_table(%class_index) + type.__instance_methods.each do |method_name, func| + state.push(method_name) + case method_name + when "__gc" + state.push_fn_closure do |_s| + _s.untrack(_s.get_userdata(-1, type.metatable_name)) + func.call(_s) + end + else + state.push(func) + end + state.set_table(instance_index) + end + + state.push_value(instance_index) + state.set_field(instance_index, "__index") + end + + # Converts *value* into full userdata with metatable *type* and returns + # the userdata pointer + def self.setup_userdata(state : LuaState, value : T, type : U.class) : Pointer(UInt64) forall T, U + {% unless U <= Luajit::LuaBinding %} + {% raise "'type' argument must include Luajit::LuaBinding" %} {% end %} - {% anno = m.annotation(Luajit::LuaBinding::LuaInstance) %} - {% if anno && anno[:name] %} - {{lua}}.push({{anno[:name]}}) - Luajit.bind_instance_method({{lua}}, {{type}}, {{anno[:name]}}, {{m.name}}) - {{lua}}.set_table(%instance_index) + box = Box(T).box(value) + state.track(box) + state.create_userdata(box, type.metatable_name) + end + + # Gets value of userdata of *type* + def self.userdata_value(state : LuaState, type : T.class) : T forall T + {% unless T <= Luajit::LuaBinding %} + {% raise "'type' argument must include Luajit::LuaBinding" %} {% end %} - {% end %} - {{lua}}.push_value(%instance_index) - {{lua}}.set_field(%instance_index, "__index") + ud_ptr = state.get_userdata(1, type.metatable_name) + Box(T).unbox(ud_ptr) + end end end