LuaJIT-to-Go/table.go
2025-03-27 21:42:58 -05:00

165 lines
3.4 KiB
Go

package luajit
/*
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdlib.h>
// Simple direct length check
size_t get_table_length(lua_State *L, int index) {
return lua_objlen(L, index);
}
*/
import "C"
import (
"fmt"
"strconv"
)
// GetTableLength returns the length of a table at the given index
func (s *State) GetTableLength(index int) int {
return int(C.get_table_length(s.L, C.int(index)))
}
// PushTable pushes a Go map onto the Lua stack as a table
func (s *State) PushTable(table map[string]any) error {
// Fast path for array tables
if arr, ok := table[""]; ok {
if floatArr, ok := arr.([]float64); ok {
s.CreateTable(len(floatArr), 0)
for i, v := range floatArr {
s.PushNumber(float64(i + 1))
s.PushNumber(v)
s.SetTable(-3)
}
return nil
} else if anyArr, ok := arr.([]any); ok {
s.CreateTable(len(anyArr), 0)
for i, v := range anyArr {
s.PushNumber(float64(i + 1))
if err := s.PushValue(v); err != nil {
return err
}
s.SetTable(-3)
}
return nil
}
}
// Regular table case - optimize capacity hint
s.CreateTable(0, len(table))
// Add each key-value pair directly
for k, v := range table {
s.PushString(k)
if err := s.PushValue(v); err != nil {
return err
}
s.SetTable(-3)
}
return nil
}
// ToTable converts a Lua table at the given index to a Go map
func (s *State) ToTable(index int) (map[string]any, error) {
absIdx := s.absIndex(index)
if !s.IsTable(absIdx) {
return nil, fmt.Errorf("value at index %d is not a table", index)
}
// Try to detect array-like tables first
length := s.GetTableLength(absIdx)
if length > 0 {
// Fast path for common array case
allNumbers := true
// Sample first few values to check if it's likely an array of numbers
for i := 1; i <= min(length, 5); i++ {
s.PushNumber(float64(i))
s.GetTable(absIdx)
if !s.IsNumber(-1) {
allNumbers = false
s.Pop(1)
break
}
s.Pop(1)
}
if allNumbers {
// Efficiently extract array values
array := make([]float64, length)
for i := 1; i <= length; i++ {
s.PushNumber(float64(i))
s.GetTable(absIdx)
array[i-1] = s.ToNumber(-1)
s.Pop(1)
}
// Return array as a special table with empty key
result := make(map[string]any, 1)
result[""] = array
return result, nil
}
}
// Handle regular table with pre-allocated capacity
table := make(map[string]any, max(length, 8))
// Iterate through all key-value pairs
s.PushNil() // Start iteration with nil key
for s.Next(absIdx) {
// Stack now has key at -2 and value at -1
// Convert key to string
var key string
keyType := s.GetType(-2)
switch keyType {
case TypeString:
key = s.ToString(-2)
case TypeNumber:
key = strconv.FormatFloat(s.ToNumber(-2), 'g', -1, 64)
default:
// Skip non-string/non-number keys
s.Pop(1) // Pop value, leave key for next iteration
continue
}
// Convert and store the value
value, err := s.ToValue(-1)
if err != nil {
s.Pop(2) // Pop both key and value
return nil, err
}
// Unwrap nested array tables
if m, ok := value.(map[string]any); ok {
if arr, ok := m[""]; ok {
value = arr
}
}
table[key] = value
s.Pop(1) // Pop value, leave key for next iteration
}
return table, nil
}
// Helper functions for min/max operations
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}