397 lines
8.8 KiB
Go
397 lines
8.8 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
|
)
|
|
|
|
// Config represents a configuration loaded from a Lua file
|
|
type Config struct {
|
|
values map[string]any
|
|
}
|
|
|
|
// New creates a new empty configuration
|
|
func New() *Config {
|
|
return &Config{
|
|
values: make(map[string]any),
|
|
}
|
|
}
|
|
|
|
// Load loads configuration from a Lua file
|
|
func Load(filePath string) (*Config, error) {
|
|
// Create a new Lua state
|
|
state := luajit.New()
|
|
if state == nil {
|
|
return nil, errors.New("failed to create Lua state")
|
|
}
|
|
defer state.Close()
|
|
|
|
// Execute the Lua file
|
|
if err := state.DoFile(filePath); err != nil {
|
|
return nil, fmt.Errorf("failed to load config file: %w", err)
|
|
}
|
|
|
|
// Create the config instance
|
|
config := New()
|
|
|
|
// Extract values from the Lua state
|
|
if err := extractGlobals(state, config.values); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// extractGlobals extracts global variables from the Lua state
|
|
func extractGlobals(state *luajit.State, values map[string]any) error {
|
|
// Get the globals table (_G)
|
|
state.GetGlobal("_G")
|
|
if !state.IsTable(-1) {
|
|
state.Pop(1)
|
|
return errors.New("failed to get globals table")
|
|
}
|
|
|
|
// Pre-populate with standard globals for reference checking
|
|
stdGlobals := map[string]bool{
|
|
"_G": true, "_VERSION": true, "assert": true, "collectgarbage": true,
|
|
"coroutine": true, "debug": true, "dofile": true, "error": true,
|
|
"getmetatable": true, "io": true, "ipairs": true, "load": true,
|
|
"loadfile": true, "loadstring": true, "math": true, "next": true,
|
|
"os": true, "package": true, "pairs": true, "pcall": true,
|
|
"print": true, "rawequal": true, "rawget": true, "rawset": true,
|
|
"require": true, "select": true, "setmetatable": true, "string": true,
|
|
"table": true, "tonumber": true, "tostring": true, "type": true,
|
|
"unpack": true, "xpcall": true,
|
|
// LuaJIT specific globals
|
|
"jit": true, "bit": true, "ffi": true, "bit32": true,
|
|
}
|
|
|
|
// First, let's get the original globals to compare with user globals
|
|
originalGlobals := make(map[string]bool)
|
|
|
|
// Execute empty Lua state to get standard globals
|
|
emptyState := luajit.New()
|
|
if emptyState != nil {
|
|
defer emptyState.Close()
|
|
|
|
emptyState.GetGlobal("_G")
|
|
emptyState.PushNil() // Start iteration
|
|
for emptyState.Next(-2) {
|
|
if emptyState.IsString(-2) {
|
|
key := emptyState.ToString(-2)
|
|
originalGlobals[key] = true
|
|
}
|
|
emptyState.Pop(1) // Pop value, leave key for next iteration
|
|
}
|
|
emptyState.Pop(1) // Pop _G
|
|
}
|
|
|
|
// Iterate through the globals table
|
|
state.PushNil() // Start iteration
|
|
for state.Next(-2) {
|
|
// Stack now has key at -2 and value at -1
|
|
|
|
// Get key as string
|
|
if !state.IsString(-2) {
|
|
state.Pop(1) // Pop value, leave key for next iteration
|
|
continue
|
|
}
|
|
|
|
key := state.ToString(-2)
|
|
|
|
// Skip standard Lua globals, but only if they're not overridden by user
|
|
// (standard globals will be functions or tables, user values usually aren't)
|
|
valueType := state.GetType(-1)
|
|
|
|
// Skip functions, userdata, and threads regardless of origin
|
|
if valueType == luajit.TypeFunction || valueType == luajit.TypeUserData || valueType == luajit.TypeThread {
|
|
state.Pop(1)
|
|
continue
|
|
}
|
|
|
|
// For known Lua globals, we need to see if they're the original or user-defined
|
|
if stdGlobals[key] {
|
|
// For simple value types, assume user-defined
|
|
if valueType == luajit.TypeBoolean || valueType == luajit.TypeNumber || valueType == luajit.TypeString {
|
|
// These are probably user values with standard names
|
|
} else if originalGlobals[key] {
|
|
// If it's in the original globals and not a simple type, skip it
|
|
state.Pop(1)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Handle primitive types directly
|
|
switch valueType {
|
|
case luajit.TypeBoolean:
|
|
values[key] = state.ToBoolean(-1)
|
|
state.Pop(1)
|
|
continue
|
|
case luajit.TypeNumber:
|
|
values[key] = state.ToNumber(-1)
|
|
state.Pop(1)
|
|
continue
|
|
case luajit.TypeString:
|
|
values[key] = state.ToString(-1)
|
|
state.Pop(1)
|
|
continue
|
|
case luajit.TypeTable:
|
|
// For tables, use the existing conversion logic
|
|
default:
|
|
// Skip unsupported types
|
|
state.Pop(1)
|
|
continue
|
|
}
|
|
|
|
// Handle tables (arrays and maps)
|
|
if valueType == luajit.TypeTable {
|
|
// Check if it looks like an array first
|
|
arrLen := state.GetTableLength(-1)
|
|
if arrLen > 0 {
|
|
// Process as array
|
|
arr := make([]any, arrLen)
|
|
for i := 1; i <= arrLen; i++ {
|
|
state.PushNumber(float64(i))
|
|
state.GetTable(-2) // Get t[i]
|
|
|
|
switch state.GetType(-1) {
|
|
case luajit.TypeBoolean:
|
|
arr[i-1] = state.ToBoolean(-1)
|
|
case luajit.TypeNumber:
|
|
arr[i-1] = state.ToNumber(-1)
|
|
case luajit.TypeString:
|
|
arr[i-1] = state.ToString(-1)
|
|
default:
|
|
// For complex elements, try to convert
|
|
if val, err := state.ToValue(-1); err == nil {
|
|
arr[i-1] = val
|
|
}
|
|
}
|
|
|
|
state.Pop(1) // Pop value
|
|
}
|
|
values[key] = arr
|
|
state.Pop(1)
|
|
continue
|
|
}
|
|
|
|
// Try normal table conversion for non-array tables
|
|
if table, err := state.ToTable(-1); err == nil {
|
|
values[key] = table
|
|
}
|
|
}
|
|
|
|
// Pop value, leave key for next iteration
|
|
state.Pop(1)
|
|
}
|
|
|
|
// Pop the globals table
|
|
state.Pop(1)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get returns a configuration value by key
|
|
func (c *Config) Get(key string) any {
|
|
return c.values[key]
|
|
}
|
|
|
|
// GetString returns a string configuration value
|
|
func (c *Config) GetString(key string, defaultValue string) string {
|
|
value, ok := c.values[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
|
|
str, ok := value.(string)
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
// GetInt returns an integer configuration value
|
|
func (c *Config) GetInt(key string, defaultValue int) int {
|
|
value, ok := c.values[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
|
|
// Handle both int and float64 (which is what Lua numbers become in Go)
|
|
switch v := value.(type) {
|
|
case int:
|
|
return v
|
|
case float64:
|
|
return int(v)
|
|
default:
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
// GetFloat returns a float configuration value
|
|
func (c *Config) GetFloat(key string, defaultValue float64) float64 {
|
|
value, ok := c.values[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
|
|
// Handle both float64 and int
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return v
|
|
case int:
|
|
return float64(v)
|
|
default:
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
// GetBool returns a boolean configuration value
|
|
func (c *Config) GetBool(key string, defaultValue bool) bool {
|
|
value, ok := c.values[key]
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
|
|
boolValue, ok := value.(bool)
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
|
|
return boolValue
|
|
}
|
|
|
|
// GetMap returns a map configuration value
|
|
func (c *Config) GetMap(key string) map[string]any {
|
|
value, ok := c.values[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
table, ok := value.(map[string]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return table
|
|
}
|
|
|
|
// GetArray returns an array of values from a Lua array
|
|
func (c *Config) GetArray(key string) []any {
|
|
value := c.Get(key)
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
|
|
// Direct array
|
|
if arr, ok := value.([]any); ok {
|
|
return arr
|
|
}
|
|
|
|
// Arrays in Lua might also be represented as maps with an empty string key
|
|
valueMap, ok := value.(map[string]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// The array data is stored with an empty key
|
|
arr, ok := valueMap[""]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Check if it's a float64 array (common for Lua numeric arrays)
|
|
if floatArr, ok := arr.([]float64); ok {
|
|
// Convert to []any
|
|
result := make([]any, len(floatArr))
|
|
for i, v := range floatArr {
|
|
result[i] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Otherwise, try to return as is
|
|
anyArr, ok := arr.([]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return anyArr
|
|
}
|
|
|
|
// GetIntArray returns an array of integers from a Lua array
|
|
func (c *Config) GetIntArray(key string) []int {
|
|
value := c.Get(key)
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
|
|
// Direct array case
|
|
if arr, ok := value.([]any); ok {
|
|
result := make([]int, 0, len(arr))
|
|
for _, v := range arr {
|
|
if num, ok := v.(float64); ok {
|
|
result = append(result, int(num))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Arrays in Lua might also be represented as maps with an empty string key
|
|
valueMap, ok := value.(map[string]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// The array data is stored with an empty key
|
|
arr, ok := valueMap[""]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// For numeric arrays, LuaJIT returns []float64
|
|
floatArr, ok := arr.([]float64)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Convert to int slice
|
|
result := make([]int, len(floatArr))
|
|
for i, v := range floatArr {
|
|
result[i] = int(v)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetStringArray returns an array of strings from a Lua array
|
|
func (c *Config) GetStringArray(key string) []string {
|
|
arr := c.GetArray(key)
|
|
if arr == nil {
|
|
return nil
|
|
}
|
|
|
|
result := make([]string, 0, len(arr))
|
|
for _, v := range arr {
|
|
if str, ok := v.(string); ok {
|
|
result = append(result, str)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Values returns all configuration values
|
|
// Note: The returned map should not be modified
|
|
func (c *Config) Values() map[string]any {
|
|
return c.values
|
|
}
|
|
|
|
// Set sets a configuration value
|
|
func (c *Config) Set(key string, value any) {
|
|
c.values[key] = value
|
|
}
|