LuaJIT-to-Go/wrapper.go
2025-02-26 07:00:01 -06:00

450 lines
11 KiB
Go

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 <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdlib.h>
#include <string.h>
// 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
}