route/vendor/layeh.com/gopher-luar/func.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))
}