end_of_eden/internal/lua/luhelp/luhelp.go
2023-12-22 20:12:22 +01:00

164 lines
3.8 KiB
Go

package luhelp
import (
"fmt"
"github.com/fatih/structs"
"github.com/gobeam/stringy"
"github.com/samber/lo"
"github.com/sanity-io/litter"
lua "github.com/yuin/gopher-lua"
"os"
"reflect"
"strings"
)
const LuaTag = "lua"
func baseToLua(val any) lua.LValue {
switch val := val.(type) {
case float64:
return lua.LNumber(val)
case float32:
return lua.LNumber(val)
case int:
return lua.LNumber(val)
case int64:
return lua.LNumber(val)
case string:
return lua.LString(val)
case bool:
return lua.LBool(val)
}
return lua.LNil
}
var intType = reflect.TypeOf(int(0))
// ToLua converts a go value to a lua value.
func ToLua(state *lua.LState, val any) lua.LValue {
if val == nil {
return lua.LNil
}
valType := reflect.TypeOf(val)
switch valType.Kind() {
case reflect.Pointer:
valValue := reflect.ValueOf(val)
if valValue.IsNil() {
return lua.LNil
}
return ToLua(state, valValue.Elem().Interface())
case reflect.Struct:
s := structs.New(val)
s.TagName = LuaTag
return ToLua(state, s.Map())
case reflect.Map:
resultTable := state.NewTable()
valValue := reflect.ValueOf(val)
keys := valValue.MapKeys()
for i := range keys {
if keys[i].Kind() == reflect.String {
key := strings.ToLower(stringy.New(keys[i].Interface().(string)).SnakeCase().Get())
luaVal := ToLua(state, valValue.MapIndex(keys[i]).Interface())
resultTable.RawSetString(key, luaVal)
} else if keys[i].CanConvert(intType) {
resultTable.RawSetInt(keys[i].Convert(intType).Interface().(int), ToLua(state, valValue.MapIndex(keys[i]).Interface()))
}
}
return resultTable
case reflect.Slice:
resultTable := state.NewTable()
valValue := reflect.ValueOf(val)
for i := 0; i < valValue.Len(); i++ {
resultTable.Append(ToLua(state, valValue.Index(i).Interface()))
}
return resultTable
default:
return baseToLua(val)
}
}
var noProtect = os.Getenv("EOE_NO_PROTECT") == "1"
// BindToLua will create a OwnedCallback from a lua function and state.
func BindToLua(state *lua.LState, value lua.LValue) OwnedCallback {
return func(args ...any) (any, error) {
// Call our lua function
if err := state.CallByParam(lua.P{
Fn: value,
NRet: 1,
Protect: !noProtect,
}, lo.Map(args, func(item any, index int) lua.LValue {
return ToLua(state, item)
})...); err != nil {
return nil, err
}
// Fetch return value
ret := state.Get(-1)
state.Pop(1)
// Parse to accepted return values
switch ret.Type() {
case lua.LTString:
return lua.LVAsString(ret), nil
case lua.LTNumber:
return float64(lua.LVAsNumber(ret)), nil
case lua.LTBool:
return lua.LVAsBool(ret), nil
case lua.LTTable:
mapper := NewMapper(state)
maxn := value.(*lua.LTable).MaxN()
if maxn == 0 {
var data map[string]any
if err := mapper.Map(ret.(*lua.LTable), &data); err != nil {
return nil, err
}
return data, nil
}
data := make([]any, 0)
if err := mapper.Map(ret.(*lua.LTable), &data); err != nil {
return nil, err
}
return data, nil
}
// Don't error for now
return nil, nil
}
}
// ToString converts a lua value to a string.
func ToString(val lua.LValue, mapper *Mapper) string {
switch val.Type() {
case lua.LTString:
return lua.LVAsString(val)
case lua.LTNumber:
return fmt.Sprint(float64(lua.LVAsNumber(val)))
case lua.LTBool:
return fmt.Sprint(lua.LVAsBool(val))
case lua.LTTable:
maxn := val.(*lua.LTable).MaxN()
if maxn == 0 {
var data map[string]interface{}
if err := mapper.Map(val.(*lua.LTable), &data); err != nil {
return "Error: " + err.Error()
}
return litter.Sdump(data)
}
ret := make([]any, 0)
if err := mapper.Map(val.(*lua.LTable), &ret); err != nil {
return "Error: " + err.Error()
}
return litter.Sdump(ret)
case lua.LTUserData:
return fmt.Sprint(val.(*lua.LUserData).Value)
case lua.LTNil:
return "nil"
}
return "<" + val.Type().String() + ">"
}