Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SetMetaTableForGoStruct #79

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 122 additions & 29 deletions lua/c-golua.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,60 @@
static const char GoStateRegistryKey = 'k'; //golua registry key
static const char PanicFIDRegistryKey = 'k';

int callback_function(lua_State* L);
int interface_index_callback(lua_State *L);
int interface_newindex_callback(lua_State *L);
int gchook_wrapper(lua_State* L);

typedef struct _chunk {
int size; // chunk size
char *buffer; // chunk data
char* toread; // chunk to read
} chunk;

/* taken from lua5.2 source */
void *testudata(lua_State *L, int ud, const char *tname)
unsigned int *testudata(lua_State *L, int n, const char * field, void * expected)
{
void *p = lua_touserdata(L, ud);
unsigned int *p = lua_touserdata(L, n);
if (p != NULL)
{ /* value is a userdata? */
if (lua_getmetatable(L, ud))
if (lua_getmetatable(L, n))
{ /* does it have a metatable? */
luaL_getmetatable(L, tname); /* get correct metatable */
if (!lua_rawequal(L, -1, -2)) /* not the same? */
p = NULL; /* value is a userdata with wrong metatable */
lua_pop(L, 2); /* remove both metatables */
lua_getfield(L, -1, field);
if (lua_tocfunction(L, -1) != expected)
p = NULL; /* wrong metatable */
lua_pop(L, 2); /* remove metatable and field values */
return p;
}
}
return NULL; /* value is not a userdata with a metatable */
}

int clua_isgofunction(lua_State *L, int n)
unsigned int *testgofunction(lua_State *L, int n)
{
return testudata(L, n, MT_GOFUNCTION) != NULL;
/* GoFunction should have __call callback */
return testudata(L, n, "__call", callback_function);
}

int clua_isgostruct(lua_State *L, int n)
unsigned int *testgostruct(lua_State *L, int n)
{
return testudata(L, n, MT_GOINTERFACE) != NULL;
/* GoInterface should have interface_index_callback */
return testudata(L, n, "__index", interface_index_callback);
}

unsigned int* clua_checkgosomething(lua_State* L, int index, const char *desired_metatable)
unsigned int *testgosomething(lua_State *L, int n)
{
if (desired_metatable != NULL)
{
return testudata(L, index, desired_metatable);
}
else
{
unsigned int *sid = testudata(L, index, MT_GOFUNCTION);
if (sid != NULL) return sid;
return testudata(L, index, MT_GOINTERFACE);
}
/* GoFunction and GoInterface should have gchook_wrapper callback */
return testudata(L, n, "__gc", gchook_wrapper);
}

int clua_isgofunction(lua_State *L, int n)
{
return testgofunction(L, n) != NULL;
}

int clua_isgostruct(lua_State *L, int n)
{
return testgostruct(L, n) != NULL;
}

size_t clua_getgostate(lua_State* L)
Expand All @@ -77,7 +85,7 @@ size_t clua_getgostate(lua_State* L)
int callback_function(lua_State* L)
{
int r;
unsigned int *fid = clua_checkgosomething(L, 1, MT_GOFUNCTION);
unsigned int *fid = testgofunction(L, 1);
size_t gostateindex = clua_getgostate(L);
//remove the go function from the stack (to present same behavior as lua_CFunctions)
lua_remove(L,1);
Expand All @@ -88,7 +96,7 @@ int callback_function(lua_State* L)
int gchook_wrapper(lua_State* L)
{
//printf("Garbage collection wrapper\n");
unsigned int* fid = clua_checkgosomething(L, -1, NULL);
unsigned int* fid = testgosomething(L, -1);
size_t gostateindex = clua_getgostate(L);
if (fid != NULL)
return golua_gchook(gostateindex,*fid);
Expand All @@ -97,13 +105,13 @@ int gchook_wrapper(lua_State* L)

unsigned int clua_togofunction(lua_State* L, int index)
{
unsigned int *r = clua_checkgosomething(L, index, MT_GOFUNCTION);
unsigned int *r = testgofunction(L, index);
return (r != NULL) ? *r : -1;
}

unsigned int clua_togostruct(lua_State *L, int index)
{
unsigned int *r = clua_checkgosomething(L, index, MT_GOINTERFACE);
unsigned int *r = testgostruct(L, index);
return (r != NULL) ? *r : -1;
}

Expand Down Expand Up @@ -204,7 +212,7 @@ int load_chunk(lua_State *L, char *b, int size, const char* chunk_name) {
/* called when lua code attempts to access a field of a published go object */
int interface_index_callback(lua_State *L)
{
unsigned int *iid = clua_checkgosomething(L, 1, MT_GOINTERFACE);
unsigned int *iid = testgostruct(L, 1);
if (iid == NULL)
{
lua_pushnil(L);
Expand All @@ -227,6 +235,31 @@ int interface_index_callback(lua_State *L)
lua_error(L);
return 0;
}
else if (r == 0) // no field
{
// If a custom metatable was set for this object with clua_gostructmetatable(),
// the original __index value should be available as upvalue[1].
switch (lua_type(L, lua_upvalueindex(1))) {
case LUA_TNIL: /* no user metatable with __index was set */
luaL_error(L, "No field: %s", field_name);
return 0;
case LUA_TFUNCTION:
FUNCTION:
lua_pushvalue(L, lua_upvalueindex(1)); // put __index onto the stack
lua_insert(L, 1); // move the function value to the beginning of the stack
lua_call(L, 2, 1); // Call __index(obj, field_name)
return 1;
case LUA_TUSERDATA:
if (clua_isgofunction(L, lua_upvalueindex(1))) {
goto FUNCTION;
}
/* fallthrough */
default:
lua_pushvalue(L, lua_upvalueindex(1)); // put __index onto the stack
lua_getfield(L, -1, field_name); // __index[field_name]
return 1;
}
}
else
{
return r;
Expand All @@ -236,7 +269,7 @@ int interface_index_callback(lua_State *L)
/* called when lua code attempts to set a field of a published go object */
int interface_newindex_callback(lua_State *L)
{
unsigned int *iid = clua_checkgosomething(L, 1, MT_GOINTERFACE);
unsigned int *iid = testgostruct(L, 1);
if (iid == NULL)
{
lua_pushnil(L);
Expand All @@ -259,6 +292,33 @@ int interface_newindex_callback(lua_State *L)
lua_error(L);
return 0;
}
else if (r == 0) // no field
{
// If a custom metatable was set for this object with clua_gostructmetatable(),
// the original __newindex value should be available as upvalue[1].
switch (lua_type(L, lua_upvalueindex(1))) {
case LUA_TNIL: /* no user metatable with __newindex was set */
luaL_error(L, "Wrong assignment to field %s", field_name);
return 0;
case LUA_TFUNCTION:
FUNCTION:
lua_pushvalue(L, lua_upvalueindex(1)); // put __newindex function onto the stack
lua_insert(L, 1); // move the function value to the beginning of the stack
lua_call(L, 3, 1); // Call __newindex(obj, field_name, value)
return 1;
case LUA_TUSERDATA:
if (clua_isgofunction(L, lua_upvalueindex(1))) {
goto FUNCTION;
}
/* fallthrough */
default:
lua_pushvalue(L, lua_upvalueindex(1)); // put __newindex onto the stack
lua_pushvalue(L, 3); // push the value
lua_setfield(L, -2, field_name); // __newindex[field_name] = value, pops the value
lua_pop(L, 1); // pop __newindex
return 1;
}
}
else
{
return r;
Expand Down Expand Up @@ -448,4 +508,37 @@ void clua_setexecutionlimit(lua_State* L, int n)
lua_sethook(L, &clua_hook_function, LUA_MASKCOUNT, n);
}

// Modifies the table at the top of the stack to use it as a metatable for GoStruct object
void clua_gostructmetatable(lua_State* L)
{
// gointerface_metatable[__gc] = &gchook_wrapper
lua_pushliteral(L, "__gc");
lua_pushcfunction(L, &gchook_wrapper);
lua_settable(L, -3);

// gointerface_metatable[__index] = &interface_index_callback
lua_pushliteral(L, "__index");
// if replacing an __index field, store the original one as an upvalue
lua_getfield(L, -2, "__index");
if (!lua_isnil(L, -1)) {
// store the original __index as an upvalue
lua_pushcclosure(L, &interface_index_callback, 1);
} else {
lua_pop(L, 1); // pop nil
lua_pushcfunction(L, &interface_index_callback);
}
lua_settable(L, -3);

// gointerface_metatable[__newindex] = &interface_newindex_callback
lua_pushliteral(L, "__newindex");
// if replacing a __newindex field, store the original one as an upvalue
lua_getfield(L, -2, "__newindex");
if (!lua_isnil(L, -1)) {
// store the original __newindex as an upvalue
lua_pushcclosure(L, &interface_newindex_callback, 1);
} else {
lua_pop(L, 1); // pop nil
lua_pushcfunction(L, &interface_newindex_callback);
}
lua_settable(L, -3);
}
8 changes: 8 additions & 0 deletions lua/golua.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func golua_interface_newindex_callback(gostateindex uintptr, iid uint, field_nam

fval := ifacevalue.FieldByName(field_name)

if !fval.IsValid() { // no field with this name
return 0
}

if fval.Kind() == reflect.Ptr {
fval = fval.Elem()
}
Expand Down Expand Up @@ -178,6 +182,10 @@ func golua_interface_index_callback(gostateindex uintptr, iid uint, field_name *

fval := ifacevalue.FieldByName(C.GoString(field_name))

if !fval.IsValid() { // no field with this name
return 0
}

if fval.Kind() == reflect.Ptr {
fval = fval.Elem()
}
Expand Down
1 change: 1 addition & 0 deletions lua/golua.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ void clua_setexecutionlimit(lua_State* L, int n);

int clua_isgofunction(lua_State *L, int n);
int clua_isgostruct(lua_State *L, int n);
void clua_gostructmetatable(lua_State *L);

12 changes: 12 additions & 0 deletions lua/lua.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,18 @@ func (L *State) SetMetaTable(index int) {
C.lua_setmetatable(L.s, C.int(index))
}

// Pops a table from the stack and sets it as the new metatable for the GoStruct at the given index.
// Note that you should never apply SetMetaTable() to a GoStruct, because GoStruct internally has a special metatable to operate.
// Use SetMetaTableForGoStruct to set a metatable for a GoStruct object.
// This function modifies the given metatable by wrapping __index and __newindex values internally.
func (L *State) SetMetaTableForGoStruct(index int) {
if (L.IsGoStruct(index)) {
C.clua_gostructmetatable(L.s)
}

L.SetMetaTable(index)
}

// lua_settable
func (L *State) SetTable(index int) {
C.lua_settable(L.s, C.int(index))
Expand Down
69 changes: 69 additions & 0 deletions lua/lua_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ type TestStruct struct {
FloatField float64
}

func (ts *TestStruct) TestMethod(s string) string {
return ts.StringField + s
}

func TestGoStruct(t *testing.T) {
L := NewState()
L.OpenLibs()
Expand All @@ -27,6 +31,9 @@ func TestGoStruct(t *testing.T) {
if !L.IsGoStruct(-1) {
t.Fatal("Not go struct")
}
if L.IsGoFunction(-1) {
t.Fatal("Should not be go function")
}

tsr := L.ToGoStruct(-1).(*TestStruct)
if tsr != ts {
Expand All @@ -43,6 +50,62 @@ func TestGoStruct(t *testing.T) {
L.Pop(1)
}

func TestGoStructMetatable(t *testing.T) {
L := NewState()
L.OpenLibs()
defer L.Close()

ts := &TestStruct{10, "test", 2.3}

L.PushGoStruct(ts)

TestMethodWrapper := func(L *State) int {
obj := L.ToGoStruct(1)
s := L.ToString(2)
result := obj.(*TestStruct).TestMethod(s)
L.PushString(result)
return 1
}

L.NewTable() // metatable
L.PushGoFunction(TestMethodWrapper)
L.SetField(-2, "TestMethod")

L.PushValue(-1) // Copy table onto the stack
L.SetField(-2, "__index") // Set table.__index = table (common technique for class implementation)

NewIndex := func(L *State) int {
obj := L.ToGoStruct(1)
key := L.ToString(2)
if L.IsNumber(3) {
obj.(*TestStruct).StringField = key // just for test
obj.(*TestStruct).FloatField = L.ToNumber(3)
}
return 0
}

L.PushGoFunction(NewIndex)
L.SetField(-2, "__newindex") // Set table.__newindex = NewIndex

L.SetMetaTableForGoStruct(-2) // set as metatable for ts

L.SetGlobal("t")

// Check method call
err := L.DoString(`res = t:TestMethod("ok"); assert(res == "testok", res)`)
if err != nil {
t.Fatalf("DoString did return an error: %v\n", err.Error())
}

// Check __newindex
err = L.DoString(`t["foo"] = 4.2; assert(t.StringField == "foo", t.StringField); assert(t.FloatField == 4.2, tostring(t.FloatField))`)
if err != nil {
t.Fatalf("DoString did return an error: %v\n", err.Error())
}

L.Pop(1)
}

func TestCheckStringSuccess(t *testing.T) {
L := NewState()
L.OpenLibs()
Expand Down Expand Up @@ -126,6 +189,12 @@ func TestCall(t *testing.T) {

L.PushString("Dummy")
L.GetGlobal("test")
if !L.IsGoFunction(-1) {
t.Fatal("not go function")
}
if L.IsGoStruct(-1) {
t.Fatal("Should not be go struct")
}
L.PushString("Argument1")
L.PushString("Argument2")
L.PushString("Argument3")
Expand Down