450 lines
11 KiB
Go
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
|
|
}
|