483 lines
14 KiB
Go
483 lines
14 KiB
Go
package luar
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
// New creates and returns a new lua.LValue for the given value. Values are
|
|
// converted in the following manner:
|
|
//
|
|
// A nil value (untyped, or a nil channel, function, map, pointer, or slice) is
|
|
// converted to lua.LNil.
|
|
//
|
|
// A lua.LValue value is returned without conversion.
|
|
//
|
|
// Boolean values are converted to lua.LBool.
|
|
//
|
|
// String values are converted to lua.LString.
|
|
//
|
|
// Real numeric values (ints, uints, and floats) are converted to lua.LNumber.
|
|
//
|
|
// Functions are converted to *lua.LFunction. When called from Lua, Lua values
|
|
// are converted to Go using the rules described in the package documentation,
|
|
// and Go return values converted to Lua values using the rules described by
|
|
// New.
|
|
//
|
|
// If a function has the signature:
|
|
// func(*LState) int // *LState defined in this package, not in lua
|
|
// The argument and return value conversions described above are skipped, and
|
|
// the function is called with the arguments passed on the Lua stack. Return
|
|
// values are pushed to the stack and the number of return values is returned
|
|
// from the function.
|
|
//
|
|
// Arrays, channels, maps, pointers, slices, and structs are all converted to
|
|
// *lua.LUserData with its Value field set to value. The userdata's metatable
|
|
// is set to a table generated for value's type. The type's method set is
|
|
// callable from the Lua type. If the type implements the fmt.Stringer
|
|
// interface, that method will be used when the value is passed to the Lua
|
|
// tostring function.
|
|
//
|
|
// With arrays, the # operator returns the array's length. Array elements can
|
|
// be accessed with the index operator (array[index]). Calling an array
|
|
// (array()) returns an iterator over the array that can be used in a for loop.
|
|
// Two arrays of the same type can be compared for equality. Additionally, a
|
|
// pointer to an array allows the array elements to be modified
|
|
// (array[index] = value).
|
|
//
|
|
// With channels, the # operator returns the number of elements buffered in the
|
|
// channel. Two channels of the same type can be compared for equality (i.e. if
|
|
// they were created with the same make call). Calling a channel value with
|
|
// no arguments reads one element from the channel, returning the value and a
|
|
// boolean indicating if the channel is closed. Calling a channel value with
|
|
// one argument sends the argument to the channel. The channel's unary minus
|
|
// operator closes the channel (_ = -channel).
|
|
//
|
|
// With maps, the # operator returns the number of elements in the map. Map
|
|
// elements can be accessed using the index operator (map[key]) and also set
|
|
// (map[key] = value). Calling a map value returns an iterator over the map that
|
|
// can be used in a for loop. If a map's key type is string, map values take
|
|
// priority over methods.
|
|
//
|
|
// With slices, the # operator returns the length of the slice. Slice elements
|
|
// can be accessed using the index operator (slice[index]) and also set
|
|
// (slice[index] = value). Calling a slice returns an iterator over its elements
|
|
// that can be used in a for loop. Elements can be appended to a slice using the
|
|
// add operator (new_slice = slice + element).
|
|
//
|
|
// With structs, fields can be accessed using the index operator
|
|
// (struct[field]). As a special case, accessing field that is an array or
|
|
// struct field will return a pointer to that value. Structs of the same type
|
|
// can be tested for equality. Additionally, a pointer to a struct can have its
|
|
// fields set (struct[field] = value).
|
|
//
|
|
// Struct field accessibility can be changed by setting the field's luar tag.
|
|
// If the tag is empty (default), the field is accessed by its name and its
|
|
// name with a lowercase first letter (e.g. "Field1" would be accessible using
|
|
// "Field1" or "field1"). If the tag is "-", the field will not be accessible.
|
|
// Any other tag value makes the field accessible through that name.
|
|
//
|
|
// Pointer values can be compared for equality. The pointed to value can be
|
|
// changed using the pow operator (pointer = pointer ^ value). A pointer can be
|
|
// dereferenced using the unary minus operator (value = -pointer).
|
|
//
|
|
// All other values (complex numbers, unsafepointer, uintptr) are converted to
|
|
// *lua.LUserData with its Value field set to value and no custom metatable.
|
|
//
|
|
func New(L *lua.LState, value interface{}) lua.LValue {
|
|
if value == nil {
|
|
return lua.LNil
|
|
}
|
|
if lval, ok := value.(lua.LValue); ok {
|
|
return lval
|
|
}
|
|
|
|
val := reflect.ValueOf(value)
|
|
switch val.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
if val.IsNil() {
|
|
return lua.LNil
|
|
}
|
|
}
|
|
|
|
switch val.Kind() {
|
|
case reflect.Bool:
|
|
return lua.LBool(val.Bool())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return lua.LNumber(float64(val.Int()))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return lua.LNumber(float64(val.Uint()))
|
|
case reflect.Float32, reflect.Float64:
|
|
return lua.LNumber(val.Float())
|
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Struct:
|
|
ud := L.NewUserData()
|
|
ud.Value = val.Interface()
|
|
ud.Metatable = getMetatableFromValue(L, val)
|
|
return ud
|
|
case reflect.Func:
|
|
return funcWrapper(L, val, false)
|
|
case reflect.String:
|
|
return lua.LString(val.String())
|
|
default:
|
|
ud := L.NewUserData()
|
|
ud.Value = val.Interface()
|
|
return ud
|
|
}
|
|
}
|
|
|
|
// NewType returns a new type generator for the given value's type.
|
|
//
|
|
// When the returned lua.LValue is called, a new value will be created that is
|
|
// dependent on value's type:
|
|
//
|
|
// If value is a channel, the first argument optionally specifies the channel's
|
|
// buffer size (defaults to 1). The new channel is returned.
|
|
//
|
|
// If value is a map, a new map is returned.
|
|
//
|
|
// If value is a slice, the first argument optionally specifies the slices's
|
|
// length (defaults to 0), and the second argument optionally specifies the
|
|
// slice's capacity (defaults to the first argument). The new slice is returned.
|
|
//
|
|
// All other types return a new pointer to the zero value of value's type.
|
|
func NewType(L *lua.LState, value interface{}) lua.LValue {
|
|
val := reflect.TypeOf(value)
|
|
ud := L.NewUserData()
|
|
ud.Value = val
|
|
ud.Metatable = getTypeMetatable(L, val)
|
|
|
|
return ud
|
|
}
|
|
|
|
type conversionError struct {
|
|
Lua lua.LValue
|
|
Hint reflect.Type
|
|
}
|
|
|
|
func (c conversionError) Error() string {
|
|
if _, isNil := c.Lua.(*lua.LNilType); isNil {
|
|
return fmt.Sprintf("cannot use nil as type %s", c.Hint)
|
|
}
|
|
|
|
var val interface{}
|
|
|
|
if userData, ok := c.Lua.(*lua.LUserData); ok {
|
|
val = userData.Value
|
|
} else {
|
|
val = c.Lua
|
|
}
|
|
|
|
return fmt.Sprintf("cannot use %v (type %T) as type %s", val, val, c.Hint)
|
|
}
|
|
|
|
type structFieldError struct {
|
|
Field string
|
|
Type reflect.Type
|
|
}
|
|
|
|
func (s structFieldError) Error() string {
|
|
return `type ` + s.Type.String() + ` has no field ` + s.Field
|
|
}
|
|
|
|
func lValueToReflect(L *lua.LState, v lua.LValue, hint reflect.Type, tryConvertPtr *bool) (reflect.Value, error) {
|
|
visited := make(map[*lua.LTable]reflect.Value)
|
|
return lValueToReflectInner(L, v, hint, visited, tryConvertPtr)
|
|
}
|
|
|
|
func lValueToReflectInner(L *lua.LState, v lua.LValue, hint reflect.Type, visited map[*lua.LTable]reflect.Value, tryConvertPtr *bool) (reflect.Value, error) {
|
|
if hint.Implements(refTypeLuaLValue) {
|
|
return reflect.ValueOf(v), nil
|
|
}
|
|
|
|
isPtr := false
|
|
|
|
switch converted := v.(type) {
|
|
case lua.LBool:
|
|
val := reflect.ValueOf(bool(converted))
|
|
if !val.Type().ConvertibleTo(hint) {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
return val.Convert(hint), nil
|
|
case lua.LChannel:
|
|
val := reflect.ValueOf(converted)
|
|
if !val.Type().ConvertibleTo(hint) {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
return val.Convert(hint), nil
|
|
case lua.LNumber:
|
|
val := reflect.ValueOf(float64(converted))
|
|
if !val.Type().ConvertibleTo(hint) {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
return val.Convert(hint), nil
|
|
case *lua.LFunction:
|
|
emptyIfaceHint := false
|
|
switch {
|
|
case hint == refTypeEmptyIface:
|
|
emptyIfaceHint = true
|
|
inOut := []reflect.Type{
|
|
reflect.SliceOf(refTypeEmptyIface),
|
|
}
|
|
hint = reflect.FuncOf(inOut, inOut, true)
|
|
case hint.Kind() != reflect.Func:
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
|
|
fn := func(args []reflect.Value) []reflect.Value {
|
|
thread, cancelFunc := L.NewThread()
|
|
defer thread.Close()
|
|
if cancelFunc != nil {
|
|
defer cancelFunc()
|
|
}
|
|
thread.Push(converted)
|
|
defer thread.SetTop(0)
|
|
|
|
argCount := 0
|
|
for i, arg := range args {
|
|
if i+1 == len(args) && hint.IsVariadic() {
|
|
// arg is a varadic slice
|
|
for j := 0; j < arg.Len(); j++ {
|
|
arg := arg.Index(j)
|
|
thread.Push(New(thread, arg.Interface()))
|
|
argCount++
|
|
}
|
|
break
|
|
}
|
|
|
|
thread.Push(New(thread, arg.Interface()))
|
|
argCount++
|
|
}
|
|
|
|
thread.Call(argCount, lua.MultRet)
|
|
top := thread.GetTop()
|
|
|
|
switch {
|
|
case emptyIfaceHint:
|
|
ret := reflect.MakeSlice(reflect.SliceOf(refTypeEmptyIface), top, top)
|
|
|
|
for i := 1; i <= top; i++ {
|
|
item, err := lValueToReflect(thread, thread.Get(i), refTypeEmptyIface, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ret.Index(i - 1).Set(item)
|
|
}
|
|
|
|
return []reflect.Value{ret}
|
|
|
|
case top == hint.NumOut():
|
|
ret := make([]reflect.Value, top)
|
|
|
|
var err error
|
|
for i := 1; i <= top; i++ {
|
|
outHint := hint.Out(i - 1)
|
|
item := thread.Get(i)
|
|
ret[i-1], err = lValueToReflect(thread, item, outHint, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
|
|
default:
|
|
panic(fmt.Errorf("expecting %d return values, got %d", hint.NumOut(), top))
|
|
}
|
|
}
|
|
return reflect.MakeFunc(hint, fn), nil
|
|
case *lua.LNilType:
|
|
switch hint.Kind() {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer, reflect.Uintptr:
|
|
return reflect.Zero(hint), nil
|
|
default:
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
case *lua.LState:
|
|
val := reflect.ValueOf(converted)
|
|
if !val.Type().ConvertibleTo(hint) {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
return val.Convert(hint), nil
|
|
case lua.LString:
|
|
val := reflect.ValueOf(string(converted))
|
|
if !val.Type().ConvertibleTo(hint) {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
return val.Convert(hint), nil
|
|
case *lua.LTable:
|
|
if existing := visited[converted]; existing.IsValid() {
|
|
return existing, nil
|
|
}
|
|
|
|
if hint == refTypeEmptyIface {
|
|
hint = reflect.MapOf(refTypeEmptyIface, refTypeEmptyIface)
|
|
}
|
|
|
|
switch {
|
|
case hint.Kind() == reflect.Array:
|
|
elemType := hint.Elem()
|
|
length := converted.Len()
|
|
if length != hint.Len() {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
s := reflect.New(hint).Elem()
|
|
visited[converted] = s
|
|
|
|
for i := 0; i < length; i++ {
|
|
value := converted.RawGetInt(i + 1)
|
|
elemValue, err := lValueToReflectInner(L, value, elemType, visited, nil)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
s.Index(i).Set(elemValue)
|
|
}
|
|
|
|
return s, nil
|
|
|
|
case hint.Kind() == reflect.Slice:
|
|
elemType := hint.Elem()
|
|
length := converted.Len()
|
|
s := reflect.MakeSlice(hint, length, length)
|
|
visited[converted] = s
|
|
|
|
for i := 0; i < length; i++ {
|
|
value := converted.RawGetInt(i + 1)
|
|
elemValue, err := lValueToReflectInner(L, value, elemType, visited, nil)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
s.Index(i).Set(elemValue)
|
|
}
|
|
|
|
return s, nil
|
|
|
|
case hint.Kind() == reflect.Map:
|
|
keyType := hint.Key()
|
|
elemType := hint.Elem()
|
|
s := reflect.MakeMap(hint)
|
|
visited[converted] = s
|
|
|
|
for key := lua.LNil; ; {
|
|
var value lua.LValue
|
|
key, value = converted.Next(key)
|
|
if key == lua.LNil {
|
|
break
|
|
}
|
|
|
|
lKey, err := lValueToReflectInner(L, key, keyType, visited, nil)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
lValue, err := lValueToReflectInner(L, value, elemType, visited, nil)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
s.SetMapIndex(lKey, lValue)
|
|
}
|
|
|
|
return s, nil
|
|
|
|
case hint.Kind() == reflect.Ptr && hint.Elem().Kind() == reflect.Struct:
|
|
hint = hint.Elem()
|
|
isPtr = true
|
|
fallthrough
|
|
case hint.Kind() == reflect.Struct:
|
|
s := reflect.New(hint)
|
|
visited[converted] = s
|
|
|
|
t := s.Elem()
|
|
|
|
mt := &Metatable{
|
|
LTable: getMetatable(L, hint),
|
|
}
|
|
|
|
for key := lua.LNil; ; {
|
|
var value lua.LValue
|
|
key, value = converted.Next(key)
|
|
if key == lua.LNil {
|
|
break
|
|
}
|
|
if _, ok := key.(lua.LString); !ok {
|
|
continue
|
|
}
|
|
|
|
fieldName := key.String()
|
|
index := mt.fieldIndex(fieldName)
|
|
if index == nil {
|
|
return reflect.Value{}, structFieldError{
|
|
Type: hint,
|
|
Field: fieldName,
|
|
}
|
|
}
|
|
field := hint.FieldByIndex(index)
|
|
|
|
lValue, err := lValueToReflectInner(L, value, field.Type, visited, nil)
|
|
if err != nil {
|
|
return reflect.Value{}, nil
|
|
}
|
|
t.FieldByIndex(field.Index).Set(lValue)
|
|
}
|
|
|
|
if isPtr {
|
|
return s, nil
|
|
}
|
|
|
|
return t, nil
|
|
|
|
default:
|
|
return reflect.Value{}, conversionError{
|
|
Lua: v,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
case *lua.LUserData:
|
|
val := reflect.ValueOf(converted.Value)
|
|
if tryConvertPtr != nil && val.Kind() != reflect.Ptr && hint.Kind() == reflect.Ptr && val.Type() == hint.Elem() {
|
|
newVal := reflect.New(hint.Elem())
|
|
newVal.Elem().Set(val)
|
|
val = newVal
|
|
*tryConvertPtr = true
|
|
} else {
|
|
if !val.Type().ConvertibleTo(hint) {
|
|
return reflect.Value{}, conversionError{
|
|
Lua: converted,
|
|
Hint: hint,
|
|
}
|
|
}
|
|
val = val.Convert(hint)
|
|
if tryConvertPtr != nil {
|
|
*tryConvertPtr = false
|
|
}
|
|
}
|
|
return val, nil
|
|
default:
|
|
panic("never reaches")
|
|
}
|
|
}
|