147 lines
3.5 KiB
Go
147 lines
3.5 KiB
Go
package luar
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
// LState is an wrapper for gopher-lua's LState. It should be used when you
|
|
// wish to have a function/method with the standard "func(*lua.LState) int"
|
|
// signature.
|
|
type LState struct {
|
|
*lua.LState
|
|
}
|
|
|
|
var (
|
|
refTypeLStatePtr reflect.Type
|
|
refTypeLuaLValue reflect.Type
|
|
refTypeInt reflect.Type
|
|
refTypeEmptyIface reflect.Type
|
|
)
|
|
|
|
func init() {
|
|
refTypeLStatePtr = reflect.TypeOf(&LState{})
|
|
refTypeLuaLValue = reflect.TypeOf((*lua.LValue)(nil)).Elem()
|
|
refTypeInt = reflect.TypeOf(int(0))
|
|
refTypeEmptyIface = reflect.TypeOf((*interface{})(nil)).Elem()
|
|
}
|
|
|
|
func getFunc(L *lua.LState) (ref reflect.Value, refType reflect.Type) {
|
|
ref = L.Get(lua.UpvalueIndex(1)).(*lua.LUserData).Value.(reflect.Value)
|
|
refType = ref.Type()
|
|
return
|
|
}
|
|
|
|
func isPtrReceiverMethod(L *lua.LState) bool {
|
|
return bool(L.Get(lua.UpvalueIndex(2)).(lua.LBool))
|
|
}
|
|
|
|
func funcIsBypass(t reflect.Type) bool {
|
|
if t.NumIn() == 1 && t.NumOut() == 1 && t.In(0) == refTypeLStatePtr && t.Out(0) == refTypeInt {
|
|
return true
|
|
}
|
|
if t.NumIn() == 2 && t.NumOut() == 1 && t.In(1) == refTypeLStatePtr && t.Out(0) == refTypeInt {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func funcBypass(L *lua.LState) int {
|
|
ref, refType := getFunc(L)
|
|
|
|
convertedPtr := false
|
|
var receiver reflect.Value
|
|
var ud lua.LValue
|
|
|
|
luarState := LState{L}
|
|
args := make([]reflect.Value, 0, 2)
|
|
if refType.NumIn() == 2 {
|
|
receiverHint := refType.In(0)
|
|
ud = L.Get(1)
|
|
var err error
|
|
if isPtrReceiverMethod(L) {
|
|
receiver, err = lValueToReflect(L, ud, receiverHint, &convertedPtr)
|
|
} else {
|
|
receiver, err = lValueToReflect(L, ud, receiverHint, nil)
|
|
}
|
|
if err != nil {
|
|
L.ArgError(1, err.Error())
|
|
}
|
|
args = append(args, receiver)
|
|
L.Remove(1)
|
|
}
|
|
args = append(args, reflect.ValueOf(&luarState))
|
|
ret := ref.Call(args)[0].Interface().(int)
|
|
if convertedPtr {
|
|
ud.(*lua.LUserData).Value = receiver.Elem().Interface()
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func funcRegular(L *lua.LState) int {
|
|
ref, refType := getFunc(L)
|
|
|
|
top := L.GetTop()
|
|
expected := refType.NumIn()
|
|
variadic := refType.IsVariadic()
|
|
if !variadic && top != expected {
|
|
L.RaiseError("invalid number of function arguments (%d expected, got %d)", expected, top)
|
|
}
|
|
if variadic && top < expected-1 {
|
|
L.RaiseError("invalid number of function arguments (%d or more expected, got %d)", expected-1, top)
|
|
}
|
|
|
|
convertedPtr := false
|
|
var receiver reflect.Value
|
|
var ud lua.LValue
|
|
|
|
args := make([]reflect.Value, top)
|
|
for i := 0; i < L.GetTop(); i++ {
|
|
var hint reflect.Type
|
|
if variadic && i >= expected-1 {
|
|
hint = refType.In(expected - 1).Elem()
|
|
} else {
|
|
hint = refType.In(i)
|
|
}
|
|
var arg reflect.Value
|
|
var err error
|
|
if i == 0 && isPtrReceiverMethod(L) {
|
|
ud = L.Get(1)
|
|
v := ud
|
|
arg, err = lValueToReflect(L, v, hint, &convertedPtr)
|
|
if err != nil {
|
|
L.ArgError(1, err.Error())
|
|
}
|
|
receiver = arg
|
|
} else {
|
|
v := L.Get(i + 1)
|
|
arg, err = lValueToReflect(L, v, hint, nil)
|
|
if err != nil {
|
|
L.ArgError(i+1, err.Error())
|
|
}
|
|
}
|
|
args[i] = arg
|
|
}
|
|
ret := ref.Call(args)
|
|
|
|
if convertedPtr {
|
|
ud.(*lua.LUserData).Value = receiver.Elem().Interface()
|
|
}
|
|
|
|
for _, val := range ret {
|
|
L.Push(New(L, val.Interface()))
|
|
}
|
|
return len(ret)
|
|
}
|
|
|
|
func funcWrapper(L *lua.LState, fn reflect.Value, isPtrReceiverMethod bool) *lua.LFunction {
|
|
up := L.NewUserData()
|
|
up.Value = fn
|
|
|
|
if funcIsBypass(fn.Type()) {
|
|
return L.NewClosure(funcBypass, up, lua.LBool(isPtrReceiverMethod))
|
|
}
|
|
return L.NewClosure(funcRegular, up, lua.LBool(isPtrReceiverMethod))
|
|
}
|