func outputNumbersToStrings(L *lua.State) { L.GetGlobal("output") if !L.IsTable(-1) { L.NewTable() L.SetGlobal("output") } L.GetField(-1, "tags") if L.IsTable(-1) { // First key. L.PushNil() for L.Next(-2) != 0 { // Use 'key' at index -2 and 'value' at index -1. if L.IsString(-2) && L.IsString(-1) { // Convert numbers to strings. L.ToString(-1) L.SetField(-3, L.ToString(-2)) } else { // Remove 'value' and keep 'key' for next iteration. L.Pop(1) } } } L.Pop(1) L.Pop(1) }
// look up a Lua value by its full name. If idx is 0, then this name // is assumed to start in the global table, e.g. "string.gsub". // With non-zero idx, can be used to look up subfields of a table func Lookup(L *lua.State, path string, idx int) { parts := strings.Split(path, ".") if idx != 0 { L.PushValue(idx) } else { L.GetGlobal("_G") } for _, field := range parts { L.GetField(-1, field) L.Remove(-2) // remove table } }
func isValueProxy(L *lua.State, idx int) bool { res := false if L.IsUserdata(idx) { L.GetMetaTable(idx) if !L.IsNil(-1) { L.GetField(-1, "luago.value") res = !L.IsNil(-1) L.Pop(1) } L.Pop(1) } return res }
func pushValue(state *lua.State, value interface{}) error { switch v := value.(type) { default: return fmt.Errorf("An item of unknown type was included in the environment or arguments of a Lua call (skipping it): %v", value) case nil: log.Println("Pushing nil onto lua stack") state.PushNil() case string: log.Println("Pushing string onto lua stack") state.PushString(v) case int: log.Println("Pushing int onto lua stack") state.PushInteger(int64(v)) case int64: log.Println("Pushing int64 onto lua stack") state.PushInteger(v) case float64: log.Println("Pushing float64 onto lua stack") state.PushNumber(v) case bool: log.Println("Pushing bool onto lua stack") state.PushBoolean(v) case map[string]interface{}: log.Println("Pushing map[string]interface{} onto lua stack") state.CreateTable(0, len(v)) for name, value := range v { err := pushValue(state, value) if err != nil { // error means nothing was added to stack. So pop our new table so *we* leave nothing added to the stack. state.Pop(1) return err } state.SetField(-2, name) } // then leave the table on the stack case ThingType: // These are singleton sentinel values, so load them from Lua-land. state.GetGlobal("world") state.GetField(-1, strings.Title(v.String())) state.Remove(-2) case *Thing: log.Println("Pushing *Thing onto lua stack") return pushValue(state, v.Id) case ThingId: log.Println("Pushing ThingId onto lua stack") // We're pushing a ThingId, so make a new userdata for it, with the Thing metatable. userdata := state.NewUserdata(uintptr(unsafe.Sizeof(int64(0)))) thingPtr := (*int64)(userdata) *thingPtr = int64(v) if !state.IsUserdata(-1) { log.Println("!!! HOGAD JUST PUSHED NEW USERDATA BUT IT ISN'T OMG !!!") } log.Println("Pushed ThingId", *thingPtr, "onto lua stack") // Now make it act like a Thing. state.LGetMetaTable(ThingMetaTableName) // ( udata -- udata mtbl ) state.SetMetaTable(-2) // ( udata mtbl -- udata ) // Let's just check that it's that, for sures. if !state.IsUserdata(-1) { log.Println("!!! WOOP WOOP DID NOT SET METATABLE RIGHT :( !!!") } } return nil }
// 'exist' is optional. func run(L *lua.State, registryIndex string, code string, input *inputInfo, output *outputInfo, exist *inputInfo) error { // Restore the sandbox. err := L.DoString(luaRestoreSandbox) if err != nil { log.Fatal("Cannot load function to restore sandbox", err) } L.PushString(registryWhitelist) L.GetTable(lua.LUA_REGISTRYINDEX) err = L.Call(1, 0) if err != nil { log.Fatal("Failed to restore sandbox", err) } goToLua(L, "input", *input) goToLua(L, "output", *output) if exist != nil { goToLua(L, "existinfo", *exist) } // Shortcut (mostly for prescript and postscript). L.GetGlobal("input") L.GetField(-1, "tags") L.SetGlobal("i") L.Pop(1) L.GetGlobal("output") L.GetField(-1, "tags") L.SetGlobal("o") L.Pop(1) // Call the compiled script. L.PushString(registryIndex) L.GetTable(lua.LUA_REGISTRYINDEX) L.PushString(code) if L.IsTable(-2) { L.GetTable(-2) if L.IsFunction(-1) { err := L.Call(0, 0) if err != nil { L.SetTop(0) return fmt.Errorf("%s", err) } } else { L.Pop(1) } } else { L.Pop(1) } L.Pop(1) // Allow tags to be numbers for convenience. outputNumbersToStrings(L) L.GetGlobal("output") r := luar.LuaToGo(L, reflect.TypeOf(*output), -1) L.Pop(1) *output = r.(outputInfo) return nil }