Skip to content

Commit

Permalink
feat: Add PushGoClosureWithUpvalues
Browse files Browse the repository at this point in the history
Also add GetUpvalue, SetUpvalue and UpvalueIndex
  • Loading branch information
TimVosch authored and aarzilli committed Dec 27, 2024
1 parent 8c04b0e commit 7609e5d
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 7 deletions.
4 changes: 2 additions & 2 deletions lua/c-golua.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ static int callback_c (lua_State* L)
return golua_callgofunction(gostateindex,fid);
}

void clua_pushcallback(lua_State* L)
void clua_pushcallback(lua_State* L, unsigned int nup)
{
lua_pushcclosure(L,callback_c,1);
lua_pushcclosure(L,callback_c, 1 + nup);
}

void clua_pushgostruct(lua_State* L, unsigned int iid)
Expand Down
2 changes: 1 addition & 1 deletion lua/golua.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ void clua_hide_pcall(lua_State *L);

unsigned int clua_togofunction(lua_State* L, int index);
unsigned int clua_togostruct(lua_State *L, int index);
void clua_pushcallback(lua_State* L);
void clua_pushcallback(lua_State* L, unsigned int nup);
void clua_pushgofunction(lua_State* L, unsigned int fid);
void clua_pushgostruct(lua_State *L, unsigned int fid);
void clua_setgostate(lua_State* L, size_t gostateindex);
Expand Down
38 changes: 34 additions & 4 deletions lua/lua.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ package lua
#include "golua.h"
int clua_upvalueindex(int n)
{
return lua_upvalueindex(n);
}
*/
import "C"

Expand Down Expand Up @@ -137,8 +142,33 @@ func (L *State) PushGoFunction(f LuaGoFunction) {
// this permits the go function to reflect lua type 'function' when checking with type()
// this implements behaviour akin to lua_pushcfunction() in lua C API.
func (L *State) PushGoClosure(f LuaGoFunction) {
L.PushGoFunction(f) // leaves Go function userdata on stack
C.clua_pushcallback(L.s) // wraps the userdata object with a closure making it into a function
L.PushGoFunction(f) // leaves Go function userdata on stack
C.clua_pushcallback(L.s, 0) // wraps the userdata object with a closure making it into a function
}

// PushGoClosureWithUpvalues pushes a GoClosure and provides 'nup' upvalues starting at index 2,
// because index 1 is to store the GoFunction in the Lua Closure
func (L *State) PushGoClosureWithUpvalues(f LuaGoFunction, nup uint) {
L.PushGoFunction(f) // leaves Go function userdata on stack
if nup > 0 { // GoFunction must be at upvalue 1 so push it back
L.Insert(-int(nup) - 1)
}
C.clua_pushcallback(L.s, C.uint(nup)) // wraps the userdata object with a closure making it into a function
}

// lua_upvalueid
func (L *State) UpvalueIndex(n int) int {
return int(C.clua_upvalueindex(C.int32_t(n)))
}

// lua_setupvalue
func (L *State) SetUpvalue(funcindex, n int) bool {
return C.lua_setupvalue(L.s, C.int(funcindex), C.int(n)) != nil
}

// lua_getupvalue
func (L *State) GetUpvalue(funcindex, n int) {
C.lua_getupvalue(L.s, C.int(funcindex), C.int(n))
}

// Sets a metamethod to execute a go function
Expand All @@ -156,8 +186,8 @@ func (L *State) PushGoClosure(f LuaGoFunction) {
//
// except this wouldn't work because pushing a go function results in user data not a cfunction
func (L *State) SetMetaMethod(methodName string, f LuaGoFunction) {
L.PushGoFunction(f) // leaves Go function userdata on stack
C.clua_pushcallback(L.s) // wraps the userdata object with a closure making it into a function
L.PushGoFunction(f) // leaves Go function userdata on stack
C.clua_pushcallback(L.s, 0) // wraps the userdata object with a closure making it into a function
L.SetField(-2, methodName)
}

Expand Down
72 changes: 72 additions & 0 deletions lua/lua_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,75 @@ func TestCustomDebugHook(t *testing.T) {
}
}
}

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

L.PushGoFunction(func(L *State) int { return 0 })
L.PushString("Hello")
if L.SetUpvalue(-2, 1) == true { // Should not be succesful
t.Fatal("SetUpvalue should return false because an upvalue can't be set on userdata")
}
if L.GetTop() != 2 {
t.Fatal("Expected SetUpvalue to pop nothing on error")
}
}

// This test is more an example to show why we can't directly set Upvalue on PushGoClosure, but rather
// have to rely on PushGoClosureWithUpvalues
func TestSetUpvalueOnGoClosureShouldErrorAt2(t *testing.T) {
L := NewState()
defer L.Close()

L.PushGoClosure(func(L *State) int { return 0 })
L.PushString("Hello")
// Should be succesful because the function returned by PushGoClosure actually has an upvalue
// at 1, which is the GoFunction userdata, that the closure calls.
if !L.SetUpvalue(-2, 1) {
t.Fatal("SetUpvalue should return false because an upvalue can't be set on userdata")
}
// Only the GoClosure should be left on the stack, since SetUpvalue was succesful
if L.GetTop() != 1 {
t.Fatalf("Expected GetTop to return 1 as SetUpvalue should pop value on success, but top is at: %d\n", L.GetTop())
}

L.PushString("Hello")
// Since this closure only has 1 upvalue. It shouldn't be possible to add a second after the fact
if L.SetUpvalue(-2, 2) {
t.Fatal("SetUpvalue should return false because setting an upvalue at a higher index after creation is not possible")
}
// Since SetUpvalue failed, the string should remain
if L.GetTop() != 2 {
t.Fatal("Expected SetUpvalue to pop nothing on error")
}
}

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

closure := func(L *State) int {
if !L.IsString(L.UpvalueIndex(2)) {
t.Fatalf("upvalue 2 not set correctly, expected it to be a string")
}
if !L.IsNumber(L.UpvalueIndex(3)) {
t.Fatalf("upvalue 3 not set correctly, expected it to be an number")
}
if L.ToString(L.UpvalueIndex(2)) != "Hello" {
t.Fatalf("upvalue 2 not set correctly, expected it to equal \"Hello\"")
}
if L.ToInteger(L.UpvalueIndex(3)) != 15 {
t.Fatalf("upvalue 3 not set correctly, expected it to equal 15")
}
return 0
}

L.PushString("Hello")
L.PushInteger(15)
L.PushGoClosureWithUpvalues(closure, 2)
err := L.Call(0, 0)
if err != nil {
t.Fatalf("Call to function returned by PushGoClosureWithUpvalues should not fail: %s\n", err.Error())
}
}

0 comments on commit 7609e5d

Please sign in to comment.