Skip to content

Commit

Permalink
feat: wip binding
Browse files Browse the repository at this point in the history
  • Loading branch information
mdwagner committed Dec 12, 2023
1 parent b101cd7 commit db9c0e5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 115 deletions.
9 changes: 4 additions & 5 deletions spec/lua_state_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
37 changes: 15 additions & 22 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions src/luajit.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
173 changes: 87 additions & 86 deletions src/luajit/lua_binding.cr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit db9c0e5

Please sign in to comment.