package luajit /* #cgo CFLAGS: -I${SRCDIR}/vendor/luajit/include #cgo windows LDFLAGS: -L${SRCDIR}/vendor/luajit/windows -lluajit -static #cgo !windows LDFLAGS: -L${SRCDIR}/vendor/luajit/linux -lluajit -static #include #include #include #include #include // Helper to simplify some common operations static int get_abs_index(lua_State *L, int idx) { if (idx > 0 || idx <= LUA_REGISTRYINDEX) return idx; return lua_gettop(L) + idx + 1; } // Combined load and execute with no results static int do_string(lua_State *L, const char *s) { int status = luaL_loadstring(L, s); if (status == 0) { status = lua_pcall(L, 0, 0, 0); } return status; } // Combined load and execute file static int do_file(lua_State *L, const char *filename) { int status = luaL_loadfile(L, filename); if (status == 0) { status = lua_pcall(L, 0, 0, 0); } return status; } */ import "C" import ( "fmt" "strings" "unsafe" ) // State represents a Lua state type State struct { L *C.lua_State } // New creates a new Lua state with all standard libraries loaded func New() *State { L := C.luaL_newstate() if L == nil { return nil } C.luaL_openlibs(L) return &State{L: L} } // Close closes the Lua state and frees resources func (s *State) Close() { if s.L != nil { s.Cleanup() // Clean up Go function registry C.lua_close(s.L) s.L = nil } } // Stack manipulation methods // GetTop returns the index of the top element in the stack func (s *State) GetTop() int { return int(C.lua_gettop(s.L)) } // SetTop sets the stack top to a specific index func (s *State) SetTop(index int) { C.lua_settop(s.L, C.int(index)) } // PushValue pushes a copy of the value at the given index onto the stack func (s *State) PushCopy(index int) { C.lua_pushvalue(s.L, C.int(index)) } // Pop pops n elements from the stack func (s *State) Pop(n int) { C.lua_settop(s.L, C.int(-n-1)) } // Remove removes the element at the given valid index func (s *State) Remove(index int) { C.lua_remove(s.L, C.int(index)) } // absIndex converts a possibly negative index to its absolute position func (s *State) absIndex(index int) int { if index > 0 || index <= LUA_REGISTRYINDEX { return index } return s.GetTop() + index + 1 } // Type checking methods // GetType returns the type of the value at the given index func (s *State) GetType(index int) LuaType { return LuaType(C.lua_type(s.L, C.int(index))) } // IsNil checks if the value at the given index is nil func (s *State) IsNil(index int) bool { return s.GetType(index) == TypeNil } // IsBoolean checks if the value at the given index is a boolean func (s *State) IsBoolean(index int) bool { return s.GetType(index) == TypeBoolean } // IsNumber checks if the value at the given index is a number func (s *State) IsNumber(index int) bool { return C.lua_isnumber(s.L, C.int(index)) != 0 } // IsString checks if the value at the given index is a string func (s *State) IsString(index int) bool { return C.lua_isstring(s.L, C.int(index)) != 0 } // IsTable checks if the value at the given index is a table func (s *State) IsTable(index int) bool { return s.GetType(index) == TypeTable } // IsFunction checks if the value at the given index is a function func (s *State) IsFunction(index int) bool { return s.GetType(index) == TypeFunction } // Value conversion methods // ToBoolean returns the value at the given index as a boolean func (s *State) ToBoolean(index int) bool { return C.lua_toboolean(s.L, C.int(index)) != 0 } // ToNumber returns the value at the given index as a number func (s *State) ToNumber(index int) float64 { return float64(C.lua_tonumber(s.L, C.int(index))) } // ToString returns the value at the given index as a string func (s *State) ToString(index int) string { var length C.size_t cstr := C.lua_tolstring(s.L, C.int(index), &length) if cstr == nil { return "" } return C.GoStringN(cstr, C.int(length)) } // Push methods // PushNil pushes a nil value onto the stack func (s *State) PushNil() { C.lua_pushnil(s.L) } // PushBoolean pushes a boolean value onto the stack func (s *State) PushBoolean(b bool) { var value C.int if b { value = 1 } C.lua_pushboolean(s.L, value) } // PushNumber pushes a number value onto the stack func (s *State) PushNumber(n float64) { C.lua_pushnumber(s.L, C.lua_Number(n)) } // PushString pushes a string value onto the stack func (s *State) PushString(str string) { cstr := C.CString(str) defer C.free(unsafe.Pointer(cstr)) C.lua_pushlstring(s.L, cstr, C.size_t(len(str))) } // Table operations // CreateTable creates a new table and pushes it onto the stack func (s *State) CreateTable(narr, nrec int) { C.lua_createtable(s.L, C.int(narr), C.int(nrec)) } // NewTable creates a new empty table and pushes it onto the stack func (s *State) NewTable() { C.lua_createtable(s.L, 0, 0) } // GetTable gets a table field (t[k]) where t is at the given index and k is at the top of the stack func (s *State) GetTable(index int) { C.lua_gettable(s.L, C.int(index)) } // SetTable sets a table field (t[k] = v) where t is at the given index, k is at -2, and v is at -1 func (s *State) SetTable(index int) { C.lua_settable(s.L, C.int(index)) } // GetField gets a table field t[k] and pushes it onto the stack func (s *State) GetField(index int, key string) { ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) C.lua_getfield(s.L, C.int(index), ckey) } // SetField sets a table field t[k] = v, where v is the value at the top of the stack func (s *State) SetField(index int, key string) { ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) C.lua_setfield(s.L, C.int(index), ckey) } // Next pops a key from the stack and pushes the next key-value pair from the table at the given index func (s *State) Next(index int) bool { return C.lua_next(s.L, C.int(index)) != 0 } // PushValue pushes a Go value onto the stack with proper type conversion func (s *State) PushValue(v interface{}) error { switch v := v.(type) { case nil: s.PushNil() case bool: s.PushBoolean(v) case int: s.PushNumber(float64(v)) case float64: s.PushNumber(v) case string: s.PushString(v) case map[string]interface{}: // Special case: handle array stored in map if arr, ok := v[""].([]float64); ok { s.CreateTable(len(arr), 0) for i, elem := range arr { s.PushNumber(float64(i + 1)) s.PushNumber(elem) s.SetTable(-3) } return nil } return s.PushTable(v) case []float64: s.CreateTable(len(v), 0) for i, elem := range v { s.PushNumber(float64(i + 1)) s.PushNumber(elem) s.SetTable(-3) } case []interface{}: s.CreateTable(len(v), 0) for i, elem := range v { s.PushNumber(float64(i + 1)) if err := s.PushValue(elem); err != nil { return err } s.SetTable(-3) } default: return fmt.Errorf("unsupported type: %T", v) } return nil } // ToValue converts a Lua value at the given index to a Go value func (s *State) ToValue(index int) (interface{}, error) { luaType := s.GetType(index) switch luaType { case TypeNil: return nil, nil case TypeBoolean: return s.ToBoolean(index), nil case TypeNumber: return s.ToNumber(index), nil case TypeString: return s.ToString(index), nil case TypeTable: return s.ToTable(index) default: return nil, fmt.Errorf("unsupported type: %s", luaType) } } // Global operations // GetGlobal pushes the global variable with the given name onto the stack func (s *State) GetGlobal(name string) { s.GetField(LUA_GLOBALSINDEX, name) } // SetGlobal sets the global variable with the given name to the value at the top of the stack func (s *State) SetGlobal(name string) { s.SetField(LUA_GLOBALSINDEX, name) } // Code execution methods // LoadString loads a Lua chunk from a string without executing it func (s *State) LoadString(code string) error { ccode := C.CString(code) defer C.free(unsafe.Pointer(ccode)) return s.safeCall(func() C.int { return C.luaL_loadstring(s.L, ccode) }) } // LoadFile loads a Lua chunk from a file without executing it func (s *State) LoadFile(filename string) error { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) return s.safeCall(func() C.int { return C.luaL_loadfile(s.L, cfilename) }) } // Call calls a function with the given number of arguments and results func (s *State) Call(nargs, nresults int) error { return s.safeCall(func() C.int { return C.lua_pcall(s.L, C.int(nargs), C.int(nresults), 0) }) } // DoString executes a Lua string and cleans up the stack func (s *State) DoString(code string) error { ccode := C.CString(code) defer C.free(unsafe.Pointer(ccode)) return s.safeCall(func() C.int { return C.do_string(s.L, ccode) }) } // DoFile executes a Lua file and cleans up the stack func (s *State) DoFile(filename string) error { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) return s.safeCall(func() C.int { return C.do_file(s.L, cfilename) }) } // Execute executes a Lua string and returns the number of results left on the stack func (s *State) Execute(code string) (int, error) { baseTop := s.GetTop() ccode := C.CString(code) defer C.free(unsafe.Pointer(ccode)) var nresults int err := s.safeCall(func() C.int { status := C.luaL_loadstring(s.L, ccode) if status != 0 { return status } status = C.lua_pcall(s.L, 0, C.LUA_MULTRET, 0) if status == 0 { nresults = s.GetTop() - baseTop } return status }) if err != nil { return 0, err } return nresults, nil } // ExecuteWithResult executes a Lua string and returns the first result func (s *State) ExecuteWithResult(code string) (interface{}, error) { top := s.GetTop() defer s.SetTop(top) // Restore stack when done nresults, err := s.Execute(code) if err != nil { return nil, err } if nresults == 0 { return nil, nil } return s.ToValue(-nresults) } // Package path operations // SetPackagePath sets the Lua package.path func (s *State) SetPackagePath(path string) error { path = strings.ReplaceAll(path, "\\", "/") // Convert Windows paths return s.DoString(fmt.Sprintf(`package.path = %q`, path)) } // AddPackagePath adds a path to package.path func (s *State) AddPackagePath(path string) error { path = strings.ReplaceAll(path, "\\", "/") // Convert Windows paths return s.DoString(fmt.Sprintf(`package.path = package.path .. ";%s"`, path)) } // Helper functions // checkStack ensures there is enough space on the Lua stack func (s *State) checkStack(n int) error { if C.lua_checkstack(s.L, C.int(n)) == 0 { return fmt.Errorf("stack overflow (cannot allocate %d slots)", n) } return nil } // safeCall wraps a potentially dangerous C call with stack checking func (s *State) safeCall(f func() C.int) error { // Ensure we have enough stack space if err := s.checkStack(LUA_MINSTACK); err != nil { return err } // Make the call status := f() // Check for errors if status != 0 { err := &LuaError{ Code: int(status), Message: s.ToString(-1), } s.Pop(1) // Remove error message return err } return nil }