Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
f47d36eb8b | |||
516d66c2f2 | |||
4e59fc6e5c | |||
c22638b51f | |||
202664f635 | |||
f4bfff470f | |||
fc7958312d | |||
83134f3dbc | |||
3817f80dc4 | |||
7a9dfd47d3 | |||
6a2eb9df63 | |||
2bdf7cfd4a | |||
231ffb0d29 | |||
6b9e2a0e20 | |||
e58f9a6028 | |||
a2b4b1c927 | |||
44337fffe3 | |||
0756cabcaa | |||
656ac1a703 | |||
5774808064 | |||
875abee366 | |||
4ad87f81f3 | |||
9e5092acdb | |||
b83f77d7a6 | |||
29679349ef | |||
fed0c2ad34 | |||
faab0a2d08 | |||
f106dfd9ea | |||
936e4ccdc2 | |||
075b45768f | |||
13686b3e66 | |||
98ca857d73 | |||
143b9333c6 | |||
865ac8859f | |||
4dc266201f | |||
7c79616cac | |||
146b0a51db | |||
c74ad4bbc9 | |||
229884ba97 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,3 +21,5 @@
|
||||
go.work
|
||||
|
||||
.idea
|
||||
|
||||
bench/profile_results
|
||||
|
82
API.md
Normal file
82
API.md
Normal file
@ -0,0 +1,82 @@
|
||||
# API Quick Reference
|
||||
|
||||
## Core State
|
||||
- New(openLibs ...bool) *State
|
||||
- Close()
|
||||
- Cleanup()
|
||||
|
||||
## Stack
|
||||
- GetTop() int
|
||||
- SetTop(index int)
|
||||
- Pop(n int)
|
||||
- PushCopy(index int)
|
||||
- Remove(index int)
|
||||
|
||||
## Type Checks
|
||||
- GetType(index int) LuaType
|
||||
- IsNil/IsBoolean/IsNumber/IsString/IsTable/IsFunction(index int) bool
|
||||
|
||||
## Values
|
||||
- ToString/ToNumber/ToBoolean(index int) T
|
||||
- ToValue(index int) (any, error)
|
||||
- ToTable(index int) (any, error)
|
||||
- PushNil/PushBoolean/PushNumber/PushString/PushValue()
|
||||
|
||||
## Tables
|
||||
- NewTable()
|
||||
- CreateTable(narr, nrec int)
|
||||
- GetTable/SetTable(index int)
|
||||
- GetField/SetField(index int, key string)
|
||||
- GetFieldString/Number/Bool/Table(index int, key string, default T) T
|
||||
- GetTableLength(index int) int
|
||||
- Next(index int) bool
|
||||
- ForEachTableKV/ForEachArray(index int, fn func)
|
||||
- NewTableBuilder() *TableBuilder
|
||||
|
||||
## Functions
|
||||
- RegisterGoFunction(name string, fn GoFunction) error
|
||||
- UnregisterGoFunction(name string)
|
||||
- PushGoFunction(fn GoFunction) error
|
||||
- Call(nargs, nresults int) error
|
||||
- CallGlobal(name string, args ...any) ([]any, error)
|
||||
|
||||
## Globals
|
||||
- GetGlobal/SetGlobal(name string)
|
||||
|
||||
## Execution
|
||||
- LoadString/LoadFile(source string) error
|
||||
- DoString/DoFile(source string) error
|
||||
- Execute(code string) (int, error)
|
||||
- ExecuteWithResult(code string) (any, error)
|
||||
- BatchExecute(statements []string) error
|
||||
|
||||
## Bytecode
|
||||
- CompileBytecode(code, name string) ([]byte, error)
|
||||
- LoadBytecode(bytecode []byte, name string) error
|
||||
- RunBytecode() error
|
||||
- RunBytecodeWithResults(nresults int) error
|
||||
- LoadAndRunBytecode(bytecode []byte, name string) error
|
||||
- LoadAndRunBytecodeWithResults(bytecode []byte, name string, nresults int) error
|
||||
- CompileAndRun(code, name string) error
|
||||
|
||||
## Validation
|
||||
- CheckArgs(specs ...ArgSpec) error
|
||||
- CheckMinArgs/CheckExactArgs(n int) error
|
||||
- SafeToString/Number/Table(index int) (T, error)
|
||||
|
||||
## Error Handling
|
||||
- PushError(format string, args ...any) int
|
||||
- GetStackTrace() string
|
||||
- GetErrorInfo(context string) *LuaError
|
||||
- CreateLuaError(code int, context string) *LuaError
|
||||
|
||||
## Package
|
||||
- SetPackagePath/AddPackagePath(path string) error
|
||||
|
||||
## Metatable
|
||||
- SetMetatable(index int)
|
||||
- GetMetatable(index int) bool
|
||||
|
||||
## Constants
|
||||
- TypeNil/Boolean/Number/String/Table/Function/UserData/Thread
|
||||
- LUA_MINSTACK/MAXSTACK/REGISTRYINDEX/GLOBALSINDEX
|
392
DOCS.md
392
DOCS.md
@ -1,19 +1,11 @@
|
||||
# LuaJIT Go Wrapper API Documentation
|
||||
# API Documentation
|
||||
|
||||
## State Management
|
||||
|
||||
### NewSafe() *State
|
||||
Creates a new Lua state with stack safety enabled.
|
||||
```go
|
||||
L := luajit.NewSafe()
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
```
|
||||
|
||||
### New() *State
|
||||
Creates a new Lua state without stack safety checks.
|
||||
New creates a new Lua state with optional standard libraries; true if not specified
|
||||
```go
|
||||
L := luajit.New()
|
||||
L := luajit.New() // or luajit.New(false)
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
```
|
||||
@ -38,6 +30,18 @@ Returns the index of the top element in the stack.
|
||||
top := L.GetTop() // 0 for empty stack
|
||||
```
|
||||
|
||||
### SetTop(index int)
|
||||
Sets the stack top to a specific index.
|
||||
```go
|
||||
L.SetTop(2) // Truncate stack to 2 elements
|
||||
```
|
||||
|
||||
### PushCopy(index int)
|
||||
Pushes a copy of the value at the given index onto the stack.
|
||||
```go
|
||||
L.PushCopy(-1) // Duplicate the top element
|
||||
```
|
||||
|
||||
### Pop(n int)
|
||||
Removes n elements from the stack.
|
||||
```go
|
||||
@ -52,14 +56,6 @@ L.Remove(-1) // Remove top element
|
||||
L.Remove(1) // Remove first element
|
||||
```
|
||||
|
||||
### checkStack(n int) error
|
||||
Internal function that ensures there's enough space for n new elements.
|
||||
```go
|
||||
if err := L.checkStack(2); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
## Type Checks
|
||||
|
||||
### GetType(index int) LuaType
|
||||
@ -70,9 +66,12 @@ if L.GetType(-1) == TypeString {
|
||||
}
|
||||
```
|
||||
|
||||
### IsFunction(index int) bool
|
||||
### IsNil(index int) bool
|
||||
### IsBoolean(index int) bool
|
||||
### IsNumber(index int) bool
|
||||
### IsString(index int) bool
|
||||
### IsTable(index int) bool
|
||||
### IsUserData(index int) bool
|
||||
### IsFunction(index int) bool
|
||||
Type checking functions for specific Lua types.
|
||||
```go
|
||||
if L.IsTable(-1) {
|
||||
@ -100,8 +99,8 @@ Converts the value to a boolean.
|
||||
bool := L.ToBoolean(-1)
|
||||
```
|
||||
|
||||
### ToValue(index int) (interface{}, error)
|
||||
Converts any Lua value to its Go equivalent.
|
||||
### ToValue(index int) (any, error)
|
||||
Converts any Lua value to its Go equivalent with automatic type detection.
|
||||
```go
|
||||
val, err := L.ToValue(-1)
|
||||
if err != nil {
|
||||
@ -109,8 +108,8 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
### ToTable(index int) (map[string]interface{}, error)
|
||||
Converts a Lua table to a Go map.
|
||||
### ToTable(index int) (any, error)
|
||||
Converts a Lua table to optimal Go type; arrays or `map[string]any`.
|
||||
```go
|
||||
table, err := L.ToTable(-1)
|
||||
if err != nil {
|
||||
@ -118,6 +117,12 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
### GetTableLength(index int) int
|
||||
Returns the length of a table at the given index.
|
||||
```go
|
||||
length := L.GetTableLength(-1)
|
||||
```
|
||||
|
||||
## Value Pushing
|
||||
|
||||
### PushNil()
|
||||
@ -132,32 +137,111 @@ L.PushBoolean(true)
|
||||
L.PushNil()
|
||||
```
|
||||
|
||||
### PushValue(v interface{}) error
|
||||
Pushes any Go value onto the stack.
|
||||
### PushValue(v any) error
|
||||
Pushes any Go value onto the stack with comprehensive type support.
|
||||
```go
|
||||
err := L.PushValue(myValue)
|
||||
```
|
||||
|
||||
### PushTable(table map[string]interface{}) error
|
||||
Pushes a Go map as a Lua table.
|
||||
```go
|
||||
data := map[string]interface{}{
|
||||
// Supports: primitives, slices, maps with various type combinations
|
||||
err := L.PushValue(map[string]any{
|
||||
"key": "value",
|
||||
"numbers": []float64{1, 2, 3},
|
||||
}
|
||||
err := L.PushTable(data)
|
||||
})
|
||||
```
|
||||
|
||||
## Function Registration
|
||||
## Table Operations
|
||||
|
||||
### RegisterGoFunction(name string, fn GoFunction) error
|
||||
Registers a Go function that can be called from Lua.
|
||||
### CreateTable(narr, nrec int)
|
||||
Creates a new table with pre-allocated space.
|
||||
```go
|
||||
adder := func(s *State) int {
|
||||
L.CreateTable(10, 5) // Space for 10 array elements, 5 records
|
||||
```
|
||||
|
||||
### NewTable()
|
||||
Creates a new empty table and pushes it onto the stack.
|
||||
```go
|
||||
L.NewTable()
|
||||
```
|
||||
|
||||
### GetTable(index int)
|
||||
Gets a table field (t[k]) where t is at the given index and k is at the top of the stack.
|
||||
```go
|
||||
L.PushString("key")
|
||||
L.GetTable(-2) // Gets table["key"]
|
||||
```
|
||||
|
||||
### SetTable(index int)
|
||||
Sets a table field (t[k] = v) where t is at the given index, k is at -2, and v is at -1.
|
||||
```go
|
||||
L.PushString("key")
|
||||
L.PushString("value")
|
||||
L.SetTable(-3) // table["key"] = "value"
|
||||
```
|
||||
|
||||
### GetField(index int, key string)
|
||||
Gets a table field t[k] and pushes it onto the stack.
|
||||
```go
|
||||
L.GetField(-1, "name") // gets table.name
|
||||
```
|
||||
|
||||
### SetField(index int, key string)
|
||||
Sets a table field t[k] = v, where v is the value at the top of the stack.
|
||||
```go
|
||||
L.PushString("value")
|
||||
L.SetField(-2, "key") // table.key = "value"
|
||||
```
|
||||
|
||||
### Next(index int) bool
|
||||
Pops a key from the stack and pushes the next key-value pair from the table.
|
||||
```go
|
||||
L.PushNil() // Start iteration
|
||||
for L.Next(-2) {
|
||||
// Stack now has key at -2 and value at -1
|
||||
key := L.ToString(-2)
|
||||
value := L.ToString(-1)
|
||||
L.Pop(1) // Remove value, keep key for next iteration
|
||||
}
|
||||
```
|
||||
|
||||
### GetFieldString/Number/Bool/Table(index int, key string, default T) T
|
||||
Get typed fields from tables with default values.
|
||||
```go
|
||||
name := L.GetFieldString(-1, "name", "unknown")
|
||||
age := L.GetFieldNumber(-1, "age", 0)
|
||||
active := L.GetFieldBool(-1, "active", false)
|
||||
config, ok := L.GetFieldTable(-1, "config")
|
||||
```
|
||||
|
||||
### ForEachTableKV(index int, fn func(key, value string) bool)
|
||||
### ForEachArray(index int, fn func(i int, state *State) bool)
|
||||
Convenient iteration helpers.
|
||||
```go
|
||||
L.ForEachTableKV(-1, func(key, value string) bool {
|
||||
fmt.Printf("%s: %s\n", key, value)
|
||||
return true // continue iteration
|
||||
})
|
||||
```
|
||||
|
||||
## Function Registration and Calling
|
||||
|
||||
### GoFunction
|
||||
Type definition for Go functions callable from Lua.
|
||||
```go
|
||||
type GoFunction func(*State) int
|
||||
```
|
||||
|
||||
### PushGoFunction(fn GoFunction) error
|
||||
Wraps a Go function and pushes it onto the Lua stack.
|
||||
```go
|
||||
adder := func(s *luajit.State) int {
|
||||
sum := s.ToNumber(1) + s.ToNumber(2)
|
||||
s.PushNumber(sum)
|
||||
return 1
|
||||
}
|
||||
err := L.PushGoFunction(adder)
|
||||
```
|
||||
|
||||
### RegisterGoFunction(name string, fn GoFunction) error
|
||||
Registers a Go function as a global Lua function.
|
||||
```go
|
||||
err := L.RegisterGoFunction("add", adder)
|
||||
```
|
||||
|
||||
@ -167,23 +251,51 @@ Removes a previously registered function.
|
||||
L.UnregisterGoFunction("add")
|
||||
```
|
||||
|
||||
## Package Management
|
||||
|
||||
### SetPackagePath(path string) error
|
||||
Sets the Lua package.path variable.
|
||||
### Call(nargs, nresults int) error
|
||||
Calls a function with the given number of arguments and results.
|
||||
```go
|
||||
err := L.SetPackagePath("./?.lua;/usr/local/share/lua/5.1/?.lua")
|
||||
L.GetGlobal("myfunction")
|
||||
L.PushNumber(1)
|
||||
L.PushNumber(2)
|
||||
err := L.Call(2, 1) // Call with 2 args, expect 1 result
|
||||
```
|
||||
|
||||
### AddPackagePath(path string) error
|
||||
Adds a path to the existing package.path.
|
||||
### CallGlobal(name string, args ...any) ([]any, error)
|
||||
Calls a global function with arguments and returns all results.
|
||||
```go
|
||||
err := L.AddPackagePath("./modules/?.lua")
|
||||
results, err := L.CallGlobal("myfunction", 1, 2, "hello")
|
||||
```
|
||||
|
||||
## Global Operations
|
||||
|
||||
### GetGlobal(name string)
|
||||
Gets a global variable and pushes it onto the stack.
|
||||
```go
|
||||
L.GetGlobal("myGlobal")
|
||||
```
|
||||
|
||||
### SetGlobal(name string)
|
||||
Sets a global variable from the value at the top of the stack.
|
||||
```go
|
||||
L.PushNumber(42)
|
||||
L.SetGlobal("answer") // answer = 42
|
||||
```
|
||||
|
||||
## Code Execution
|
||||
|
||||
### DoString(str string) error
|
||||
### LoadString(code string) error
|
||||
Loads a Lua chunk from a string without executing it.
|
||||
```go
|
||||
err := L.LoadString("return 42")
|
||||
```
|
||||
|
||||
### LoadFile(filename string) error
|
||||
Loads a Lua chunk from a file without executing it.
|
||||
```go
|
||||
err := L.LoadFile("script.lua")
|
||||
```
|
||||
|
||||
### DoString(code string) error
|
||||
Executes a string of Lua code.
|
||||
```go
|
||||
err := L.DoString(`
|
||||
@ -199,52 +311,185 @@ Executes a Lua file.
|
||||
err := L.DoFile("script.lua")
|
||||
```
|
||||
|
||||
## Table Operations
|
||||
|
||||
### GetField(index int, key string)
|
||||
Gets a field from a table at the given index.
|
||||
### Execute(code string) (int, error)
|
||||
Executes a Lua string and returns the number of results left on the stack.
|
||||
```go
|
||||
L.GetField(-1, "name") // gets table.name
|
||||
nresults, err := L.Execute("return 1, 2, 3")
|
||||
// nresults would be 3
|
||||
```
|
||||
|
||||
### SetField(index int, key string)
|
||||
Sets a field in a table at the given index.
|
||||
### ExecuteWithResult(code string) (any, error)
|
||||
Executes a Lua string and returns the first result.
|
||||
```go
|
||||
L.PushString("value")
|
||||
L.SetField(-2, "key") // table.key = "value"
|
||||
result, err := L.ExecuteWithResult("return 'hello'")
|
||||
// result would be "hello"
|
||||
```
|
||||
|
||||
### GetGlobal(name string)
|
||||
Gets a global variable.
|
||||
### BatchExecute(statements []string) error
|
||||
Executes multiple statements as a single batch.
|
||||
```go
|
||||
L.GetGlobal("myGlobal")
|
||||
err := L.BatchExecute([]string{
|
||||
"x = 10",
|
||||
"y = 20",
|
||||
"result = x + y",
|
||||
})
|
||||
```
|
||||
|
||||
### SetGlobal(name string)
|
||||
Sets a global variable from the value at the top of the stack.
|
||||
## Bytecode Operations
|
||||
|
||||
### CompileBytecode(code string, name string) ([]byte, error)
|
||||
Compiles a Lua chunk to bytecode without executing it.
|
||||
```go
|
||||
L.PushNumber(42)
|
||||
L.SetGlobal("answer") // answer = 42
|
||||
bytecode, err := L.CompileBytecode("return 42", "test")
|
||||
```
|
||||
|
||||
### LoadBytecode(bytecode []byte, name string) error
|
||||
Loads precompiled bytecode without executing it.
|
||||
```go
|
||||
err := L.LoadBytecode(bytecode, "test")
|
||||
```
|
||||
|
||||
### RunBytecode() error
|
||||
Executes previously loaded bytecode with 0 results.
|
||||
```go
|
||||
err := L.RunBytecode()
|
||||
```
|
||||
|
||||
### RunBytecodeWithResults(nresults int) error
|
||||
Executes bytecode and keeps nresults on the stack.
|
||||
```go
|
||||
err := L.RunBytecodeWithResults(1)
|
||||
```
|
||||
|
||||
### LoadAndRunBytecode(bytecode []byte, name string) error
|
||||
Loads and executes bytecode.
|
||||
```go
|
||||
err := L.LoadAndRunBytecode(bytecode, "test")
|
||||
```
|
||||
|
||||
### LoadAndRunBytecodeWithResults(bytecode []byte, name string, nresults int) error
|
||||
Loads and executes bytecode, preserving results.
|
||||
```go
|
||||
err := L.LoadAndRunBytecodeWithResults(bytecode, "test", 1)
|
||||
```
|
||||
|
||||
### CompileAndRun(code string, name string) error
|
||||
Compiles and immediately executes Lua code.
|
||||
```go
|
||||
err := L.CompileAndRun("answer = 42", "test")
|
||||
```
|
||||
|
||||
## Package Path Operations
|
||||
|
||||
### SetPackagePath(path string) error
|
||||
Sets the Lua package.path.
|
||||
```go
|
||||
err := L.SetPackagePath("./?.lua;/usr/local/share/lua/5.1/?.lua")
|
||||
```
|
||||
|
||||
### AddPackagePath(path string) error
|
||||
Adds a path to package.path.
|
||||
```go
|
||||
err := L.AddPackagePath("./modules/?.lua")
|
||||
```
|
||||
|
||||
## Metatable Operations
|
||||
|
||||
### SetMetatable(index int)
|
||||
Sets the metatable for the value at the given index.
|
||||
```go
|
||||
L.SetMetatable(-1)
|
||||
```
|
||||
|
||||
### GetMetatable(index int) bool
|
||||
Gets the metatable for the value at the given index.
|
||||
```go
|
||||
if L.GetMetatable(-1) {
|
||||
// Metatable is now on stack
|
||||
L.Pop(1)
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### LuaError
|
||||
Error type containing both an error code and message.
|
||||
Enhanced error type with detailed context information.
|
||||
```go
|
||||
type LuaError struct {
|
||||
Code int
|
||||
Message string
|
||||
Code int
|
||||
Message string
|
||||
File string
|
||||
Line int
|
||||
StackTrace string
|
||||
Context string
|
||||
}
|
||||
```
|
||||
|
||||
### getStackTrace() string
|
||||
### GetStackTrace() string
|
||||
Gets the current Lua stack trace.
|
||||
```go
|
||||
trace := L.getStackTrace()
|
||||
trace := L.GetStackTrace()
|
||||
fmt.Println(trace)
|
||||
```
|
||||
|
||||
### GetErrorInfo(context string) *LuaError
|
||||
Extracts detailed error information from the Lua stack.
|
||||
```go
|
||||
err := L.GetErrorInfo("MyFunction")
|
||||
```
|
||||
|
||||
### CreateLuaError(code int, context string) *LuaError
|
||||
Creates a LuaError with full context information.
|
||||
```go
|
||||
err := L.CreateLuaError(status, "DoString")
|
||||
```
|
||||
|
||||
### PushError(format string, args ...any) int
|
||||
Pushes an error string and returns -1.
|
||||
```go
|
||||
return s.PushError("invalid argument: %v", arg)
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
### CheckArgs(specs ...ArgSpec) error
|
||||
Validates function arguments against specifications.
|
||||
```go
|
||||
err := s.CheckArgs(
|
||||
ArgSpec{Name: "name", Type: "string", Required: true, Check: CheckString},
|
||||
ArgSpec{Name: "age", Type: "number", Required: false, Check: CheckNumber},
|
||||
)
|
||||
```
|
||||
|
||||
### CheckMinArgs/CheckExactArgs(n int) error
|
||||
Argument count validation.
|
||||
```go
|
||||
if err := s.CheckMinArgs(2); err != nil {
|
||||
return s.PushError(err.Error())
|
||||
}
|
||||
```
|
||||
|
||||
### SafeToString/Number/Table(index int) (T, error)
|
||||
Safe value conversion with error handling.
|
||||
```go
|
||||
str, err := s.SafeToString(1)
|
||||
if err != nil {
|
||||
return s.PushError(err.Error())
|
||||
}
|
||||
```
|
||||
|
||||
## Table Building
|
||||
|
||||
### NewTableBuilder() *TableBuilder
|
||||
Creates a new table builder for fluent table construction.
|
||||
```go
|
||||
L.NewTableBuilder().
|
||||
SetString("name", "John").
|
||||
SetNumber("age", 30).
|
||||
SetBool("active", true).
|
||||
Build()
|
||||
```
|
||||
|
||||
## Thread Safety Notes
|
||||
|
||||
- The function registry is thread-safe
|
||||
@ -256,15 +501,14 @@ fmt.Println(trace)
|
||||
|
||||
Always pair state creation with cleanup:
|
||||
```go
|
||||
L := luajit.NewSafe()
|
||||
L := luajit.New()
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
```
|
||||
|
||||
Stack management in unsafe mode requires manual attention:
|
||||
Stack management requires manual attention:
|
||||
```go
|
||||
L := luajit.New()
|
||||
L.PushString("hello")
|
||||
// ... use the string
|
||||
L.Pop(1) // Clean up when done
|
||||
```
|
||||
```
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Sky
|
||||
Copyright (c) 2025 Sharkk, Skylear Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
284
README.md
284
README.md
@ -1,6 +1,6 @@
|
||||
# LuaJIT Go Wrapper
|
||||
|
||||
Hey there! This is a Go wrapper for LuaJIT that makes it easy to embed Lua in your Go applications. We've focused on making it both safe and fast, while keeping the API clean and intuitive.
|
||||
This is a Go wrapper for LuaJIT that makes it easy to embed Lua in your Go applications. We've focused on making it both performant and developer-friendly, with an API that feels natural to use.
|
||||
|
||||
## What's This For?
|
||||
|
||||
@ -18,55 +18,15 @@ First, grab the package:
|
||||
go get git.sharkk.net/Sky/LuaJIT-to-Go
|
||||
```
|
||||
|
||||
You'll need LuaJIT's development files, but don't worry - we include libraries for Windows and Linux in the vendor directory.
|
||||
|
||||
Here's the simplest thing you can do:
|
||||
```go
|
||||
L := luajit.NewSafe()
|
||||
L := luajit.New() // pass false to not load standard libs
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
|
||||
err := L.DoString(`print("Hey from Lua!")`)
|
||||
```
|
||||
|
||||
## Stack Safety: Choose Your Adventure
|
||||
|
||||
One of the key decisions you'll make is whether to use stack-safe mode. Here's what that means:
|
||||
|
||||
### Stack-Safe Mode (NewSafe())
|
||||
```go
|
||||
L := luajit.NewSafe()
|
||||
```
|
||||
Think of this as driving with guardrails. It's perfect when:
|
||||
- You're new to Lua or embedding scripting languages
|
||||
- You're writing a server or long-running application
|
||||
- You want to handle untrusted Lua code
|
||||
- You'd rather have slightly slower code than mysterious crashes
|
||||
|
||||
The safe mode will:
|
||||
- Prevent stack overflows
|
||||
- Check types more thoroughly
|
||||
- Clean up after messy Lua code
|
||||
- Give you better error messages
|
||||
|
||||
### Non-Stack-Safe Mode (New())
|
||||
```go
|
||||
L := luajit.New()
|
||||
```
|
||||
This is like taking off the training wheels. Use it when:
|
||||
- You know exactly how your Lua code behaves
|
||||
- You've profiled your application and need more speed
|
||||
- You're doing lots of rapid, simple Lua calls
|
||||
- You're writing performance-critical code
|
||||
|
||||
The unsafe mode:
|
||||
- Skips most safety checks
|
||||
- Runs noticeably faster
|
||||
- Gives you direct control over the stack
|
||||
- Can crash spectacularly if you make a mistake
|
||||
|
||||
Most applications should start with stack-safe mode and only switch to unsafe mode if profiling shows it's necessary.
|
||||
|
||||
## Working with Bytecode
|
||||
|
||||
Need even more performance? You can compile your Lua code to bytecode and reuse it:
|
||||
@ -74,53 +34,45 @@ Need even more performance? You can compile your Lua code to bytecode and reuse
|
||||
```go
|
||||
// Compile once
|
||||
bytecode, err := L.CompileBytecode(`
|
||||
local function calculate(x)
|
||||
return x * x + x + 1
|
||||
end
|
||||
return calculate(10)
|
||||
local function calculate(x)
|
||||
return x * x + x + 1
|
||||
end
|
||||
return calculate(10)
|
||||
`, "calc")
|
||||
|
||||
// Execute many times
|
||||
for i := 0; i < 1000; i++ {
|
||||
err := L.LoadBytecode(bytecode, "calc")
|
||||
err := L.LoadAndRunBytecode(bytecode, "calc")
|
||||
}
|
||||
|
||||
// Or do both at once
|
||||
err := L.CompileAndLoad(`return "hello"`, "greeting")
|
||||
err := L.CompileAndRun(`return "hello"`, "greeting")
|
||||
```
|
||||
|
||||
### When to Use Bytecode
|
||||
|
||||
Bytecode execution is consistently faster than direct execution:
|
||||
- Simple operations: 20-60% faster
|
||||
- String operations: Up to 60% speedup
|
||||
- Loop-heavy code: 10-15% improvement
|
||||
- Table operations: 10-15% faster
|
||||
|
||||
Some benchmark results on a typical system:
|
||||
```
|
||||
Operation Direct Exec Bytecode Exec
|
||||
----------------------------------------
|
||||
Simple Math 1.5M ops/sec 2.4M ops/sec
|
||||
String Ops 370K ops/sec 600K ops/sec
|
||||
Table Creation 127K ops/sec 146K ops/sec
|
||||
Benchmark Ops/sec Comparison
|
||||
----------------------------------------------------------------------------
|
||||
BenchmarkSimpleDoString 2,561,012 Base
|
||||
BenchmarkSimplePrecompiledBytecode 3,828,841 +49.5% faster
|
||||
BenchmarkFunctionCallDoString 2,021,098 Base
|
||||
BenchmarkFunctionCallPrecompiled 3,482,074 +72.3% faster
|
||||
BenchmarkLoopDoString 188,119 Base
|
||||
BenchmarkLoopPrecompiled 211,081 +12.2% faster
|
||||
BenchmarkTableOperationsDoString 84,086 Base
|
||||
BenchmarkTableOperationsPrecompiled 93,655 +11.4% faster
|
||||
BenchmarkComplexScript 33,133 Base
|
||||
BenchmarkComplexScriptPrecompiled 41,044 +23.9% faster
|
||||
```
|
||||
|
||||
Use bytecode when you:
|
||||
- Have code that runs frequently
|
||||
- Need maximum performance
|
||||
- Want to precompile your Lua code
|
||||
- Are distributing Lua code to many instances
|
||||
|
||||
## Registering Go Functions
|
||||
|
||||
Want to call Go code from Lua? Easy:
|
||||
Want to call Go code from Lua? It's straightforward:
|
||||
```go
|
||||
// This function adds two numbers and returns the result
|
||||
adder := func(s *luajit.State) int {
|
||||
sum := s.ToNumber(1) + s.ToNumber(2)
|
||||
s.PushNumber(sum)
|
||||
return 1 // we're returning one value
|
||||
sum := s.ToNumber(1) + s.ToNumber(2)
|
||||
s.PushNumber(sum)
|
||||
return 1 // we're returning one value
|
||||
}
|
||||
|
||||
L.RegisterGoFunction("add", adder)
|
||||
@ -131,45 +83,197 @@ Now in Lua:
|
||||
result = add(40, 2) -- result = 42
|
||||
```
|
||||
|
||||
### Function Validation
|
||||
|
||||
Validate arguments easily:
|
||||
```go
|
||||
calculator := func(s *luajit.State) int {
|
||||
if err := s.CheckArgs(
|
||||
luajit.ArgSpec{Name: "x", Type: "number", Required: true, Check: luajit.CheckNumber},
|
||||
luajit.ArgSpec{Name: "y", Type: "number", Required: true, Check: luajit.CheckNumber},
|
||||
); err != nil {
|
||||
return s.PushError(err.Error())
|
||||
}
|
||||
|
||||
result := s.ToNumber(1) + s.ToNumber(2)
|
||||
s.PushNumber(result)
|
||||
return 1
|
||||
}
|
||||
```
|
||||
|
||||
## Working with Tables
|
||||
|
||||
Lua tables are pretty powerful - they're like a mix of Go's maps and slices. We make it easy to work with them:
|
||||
Lua tables are powerful - they're like a mix of Go's maps and slices. We make it easy to work with them:
|
||||
|
||||
```go
|
||||
// Go → Lua
|
||||
stuff := map[string]interface{}{
|
||||
"name": "Arthur Dent",
|
||||
"age": 30,
|
||||
"items": []float64{1, 2, 3},
|
||||
stuff := map[string]any{
|
||||
"name": "Arthur Dent",
|
||||
"age": 30,
|
||||
"items": []float64{1, 2, 3},
|
||||
}
|
||||
L.PushTable(stuff)
|
||||
L.PushValue(stuff) // Handles all Go types automatically
|
||||
|
||||
// Lua → Go
|
||||
// Lua → Go with automatic type detection
|
||||
L.GetGlobal("some_table")
|
||||
result, err := L.ToTable(-1)
|
||||
result, err := L.ToTable(-1) // Returns optimal Go type (typed array, or map[string]any)
|
||||
```
|
||||
|
||||
### Table Builder
|
||||
|
||||
Build tables fluently:
|
||||
```go
|
||||
L.NewTableBuilder().
|
||||
SetString("name", "John").
|
||||
SetNumber("age", 30).
|
||||
SetBool("active", true).
|
||||
SetArray("scores", []any{95, 87, 92}).
|
||||
Build()
|
||||
```
|
||||
|
||||
### Table Field Access
|
||||
|
||||
Get fields with defaults:
|
||||
```go
|
||||
L.GetGlobal("config")
|
||||
host := L.GetFieldString(-1, "host", "localhost")
|
||||
port := L.GetFieldNumber(-1, "port", 8080)
|
||||
debug := L.GetFieldBool(-1, "debug", false)
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
We try to give you useful errors instead of mysterious panics:
|
||||
We provide useful errors instead of mysterious panics:
|
||||
```go
|
||||
if err := L.DoString("this isn't valid Lua!"); err != nil {
|
||||
if luaErr, ok := err.(*luajit.LuaError); ok {
|
||||
fmt.Printf("Oops: %s\n", luaErr.Message)
|
||||
}
|
||||
if luaErr, ok := err.(*luajit.LuaError); ok {
|
||||
fmt.Printf("Error in %s:%d - %s\n", luaErr.File, luaErr.Line, luaErr.Message)
|
||||
fmt.Printf("Stack trace:\n%s\n", luaErr.StackTrace)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## A Few Tips
|
||||
## Memory Management
|
||||
|
||||
- Always use those `defer L.Close()` and `defer L.Cleanup()` calls - they prevent memory leaks
|
||||
The wrapper uses bytecode buffer pooling to reduce allocations:
|
||||
|
||||
```go
|
||||
// Bytecode buffers are pooled and reused internally
|
||||
for i := 0; i < 1000; i++ {
|
||||
bytecode, _ := L.CompileBytecode(code, "test")
|
||||
// Buffer automatically returned to pool
|
||||
}
|
||||
```
|
||||
|
||||
Function pointers are managed safely:
|
||||
|
||||
```go
|
||||
// Functions are registered in a thread-safe registry
|
||||
L.RegisterGoFunction("myFunc", myGoFunc)
|
||||
defer L.Cleanup() // Cleans up all registered functions
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### State Management
|
||||
- Always use `defer L.Close()` and `defer L.Cleanup()` to prevent memory leaks
|
||||
- Each Lua state should stick to one goroutine
|
||||
- For concurrent stuff, create multiple states
|
||||
- For concurrent operations, create multiple states
|
||||
- You can share functions between states safely
|
||||
- Keep an eye on your stack in unsafe mode - it won't clean up after itself
|
||||
- Start with stack-safe mode and measure before optimizing
|
||||
- Keep an eye on your stack management - pop as many items as you push
|
||||
|
||||
### Bytecode Optimization
|
||||
- Use bytecode for frequently executed code paths
|
||||
- Consider compiling critical Lua code to bytecode at startup
|
||||
- For small scripts (< 1024 bytes), direct execution might be faster due to compilation overhead
|
||||
|
||||
### Type Conversion
|
||||
- Use `ToTable()` for automagic type detection and optimized Go arrays/maps
|
||||
- Use `PushValue()` for automagic Go-to-Lua conversion
|
||||
- Leverage typed field accessors for config-style tables
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Bytecode Serialization
|
||||
|
||||
You can serialize bytecode for distribution or caching:
|
||||
|
||||
```go
|
||||
// Compile once
|
||||
bytecode, _ := L.CompileBytecode(complexScript, "module")
|
||||
|
||||
// Save to file
|
||||
ioutil.WriteFile("module.luac", bytecode, 0644)
|
||||
|
||||
// Later, load from file
|
||||
bytecode, _ := ioutil.ReadFile("module.luac")
|
||||
L.LoadAndRunBytecode(bytecode, "module")
|
||||
```
|
||||
|
||||
### Closures and Upvalues
|
||||
|
||||
Bytecode properly preserves closures and upvalues:
|
||||
|
||||
```go
|
||||
code := `
|
||||
local counter = 0
|
||||
return function()
|
||||
counter = counter + 1
|
||||
return counter
|
||||
end
|
||||
`
|
||||
|
||||
bytecode, _ := L.CompileBytecode(code, "counter")
|
||||
L.LoadAndRunBytecodeWithResults(bytecode, "counter", 1)
|
||||
L.SetGlobal("increment")
|
||||
|
||||
// Later...
|
||||
results, _ := L.CallGlobal("increment") // Returns []any{1}
|
||||
results, _ = L.CallGlobal("increment") // Returns []any{2}
|
||||
```
|
||||
|
||||
### Batch Execution
|
||||
|
||||
Execute multiple statements efficiently:
|
||||
|
||||
```go
|
||||
statements := []string{
|
||||
"x = 10",
|
||||
"y = 20",
|
||||
"result = x + y",
|
||||
}
|
||||
err := L.BatchExecute(statements)
|
||||
```
|
||||
|
||||
### Package Path Management
|
||||
|
||||
Manage Lua module paths:
|
||||
|
||||
```go
|
||||
L.SetPackagePath("./?.lua;./modules/?.lua")
|
||||
L.AddPackagePath("./vendor/?.lua")
|
||||
```
|
||||
|
||||
### Type Conversion System
|
||||
|
||||
The wrapper includes a comprehensive type conversion system:
|
||||
|
||||
```go
|
||||
// Get typed values with automatic conversion
|
||||
value, ok := luajit.GetTypedValue[int](L, -1)
|
||||
global, ok := luajit.GetGlobalTyped[[]string](L, "myArray")
|
||||
|
||||
// Convert between compatible types
|
||||
result, ok := luajit.ConvertValue[map[string]int](someMap)
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- Use bytecode for repeated execution
|
||||
- Prefer `CallGlobal()` for simple function calls
|
||||
- Use typed field accessors for configuration parsing
|
||||
- Leverage automatic type detection in `ToTable()`
|
||||
- Pool your Lua states for high-throughput scenarios
|
||||
|
||||
## Need Help?
|
||||
|
||||
@ -177,4 +281,4 @@ Check out the tests in the repository - they're full of examples. If you're stuc
|
||||
|
||||
## License
|
||||
|
||||
MIT Licensed - do whatever you want with it!
|
||||
MIT Licensed - do whatever you want with it!
|
||||
|
171
bench/bench_profile.go
Normal file
171
bench/bench_profile.go
Normal file
@ -0,0 +1,171 @@
|
||||
package luajit_bench
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Profiling flags
|
||||
var (
|
||||
cpuProfile = flag.String("cpuprofile", "", "write cpu profile to `file`")
|
||||
memProfile = flag.String("memprofile", "", "write memory profile to `file`")
|
||||
memProfileGC = flag.Bool("memprofilegc", false, "force GC before writing memory profile")
|
||||
blockProfile = flag.String("blockprofile", "", "write block profile to `file`")
|
||||
mutexProfile = flag.String("mutexprofile", "", "write mutex profile to `file`")
|
||||
)
|
||||
|
||||
// setupTestMain configures profiling for benchmarks
|
||||
func setupTestMain() {
|
||||
// Make sure the flags are parsed
|
||||
if !flag.Parsed() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
// CPU profiling
|
||||
if *cpuProfile != "" {
|
||||
f, err := os.Create(*cpuProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create CPU profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to start CPU profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("CPU profiling enabled")
|
||||
}
|
||||
|
||||
// Block profiling (goroutine blocking)
|
||||
if *blockProfile != "" {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
fmt.Println("Block profiling enabled")
|
||||
}
|
||||
|
||||
// Mutex profiling (lock contention)
|
||||
if *mutexProfile != "" {
|
||||
runtime.SetMutexProfileFraction(1)
|
||||
fmt.Println("Mutex profiling enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// teardownTestMain completes profiling and writes output files
|
||||
func teardownTestMain() {
|
||||
// Stop CPU profile
|
||||
if *cpuProfile != "" {
|
||||
pprof.StopCPUProfile()
|
||||
fmt.Println("CPU profile written to", *cpuProfile)
|
||||
}
|
||||
|
||||
// Write memory profile
|
||||
if *memProfile != "" {
|
||||
f, err := os.Create(*memProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create memory profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Force garbage collection before writing memory profile if requested
|
||||
if *memProfileGC {
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write memory profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Memory profile written to", *memProfile)
|
||||
}
|
||||
|
||||
// Write block profile
|
||||
if *blockProfile != "" {
|
||||
f, err := os.Create(*blockProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create block profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := pprof.Lookup("block").WriteTo(f, 0); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write block profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Block profile written to", *blockProfile)
|
||||
}
|
||||
|
||||
// Write mutex profile
|
||||
if *mutexProfile != "" {
|
||||
f, err := os.Create(*mutexProfile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create mutex profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := pprof.Lookup("mutex").WriteTo(f, 0); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write mutex profile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Mutex profile written to", *mutexProfile)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMain is the entry point for all tests in this package
|
||||
func TestMain(m *testing.M) {
|
||||
setupTestMain()
|
||||
code := m.Run()
|
||||
teardownTestMain()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// MemStats captures a snapshot of memory statistics
|
||||
type MemStats struct {
|
||||
Alloc uint64
|
||||
TotalAlloc uint64
|
||||
Sys uint64
|
||||
Mallocs uint64
|
||||
Frees uint64
|
||||
HeapAlloc uint64
|
||||
}
|
||||
|
||||
// CaptureMemStats returns current memory statistics
|
||||
func CaptureMemStats() MemStats {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
return MemStats{
|
||||
Alloc: m.Alloc,
|
||||
TotalAlloc: m.TotalAlloc,
|
||||
Sys: m.Sys,
|
||||
Mallocs: m.Mallocs,
|
||||
Frees: m.Frees,
|
||||
HeapAlloc: m.HeapAlloc,
|
||||
}
|
||||
}
|
||||
|
||||
// TrackMemoryUsage runs fn and reports memory usage before and after
|
||||
func TrackMemoryUsage(b *testing.B, name string, fn func()) {
|
||||
b.Helper()
|
||||
|
||||
// Force GC before measurement
|
||||
runtime.GC()
|
||||
|
||||
// Capture memory stats before
|
||||
before := CaptureMemStats()
|
||||
|
||||
// Run the function
|
||||
fn()
|
||||
|
||||
// Force GC after measurement to get accurate stats
|
||||
runtime.GC()
|
||||
|
||||
// Capture memory stats after
|
||||
after := CaptureMemStats()
|
||||
|
||||
// Report stats
|
||||
b.ReportMetric(float64(after.Mallocs-before.Mallocs), name+"-mallocs")
|
||||
b.ReportMetric(float64(after.TotalAlloc-before.TotalAlloc)/float64(b.N), name+"-bytes/op")
|
||||
}
|
472
bench/bench_test.go
Normal file
472
bench/bench_test.go
Normal file
@ -0,0 +1,472 @@
|
||||
package luajit_bench
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
// BenchmarkSimpleDoString benchmarks direct execution of a simple expression
|
||||
func BenchmarkSimpleDoString(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := "local x = 1 + 1"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkSimpleCompileAndRun benchmarks compile and run of a simple expression
|
||||
func BenchmarkSimpleCompileAndRun(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := "local x = 1 + 1"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "compile-run", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.CompileAndRun(code, "simple"); err != nil {
|
||||
b.Fatalf("CompileAndRun failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkSimpleCompileLoadRun benchmarks compile, load, and run of a simple expression
|
||||
func BenchmarkSimpleCompileLoadRun(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := "local x = 1 + 1"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "compile-load-run", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bytecode, err := state.CompileBytecode(code, "simple")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkSimplePrecompiledBytecode benchmarks running precompiled bytecode
|
||||
func BenchmarkSimplePrecompiledBytecode(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := "local x = 1 + 1"
|
||||
bytecode, err := state.CompileBytecode(code, "simple")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "simple"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkFunctionCallDoString benchmarks direct execution of a function call
|
||||
func BenchmarkFunctionCallDoString(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Setup function
|
||||
setupCode := `
|
||||
function add(a, b)
|
||||
return a + b
|
||||
end
|
||||
`
|
||||
if err := state.DoString(setupCode); err != nil {
|
||||
b.Fatalf("Failed to set up function: %v", err)
|
||||
}
|
||||
|
||||
code := "local result = add(10, 20)"
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "func-dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkFunctionCallPrecompiled benchmarks precompiled function call
|
||||
func BenchmarkFunctionCallPrecompiled(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Setup function
|
||||
setupCode := `
|
||||
function add(a, b)
|
||||
return a + b
|
||||
end
|
||||
`
|
||||
if err := state.DoString(setupCode); err != nil {
|
||||
b.Fatalf("Failed to set up function: %v", err)
|
||||
}
|
||||
|
||||
code := "local result = add(10, 20)"
|
||||
bytecode, err := state.CompileBytecode(code, "call")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "func-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "call"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkLoopDoString benchmarks direct execution of a loop
|
||||
func BenchmarkLoopDoString(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := `
|
||||
local sum = 0
|
||||
for i = 1, 1000 do
|
||||
sum = sum + i
|
||||
end
|
||||
`
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "loop-dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkLoopPrecompiled benchmarks precompiled loop execution
|
||||
func BenchmarkLoopPrecompiled(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := `
|
||||
local sum = 0
|
||||
for i = 1, 1000 do
|
||||
sum = sum + i
|
||||
end
|
||||
`
|
||||
bytecode, err := state.CompileBytecode(code, "loop")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "loop-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "loop"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkTableOperationsDoString benchmarks direct execution of table operations
|
||||
func BenchmarkTableOperationsDoString(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := `
|
||||
local t = {}
|
||||
for i = 1, 100 do
|
||||
t[i] = i * 2
|
||||
end
|
||||
local sum = 0
|
||||
for i, v in ipairs(t) do
|
||||
sum = sum + v
|
||||
end
|
||||
`
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "table-dostring", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.DoString(code); err != nil {
|
||||
b.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkTableOperationsPrecompiled benchmarks precompiled table operations
|
||||
func BenchmarkTableOperationsPrecompiled(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := `
|
||||
local t = {}
|
||||
for i = 1, 100 do
|
||||
t[i] = i * 2
|
||||
end
|
||||
local sum = 0
|
||||
for i, v in ipairs(t) do
|
||||
sum = sum + v
|
||||
end
|
||||
`
|
||||
bytecode, err := state.CompileBytecode(code, "table")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "table-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "table"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkGoFunctionCall benchmarks calling a Go function from Lua
|
||||
func BenchmarkGoFunctionCall(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Register a simple Go function
|
||||
add := func(s *luajit.State) int {
|
||||
a := s.ToNumber(1)
|
||||
b := s.ToNumber(2)
|
||||
s.PushNumber(a + b)
|
||||
return 1
|
||||
}
|
||||
if err := state.RegisterGoFunction("add", add); err != nil {
|
||||
b.Fatalf("RegisterGoFunction failed: %v", err)
|
||||
}
|
||||
|
||||
code := "local result = add(10, 20)"
|
||||
bytecode, err := state.CompileBytecode(code, "gofunc")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "go-func-call", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadAndRunBytecode(bytecode, "gofunc"); err != nil {
|
||||
b.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkComplexScript benchmarks a more complex script
|
||||
func BenchmarkComplexScript(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := `
|
||||
-- Define a simple class
|
||||
local Class = {}
|
||||
Class.__index = Class
|
||||
|
||||
function Class.new(x, y)
|
||||
local self = setmetatable({}, Class)
|
||||
self.x = x or 0
|
||||
self.y = y or 0
|
||||
return self
|
||||
end
|
||||
|
||||
function Class:move(dx, dy)
|
||||
self.x = self.x + dx
|
||||
self.y = self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
function Class:getPosition()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
-- Create instances and operate on them
|
||||
local instances = {}
|
||||
for i = 1, 50 do
|
||||
instances[i] = Class.new(i, i*2)
|
||||
end
|
||||
|
||||
local result = 0
|
||||
for i, obj in ipairs(instances) do
|
||||
obj:move(i, -i)
|
||||
local x, y = obj:getPosition()
|
||||
result = result + x + y
|
||||
end
|
||||
|
||||
return result
|
||||
`
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "complex-script", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := state.ExecuteWithResult(code); err != nil {
|
||||
b.Fatalf("ExecuteWithResult failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkComplexScriptPrecompiled benchmarks a precompiled complex script
|
||||
func BenchmarkComplexScriptPrecompiled(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := `
|
||||
-- Define a simple class
|
||||
local Class = {}
|
||||
Class.__index = Class
|
||||
|
||||
function Class.new(x, y)
|
||||
local self = setmetatable({}, Class)
|
||||
self.x = x or 0
|
||||
self.y = y or 0
|
||||
return self
|
||||
end
|
||||
|
||||
function Class:move(dx, dy)
|
||||
self.x = self.x + dx
|
||||
self.y = self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
function Class:getPosition()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
-- Create instances and operate on them
|
||||
local instances = {}
|
||||
for i = 1, 50 do
|
||||
instances[i] = Class.new(i, i*2)
|
||||
end
|
||||
|
||||
local result = 0
|
||||
for i, obj in ipairs(instances) do
|
||||
obj:move(i, -i)
|
||||
local x, y = obj:getPosition()
|
||||
result = result + x + y
|
||||
end
|
||||
|
||||
return result
|
||||
`
|
||||
bytecode, err := state.CompileBytecode(code, "complex")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "complex-precompiled", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadBytecode(bytecode, "complex"); err != nil {
|
||||
b.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
b.Fatalf("RunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
state.Pop(1) // Pop the result
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkMultipleExecutions benchmarks executing the same bytecode multiple times
|
||||
func BenchmarkMultipleExecutions(b *testing.B) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Setup a stateful environment
|
||||
setupCode := `
|
||||
counter = 0
|
||||
function increment(amount)
|
||||
counter = counter + (amount or 1)
|
||||
return counter
|
||||
end
|
||||
`
|
||||
if err := state.DoString(setupCode); err != nil {
|
||||
b.Fatalf("Failed to set up environment: %v", err)
|
||||
}
|
||||
|
||||
// Compile the function call
|
||||
code := "return increment(5)"
|
||||
bytecode, err := state.CompileBytecode(code, "increment")
|
||||
if err != nil {
|
||||
b.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "multiple-executions", func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := state.LoadBytecode(bytecode, "increment"); err != nil {
|
||||
b.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
b.Fatalf("RunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
state.Pop(1) // Pop the result
|
||||
}
|
||||
})
|
||||
}
|
138
bench/ezbench_test.go
Normal file
138
bench/ezbench_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
package luajit_bench
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
var benchCases = []struct {
|
||||
name string
|
||||
code string
|
||||
}{
|
||||
{
|
||||
name: "SimpleAddition",
|
||||
code: `return 1 + 1`,
|
||||
},
|
||||
{
|
||||
name: "LoopSum",
|
||||
code: `
|
||||
local sum = 0
|
||||
for i = 1, 1000 do
|
||||
sum = sum + i
|
||||
end
|
||||
return sum
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "FunctionCall",
|
||||
code: `
|
||||
local result = 0
|
||||
for i = 1, 100 do
|
||||
result = result + i
|
||||
end
|
||||
return result
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "TableCreation",
|
||||
code: `
|
||||
local t = {}
|
||||
for i = 1, 100 do
|
||||
t[i] = i * 2
|
||||
end
|
||||
return t[50]
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "StringOperations",
|
||||
code: `
|
||||
local s = "hello"
|
||||
for i = 1, 10 do
|
||||
s = s .. " world"
|
||||
end
|
||||
return #s
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
func BenchmarkLuaDirectExecution(b *testing.B) {
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
L := luajit.New()
|
||||
if L == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
|
||||
// First verify we can execute the code
|
||||
if err := L.DoString(bc.code); err != nil {
|
||||
b.Fatalf("Failed to execute test code: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
TrackMemoryUsage(b, "direct-"+bc.name, func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Execute string and get results
|
||||
nresults, err := L.Execute(bc.code)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to execute code: %v", err)
|
||||
}
|
||||
L.Pop(nresults) // Clean up any results
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLuaBytecodeExecution(b *testing.B) {
|
||||
// First compile all bytecode
|
||||
bytecodes := make(map[string][]byte)
|
||||
for _, bc := range benchCases {
|
||||
L := luajit.New()
|
||||
if L == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Cleanup()
|
||||
|
||||
bytecode, err := L.CompileBytecode(bc.code, bc.name)
|
||||
if err != nil {
|
||||
L.Close()
|
||||
b.Fatalf("Error compiling bytecode for %s: %v", bc.name, err)
|
||||
}
|
||||
bytecodes[bc.name] = bytecode
|
||||
L.Close()
|
||||
}
|
||||
|
||||
for _, bc := range benchCases {
|
||||
b.Run(bc.name, func(b *testing.B) {
|
||||
L := luajit.New()
|
||||
if L == nil {
|
||||
b.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
|
||||
bytecode := bytecodes[bc.name]
|
||||
|
||||
// First verify we can execute the bytecode
|
||||
if err := L.LoadAndRunBytecodeWithResults(bytecode, bc.name, 1); err != nil {
|
||||
b.Fatalf("Failed to execute test bytecode: %v", err)
|
||||
}
|
||||
L.Pop(1) // Clean up the result
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(int64(len(bytecode))) // Track bytecode size in benchmarks
|
||||
|
||||
TrackMemoryUsage(b, "bytecode-"+bc.name, func() {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := L.LoadAndRunBytecode(bytecode, bc.name); err != nil {
|
||||
b.Fatalf("Error executing bytecode: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
78
bench/profile.sh
Executable file
78
bench/profile.sh
Executable file
@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Easy script to run benchmarks with profiling enabled
|
||||
# Usage: ./profile_benchmarks.sh [benchmark_pattern]
|
||||
|
||||
set -e
|
||||
|
||||
# Default values
|
||||
BENCHMARK=${1:-"."}
|
||||
OUTPUT_DIR="./profile_results"
|
||||
CPU_PROFILE="$OUTPUT_DIR/cpu.prof"
|
||||
MEM_PROFILE="$OUTPUT_DIR/mem.prof"
|
||||
BLOCK_PROFILE="$OUTPUT_DIR/block.prof"
|
||||
MUTEX_PROFILE="$OUTPUT_DIR/mutex.prof"
|
||||
TRACE_FILE="$OUTPUT_DIR/trace.out"
|
||||
HTML_OUTPUT="$OUTPUT_DIR/profile_report.html"
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "Running benchmarks with profiling enabled..."
|
||||
|
||||
# Run benchmarks with profiling flags
|
||||
go test -bench="$BENCHMARK" -benchmem -cpuprofile="$CPU_PROFILE" -memprofile="$MEM_PROFILE" -blockprofile="$BLOCK_PROFILE" -mutexprofile="$MUTEX_PROFILE" -count=5 -timeout=30m
|
||||
|
||||
echo "Generating CPU profile analysis..."
|
||||
go tool pprof -http=":1880" -output="$OUTPUT_DIR/cpu_graph.svg" "$CPU_PROFILE"
|
||||
|
||||
echo "Generating memory profile analysis..."
|
||||
go tool pprof -http=":1880" -output="$OUTPUT_DIR/mem_graph.svg" "$MEM_PROFILE"
|
||||
|
||||
# Generate a simple HTML report
|
||||
cat > "$HTML_OUTPUT" << EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>LuaJIT Benchmark Profiling Results</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
h1, h2 { color: #333; }
|
||||
.profile { margin-bottom: 30px; }
|
||||
img { max-width: 100%; border: 1px solid #ddd; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>LuaJIT Benchmark Profiling Results</h1>
|
||||
<p>Generated on: $(date)</p>
|
||||
|
||||
<div class="profile">
|
||||
<h2>CPU Profile</h2>
|
||||
<img src="cpu_graph.svg" alt="CPU Profile Graph">
|
||||
<p>Command to explore: <code>go tool pprof $CPU_PROFILE</code></p>
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<h2>Memory Profile</h2>
|
||||
<img src="mem_graph.svg" alt="Memory Profile Graph">
|
||||
<p>Command to explore: <code>go tool pprof $MEM_PROFILE</code></p>
|
||||
</div>
|
||||
|
||||
<div class="profile">
|
||||
<h2>Tips for Profile Analysis</h2>
|
||||
<ul>
|
||||
<li>Use <code>go tool pprof -http=:8080 $CPU_PROFILE</code> for interactive web UI</li>
|
||||
<li>Use <code>top10</code> in pprof to see the top 10 functions by CPU/memory usage</li>
|
||||
<li>Use <code>list FunctionName</code> to see line-by-line stats for a specific function</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo "Profiling complete! Results available in $OUTPUT_DIR"
|
||||
echo "View the HTML report at $HTML_OUTPUT"
|
||||
echo ""
|
||||
echo "For detailed interactive analysis, run:"
|
||||
echo " go tool pprof -http=:1880 $CPU_PROFILE # For CPU profile"
|
||||
echo " go tool pprof -http=:1880 $MEM_PROFILE # For memory profile"
|
@ -1,148 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
type benchCase struct {
|
||||
name string
|
||||
code string
|
||||
}
|
||||
|
||||
var cases = []benchCase{
|
||||
{
|
||||
name: "Simple Addition",
|
||||
code: `return 1 + 1`,
|
||||
},
|
||||
{
|
||||
name: "Loop Sum",
|
||||
code: `
|
||||
local sum = 0
|
||||
for i = 1, 1000 do
|
||||
sum = sum + i
|
||||
end
|
||||
return sum
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Function Call",
|
||||
code: `
|
||||
local result = 0
|
||||
for i = 1, 100 do
|
||||
result = result + i
|
||||
end
|
||||
return result
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Table Creation",
|
||||
code: `
|
||||
local t = {}
|
||||
for i = 1, 100 do
|
||||
t[i] = i * 2
|
||||
end
|
||||
return t[50]
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "String Operations",
|
||||
code: `
|
||||
local s = "hello"
|
||||
for i = 1, 10 do
|
||||
s = s .. " world"
|
||||
end
|
||||
return #s
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
func runBenchmark(L *luajit.State, code string, duration time.Duration) (time.Duration, int64) {
|
||||
start := time.Now()
|
||||
deadline := start.Add(duration)
|
||||
var ops int64
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
if err := L.DoString(code); err != nil {
|
||||
fmt.Printf("Error executing code: %v\n", err)
|
||||
return 0, 0
|
||||
}
|
||||
L.Pop(1)
|
||||
ops++
|
||||
}
|
||||
|
||||
return time.Since(start), ops
|
||||
}
|
||||
|
||||
func runBytecodeTest(L *luajit.State, code string, duration time.Duration) (time.Duration, int64) {
|
||||
// First compile the bytecode
|
||||
bytecode, err := L.CompileBytecode(code, "bench")
|
||||
if err != nil {
|
||||
fmt.Printf("Error compiling bytecode: %v\n", err)
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
deadline := start.Add(duration)
|
||||
var ops int64
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
if err := L.LoadBytecode(bytecode, "bench"); err != nil {
|
||||
fmt.Printf("Error executing bytecode: %v\n", err)
|
||||
return 0, 0
|
||||
}
|
||||
ops++
|
||||
}
|
||||
|
||||
return time.Since(start), ops
|
||||
}
|
||||
|
||||
func benchmarkCase(newState func() *luajit.State, bc benchCase) {
|
||||
fmt.Printf("\n%s:\n", bc.name)
|
||||
|
||||
// Direct execution benchmark
|
||||
L := newState()
|
||||
if L == nil {
|
||||
fmt.Printf(" Failed to create Lua state\n")
|
||||
return
|
||||
}
|
||||
execTime, ops := runBenchmark(L, bc.code, 2*time.Second)
|
||||
L.Close()
|
||||
if ops > 0 {
|
||||
opsPerSec := float64(ops) / execTime.Seconds()
|
||||
fmt.Printf(" Direct: %.0f ops/sec\n", opsPerSec)
|
||||
}
|
||||
|
||||
// Bytecode execution benchmark
|
||||
L = newState()
|
||||
if L == nil {
|
||||
fmt.Printf(" Failed to create Lua state\n")
|
||||
return
|
||||
}
|
||||
execTime, ops = runBytecodeTest(L, bc.code, 2*time.Second)
|
||||
L.Close()
|
||||
if ops > 0 {
|
||||
opsPerSec := float64(ops) / execTime.Seconds()
|
||||
fmt.Printf(" Bytecode: %.0f ops/sec\n", opsPerSec)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
modes := []struct {
|
||||
name string
|
||||
newState func() *luajit.State
|
||||
}{
|
||||
{"Safe", luajit.NewSafe},
|
||||
{"Unsafe", luajit.New},
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
fmt.Printf("\n=== %s Mode ===\n", mode.name)
|
||||
|
||||
for _, c := range cases {
|
||||
benchmarkCase(mode.newState, c)
|
||||
}
|
||||
}
|
||||
}
|
72
builder.go
Normal file
72
builder.go
Normal file
@ -0,0 +1,72 @@
|
||||
package luajit
|
||||
|
||||
// TableBuilder provides a fluent interface for building Lua tables
|
||||
type TableBuilder struct {
|
||||
state *State
|
||||
index int
|
||||
}
|
||||
|
||||
// NewTableBuilder creates a new table and returns a builder
|
||||
func (s *State) NewTableBuilder() *TableBuilder {
|
||||
s.NewTable()
|
||||
return &TableBuilder{
|
||||
state: s,
|
||||
index: s.GetTop(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetString sets a string field
|
||||
func (tb *TableBuilder) SetString(key, value string) *TableBuilder {
|
||||
tb.state.PushString(value)
|
||||
tb.state.SetField(tb.index, key)
|
||||
return tb
|
||||
}
|
||||
|
||||
// SetNumber sets a number field
|
||||
func (tb *TableBuilder) SetNumber(key string, value float64) *TableBuilder {
|
||||
tb.state.PushNumber(value)
|
||||
tb.state.SetField(tb.index, key)
|
||||
return tb
|
||||
}
|
||||
|
||||
// SetBool sets a boolean field
|
||||
func (tb *TableBuilder) SetBool(key string, value bool) *TableBuilder {
|
||||
tb.state.PushBoolean(value)
|
||||
tb.state.SetField(tb.index, key)
|
||||
return tb
|
||||
}
|
||||
|
||||
// SetNil sets a nil field
|
||||
func (tb *TableBuilder) SetNil(key string) *TableBuilder {
|
||||
tb.state.PushNil()
|
||||
tb.state.SetField(tb.index, key)
|
||||
return tb
|
||||
}
|
||||
|
||||
// SetTable sets a table field
|
||||
func (tb *TableBuilder) SetTable(key string, value any) *TableBuilder {
|
||||
if err := tb.state.PushValue(value); err == nil {
|
||||
tb.state.SetField(tb.index, key)
|
||||
}
|
||||
return tb
|
||||
}
|
||||
|
||||
// SetArray sets an array field
|
||||
func (tb *TableBuilder) SetArray(key string, values []any) *TableBuilder {
|
||||
tb.state.CreateTable(len(values), 0)
|
||||
for i, v := range values {
|
||||
tb.state.PushNumber(float64(i + 1))
|
||||
if err := tb.state.PushValue(v); err == nil {
|
||||
tb.state.SetTable(-3)
|
||||
} else {
|
||||
tb.state.Pop(1)
|
||||
}
|
||||
}
|
||||
tb.state.SetField(tb.index, key)
|
||||
return tb
|
||||
}
|
||||
|
||||
// Build finalizes the table (no-op, table is already on stack)
|
||||
func (tb *TableBuilder) Build() {
|
||||
// Table is already on the stack at tb.index
|
||||
}
|
260
bytecode.go
260
bytecode.go
@ -7,99 +7,128 @@ package luajit
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const unsigned char *buf;
|
||||
size_t size;
|
||||
const char *name;
|
||||
const unsigned char *buf;
|
||||
size_t size;
|
||||
const char *name;
|
||||
} BytecodeReader;
|
||||
|
||||
static const char *bytecode_reader(lua_State *L, void *ud, size_t *size) {
|
||||
BytecodeReader *r = (BytecodeReader *)ud;
|
||||
(void)L; // unused
|
||||
if (r->size == 0) return NULL;
|
||||
*size = r->size;
|
||||
r->size = 0; // Only read once
|
||||
return (const char *)r->buf;
|
||||
}
|
||||
|
||||
static int load_bytecode_chunk(lua_State *L, const unsigned char *buf, size_t len, const char *name) {
|
||||
BytecodeReader reader = {buf, len, name};
|
||||
return lua_load(L, bytecode_reader, &reader, name);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned char *buf;
|
||||
size_t len;
|
||||
} BytecodeWriter;
|
||||
unsigned char *buf;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} BytecodeBuffer;
|
||||
|
||||
int bytecode_writer(lua_State *L, const void *p, size_t sz, void *ud) {
|
||||
BytecodeWriter *w = (BytecodeWriter *)ud;
|
||||
unsigned char *newbuf;
|
||||
(void)L; // unused
|
||||
const char *bytecode_reader(lua_State *L, void *ud, size_t *size) {
|
||||
BytecodeReader *r = (BytecodeReader *)ud;
|
||||
(void)L; // unused
|
||||
if (r->size == 0) return NULL;
|
||||
*size = r->size;
|
||||
r->size = 0; // Only read once
|
||||
return (const char *)r->buf;
|
||||
}
|
||||
|
||||
newbuf = (unsigned char *)realloc(w->buf, w->len + sz);
|
||||
if (newbuf == NULL) return 1;
|
||||
int load_bytecode(lua_State *L, const unsigned char *buf, size_t len, const char *name) {
|
||||
BytecodeReader reader = {buf, len, name};
|
||||
return lua_load(L, bytecode_reader, &reader, name);
|
||||
}
|
||||
|
||||
memcpy(newbuf + w->len, p, sz);
|
||||
w->buf = newbuf;
|
||||
w->len += sz;
|
||||
return 0;
|
||||
// Optimized bytecode writer with pre-allocated buffer
|
||||
int buffered_bytecode_writer(lua_State *L, const void *p, size_t sz, void *ud) {
|
||||
BytecodeBuffer *buf = (BytecodeBuffer *)ud;
|
||||
|
||||
// Grow buffer if needed (double size to avoid frequent reallocs)
|
||||
if (buf->size + sz > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity;
|
||||
while (new_capacity < buf->size + sz) {
|
||||
new_capacity *= 2;
|
||||
}
|
||||
unsigned char *newbuf = realloc(buf->buf, new_capacity);
|
||||
if (newbuf == NULL) return 1;
|
||||
buf->buf = newbuf;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy(buf->buf + buf->size, p, sz);
|
||||
buf->size += sz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Combined load and run bytecode in a single call
|
||||
int load_and_run_bytecode(lua_State *L, const unsigned char *buf, size_t len,
|
||||
const char *name, int nresults) {
|
||||
BytecodeReader reader = {buf, len, name};
|
||||
int status = lua_load(L, bytecode_reader, &reader, name);
|
||||
if (status != 0) return status;
|
||||
|
||||
return lua_pcall(L, 0, nresults, 0);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func (s *State) compileBytecodeUnsafe(code string, name string) ([]byte, error) {
|
||||
// First load the string but don't execute it
|
||||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
// bytecodeBuffer wraps []byte to avoid boxing allocations in sync.Pool
|
||||
type bytecodeBuffer struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
// Buffer pool for bytecode generation
|
||||
var bytecodeBufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &bytecodeBuffer{data: make([]byte, 0, 1024)}
|
||||
},
|
||||
}
|
||||
|
||||
if C.luaL_loadstring(s.L, ccode) != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(C.lua_status(s.L)),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1)
|
||||
// CompileBytecode compiles a Lua chunk to bytecode without executing it
|
||||
func (s *State) CompileBytecode(code string, name string) ([]byte, error) {
|
||||
if err := s.LoadString(code); err != nil {
|
||||
return nil, fmt.Errorf("failed to load string: %w", err)
|
||||
}
|
||||
|
||||
// Set up writer
|
||||
var writer C.BytecodeWriter
|
||||
writer.buf = nil
|
||||
writer.len = 0
|
||||
// Always use C memory for dump operation to avoid cgo pointer issues
|
||||
cbuf := C.BytecodeBuffer{
|
||||
buf: (*C.uchar)(C.malloc(1024)),
|
||||
size: 0,
|
||||
capacity: 1024,
|
||||
}
|
||||
if cbuf.buf == nil {
|
||||
return nil, fmt.Errorf("failed to allocate initial buffer")
|
||||
}
|
||||
|
||||
// Dump the function to bytecode
|
||||
if C.lua_dump(s.L, (*[0]byte)(C.bytecode_writer), unsafe.Pointer(&writer)) != 0 {
|
||||
if writer.buf != nil {
|
||||
C.free(unsafe.Pointer(writer.buf))
|
||||
}
|
||||
s.Pop(1)
|
||||
return nil, fmt.Errorf("failed to dump bytecode")
|
||||
status := C.lua_dump(s.L, (*[0]byte)(unsafe.Pointer(C.buffered_bytecode_writer)), unsafe.Pointer(&cbuf))
|
||||
|
||||
s.Pop(1) // Remove the function from stack
|
||||
|
||||
if status != 0 {
|
||||
C.free(unsafe.Pointer(cbuf.buf))
|
||||
return nil, fmt.Errorf("failed to dump bytecode: status %d", status)
|
||||
}
|
||||
|
||||
// Copy to Go slice
|
||||
bytecode := C.GoBytes(unsafe.Pointer(writer.buf), C.int(writer.len))
|
||||
|
||||
// Clean up
|
||||
if writer.buf != nil {
|
||||
C.free(unsafe.Pointer(writer.buf))
|
||||
// Copy to Go memory and free C buffer
|
||||
var result []byte
|
||||
if cbuf.size > 0 {
|
||||
result = C.GoBytes(unsafe.Pointer(cbuf.buf), C.int(cbuf.size))
|
||||
}
|
||||
s.Pop(1) // Remove the function
|
||||
C.free(unsafe.Pointer(cbuf.buf))
|
||||
|
||||
return bytecode, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *State) loadBytecodeUnsafe(bytecode []byte, name string) error {
|
||||
// LoadBytecode loads precompiled bytecode without executing it
|
||||
func (s *State) LoadBytecode(bytecode []byte, name string) error {
|
||||
if len(bytecode) == 0 {
|
||||
return fmt.Errorf("empty bytecode")
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
// Load the bytecode
|
||||
status := C.load_bytecode_chunk(
|
||||
status := C.load_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
@ -107,53 +136,96 @@ func (s *State) loadBytecodeUnsafe(bytecode []byte, name string) error {
|
||||
)
|
||||
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
s.Pop(1)
|
||||
return fmt.Errorf("failed to load bytecode: %w", err)
|
||||
}
|
||||
|
||||
// Execute the loaded chunk
|
||||
if err := s.safeCall(func() C.int {
|
||||
return C.lua_pcall(s.L, 0, 0, 0)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to execute bytecode: %w", err)
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("LoadBytecode(%s)", name))
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompileBytecode compiles a Lua chunk to bytecode without executing it
|
||||
func (s *State) CompileBytecode(code string, name string) ([]byte, error) {
|
||||
if s.safeStack {
|
||||
return stackGuardValue[[]byte](s, func() ([]byte, error) {
|
||||
return s.compileBytecodeUnsafe(code, name)
|
||||
})
|
||||
}
|
||||
return s.compileBytecodeUnsafe(code, name)
|
||||
// RunBytecode executes previously loaded bytecode with 0 results
|
||||
func (s *State) RunBytecode() error {
|
||||
return s.RunBytecodeWithResults(0)
|
||||
}
|
||||
|
||||
// LoadBytecode loads precompiled bytecode and executes it
|
||||
func (s *State) LoadBytecode(bytecode []byte, name string) error {
|
||||
if s.safeStack {
|
||||
return stackGuardErr(s, func() error {
|
||||
return s.loadBytecodeUnsafe(bytecode, name)
|
||||
})
|
||||
// RunBytecodeWithResults executes bytecode and keeps nresults on the stack
|
||||
func (s *State) RunBytecodeWithResults(nresults int) error {
|
||||
status := C.lua_pcall(s.L, 0, C.int(nresults), 0)
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("RunBytecode(nresults=%d)", nresults))
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
return s.loadBytecodeUnsafe(bytecode, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function to compile and immediately load/execute bytecode
|
||||
func (s *State) CompileAndLoad(code string, name string) error {
|
||||
// LoadAndRunBytecode loads and executes bytecode in a single CGO transition
|
||||
func (s *State) LoadAndRunBytecode(bytecode []byte, name string) error {
|
||||
if len(bytecode) == 0 {
|
||||
return fmt.Errorf("empty bytecode")
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
status := C.load_and_run_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
cname,
|
||||
0,
|
||||
)
|
||||
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("LoadAndRunBytecode(%s)", name))
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAndRunBytecodeWithResults loads and executes bytecode, preserving results
|
||||
func (s *State) LoadAndRunBytecodeWithResults(bytecode []byte, name string, nresults int) error {
|
||||
if len(bytecode) == 0 {
|
||||
return fmt.Errorf("empty bytecode")
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
|
||||
status := C.load_and_run_bytecode(
|
||||
s.L,
|
||||
(*C.uchar)(unsafe.Pointer(&bytecode[0])),
|
||||
C.size_t(len(bytecode)),
|
||||
cname,
|
||||
C.int(nresults),
|
||||
)
|
||||
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("LoadAndRunBytecodeWithResults(%s, nresults=%d)", name, nresults))
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompileAndRun compiles and immediately executes Lua code
|
||||
func (s *State) CompileAndRun(code string, name string) error {
|
||||
// Skip bytecode step for small scripts - direct execution is faster
|
||||
if len(code) < 1024 {
|
||||
return s.DoString(code)
|
||||
}
|
||||
|
||||
bytecode, err := s.CompileBytecode(code, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compile error: %w", err)
|
||||
}
|
||||
|
||||
if err := s.LoadBytecode(bytecode, name); err != nil {
|
||||
return fmt.Errorf("load error: %w", err)
|
||||
if err := s.LoadAndRunBytecode(bytecode, name); err != nil {
|
||||
return fmt.Errorf("execution error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
178
bytecode_test.go
178
bytecode_test.go
@ -1,178 +0,0 @@
|
||||
package luajit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBytecodeCompilation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple assignment",
|
||||
code: "x = 42",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "function definition",
|
||||
code: "function add(a,b) return a+b end",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "syntax error",
|
||||
code: "function bad syntax",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range factories {
|
||||
for _, tt := range tests {
|
||||
t.Run(f.name+"/"+tt.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
bytecode, err := L.CompileBytecode(tt.code, "test")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CompileBytecode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr {
|
||||
if len(bytecode) == 0 {
|
||||
t.Error("CompileBytecode() returned empty bytecode")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBytecodeExecution(t *testing.T) {
|
||||
for _, f := range factories {
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
// Compile some test code
|
||||
code := `
|
||||
function add(a, b)
|
||||
return a + b
|
||||
end
|
||||
result = add(40, 2)
|
||||
`
|
||||
|
||||
bytecode, err := L.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode() error = %v", err)
|
||||
}
|
||||
|
||||
// Load and execute the bytecode
|
||||
if err := L.LoadBytecode(bytecode, "test"); err != nil {
|
||||
t.Fatalf("LoadBytecode() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify the result
|
||||
L.GetGlobal("result")
|
||||
if result := L.ToNumber(-1); result != 42 {
|
||||
t.Errorf("got result = %v, want 42", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidBytecode(t *testing.T) {
|
||||
for _, f := range factories {
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
// Test with invalid bytecode
|
||||
invalidBytecode := []byte("this is not valid bytecode")
|
||||
if err := L.LoadBytecode(invalidBytecode, "test"); err == nil {
|
||||
t.Error("LoadBytecode() expected error with invalid bytecode")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBytecodeRoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
check func(*State) error
|
||||
}{
|
||||
{
|
||||
name: "global variable",
|
||||
code: "x = 42",
|
||||
check: func(L *State) error {
|
||||
L.GetGlobal("x")
|
||||
if x := L.ToNumber(-1); x != 42 {
|
||||
return fmt.Errorf("got x = %v, want 42", x)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "function definition",
|
||||
code: "function test() return 'hello' end",
|
||||
check: func(L *State) error {
|
||||
if err := L.DoString("result = test()"); err != nil {
|
||||
return err
|
||||
}
|
||||
L.GetGlobal("result")
|
||||
if s := L.ToString(-1); s != "hello" {
|
||||
return fmt.Errorf("got result = %q, want 'hello'", s)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range factories {
|
||||
for _, tt := range tests {
|
||||
t.Run(f.name+"/"+tt.name, func(t *testing.T) {
|
||||
// First state for compilation
|
||||
L1 := f.new()
|
||||
if L1 == nil {
|
||||
t.Fatal("Failed to create first Lua state")
|
||||
}
|
||||
defer L1.Close()
|
||||
|
||||
// Compile the code
|
||||
bytecode, err := L1.CompileBytecode(tt.code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode() error = %v", err)
|
||||
}
|
||||
|
||||
// Second state for execution
|
||||
L2 := f.new()
|
||||
if L2 == nil {
|
||||
t.Fatal("Failed to create second Lua state")
|
||||
}
|
||||
defer L2.Close()
|
||||
|
||||
// Load and execute the bytecode
|
||||
if err := L2.LoadBytecode(bytecode, "test"); err != nil {
|
||||
t.Fatalf("LoadBytecode() error = %v", err)
|
||||
}
|
||||
|
||||
// Run the check function
|
||||
if err := tt.check(L2); err != nil {
|
||||
t.Errorf("check failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
70
example/main.go
Normal file
70
example/main.go
Normal file
@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: go run main.go script.lua")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
scriptPath := os.Args[1]
|
||||
|
||||
// Create a new Lua state
|
||||
L := luajit.New()
|
||||
if L == nil {
|
||||
log.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
// Register a Go function to be called from Lua
|
||||
L.RegisterGoFunction("printFromGo", func(s *luajit.State) int {
|
||||
msg := s.ToString(1) // Get first argument
|
||||
fmt.Printf("Go received from Lua: %s\n", msg)
|
||||
|
||||
// Return a value to Lua
|
||||
s.PushString("Hello from Go!")
|
||||
return 1 // Number of return values
|
||||
})
|
||||
|
||||
// Add some values to the Lua environment
|
||||
L.PushValue(map[string]any{
|
||||
"appName": "LuaJIT Example",
|
||||
"version": 1.0,
|
||||
"features": []float64{1, 2, 3},
|
||||
})
|
||||
L.SetGlobal("config")
|
||||
|
||||
// Get the directory of the script to properly handle requires
|
||||
dir := filepath.Dir(scriptPath)
|
||||
L.AddPackagePath(filepath.Join(dir, "?.lua"))
|
||||
|
||||
// Execute the script
|
||||
fmt.Printf("Running Lua script: %s\n", scriptPath)
|
||||
if err := L.DoFile(scriptPath); err != nil {
|
||||
log.Fatalf("Error executing script: %v", err)
|
||||
}
|
||||
|
||||
// Call a Lua function and get its result
|
||||
L.GetGlobal("getResult")
|
||||
if L.IsFunction(-1) {
|
||||
if err := L.Call(0, 1); err != nil {
|
||||
log.Fatalf("Error calling Lua function: %v", err)
|
||||
}
|
||||
|
||||
result, err := L.ToValue(-1)
|
||||
if err != nil {
|
||||
log.Fatalf("Error converting Lua result: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Result from Lua: %v\n", result)
|
||||
L.Pop(1) // Clean up the result
|
||||
}
|
||||
}
|
35
example/script.lua
Normal file
35
example/script.lua
Normal file
@ -0,0 +1,35 @@
|
||||
-- Example Lua script to demonstrate Go-Lua integration
|
||||
|
||||
-- Access the config table passed from Go
|
||||
print("Script started")
|
||||
print("App name:", config.appName)
|
||||
print("Version:", config.version)
|
||||
print("Features:", table.concat(config.features, ", "))
|
||||
|
||||
-- Call the Go function
|
||||
local response = printFromGo("Hello from Lua!")
|
||||
print("Response from Go:", response)
|
||||
|
||||
-- Function that will be called from Go
|
||||
function getResult()
|
||||
local result = {
|
||||
status = "success",
|
||||
calculations = {
|
||||
sum = 10 + 20,
|
||||
product = 5 * 7
|
||||
},
|
||||
message = "Calculation completed"
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
-- Load external module (if available)
|
||||
local success, utils = pcall(require, "utils")
|
||||
if success then
|
||||
print("Utils module loaded")
|
||||
utils.doSomething()
|
||||
else
|
||||
print("Utils module not available:", utils)
|
||||
end
|
||||
|
||||
print("Script completed")
|
19
example/utils.lua
Normal file
19
example/utils.lua
Normal file
@ -0,0 +1,19 @@
|
||||
-- Optional utility module
|
||||
|
||||
local utils = {}
|
||||
|
||||
function utils.doSomething()
|
||||
print("Utils module function called")
|
||||
return true
|
||||
end
|
||||
|
||||
function utils.calculate(a, b)
|
||||
return {
|
||||
sum = a + b,
|
||||
difference = a - b,
|
||||
product = a * b,
|
||||
quotient = a / b
|
||||
}
|
||||
end
|
||||
|
||||
return utils
|
52
functions.go
52
functions.go
@ -7,8 +7,9 @@ package luajit
|
||||
|
||||
extern int goFunctionWrapper(lua_State* L);
|
||||
|
||||
// Helper function to access upvalues
|
||||
static int get_upvalue_index(int i) {
|
||||
return -10002 - i; // LUA_GLOBALSINDEX - i
|
||||
return lua_upvalueindex(i);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
@ -18,25 +19,39 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// GoFunction defines the signature for Go functions callable from Lua
|
||||
type GoFunction func(*State) int
|
||||
|
||||
// Static registry size reduces resizing operations
|
||||
const initialRegistrySize = 64
|
||||
|
||||
var (
|
||||
// functionRegistry stores all registered Go functions
|
||||
functionRegistry = struct {
|
||||
sync.RWMutex
|
||||
funcs map[unsafe.Pointer]GoFunction
|
||||
funcs map[unsafe.Pointer]GoFunction
|
||||
initOnce sync.Once
|
||||
}{
|
||||
funcs: make(map[unsafe.Pointer]GoFunction),
|
||||
funcs: make(map[unsafe.Pointer]GoFunction, initialRegistrySize),
|
||||
}
|
||||
|
||||
// statePool reuses State structs to avoid allocations
|
||||
statePool = sync.Pool{
|
||||
New: func() any {
|
||||
return &State{}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
//export goFunctionWrapper
|
||||
func goFunctionWrapper(L *C.lua_State) C.int {
|
||||
state := &State{L: L, safeStack: true}
|
||||
state := statePool.Get().(*State)
|
||||
state.L = L
|
||||
defer statePool.Put(state)
|
||||
|
||||
// Get upvalue using standard Lua 5.1 macro
|
||||
ptr := C.lua_touserdata(L, C.get_upvalue_index(1))
|
||||
if ptr == nil {
|
||||
state.PushString("error: function not found")
|
||||
state.PushString("error: function pointer not found")
|
||||
return -1
|
||||
}
|
||||
|
||||
@ -49,50 +64,53 @@ func goFunctionWrapper(L *C.lua_State) C.int {
|
||||
return -1
|
||||
}
|
||||
|
||||
result := fn(state)
|
||||
return C.int(result)
|
||||
return C.int(fn(state))
|
||||
}
|
||||
|
||||
// PushGoFunction wraps a Go function and pushes it onto the Lua stack
|
||||
func (s *State) PushGoFunction(fn GoFunction) error {
|
||||
// Push lightuserdata as upvalue and create closure
|
||||
ptr := C.malloc(1)
|
||||
// Allocate unique memory for each function
|
||||
ptr := C.malloc(C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||
if ptr == nil {
|
||||
return fmt.Errorf("failed to allocate memory for function pointer")
|
||||
}
|
||||
|
||||
// Register the function
|
||||
functionRegistry.Lock()
|
||||
functionRegistry.funcs[ptr] = fn
|
||||
functionRegistry.Unlock()
|
||||
|
||||
// Push the pointer as lightuserdata
|
||||
C.lua_pushlightuserdata(s.L, ptr)
|
||||
C.lua_pushcclosure(s.L, (*[0]byte)(C.goFunctionWrapper), 1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterGoFunction registers a Go function as a global Lua function
|
||||
func (s *State) RegisterGoFunction(name string, fn GoFunction) error {
|
||||
if err := s.PushGoFunction(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
C.lua_setfield(s.L, C.LUA_GLOBALSINDEX, cname)
|
||||
s.SetGlobal(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterGoFunction removes a global function
|
||||
func (s *State) UnregisterGoFunction(name string) {
|
||||
s.PushNil()
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
C.lua_setfield(s.L, C.LUA_GLOBALSINDEX, cname)
|
||||
s.SetGlobal(name)
|
||||
}
|
||||
|
||||
// Cleanup frees all function pointers and clears the registry
|
||||
func (s *State) Cleanup() {
|
||||
functionRegistry.Lock()
|
||||
defer functionRegistry.Unlock()
|
||||
|
||||
// Free all allocated pointers
|
||||
for ptr := range functionRegistry.funcs {
|
||||
C.free(ptr)
|
||||
delete(functionRegistry.funcs, ptr)
|
||||
}
|
||||
functionRegistry.funcs = make(map[unsafe.Pointer]GoFunction)
|
||||
}
|
||||
|
@ -1,109 +0,0 @@
|
||||
package luajit
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGoFunctions(t *testing.T) {
|
||||
for _, f := range factories {
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
|
||||
addFunc := func(s *State) int {
|
||||
s.PushNumber(s.ToNumber(1) + s.ToNumber(2))
|
||||
return 1
|
||||
}
|
||||
|
||||
if err := L.RegisterGoFunction("add", addFunc); err != nil {
|
||||
t.Fatalf("Failed to register function: %v", err)
|
||||
}
|
||||
|
||||
// Test basic function call
|
||||
if err := L.DoString("result = add(40, 2)"); err != nil {
|
||||
t.Fatalf("Failed to call function: %v", err)
|
||||
}
|
||||
|
||||
L.GetGlobal("result")
|
||||
if result := L.ToNumber(-1); result != 42 {
|
||||
t.Errorf("got %v, want 42", result)
|
||||
}
|
||||
L.Pop(1)
|
||||
|
||||
// Test multiple return values
|
||||
multiFunc := func(s *State) int {
|
||||
s.PushString("hello")
|
||||
s.PushNumber(42)
|
||||
s.PushBoolean(true)
|
||||
return 3
|
||||
}
|
||||
|
||||
if err := L.RegisterGoFunction("multi", multiFunc); err != nil {
|
||||
t.Fatalf("Failed to register multi function: %v", err)
|
||||
}
|
||||
|
||||
code := `
|
||||
a, b, c = multi()
|
||||
result = (a == "hello" and b == 42 and c == true)
|
||||
`
|
||||
|
||||
if err := L.DoString(code); err != nil {
|
||||
t.Fatalf("Failed to call multi function: %v", err)
|
||||
}
|
||||
|
||||
L.GetGlobal("result")
|
||||
if !L.ToBoolean(-1) {
|
||||
t.Error("Multiple return values test failed")
|
||||
}
|
||||
L.Pop(1)
|
||||
|
||||
// Test error handling
|
||||
errFunc := func(s *State) int {
|
||||
s.PushString("test error")
|
||||
return -1
|
||||
}
|
||||
|
||||
if err := L.RegisterGoFunction("err", errFunc); err != nil {
|
||||
t.Fatalf("Failed to register error function: %v", err)
|
||||
}
|
||||
|
||||
if err := L.DoString("err()"); err == nil {
|
||||
t.Error("Expected error from error function")
|
||||
}
|
||||
|
||||
// Test unregistering
|
||||
L.UnregisterGoFunction("add")
|
||||
if err := L.DoString("add(1, 2)"); err == nil {
|
||||
t.Error("Expected error calling unregistered function")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackSafety(t *testing.T) {
|
||||
L := NewSafe()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
defer L.Cleanup()
|
||||
|
||||
// Test stack overflow protection
|
||||
overflowFunc := func(s *State) int {
|
||||
for i := 0; i < 100; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
}
|
||||
s.PushString("done")
|
||||
return 101
|
||||
}
|
||||
|
||||
if err := L.RegisterGoFunction("overflow", overflowFunc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := L.DoString("overflow()"); err != nil {
|
||||
t.Logf("Got expected error: %v", err)
|
||||
}
|
||||
}
|
175
stack.go
175
stack.go
@ -5,104 +5,115 @@ package luajit
|
||||
#include <lauxlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LuaError represents an error from the Lua state
|
||||
// LuaError represents an enhanced error from the Lua state
|
||||
type LuaError struct {
|
||||
Code int
|
||||
Message string
|
||||
Code int
|
||||
Message string
|
||||
File string
|
||||
Line int
|
||||
StackTrace string
|
||||
Context string
|
||||
}
|
||||
|
||||
func (e *LuaError) Error() string {
|
||||
return fmt.Sprintf("lua error (code=%d): %s", e.Code, e.Message)
|
||||
}
|
||||
var parts []string
|
||||
|
||||
// Stack management constants from lua.h
|
||||
const (
|
||||
LUA_MINSTACK = 20 // Minimum Lua stack size
|
||||
LUA_MAXSTACK = 1000000 // Maximum Lua stack size
|
||||
LUA_REGISTRYINDEX = -10000 // Pseudo-index for the Lua registry
|
||||
LUA_GLOBALSINDEX = -10002 // Pseudo-index for globals table
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// Save current stack size
|
||||
top := s.GetTop()
|
||||
|
||||
// Ensure we have enough stack space (minimum 20 slots as per Lua standard)
|
||||
if err := s.checkStack(LUA_MINSTACK); err != nil {
|
||||
return err
|
||||
if e.File != "" && e.Line > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%s:%d", e.File, e.Line))
|
||||
}
|
||||
|
||||
// Make the call
|
||||
status := f()
|
||||
if e.Context != "" {
|
||||
parts = append(parts, fmt.Sprintf("[%s]", e.Context))
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if status != 0 {
|
||||
err := &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
parts = append(parts, e.Message)
|
||||
|
||||
if e.Code != 0 {
|
||||
parts = append(parts, fmt.Sprintf("(code=%d)", e.Code))
|
||||
}
|
||||
|
||||
result := strings.Join(parts, " ")
|
||||
|
||||
if e.StackTrace != "" {
|
||||
result += "\n" + e.StackTrace
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetStackTrace returns the current Lua stack trace
|
||||
func (s *State) GetStackTrace() string {
|
||||
s.GetGlobal("debug")
|
||||
if !s.IsTable(-1) {
|
||||
s.Pop(1)
|
||||
return "debug table not available"
|
||||
}
|
||||
|
||||
s.GetField(-1, "traceback")
|
||||
if !s.IsFunction(-1) {
|
||||
s.Pop(2)
|
||||
return "debug.traceback not available"
|
||||
}
|
||||
|
||||
s.Call(0, 1)
|
||||
trace := s.ToString(-1)
|
||||
s.Pop(1)
|
||||
|
||||
return trace
|
||||
}
|
||||
|
||||
// GetErrorInfo extracts detailed error information from the Lua stack
|
||||
func (s *State) GetErrorInfo(context string) *LuaError {
|
||||
if s.GetTop() == 0 {
|
||||
return &LuaError{
|
||||
Message: "unknown error (empty stack)",
|
||||
Context: context,
|
||||
}
|
||||
s.Pop(1) // Remove error message
|
||||
return err
|
||||
}
|
||||
|
||||
// For lua_pcall, the function and arguments are popped before results are pushed
|
||||
// So we don't consider it an underflow if the new top is less than the original
|
||||
if status == 0 && s.GetType(-1) == TypeFunction {
|
||||
// If we still have a function on the stack, restore original size
|
||||
s.SetTop(top)
|
||||
message := s.ToString(-1)
|
||||
|
||||
// Parse file:line from common Lua error format
|
||||
var file string
|
||||
var line int
|
||||
|
||||
if colonPos := strings.Index(message, ":"); colonPos > 0 {
|
||||
beforeColon := message[:colonPos]
|
||||
afterColon := message[colonPos+1:]
|
||||
|
||||
if secondColonPos := strings.Index(afterColon, ":"); secondColonPos > 0 {
|
||||
file = beforeColon
|
||||
if n, err := fmt.Sscanf(afterColon[:secondColonPos], "%d", &line); n == 1 && err == nil {
|
||||
message = strings.TrimSpace(afterColon[secondColonPos+1:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
stackTrace := s.GetStackTrace()
|
||||
|
||||
return &LuaError{
|
||||
Message: message,
|
||||
File: file,
|
||||
Line: line,
|
||||
StackTrace: stackTrace,
|
||||
Context: context,
|
||||
}
|
||||
}
|
||||
|
||||
// stackGuard wraps a function with stack checking
|
||||
func stackGuard[T any](s *State, f func() (T, error)) (T, error) {
|
||||
// Save current stack size
|
||||
top := s.GetTop()
|
||||
defer func() {
|
||||
// Only restore if stack is larger than original
|
||||
if s.GetTop() > top {
|
||||
s.SetTop(top)
|
||||
}
|
||||
}()
|
||||
|
||||
// Run the protected function
|
||||
return f()
|
||||
// CreateLuaError creates a LuaError with full context information
|
||||
func (s *State) CreateLuaError(code int, context string) *LuaError {
|
||||
err := s.GetErrorInfo(context)
|
||||
err.Code = code
|
||||
return err
|
||||
}
|
||||
|
||||
// stackGuardValue executes a function with stack protection
|
||||
func stackGuardValue[T any](s *State, f func() (T, error)) (T, error) {
|
||||
return stackGuard(s, f)
|
||||
}
|
||||
|
||||
// stackGuardErr executes a function that only returns an error with stack protection
|
||||
func stackGuardErr(s *State, f func() error) error {
|
||||
// Save current stack size
|
||||
top := s.GetTop()
|
||||
defer func() {
|
||||
// Only restore if stack is larger than original
|
||||
if s.GetTop() > top {
|
||||
s.SetTop(top)
|
||||
}
|
||||
}()
|
||||
|
||||
// Run the protected function
|
||||
return f()
|
||||
}
|
||||
|
||||
// getStackTrace returns the current Lua stack trace
|
||||
func (s *State) getStackTrace() string {
|
||||
// Same implementation...
|
||||
return ""
|
||||
// PushError pushes an error string and returns -1
|
||||
func (s *State) PushError(format string, args ...any) int {
|
||||
s.PushString(fmt.Sprintf(format, args...))
|
||||
return -1
|
||||
}
|
||||
|
177
table.go
177
table.go
@ -1,177 +0,0 @@
|
||||
package luajit
|
||||
|
||||
/*
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int get_table_length(lua_State *L, int index) {
|
||||
return lua_objlen(L, index);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TableValue represents any value that can be stored in a Lua table
|
||||
type TableValue interface {
|
||||
~string | ~float64 | ~bool | ~int | ~map[string]interface{} | ~[]float64 | ~[]interface{}
|
||||
}
|
||||
|
||||
func (s *State) GetTableLength(index int) int { return int(C.get_table_length(s.L, C.int(index))) }
|
||||
|
||||
// ToTable converts a Lua table to a Go map
|
||||
func (s *State) ToTable(index int) (map[string]interface{}, error) {
|
||||
if s.safeStack {
|
||||
return stackGuardValue[map[string]interface{}](s, func() (map[string]interface{}, error) {
|
||||
if !s.IsTable(index) {
|
||||
return nil, fmt.Errorf("not a table at index %d", index)
|
||||
}
|
||||
return s.toTableUnsafe(index)
|
||||
})
|
||||
}
|
||||
if !s.IsTable(index) {
|
||||
return nil, fmt.Errorf("not a table at index %d", index)
|
||||
}
|
||||
return s.toTableUnsafe(index)
|
||||
}
|
||||
|
||||
func (s *State) pushTableSafe(table map[string]interface{}) error {
|
||||
size := 2
|
||||
if err := s.checkStack(size); err != nil {
|
||||
return fmt.Errorf("insufficient stack space: %w", err)
|
||||
}
|
||||
|
||||
s.NewTable()
|
||||
for k, v := range table {
|
||||
if err := s.pushValueSafe(v); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetField(-2, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushTableUnsafe(table map[string]interface{}) error {
|
||||
s.NewTable()
|
||||
for k, v := range table {
|
||||
if err := s.pushValueUnsafe(v); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetField(-2, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) toTableSafe(index int) (map[string]interface{}, error) {
|
||||
if err := s.checkStack(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.toTableUnsafe(index)
|
||||
}
|
||||
|
||||
func (s *State) toTableUnsafe(index int) (map[string]interface{}, error) {
|
||||
absIdx := s.absIndex(index)
|
||||
table := make(map[string]interface{})
|
||||
|
||||
// Check if it's an array-like table
|
||||
length := s.GetTableLength(absIdx)
|
||||
if length > 0 {
|
||||
array := make([]float64, length)
|
||||
isArray := true
|
||||
|
||||
// Try to convert to array
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(absIdx)
|
||||
if s.GetType(-1) != TypeNumber {
|
||||
isArray = false
|
||||
s.Pop(1)
|
||||
break
|
||||
}
|
||||
array[i-1] = s.ToNumber(-1)
|
||||
s.Pop(1)
|
||||
}
|
||||
|
||||
if isArray {
|
||||
return map[string]interface{}{"": array}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Handle regular table
|
||||
s.PushNil()
|
||||
for C.lua_next(s.L, C.int(absIdx)) != 0 {
|
||||
key := ""
|
||||
valueType := C.lua_type(s.L, -2)
|
||||
if valueType == C.LUA_TSTRING {
|
||||
key = s.ToString(-2)
|
||||
} else if valueType == C.LUA_TNUMBER {
|
||||
key = fmt.Sprintf("%g", s.ToNumber(-2))
|
||||
}
|
||||
|
||||
value, err := s.toValueUnsafe(-1)
|
||||
if err != nil {
|
||||
s.Pop(1)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle nested array case
|
||||
if m, ok := value.(map[string]interface{}); ok {
|
||||
if arr, ok := m[""]; ok {
|
||||
value = arr
|
||||
}
|
||||
}
|
||||
|
||||
table[key] = value
|
||||
s.Pop(1)
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// NewTable creates a new table and pushes it onto the stack
|
||||
func (s *State) NewTable() {
|
||||
if s.safeStack {
|
||||
if err := s.checkStack(1); err != nil {
|
||||
// Since we can't return an error, we'll push nil instead
|
||||
s.PushNil()
|
||||
return
|
||||
}
|
||||
}
|
||||
C.lua_createtable(s.L, 0, 0)
|
||||
}
|
||||
|
||||
// SetTable sets a table field with cached absolute index
|
||||
func (s *State) SetTable(index int) {
|
||||
absIdx := index
|
||||
if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
|
||||
absIdx = s.GetTop() + index + 1
|
||||
}
|
||||
C.lua_settable(s.L, C.int(absIdx))
|
||||
}
|
||||
|
||||
// GetTable gets a table field with cached absolute index
|
||||
func (s *State) GetTable(index int) {
|
||||
absIdx := index
|
||||
if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
|
||||
absIdx = s.GetTop() + index + 1
|
||||
}
|
||||
|
||||
if s.safeStack {
|
||||
if err := s.checkStack(1); err != nil {
|
||||
s.PushNil()
|
||||
return
|
||||
}
|
||||
}
|
||||
C.lua_gettable(s.L, C.int(absIdx))
|
||||
}
|
||||
|
||||
// PushTable pushes a Go map onto the Lua stack as a table with stack checking
|
||||
func (s *State) PushTable(table map[string]interface{}) error {
|
||||
if s.safeStack {
|
||||
return s.pushTableSafe(table)
|
||||
}
|
||||
return s.pushTableUnsafe(table)
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package luajit
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTableOperations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
data: map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
name: "primitives",
|
||||
data: map[string]interface{}{
|
||||
"str": "hello",
|
||||
"num": 42.0,
|
||||
"bool": true,
|
||||
"array": []float64{1.1, 2.2, 3.3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested",
|
||||
data: map[string]interface{}{
|
||||
"nested": map[string]interface{}{
|
||||
"value": 123.0,
|
||||
"array": []float64{4.4, 5.5},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range factories {
|
||||
for _, tt := range tests {
|
||||
t.Run(f.name+"/"+tt.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
if err := L.PushTable(tt.data); err != nil {
|
||||
t.Fatalf("PushTable() error = %v", err)
|
||||
}
|
||||
|
||||
got, err := L.ToTable(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("ToTable() error = %v", err)
|
||||
}
|
||||
|
||||
if !tablesEqual(got, tt.data) {
|
||||
t.Errorf("table mismatch\ngot = %v\nwant = %v", got, tt.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tablesEqual(a, b map[string]interface{}) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k, v1 := range a {
|
||||
v2, ok := b[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
switch v1 := v1.(type) {
|
||||
case map[string]interface{}:
|
||||
v2, ok := v2.(map[string]interface{})
|
||||
if !ok || !tablesEqual(v1, v2) {
|
||||
return false
|
||||
}
|
||||
case []float64:
|
||||
v2, ok := v2.([]float64)
|
||||
if !ok || len(v1) != len(v2) {
|
||||
return false
|
||||
}
|
||||
for i := range v1 {
|
||||
if math.Abs(v1[i]-v2[i]) > 1e-10 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
if v1 != v2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
443
tests/bytecode_test.go
Normal file
443
tests/bytecode_test.go
Normal file
@ -0,0 +1,443 @@
|
||||
package luajit_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
// TestCompileBytecode tests basic bytecode compilation
|
||||
func TestCompileBytecode(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
code := "return 42"
|
||||
bytecode, err := state.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
if len(bytecode) == 0 {
|
||||
t.Fatal("Expected non-empty bytecode")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadBytecode tests loading precompiled bytecode
|
||||
func TestLoadBytecode(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// First compile some bytecode
|
||||
code := "answer = 42"
|
||||
bytecode, err := state.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Then load it
|
||||
err = state.LoadBytecode(bytecode, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify a function is on the stack
|
||||
if !state.IsFunction(-1) {
|
||||
t.Fatal("Expected function at top of stack after LoadBytecode")
|
||||
}
|
||||
|
||||
// Pop the function
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
// TestRunBytecode tests running previously loaded bytecode
|
||||
func TestRunBytecode(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// First compile and load bytecode
|
||||
code := "answer = 42"
|
||||
bytecode, err := state.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
err = state.LoadBytecode(bytecode, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Run the bytecode
|
||||
err = state.RunBytecode()
|
||||
if err != nil {
|
||||
t.Fatalf("RunBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify the code has executed correctly
|
||||
state.GetGlobal("answer")
|
||||
if !state.IsNumber(-1) || state.ToNumber(-1) != 42 {
|
||||
t.Fatalf("Expected answer to be 42, got %v", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
// TestLoadAndRunBytecode tests the combined load and run functionality
|
||||
func TestLoadAndRunBytecode(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Compile bytecode
|
||||
code := "answer = 42"
|
||||
bytecode, err := state.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Load and run in one step
|
||||
err = state.LoadAndRunBytecode(bytecode, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify execution
|
||||
state.GetGlobal("answer")
|
||||
if !state.IsNumber(-1) || state.ToNumber(-1) != 42 {
|
||||
t.Fatalf("Expected answer to be 42, got %v", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
// TestCompileAndRun tests compile and run functionality
|
||||
func TestCompileAndRun(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Compile and run in one step
|
||||
code := "answer = 42"
|
||||
err := state.CompileAndRun(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileAndRun failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify execution
|
||||
state.GetGlobal("answer")
|
||||
if !state.IsNumber(-1) || state.ToNumber(-1) != 42 {
|
||||
t.Fatalf("Expected answer to be 42, got %v", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
// TestEmptyBytecode tests error handling for empty bytecode
|
||||
func TestEmptyBytecode(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Try to load empty bytecode
|
||||
err := state.LoadBytecode([]byte{}, "empty")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for empty bytecode, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvalidBytecode tests error handling for invalid bytecode
|
||||
func TestInvalidBytecode(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Create some invalid bytecode
|
||||
invalidBytecode := []byte("not valid bytecode")
|
||||
|
||||
// Try to load invalid bytecode
|
||||
err := state.LoadBytecode(invalidBytecode, "invalid")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid bytecode, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBytecodeSerialization tests serializing and deserializing bytecode
|
||||
func TestBytecodeSerialization(t *testing.T) {
|
||||
// First state to compile
|
||||
state1 := luajit.New()
|
||||
if state1 == nil {
|
||||
t.Fatal("Failed to create first Lua state")
|
||||
}
|
||||
defer state1.Close()
|
||||
|
||||
// Compile bytecode
|
||||
code := `
|
||||
function add(a, b)
|
||||
return a + b
|
||||
end
|
||||
result = add(10, 20)
|
||||
`
|
||||
bytecode, err := state1.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Second state to execute
|
||||
state2 := luajit.New()
|
||||
if state2 == nil {
|
||||
t.Fatal("Failed to create second Lua state")
|
||||
}
|
||||
defer state2.Close()
|
||||
|
||||
// Load and run the bytecode in the second state
|
||||
err = state2.LoadAndRunBytecode(bytecode, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify execution
|
||||
state2.GetGlobal("result")
|
||||
if !state2.IsNumber(-1) || state2.ToNumber(-1) != 30 {
|
||||
t.Fatalf("Expected result to be 30, got %v", state2.ToNumber(-1))
|
||||
}
|
||||
state2.Pop(1)
|
||||
|
||||
// Call the function to verify it was properly transferred
|
||||
state2.GetGlobal("add")
|
||||
if !state2.IsFunction(-1) {
|
||||
t.Fatal("Expected add to be a function")
|
||||
}
|
||||
state2.PushNumber(5)
|
||||
state2.PushNumber(7)
|
||||
if err := state2.Call(2, 1); err != nil {
|
||||
t.Fatalf("Failed to call function: %v", err)
|
||||
}
|
||||
if state2.ToNumber(-1) != 12 {
|
||||
t.Fatalf("Expected add(5, 7) to return 12, got %v", state2.ToNumber(-1))
|
||||
}
|
||||
state2.Pop(1)
|
||||
}
|
||||
|
||||
// TestCompilationError tests error handling for compilation errors
|
||||
func TestCompilationError(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Invalid Lua code that should fail to compile
|
||||
code := "function without end"
|
||||
|
||||
// Try to compile
|
||||
_, err := state.CompileBytecode(code, "invalid")
|
||||
if err == nil {
|
||||
t.Fatal("Expected compilation error, got nil")
|
||||
}
|
||||
|
||||
// Check error type
|
||||
var luaErr *luajit.LuaError
|
||||
if !errors.As(err, &luaErr) {
|
||||
t.Fatalf("Expected error to wrap *luajit.LuaError, got %T", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestExecutionError tests error handling for runtime errors
|
||||
func TestExecutionError(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Code that compiles but fails at runtime
|
||||
code := "error('deliberate error')"
|
||||
|
||||
// Compile bytecode
|
||||
bytecode, err := state.CompileBytecode(code, "error")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Try to execute
|
||||
err = state.LoadAndRunBytecode(bytecode, "error")
|
||||
if err == nil {
|
||||
t.Fatal("Expected execution error, got nil")
|
||||
}
|
||||
|
||||
// Check error type
|
||||
if _, ok := err.(*luajit.LuaError); !ok {
|
||||
t.Fatalf("Expected *luajit.LuaError, got %T", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBytecodeEquivalence tests that bytecode execution produces the same results as direct execution
|
||||
func TestBytecodeEquivalence(t *testing.T) {
|
||||
code := `
|
||||
local result = 0
|
||||
for i = 1, 10 do
|
||||
result = result + i
|
||||
end
|
||||
return result
|
||||
`
|
||||
|
||||
// First, execute directly
|
||||
state1 := luajit.New()
|
||||
if state1 == nil {
|
||||
t.Fatal("Failed to create first Lua state")
|
||||
}
|
||||
defer state1.Close()
|
||||
|
||||
directResult, err := state1.ExecuteWithResult(code)
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteWithResult failed: %v", err)
|
||||
}
|
||||
|
||||
// Then, compile and execute bytecode
|
||||
state2 := luajit.New()
|
||||
if state2 == nil {
|
||||
t.Fatal("Failed to create second Lua state")
|
||||
}
|
||||
defer state2.Close()
|
||||
|
||||
bytecode, err := state2.CompileBytecode(code, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
err = state2.LoadBytecode(bytecode, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
err = state2.Call(0, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
bytecodeResult, err := state2.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("ToValue failed: %v", err)
|
||||
}
|
||||
state2.Pop(1)
|
||||
|
||||
// Compare results
|
||||
if directResult != bytecodeResult {
|
||||
t.Fatalf("Results differ: direct=%v, bytecode=%v", directResult, bytecodeResult)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBytecodeReuse tests reusing the same bytecode multiple times
|
||||
func TestBytecodeReuse(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Create a function in bytecode
|
||||
code := `
|
||||
return function(x)
|
||||
return x * 2
|
||||
end
|
||||
`
|
||||
bytecode, err := state.CompileBytecode(code, "func")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Execute it several times
|
||||
for i := 1; i <= 3; i++ {
|
||||
// Load and run to get the function
|
||||
err = state.LoadAndRunBytecodeWithResults(bytes.Clone(bytecode), "func", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndRunBytecodeWithResults failed: %v", err)
|
||||
}
|
||||
|
||||
// Stack now has the function at the top
|
||||
if !state.IsFunction(-1) {
|
||||
t.Fatal("Expected function at top of stack")
|
||||
}
|
||||
|
||||
// Call with parameter i
|
||||
state.PushNumber(float64(i))
|
||||
if err := state.Call(1, 1); err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
// Check result
|
||||
expected := float64(i * 2)
|
||||
if state.ToNumber(-1) != expected {
|
||||
t.Fatalf("Expected %v, got %v", expected, state.ToNumber(-1))
|
||||
}
|
||||
|
||||
// Pop the result
|
||||
state.Pop(1)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBytecodeClosure tests that bytecode properly handles closures and upvalues
|
||||
func TestBytecodeClosure(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Create a closure
|
||||
code := `
|
||||
local counter = 0
|
||||
return function()
|
||||
counter = counter + 1
|
||||
return counter
|
||||
end
|
||||
`
|
||||
|
||||
// Compile to bytecode
|
||||
bytecode, err := state.CompileBytecode(code, "closure")
|
||||
if err != nil {
|
||||
t.Fatalf("CompileBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Load and run to get the counter function
|
||||
err = state.LoadAndRunBytecodeWithResults(bytecode, "closure", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndRunBytecode failed: %v", err)
|
||||
}
|
||||
|
||||
// Stack now has the function at the top
|
||||
if !state.IsFunction(-1) {
|
||||
t.Fatal("Expected function at top of stack")
|
||||
}
|
||||
|
||||
// Store in a global
|
||||
state.SetGlobal("counter_func")
|
||||
|
||||
// Call it multiple times and check the results
|
||||
for i := 1; i <= 3; i++ {
|
||||
state.GetGlobal("counter_func")
|
||||
if err := state.Call(0, 1); err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
|
||||
if state.ToNumber(-1) != float64(i) {
|
||||
t.Fatalf("Expected counter to be %d, got %v", i, state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
}
|
178
tests/functions_test.go
Normal file
178
tests/functions_test.go
Normal file
@ -0,0 +1,178 @@
|
||||
package luajit_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
func TestPushGoFunction(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Define a simple function that adds two numbers
|
||||
add := func(s *luajit.State) int {
|
||||
a := s.ToNumber(1)
|
||||
b := s.ToNumber(2)
|
||||
s.PushNumber(a + b)
|
||||
return 1 // Return one result
|
||||
}
|
||||
|
||||
// Push the function onto the stack
|
||||
if err := state.PushGoFunction(add); err != nil {
|
||||
t.Fatalf("PushGoFunction failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify that a function is on the stack
|
||||
if !state.IsFunction(-1) {
|
||||
t.Fatalf("Expected function at top of stack")
|
||||
}
|
||||
|
||||
// Push arguments
|
||||
state.PushNumber(3)
|
||||
state.PushNumber(4)
|
||||
|
||||
// Call the function
|
||||
if err := state.Call(2, 1); err != nil {
|
||||
t.Fatalf("Failed to call function: %v", err)
|
||||
}
|
||||
|
||||
// Check the result
|
||||
if state.ToNumber(-1) != 7 {
|
||||
t.Fatalf("Function returned %f, expected 7", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestRegisterGoFunction(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Define a function that squares a number
|
||||
square := func(s *luajit.State) int {
|
||||
x := s.ToNumber(1)
|
||||
s.PushNumber(x * x)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Register the function
|
||||
if err := state.RegisterGoFunction("square", square); err != nil {
|
||||
t.Fatalf("RegisterGoFunction failed: %v", err)
|
||||
}
|
||||
|
||||
// Call the function from Lua
|
||||
if err := state.DoString("result = square(5)"); err != nil {
|
||||
t.Fatalf("Failed to call registered function: %v", err)
|
||||
}
|
||||
|
||||
// Check the result
|
||||
state.GetGlobal("result")
|
||||
if state.ToNumber(-1) != 25 {
|
||||
t.Fatalf("Function returned %f, expected 25", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test UnregisterGoFunction
|
||||
state.UnregisterGoFunction("square")
|
||||
|
||||
// Function should no longer exist
|
||||
err := state.DoString("result = square(5)")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error after unregistering function, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoFunctionWithErrorHandling(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Function that returns an error in Lua
|
||||
errFunc := func(s *luajit.State) int {
|
||||
s.PushString("error from Go function")
|
||||
return -1 // Signal error
|
||||
}
|
||||
|
||||
// Register the function
|
||||
if err := state.RegisterGoFunction("errorFunc", errFunc); err != nil {
|
||||
t.Fatalf("RegisterGoFunction failed: %v", err)
|
||||
}
|
||||
|
||||
// Call the function expecting an error
|
||||
err := state.DoString("result = errorFunc()")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error from function, got nil")
|
||||
}
|
||||
|
||||
// Error message should contain our message
|
||||
luaErr, ok := err.(*luajit.LuaError)
|
||||
if !ok {
|
||||
t.Fatalf("Expected LuaError, got %T: %v", err, err)
|
||||
}
|
||||
|
||||
if luaErr.Message == "" {
|
||||
t.Fatalf("Expected non-empty error message from Go function")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanup(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
|
||||
// Register several functions
|
||||
for i := 0; i < 5; i++ {
|
||||
dummy := func(s *luajit.State) int { return 0 }
|
||||
if err := state.RegisterGoFunction("dummy", dummy); err != nil {
|
||||
t.Fatalf("RegisterGoFunction failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Call Cleanup explicitly
|
||||
state.Cleanup()
|
||||
|
||||
// Make sure we can still close the state
|
||||
state.Close()
|
||||
|
||||
// Also test that Close can be called after Cleanup
|
||||
state = luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create second Lua state")
|
||||
}
|
||||
|
||||
state.Close() // Should call Cleanup internally
|
||||
}
|
||||
|
||||
func TestGoFunctionErrorPointer(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Create a Lua function that calls a non-existent Go function pointer
|
||||
// This isn't a direct test of internal implementation, but tries to cover
|
||||
// error cases in the goFunctionWrapper
|
||||
code := `
|
||||
function test()
|
||||
-- This is a stub that doesn't actually call the wrapper,
|
||||
-- but we're testing error handling in our State.DoString
|
||||
return "test"
|
||||
end
|
||||
`
|
||||
if err := state.DoString(code); err != nil {
|
||||
t.Fatalf("Failed to define test function: %v", err)
|
||||
}
|
||||
|
||||
// The real test is that Cleanup doesn't crash
|
||||
state.Cleanup()
|
||||
}
|
53
tests/stack_test.go
Normal file
53
tests/stack_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package luajit_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
func TestLuaError(t *testing.T) {
|
||||
err := &luajit.LuaError{
|
||||
Code: 123,
|
||||
Message: "test error",
|
||||
}
|
||||
|
||||
expected := "test error (code=123)"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Expected error message %q, got %q", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStackTrace(t *testing.T) {
|
||||
s := luajit.New()
|
||||
defer s.Close()
|
||||
|
||||
// Test with debug library available
|
||||
trace := s.GetStackTrace()
|
||||
if !strings.Contains(trace, "stack traceback:") {
|
||||
t.Errorf("Expected trace to contain 'stack traceback:', got %q", trace)
|
||||
}
|
||||
|
||||
// Test when debug table is not available
|
||||
err := s.DoString("debug = nil")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set debug to nil: %v", err)
|
||||
}
|
||||
|
||||
trace = s.GetStackTrace()
|
||||
if trace != "debug table not available" {
|
||||
t.Errorf("Expected 'debug table not available', got %q", trace)
|
||||
}
|
||||
|
||||
// Test when debug.traceback is not available
|
||||
err = s.DoString("debug = {}")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set debug to empty table: %v", err)
|
||||
}
|
||||
|
||||
trace = s.GetStackTrace()
|
||||
if trace != "debug.traceback not available" {
|
||||
t.Errorf("Expected 'debug.traceback not available', got %q", trace)
|
||||
}
|
||||
}
|
272
tests/table_test.go
Normal file
272
tests/table_test.go
Normal file
@ -0,0 +1,272 @@
|
||||
package luajit_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
func TestGetTableLength(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Create a table with numeric indices
|
||||
if err := state.DoString("t = {10, 20, 30, 40, 50}"); err != nil {
|
||||
t.Fatalf("Failed to create test table: %v", err)
|
||||
}
|
||||
|
||||
state.GetGlobal("t")
|
||||
length := state.GetTableLength(-1)
|
||||
if length != 5 {
|
||||
t.Fatalf("Expected length 5, got %d", length)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Create a table with string keys
|
||||
if err := state.DoString("t2 = {a=1, b=2, c=3}"); err != nil {
|
||||
t.Fatalf("Failed to create test table: %v", err)
|
||||
}
|
||||
|
||||
state.GetGlobal("t2")
|
||||
length = state.GetTableLength(-1)
|
||||
if length != 0 {
|
||||
t.Fatalf("Expected length 0 for string-keyed table, got %d", length)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestPushTypedArrays(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test []int
|
||||
intArr := []int{1, 2, 3, 4, 5}
|
||||
if err := state.PushValue(intArr); err != nil {
|
||||
t.Fatalf("Failed to push int array: %v", err)
|
||||
}
|
||||
state.SetGlobal("int_arr")
|
||||
|
||||
// Test []string
|
||||
stringArr := []string{"hello", "world", "test"}
|
||||
if err := state.PushValue(stringArr); err != nil {
|
||||
t.Fatalf("Failed to push string array: %v", err)
|
||||
}
|
||||
state.SetGlobal("string_arr")
|
||||
|
||||
// Test []bool
|
||||
boolArr := []bool{true, false, true}
|
||||
if err := state.PushValue(boolArr); err != nil {
|
||||
t.Fatalf("Failed to push bool array: %v", err)
|
||||
}
|
||||
state.SetGlobal("bool_arr")
|
||||
|
||||
// Test []float64
|
||||
floatArr := []float64{1.1, 2.2, 3.3}
|
||||
if err := state.PushValue(floatArr); err != nil {
|
||||
t.Fatalf("Failed to push float array: %v", err)
|
||||
}
|
||||
state.SetGlobal("float_arr")
|
||||
|
||||
// Verify arrays in Lua
|
||||
if err := state.DoString(`
|
||||
assert(int_arr[1] == 1 and int_arr[5] == 5)
|
||||
assert(string_arr[1] == "hello" and string_arr[3] == "test")
|
||||
assert(bool_arr[1] == true and bool_arr[2] == false)
|
||||
assert(math.abs(float_arr[1] - 1.1) < 0.0001)
|
||||
`); err != nil {
|
||||
t.Fatalf("Array verification failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushTypedMaps(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test map[string]string
|
||||
stringMap := map[string]string{"name": "John", "city": "NYC"}
|
||||
if err := state.PushValue(stringMap); err != nil {
|
||||
t.Fatalf("Failed to push string map: %v", err)
|
||||
}
|
||||
state.SetGlobal("string_map")
|
||||
|
||||
// Test map[string]int
|
||||
intMap := map[string]int{"age": 25, "score": 100}
|
||||
if err := state.PushValue(intMap); err != nil {
|
||||
t.Fatalf("Failed to push int map: %v", err)
|
||||
}
|
||||
state.SetGlobal("int_map")
|
||||
|
||||
// Test map[int]any
|
||||
intKeyMap := map[int]any{1: "first", 2: 42, 3: true}
|
||||
if err := state.PushValue(intKeyMap); err != nil {
|
||||
t.Fatalf("Failed to push int key map: %v", err)
|
||||
}
|
||||
state.SetGlobal("int_key_map")
|
||||
|
||||
// Verify maps in Lua
|
||||
if err := state.DoString(`
|
||||
assert(string_map.name == "John" and string_map.city == "NYC")
|
||||
assert(int_map.age == 25 and int_map.score == 100)
|
||||
assert(int_key_map[1] == "first" and int_key_map[2] == 42 and int_key_map[3] == true)
|
||||
`); err != nil {
|
||||
t.Fatalf("Map verification failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToTableTypedArrays(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test integer array detection
|
||||
if err := state.DoString("int_arr = {10, 20, 30}"); err != nil {
|
||||
t.Fatalf("Failed to create int array: %v", err)
|
||||
}
|
||||
state.GetGlobal("int_arr")
|
||||
result, err := state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert int array: %v", err)
|
||||
}
|
||||
intArr, ok := result.([]int)
|
||||
if !ok {
|
||||
t.Fatalf("Expected []int, got %T", result)
|
||||
}
|
||||
expected := []int{10, 20, 30}
|
||||
if !reflect.DeepEqual(intArr, expected) {
|
||||
t.Fatalf("Expected %v, got %v", expected, intArr)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test float array detection
|
||||
if err := state.DoString("float_arr = {1.5, 2.7, 3.9}"); err != nil {
|
||||
t.Fatalf("Failed to create float array: %v", err)
|
||||
}
|
||||
state.GetGlobal("float_arr")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert float array: %v", err)
|
||||
}
|
||||
floatArr, ok := result.([]float64)
|
||||
if !ok {
|
||||
t.Fatalf("Expected []float64, got %T", result)
|
||||
}
|
||||
expectedFloat := []float64{1.5, 2.7, 3.9}
|
||||
if !reflect.DeepEqual(floatArr, expectedFloat) {
|
||||
t.Fatalf("Expected %v, got %v", expectedFloat, floatArr)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test string array detection
|
||||
if err := state.DoString(`string_arr = {"hello", "world"}`); err != nil {
|
||||
t.Fatalf("Failed to create string array: %v", err)
|
||||
}
|
||||
state.GetGlobal("string_arr")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert string array: %v", err)
|
||||
}
|
||||
stringArr, ok := result.([]string)
|
||||
if !ok {
|
||||
t.Fatalf("Expected []string, got %T", result)
|
||||
}
|
||||
expectedString := []string{"hello", "world"}
|
||||
if !reflect.DeepEqual(stringArr, expectedString) {
|
||||
t.Fatalf("Expected %v, got %v", expectedString, stringArr)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test bool array detection
|
||||
if err := state.DoString("bool_arr = {true, false, true}"); err != nil {
|
||||
t.Fatalf("Failed to create bool array: %v", err)
|
||||
}
|
||||
state.GetGlobal("bool_arr")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert bool array: %v", err)
|
||||
}
|
||||
boolArr, ok := result.([]bool)
|
||||
if !ok {
|
||||
t.Fatalf("Expected []bool, got %T", result)
|
||||
}
|
||||
expectedBool := []bool{true, false, true}
|
||||
if !reflect.DeepEqual(boolArr, expectedBool) {
|
||||
t.Fatalf("Expected %v, got %v", expectedBool, boolArr)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestToTableTypedMaps(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test string map detection
|
||||
if err := state.DoString(`string_map = {name="John", city="NYC"}`); err != nil {
|
||||
t.Fatalf("Failed to create string map: %v", err)
|
||||
}
|
||||
state.GetGlobal("string_map")
|
||||
result, err := state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert string map: %v", err)
|
||||
}
|
||||
stringMap, ok := result.(map[string]string)
|
||||
if !ok {
|
||||
t.Fatalf("Expected map[string]string, got %T", result)
|
||||
}
|
||||
expectedStringMap := map[string]string{"name": "John", "city": "NYC"}
|
||||
if !reflect.DeepEqual(stringMap, expectedStringMap) {
|
||||
t.Fatalf("Expected %v, got %v", expectedStringMap, stringMap)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test int map detection
|
||||
if err := state.DoString("int_map = {age=25, score=100}"); err != nil {
|
||||
t.Fatalf("Failed to create int map: %v", err)
|
||||
}
|
||||
state.GetGlobal("int_map")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert int map: %v", err)
|
||||
}
|
||||
intMap, ok := result.(map[string]int)
|
||||
if !ok {
|
||||
t.Fatalf("Expected map[string]int, got %T", result)
|
||||
}
|
||||
expectedIntMap := map[string]int{"age": 25, "score": 100}
|
||||
if !reflect.DeepEqual(intMap, expectedIntMap) {
|
||||
t.Fatalf("Expected %v, got %v", expectedIntMap, intMap)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test mixed map (should fallback to map[string]any)
|
||||
if err := state.DoString(`mixed_map = {name="John", age=25, active=true}`); err != nil {
|
||||
t.Fatalf("Failed to create mixed map: %v", err)
|
||||
}
|
||||
state.GetGlobal("mixed_map")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert mixed map: %v", err)
|
||||
}
|
||||
mixedMap, ok := result.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("Expected map[string]any, got %T", result)
|
||||
}
|
||||
if mixedMap["name"] != "John" || mixedMap["age"] != 25 || mixedMap["active"] != true {
|
||||
t.Fatalf("Mixed map conversion failed: %v", mixedMap)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
425
tests/wrapper_test.go
Normal file
425
tests/wrapper_test.go
Normal file
@ -0,0 +1,425 @@
|
||||
package luajit_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
func TestStateLifecycle(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
state.Close()
|
||||
state.Close() // Test idempotent close
|
||||
}
|
||||
|
||||
func TestStackOperations(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test stack manipulation
|
||||
if state.GetTop() != 0 {
|
||||
t.Fatalf("Expected empty stack, got %d", state.GetTop())
|
||||
}
|
||||
|
||||
state.PushNil()
|
||||
state.PushBoolean(true)
|
||||
state.PushNumber(42)
|
||||
state.PushString("hello")
|
||||
|
||||
if state.GetTop() != 4 {
|
||||
t.Fatalf("Expected 4 elements, got %d", state.GetTop())
|
||||
}
|
||||
|
||||
state.SetTop(2)
|
||||
if state.GetTop() != 2 {
|
||||
t.Fatalf("Expected 2 elements after SetTop, got %d", state.GetTop())
|
||||
}
|
||||
|
||||
state.PushCopy(2)
|
||||
if !state.IsBoolean(-1) {
|
||||
t.Fatal("Expected boolean at top")
|
||||
}
|
||||
|
||||
state.Pop(1)
|
||||
state.PushNumber(99)
|
||||
state.Remove(1)
|
||||
if !state.IsBoolean(1) {
|
||||
t.Fatal("Expected boolean at index 1 after Remove")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeChecking(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
values := []struct {
|
||||
push func()
|
||||
luaType luajit.LuaType
|
||||
checkFn func(int) bool
|
||||
}{
|
||||
{state.PushNil, luajit.TypeNil, state.IsNil},
|
||||
{func() { state.PushBoolean(true) }, luajit.TypeBoolean, state.IsBoolean},
|
||||
{func() { state.PushNumber(42) }, luajit.TypeNumber, state.IsNumber},
|
||||
{func() { state.PushString("test") }, luajit.TypeString, state.IsString},
|
||||
{state.NewTable, luajit.TypeTable, state.IsTable},
|
||||
}
|
||||
|
||||
for i, v := range values {
|
||||
v.push()
|
||||
idx := i + 1
|
||||
if state.GetType(idx) != v.luaType {
|
||||
t.Fatalf("Type mismatch at %d: expected %s, got %s", idx, v.luaType, state.GetType(idx))
|
||||
}
|
||||
if !v.checkFn(idx) {
|
||||
t.Fatalf("Type check failed at %d", idx)
|
||||
}
|
||||
}
|
||||
|
||||
state.DoString("function test() return true end")
|
||||
state.GetGlobal("test")
|
||||
if !state.IsFunction(-1) {
|
||||
t.Fatal("IsFunction failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueConversion(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
state.PushBoolean(true)
|
||||
state.PushNumber(42.5)
|
||||
state.PushString("hello")
|
||||
|
||||
if !state.ToBoolean(1) {
|
||||
t.Fatal("ToBoolean failed")
|
||||
}
|
||||
if state.ToNumber(2) != 42.5 {
|
||||
t.Fatalf("ToNumber failed: expected 42.5, got %f", state.ToNumber(2))
|
||||
}
|
||||
if state.ToString(3) != "hello" {
|
||||
t.Fatalf("ToString failed: expected 'hello', got '%s'", state.ToString(3))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTableOperations(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
state.CreateTable(0, 3)
|
||||
|
||||
// Set fields
|
||||
state.PushNumber(42)
|
||||
state.SetField(-2, "answer")
|
||||
state.PushString("hello")
|
||||
state.SetField(-2, "greeting")
|
||||
state.PushBoolean(true)
|
||||
state.SetField(-2, "flag")
|
||||
|
||||
// Get fields
|
||||
state.GetField(-1, "answer")
|
||||
if state.ToNumber(-1) != 42 {
|
||||
t.Fatal("GetField failed for 'answer'")
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test iteration
|
||||
state.PushNil()
|
||||
count := 0
|
||||
for state.Next(-2) {
|
||||
count++
|
||||
state.Pop(1)
|
||||
}
|
||||
if count != 3 {
|
||||
t.Fatalf("Expected 3 entries, found %d", count)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestGlobalOperations(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
state.PushNumber(42)
|
||||
state.SetGlobal("answer")
|
||||
|
||||
state.GetGlobal("answer")
|
||||
if state.ToNumber(-1) != 42 {
|
||||
t.Fatalf("GetGlobal failed: expected 42, got %f", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
state.GetGlobal("nonexistent")
|
||||
if !state.IsNil(-1) {
|
||||
t.Fatal("Expected nil for non-existent global")
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestCodeExecution(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test LoadString and Call
|
||||
if err := state.LoadString("return 42"); err != nil {
|
||||
t.Fatalf("LoadString failed: %v", err)
|
||||
}
|
||||
if err := state.Call(0, 1); err != nil {
|
||||
t.Fatalf("Call failed: %v", err)
|
||||
}
|
||||
if state.ToNumber(-1) != 42 {
|
||||
t.Fatalf("Call result incorrect: expected 42, got %f", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test DoString
|
||||
if err := state.DoString("answer = 42 + 1"); err != nil {
|
||||
t.Fatalf("DoString failed: %v", err)
|
||||
}
|
||||
state.GetGlobal("answer")
|
||||
if state.ToNumber(-1) != 43 {
|
||||
t.Fatalf("DoString result incorrect: expected 43, got %f", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test Execute
|
||||
nresults, err := state.Execute("return 5, 10, 15")
|
||||
if err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
if nresults != 3 {
|
||||
t.Fatalf("Execute returned %d results, expected 3", nresults)
|
||||
}
|
||||
if state.ToNumber(-3) != 5 || state.ToNumber(-2) != 10 || state.ToNumber(-1) != 15 {
|
||||
t.Fatal("Execute results incorrect")
|
||||
}
|
||||
state.Pop(3)
|
||||
|
||||
// Test ExecuteWithResult
|
||||
result, err := state.ExecuteWithResult("return 'hello'")
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteWithResult failed: %v", err)
|
||||
}
|
||||
if result != "hello" {
|
||||
t.Fatalf("ExecuteWithResult returned %v, expected 'hello'", result)
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
if err := state.DoString("invalid lua code"); err == nil {
|
||||
t.Fatal("Expected error for invalid code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileOperations(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Create temp file
|
||||
content := []byte("answer = 42")
|
||||
tmpfile, err := os.CreateTemp("", "test-*.lua")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
t.Fatalf("Failed to write temp file: %v", err)
|
||||
}
|
||||
tmpfile.Close()
|
||||
|
||||
// Test DoFile
|
||||
if err := state.DoFile(tmpfile.Name()); err != nil {
|
||||
t.Fatalf("DoFile failed: %v", err)
|
||||
}
|
||||
state.GetGlobal("answer")
|
||||
if state.ToNumber(-1) != 42 {
|
||||
t.Fatalf("DoFile result incorrect: expected 42, got %f", state.ToNumber(-1))
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestPackagePath(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
testPath := "/test/path/?.lua"
|
||||
if err := state.SetPackagePath(testPath); err != nil {
|
||||
t.Fatalf("SetPackagePath failed: %v", err)
|
||||
}
|
||||
|
||||
result, err := state.ExecuteWithResult("return package.path")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get package.path: %v", err)
|
||||
}
|
||||
if result != testPath {
|
||||
t.Fatalf("SetPackagePath failed: expected '%s', got '%s'", testPath, result)
|
||||
}
|
||||
|
||||
addPath := "/another/path/?.lua"
|
||||
if err := state.AddPackagePath(addPath); err != nil {
|
||||
t.Fatalf("AddPackagePath failed: %v", err)
|
||||
}
|
||||
|
||||
result, err = state.ExecuteWithResult("return package.path")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get package.path: %v", err)
|
||||
}
|
||||
expected := testPath + ";" + addPath
|
||||
if result != expected {
|
||||
t.Fatalf("AddPackagePath failed: expected '%s', got '%s'", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnhancedTypes(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test typed arrays
|
||||
testCases := []struct {
|
||||
input any
|
||||
expected any
|
||||
}{
|
||||
// Primitive types
|
||||
{nil, nil},
|
||||
{true, true},
|
||||
{42, 42}, // Should preserve as int
|
||||
{42.5, 42.5}, // Should be float64
|
||||
{"hello", "hello"},
|
||||
|
||||
// Typed arrays
|
||||
{[]int{1, 2, 3}, []int{1, 2, 3}},
|
||||
{[]string{"a", "b"}, []string{"a", "b"}},
|
||||
{[]bool{true, false}, []bool{true, false}},
|
||||
{[]float64{1.1, 2.2}, []float64{1.1, 2.2}},
|
||||
|
||||
// Typed maps
|
||||
{map[string]string{"name": "John"}, map[string]string{"name": "John"}},
|
||||
{map[string]int{"age": 25}, map[string]int{"age": 25}},
|
||||
{map[int]any{10: "first", 20: 42}, map[string]any{"10": "first", "20": 42}},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
// Push and retrieve value
|
||||
if err := state.PushValue(tc.input); err != nil {
|
||||
t.Fatalf("Case %d: PushValue failed: %v", i, err)
|
||||
}
|
||||
|
||||
result, err := state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Case %d: ToValue failed: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, tc.expected) {
|
||||
t.Fatalf("Case %d: expected %v (%T), got %v (%T)",
|
||||
i, tc.expected, tc.expected, result, result)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
// Test mixed array (should become []any)
|
||||
state.DoString("mixed = {1, 'hello', true}")
|
||||
state.GetGlobal("mixed")
|
||||
result, err := state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Mixed array conversion failed: %v", err)
|
||||
}
|
||||
if _, ok := result.([]any); !ok {
|
||||
t.Fatalf("Expected []any for mixed array, got %T", result)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test mixed map (should become map[string]any)
|
||||
state.DoString("mixedMap = {name='John', age=25, active=true}")
|
||||
state.GetGlobal("mixedMap")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Mixed map conversion failed: %v", err)
|
||||
}
|
||||
if _, ok := result.(map[string]any); !ok {
|
||||
t.Fatalf("Expected map[string]any for mixed map, got %T", result)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestIntegerPreservation(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test that integers are preserved
|
||||
state.DoString("num = 42")
|
||||
state.GetGlobal("num")
|
||||
result, err := state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Integer conversion failed: %v", err)
|
||||
}
|
||||
if val, ok := result.(int); !ok || val != 42 {
|
||||
t.Fatalf("Expected int 42, got %T %v", result, result)
|
||||
}
|
||||
state.Pop(1)
|
||||
|
||||
// Test that floats remain floats
|
||||
state.DoString("fnum = 42.5")
|
||||
state.GetGlobal("fnum")
|
||||
result, err = state.ToValue(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("Float conversion failed: %v", err)
|
||||
}
|
||||
if val, ok := result.(float64); !ok || val != 42.5 {
|
||||
t.Fatalf("Expected float64 42.5, got %T %v", result, result)
|
||||
}
|
||||
state.Pop(1)
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
state := luajit.New()
|
||||
if state == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
// Test unsupported type
|
||||
type customStruct struct{ Field int }
|
||||
if err := state.PushValue(customStruct{Field: 42}); err == nil {
|
||||
t.Fatal("Expected error for unsupported type")
|
||||
}
|
||||
|
||||
// Test invalid stack index
|
||||
_, err := state.ToValue(100)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid index")
|
||||
}
|
||||
}
|
319
types.go
319
types.go
@ -4,12 +4,15 @@ package luajit
|
||||
#include <lua.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// LuaType represents Lua value types
|
||||
type LuaType int
|
||||
|
||||
const (
|
||||
// These constants must match lua.h's LUA_T* values
|
||||
TypeNone LuaType = -1
|
||||
TypeNil LuaType = 0
|
||||
TypeBoolean LuaType = 1
|
||||
@ -22,7 +25,6 @@ const (
|
||||
TypeThread LuaType = 8
|
||||
)
|
||||
|
||||
// String returns the string representation of the Lua type
|
||||
func (t LuaType) String() string {
|
||||
switch t {
|
||||
case TypeNone:
|
||||
@ -49,3 +51,316 @@ func (t LuaType) String() string {
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertValue converts a value to the requested type with comprehensive type conversion
|
||||
func ConvertValue[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
|
||||
if value == nil {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
if result, ok := value.(T); ok {
|
||||
return result, true
|
||||
}
|
||||
|
||||
switch any(zero).(type) {
|
||||
case string:
|
||||
return convertToString[T](value)
|
||||
case int:
|
||||
return convertToInt[T](value)
|
||||
case float64:
|
||||
return convertToFloat[T](value)
|
||||
case bool:
|
||||
return convertToBool[T](value)
|
||||
case []int:
|
||||
return convertToIntSlice[T](value)
|
||||
case []string:
|
||||
return convertToStringSlice[T](value)
|
||||
case []bool:
|
||||
return convertToBoolSlice[T](value)
|
||||
case []float64:
|
||||
return convertToFloatSlice[T](value)
|
||||
case []any:
|
||||
return convertToAnySlice[T](value)
|
||||
case map[string]string:
|
||||
return convertToStringMap[T](value)
|
||||
case map[string]int:
|
||||
return convertToIntMap[T](value)
|
||||
case map[int]any:
|
||||
return convertToIntKeyMap[T](value)
|
||||
case map[string]any:
|
||||
return convertToAnyMap[T](value)
|
||||
}
|
||||
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToString[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
if v == float64(int(v)) {
|
||||
return any(strconv.Itoa(int(v))).(T), true
|
||||
}
|
||||
return any(fmt.Sprintf("%g", v)).(T), true
|
||||
case int:
|
||||
return any(strconv.Itoa(v)).(T), true
|
||||
case bool:
|
||||
return any(strconv.FormatBool(v)).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToInt[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
return any(int(v)).(T), true
|
||||
case string:
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
return any(i).(T), true
|
||||
}
|
||||
case bool:
|
||||
if v {
|
||||
return any(1).(T), true
|
||||
}
|
||||
return any(0).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToFloat[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return any(float64(v)).(T), true
|
||||
case string:
|
||||
if f, err := strconv.ParseFloat(v, 64); err == nil {
|
||||
return any(f).(T), true
|
||||
}
|
||||
case bool:
|
||||
if v {
|
||||
return any(1.0).(T), true
|
||||
}
|
||||
return any(0.0).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToBool[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
switch v {
|
||||
case "true", "yes", "1":
|
||||
return any(true).(T), true
|
||||
case "false", "no", "0":
|
||||
return any(false).(T), true
|
||||
}
|
||||
case int:
|
||||
return any(v != 0).(T), true
|
||||
case float64:
|
||||
return any(v != 0).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToIntSlice[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case []float64:
|
||||
result := make([]int, len(v))
|
||||
for i, f := range v {
|
||||
result[i] = int(f)
|
||||
}
|
||||
return any(result).(T), true
|
||||
case []any:
|
||||
result := make([]int, 0, len(v))
|
||||
for _, item := range v {
|
||||
if i, ok := ConvertValue[int](item); ok {
|
||||
result = append(result, i)
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToStringSlice[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
if v, ok := value.([]any); ok {
|
||||
result := make([]string, 0, len(v))
|
||||
for _, item := range v {
|
||||
if s, ok := ConvertValue[string](item); ok {
|
||||
result = append(result, s)
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToBoolSlice[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
if v, ok := value.([]any); ok {
|
||||
result := make([]bool, 0, len(v))
|
||||
for _, item := range v {
|
||||
if b, ok := ConvertValue[bool](item); ok {
|
||||
result = append(result, b)
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToFloatSlice[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case []int:
|
||||
result := make([]float64, len(v))
|
||||
for i, n := range v {
|
||||
result[i] = float64(n)
|
||||
}
|
||||
return any(result).(T), true
|
||||
case []any:
|
||||
result := make([]float64, 0, len(v))
|
||||
for _, item := range v {
|
||||
if f, ok := ConvertValue[float64](item); ok {
|
||||
result = append(result, f)
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToAnySlice[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case []int:
|
||||
result := make([]any, len(v))
|
||||
for i, n := range v {
|
||||
result[i] = n
|
||||
}
|
||||
return any(result).(T), true
|
||||
case []string:
|
||||
result := make([]any, len(v))
|
||||
for i, s := range v {
|
||||
result[i] = s
|
||||
}
|
||||
return any(result).(T), true
|
||||
case []bool:
|
||||
result := make([]any, len(v))
|
||||
for i, b := range v {
|
||||
result[i] = b
|
||||
}
|
||||
return any(result).(T), true
|
||||
case []float64:
|
||||
result := make([]any, len(v))
|
||||
for i, f := range v {
|
||||
result[i] = f
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToStringMap[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
if v, ok := value.(map[string]any); ok {
|
||||
result := make(map[string]string, len(v))
|
||||
for k, val := range v {
|
||||
if s, ok := ConvertValue[string](val); ok {
|
||||
result[k] = s
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToIntMap[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
if v, ok := value.(map[string]any); ok {
|
||||
result := make(map[string]int, len(v))
|
||||
for k, val := range v {
|
||||
if i, ok := ConvertValue[int](val); ok {
|
||||
result[k] = i
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToIntKeyMap[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
if v, ok := value.(map[string]any); ok {
|
||||
result := make(map[int]any, len(v))
|
||||
for k, val := range v {
|
||||
if i, err := strconv.Atoi(k); err == nil {
|
||||
result[i] = val
|
||||
} else {
|
||||
return zero, false
|
||||
}
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func convertToAnyMap[T any](value any) (T, bool) {
|
||||
var zero T
|
||||
switch v := value.(type) {
|
||||
case map[string]string:
|
||||
result := make(map[string]any, len(v))
|
||||
for k, s := range v {
|
||||
result[k] = s
|
||||
}
|
||||
return any(result).(T), true
|
||||
case map[string]int:
|
||||
result := make(map[string]any, len(v))
|
||||
for k, i := range v {
|
||||
result[k] = i
|
||||
}
|
||||
return any(result).(T), true
|
||||
case map[int]any:
|
||||
result := make(map[string]any, len(v))
|
||||
for k, val := range v {
|
||||
result[strconv.Itoa(k)] = val
|
||||
}
|
||||
return any(result).(T), true
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// GetTypedValue gets a value from the state with type conversion
|
||||
func GetTypedValue[T any](s *State, index int) (T, bool) {
|
||||
value, err := s.ToValue(index)
|
||||
if err != nil {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
return ConvertValue[T](value)
|
||||
}
|
||||
|
||||
// GetGlobalTyped gets a global variable with type conversion
|
||||
func GetGlobalTyped[T any](s *State, name string) (T, bool) {
|
||||
s.GetGlobal(name)
|
||||
defer s.Pop(1)
|
||||
return GetTypedValue[T](s, -1)
|
||||
}
|
||||
|
59
validation.go
Normal file
59
validation.go
Normal file
@ -0,0 +1,59 @@
|
||||
package luajit
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ArgSpec defines an argument specification for validation
|
||||
type ArgSpec struct {
|
||||
Name string
|
||||
Type string
|
||||
Required bool
|
||||
Check func(*State, int) bool
|
||||
}
|
||||
|
||||
// Common argument checkers
|
||||
var (
|
||||
CheckString = func(s *State, i int) bool { return s.IsString(i) }
|
||||
CheckNumber = func(s *State, i int) bool { return s.IsNumber(i) }
|
||||
CheckBool = func(s *State, i int) bool { return s.IsBoolean(i) }
|
||||
CheckTable = func(s *State, i int) bool { return s.IsTable(i) }
|
||||
CheckFunc = func(s *State, i int) bool { return s.IsFunction(i) }
|
||||
CheckAny = func(s *State, i int) bool { return true }
|
||||
)
|
||||
|
||||
// CheckArgs validates function arguments against specifications
|
||||
func (s *State) CheckArgs(specs ...ArgSpec) error {
|
||||
for i, spec := range specs {
|
||||
argIdx := i + 1
|
||||
if argIdx > s.GetTop() {
|
||||
if spec.Required {
|
||||
return fmt.Errorf("missing argument %d: %s", argIdx, spec.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if s.IsNil(argIdx) && !spec.Required {
|
||||
continue
|
||||
}
|
||||
|
||||
if !spec.Check(s, argIdx) {
|
||||
return fmt.Errorf("argument %d (%s) must be %s", argIdx, spec.Name, spec.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckMinArgs checks for minimum number of arguments
|
||||
func (s *State) CheckMinArgs(min int) error {
|
||||
if s.GetTop() < min {
|
||||
return fmt.Errorf("expected at least %d arguments, got %d", min, s.GetTop())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckExactArgs checks for exact number of arguments
|
||||
func (s *State) CheckExactArgs(count int) error {
|
||||
if s.GetTop() != count {
|
||||
return fmt.Errorf("expected exactly %d arguments, got %d", count, s.GetTop())
|
||||
}
|
||||
return nil
|
||||
}
|
161
vendor/luajit/include/lauxlib.h
vendored
161
vendor/luajit/include/lauxlib.h
vendored
@ -1,161 +0,0 @@
|
||||
/*
|
||||
** $Id: lauxlib.h,v 1.88.1.1 2007/12/27 13:02:25 roberto Exp $
|
||||
** Auxiliary functions for building Lua libraries
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
|
||||
#ifndef lauxlib_h
|
||||
#define lauxlib_h
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
/* extra error code for `luaL_load' */
|
||||
#define LUA_ERRFILE (LUA_ERRERR+1)
|
||||
|
||||
typedef struct luaL_Reg {
|
||||
const char *name;
|
||||
lua_CFunction func;
|
||||
} luaL_Reg;
|
||||
|
||||
LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname,
|
||||
const luaL_Reg *l, int nup);
|
||||
LUALIB_API void (luaL_register) (lua_State *L, const char *libname,
|
||||
const luaL_Reg *l);
|
||||
LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);
|
||||
LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);
|
||||
LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname);
|
||||
LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
|
||||
LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg,
|
||||
size_t *l);
|
||||
LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg,
|
||||
const char *def, size_t *l);
|
||||
LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg);
|
||||
LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
|
||||
|
||||
LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg);
|
||||
LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg,
|
||||
lua_Integer def);
|
||||
|
||||
LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);
|
||||
LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t);
|
||||
LUALIB_API void (luaL_checkany) (lua_State *L, int narg);
|
||||
|
||||
LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname);
|
||||
LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname);
|
||||
|
||||
LUALIB_API void (luaL_where) (lua_State *L, int lvl);
|
||||
LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);
|
||||
|
||||
LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def,
|
||||
const char *const lst[]);
|
||||
|
||||
/* pre-defined references */
|
||||
#define LUA_NOREF (-2)
|
||||
#define LUA_REFNIL (-1)
|
||||
|
||||
LUALIB_API int (luaL_ref) (lua_State *L, int t);
|
||||
LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref);
|
||||
|
||||
LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename);
|
||||
LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz,
|
||||
const char *name);
|
||||
LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);
|
||||
|
||||
LUALIB_API lua_State *(luaL_newstate) (void);
|
||||
|
||||
|
||||
LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p,
|
||||
const char *r);
|
||||
|
||||
LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx,
|
||||
const char *fname, int szhint);
|
||||
|
||||
/* From Lua 5.2. */
|
||||
LUALIB_API int luaL_fileresult(lua_State *L, int stat, const char *fname);
|
||||
LUALIB_API int luaL_execresult(lua_State *L, int stat);
|
||||
LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename,
|
||||
const char *mode);
|
||||
LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,
|
||||
const char *name, const char *mode);
|
||||
LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg,
|
||||
int level);
|
||||
LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
|
||||
LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname,
|
||||
int sizehint);
|
||||
LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);
|
||||
LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname);
|
||||
|
||||
|
||||
/*
|
||||
** ===============================================================
|
||||
** some useful macros
|
||||
** ===============================================================
|
||||
*/
|
||||
|
||||
#define luaL_argcheck(L, cond,numarg,extramsg) \
|
||||
((void)((cond) || luaL_argerror(L, (numarg), (extramsg))))
|
||||
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
|
||||
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
|
||||
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
|
||||
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
|
||||
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
|
||||
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
|
||||
|
||||
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
|
||||
|
||||
#define luaL_dofile(L, fn) \
|
||||
(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
|
||||
|
||||
#define luaL_dostring(L, s) \
|
||||
(luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
|
||||
|
||||
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
|
||||
|
||||
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
|
||||
|
||||
/* From Lua 5.2. */
|
||||
#define luaL_newlibtable(L, l) \
|
||||
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
|
||||
#define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0))
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Generic Buffer manipulation
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
|
||||
typedef struct luaL_Buffer {
|
||||
char *p; /* current position in buffer */
|
||||
int lvl; /* number of strings in the stack (level) */
|
||||
lua_State *L;
|
||||
char buffer[LUAL_BUFFERSIZE];
|
||||
} luaL_Buffer;
|
||||
|
||||
#define luaL_addchar(B,c) \
|
||||
((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \
|
||||
(*(B)->p++ = (char)(c)))
|
||||
|
||||
/* compatibility only */
|
||||
#define luaL_putchar(B,c) luaL_addchar(B,c)
|
||||
|
||||
#define luaL_addsize(B,n) ((B)->p += (n))
|
||||
|
||||
LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B);
|
||||
LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B);
|
||||
LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
|
||||
LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s);
|
||||
LUALIB_API void (luaL_addvalue) (luaL_Buffer *B);
|
||||
LUALIB_API void (luaL_pushresult) (luaL_Buffer *B);
|
||||
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
#endif
|
402
vendor/luajit/include/lua.h
vendored
402
vendor/luajit/include/lua.h
vendored
@ -1,402 +0,0 @@
|
||||
/*
|
||||
** $Id: lua.h,v 1.218.1.5 2008/08/06 13:30:12 roberto Exp $
|
||||
** Lua - An Extensible Extension Language
|
||||
** Lua.org, PUC-Rio, Brazil (https://www.lua.org)
|
||||
** See Copyright Notice at the end of this file
|
||||
*/
|
||||
|
||||
|
||||
#ifndef lua_h
|
||||
#define lua_h
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#include "luaconf.h"
|
||||
|
||||
|
||||
#define LUA_VERSION "Lua 5.1"
|
||||
#define LUA_RELEASE "Lua 5.1.4"
|
||||
#define LUA_VERSION_NUM 501
|
||||
#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio"
|
||||
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes"
|
||||
|
||||
|
||||
/* mark for precompiled code (`<esc>Lua') */
|
||||
#define LUA_SIGNATURE "\033Lua"
|
||||
|
||||
/* option for multiple returns in `lua_pcall' and `lua_call' */
|
||||
#define LUA_MULTRET (-1)
|
||||
|
||||
|
||||
/*
|
||||
** pseudo-indices
|
||||
*/
|
||||
#define LUA_REGISTRYINDEX (-10000)
|
||||
#define LUA_ENVIRONINDEX (-10001)
|
||||
#define LUA_GLOBALSINDEX (-10002)
|
||||
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))
|
||||
|
||||
|
||||
/* thread status */
|
||||
#define LUA_OK 0
|
||||
#define LUA_YIELD 1
|
||||
#define LUA_ERRRUN 2
|
||||
#define LUA_ERRSYNTAX 3
|
||||
#define LUA_ERRMEM 4
|
||||
#define LUA_ERRERR 5
|
||||
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef int (*lua_CFunction) (lua_State *L);
|
||||
|
||||
|
||||
/*
|
||||
** functions that read/write blocks when loading/dumping Lua chunks
|
||||
*/
|
||||
typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
|
||||
|
||||
typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud);
|
||||
|
||||
|
||||
/*
|
||||
** prototype for memory-allocation functions
|
||||
*/
|
||||
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
|
||||
|
||||
|
||||
/*
|
||||
** basic types
|
||||
*/
|
||||
#define LUA_TNONE (-1)
|
||||
|
||||
#define LUA_TNIL 0
|
||||
#define LUA_TBOOLEAN 1
|
||||
#define LUA_TLIGHTUSERDATA 2
|
||||
#define LUA_TNUMBER 3
|
||||
#define LUA_TSTRING 4
|
||||
#define LUA_TTABLE 5
|
||||
#define LUA_TFUNCTION 6
|
||||
#define LUA_TUSERDATA 7
|
||||
#define LUA_TTHREAD 8
|
||||
|
||||
|
||||
|
||||
/* minimum Lua stack available to a C function */
|
||||
#define LUA_MINSTACK 20
|
||||
|
||||
|
||||
/*
|
||||
** generic extra include file
|
||||
*/
|
||||
#if defined(LUA_USER_H)
|
||||
#include LUA_USER_H
|
||||
#endif
|
||||
|
||||
|
||||
/* type of numbers in Lua */
|
||||
typedef LUA_NUMBER lua_Number;
|
||||
|
||||
|
||||
/* type for integer functions */
|
||||
typedef LUA_INTEGER lua_Integer;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** state manipulation
|
||||
*/
|
||||
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
|
||||
LUA_API void (lua_close) (lua_State *L);
|
||||
LUA_API lua_State *(lua_newthread) (lua_State *L);
|
||||
|
||||
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
|
||||
|
||||
|
||||
/*
|
||||
** basic stack manipulation
|
||||
*/
|
||||
LUA_API int (lua_gettop) (lua_State *L);
|
||||
LUA_API void (lua_settop) (lua_State *L, int idx);
|
||||
LUA_API void (lua_pushvalue) (lua_State *L, int idx);
|
||||
LUA_API void (lua_remove) (lua_State *L, int idx);
|
||||
LUA_API void (lua_insert) (lua_State *L, int idx);
|
||||
LUA_API void (lua_replace) (lua_State *L, int idx);
|
||||
LUA_API int (lua_checkstack) (lua_State *L, int sz);
|
||||
|
||||
LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n);
|
||||
|
||||
|
||||
/*
|
||||
** access functions (stack -> C)
|
||||
*/
|
||||
|
||||
LUA_API int (lua_isnumber) (lua_State *L, int idx);
|
||||
LUA_API int (lua_isstring) (lua_State *L, int idx);
|
||||
LUA_API int (lua_iscfunction) (lua_State *L, int idx);
|
||||
LUA_API int (lua_isuserdata) (lua_State *L, int idx);
|
||||
LUA_API int (lua_type) (lua_State *L, int idx);
|
||||
LUA_API const char *(lua_typename) (lua_State *L, int tp);
|
||||
|
||||
LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2);
|
||||
LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2);
|
||||
LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2);
|
||||
|
||||
LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx);
|
||||
LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx);
|
||||
LUA_API int (lua_toboolean) (lua_State *L, int idx);
|
||||
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
|
||||
LUA_API size_t (lua_objlen) (lua_State *L, int idx);
|
||||
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
|
||||
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
|
||||
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
|
||||
LUA_API const void *(lua_topointer) (lua_State *L, int idx);
|
||||
|
||||
|
||||
/*
|
||||
** push functions (C -> stack)
|
||||
*/
|
||||
LUA_API void (lua_pushnil) (lua_State *L);
|
||||
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
|
||||
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
|
||||
LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l);
|
||||
LUA_API void (lua_pushstring) (lua_State *L, const char *s);
|
||||
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
|
||||
va_list argp);
|
||||
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
|
||||
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
|
||||
LUA_API void (lua_pushboolean) (lua_State *L, int b);
|
||||
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
|
||||
LUA_API int (lua_pushthread) (lua_State *L);
|
||||
|
||||
|
||||
/*
|
||||
** get functions (Lua -> stack)
|
||||
*/
|
||||
LUA_API void (lua_gettable) (lua_State *L, int idx);
|
||||
LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k);
|
||||
LUA_API void (lua_rawget) (lua_State *L, int idx);
|
||||
LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n);
|
||||
LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec);
|
||||
LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz);
|
||||
LUA_API int (lua_getmetatable) (lua_State *L, int objindex);
|
||||
LUA_API void (lua_getfenv) (lua_State *L, int idx);
|
||||
|
||||
|
||||
/*
|
||||
** set functions (stack -> Lua)
|
||||
*/
|
||||
LUA_API void (lua_settable) (lua_State *L, int idx);
|
||||
LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k);
|
||||
LUA_API void (lua_rawset) (lua_State *L, int idx);
|
||||
LUA_API void (lua_rawseti) (lua_State *L, int idx, int n);
|
||||
LUA_API int (lua_setmetatable) (lua_State *L, int objindex);
|
||||
LUA_API int (lua_setfenv) (lua_State *L, int idx);
|
||||
|
||||
|
||||
/*
|
||||
** `load' and `call' functions (load and run Lua code)
|
||||
*/
|
||||
LUA_API void (lua_call) (lua_State *L, int nargs, int nresults);
|
||||
LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc);
|
||||
LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud);
|
||||
LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt,
|
||||
const char *chunkname);
|
||||
|
||||
LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data);
|
||||
|
||||
|
||||
/*
|
||||
** coroutine functions
|
||||
*/
|
||||
LUA_API int (lua_yield) (lua_State *L, int nresults);
|
||||
LUA_API int (lua_resume) (lua_State *L, int narg);
|
||||
LUA_API int (lua_status) (lua_State *L);
|
||||
|
||||
/*
|
||||
** garbage-collection function and options
|
||||
*/
|
||||
|
||||
#define LUA_GCSTOP 0
|
||||
#define LUA_GCRESTART 1
|
||||
#define LUA_GCCOLLECT 2
|
||||
#define LUA_GCCOUNT 3
|
||||
#define LUA_GCCOUNTB 4
|
||||
#define LUA_GCSTEP 5
|
||||
#define LUA_GCSETPAUSE 6
|
||||
#define LUA_GCSETSTEPMUL 7
|
||||
#define LUA_GCISRUNNING 9
|
||||
|
||||
LUA_API int (lua_gc) (lua_State *L, int what, int data);
|
||||
|
||||
|
||||
/*
|
||||
** miscellaneous functions
|
||||
*/
|
||||
|
||||
LUA_API int (lua_error) (lua_State *L);
|
||||
|
||||
LUA_API int (lua_next) (lua_State *L, int idx);
|
||||
|
||||
LUA_API void (lua_concat) (lua_State *L, int n);
|
||||
|
||||
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
|
||||
LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** ===============================================================
|
||||
** some useful macros
|
||||
** ===============================================================
|
||||
*/
|
||||
|
||||
#define lua_pop(L,n) lua_settop(L, -(n)-1)
|
||||
|
||||
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||
|
||||
#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
|
||||
|
||||
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)
|
||||
|
||||
#define lua_strlen(L,i) lua_objlen(L, (i))
|
||||
|
||||
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
|
||||
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
|
||||
#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
|
||||
#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
|
||||
#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
|
||||
#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
|
||||
#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
|
||||
#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0)
|
||||
|
||||
#define lua_pushliteral(L, s) \
|
||||
lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1)
|
||||
|
||||
#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
|
||||
#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
|
||||
|
||||
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** compatibility macros and functions
|
||||
*/
|
||||
|
||||
#define lua_open() luaL_newstate()
|
||||
|
||||
#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX)
|
||||
|
||||
#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0)
|
||||
|
||||
#define lua_Chunkreader lua_Reader
|
||||
#define lua_Chunkwriter lua_Writer
|
||||
|
||||
|
||||
/* hack */
|
||||
LUA_API void lua_setlevel (lua_State *from, lua_State *to);
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================================
|
||||
** Debug API
|
||||
** =======================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** Event codes
|
||||
*/
|
||||
#define LUA_HOOKCALL 0
|
||||
#define LUA_HOOKRET 1
|
||||
#define LUA_HOOKLINE 2
|
||||
#define LUA_HOOKCOUNT 3
|
||||
#define LUA_HOOKTAILRET 4
|
||||
|
||||
|
||||
/*
|
||||
** Event masks
|
||||
*/
|
||||
#define LUA_MASKCALL (1 << LUA_HOOKCALL)
|
||||
#define LUA_MASKRET (1 << LUA_HOOKRET)
|
||||
#define LUA_MASKLINE (1 << LUA_HOOKLINE)
|
||||
#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT)
|
||||
|
||||
typedef struct lua_Debug lua_Debug; /* activation record */
|
||||
|
||||
|
||||
/* Functions to be called by the debuger in specific events */
|
||||
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
|
||||
|
||||
|
||||
LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar);
|
||||
LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);
|
||||
LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);
|
||||
LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);
|
||||
LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n);
|
||||
LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n);
|
||||
LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count);
|
||||
LUA_API lua_Hook lua_gethook (lua_State *L);
|
||||
LUA_API int lua_gethookmask (lua_State *L);
|
||||
LUA_API int lua_gethookcount (lua_State *L);
|
||||
|
||||
/* From Lua 5.2. */
|
||||
LUA_API void *lua_upvalueid (lua_State *L, int idx, int n);
|
||||
LUA_API void lua_upvaluejoin (lua_State *L, int idx1, int n1, int idx2, int n2);
|
||||
LUA_API int lua_loadx (lua_State *L, lua_Reader reader, void *dt,
|
||||
const char *chunkname, const char *mode);
|
||||
LUA_API const lua_Number *lua_version (lua_State *L);
|
||||
LUA_API void lua_copy (lua_State *L, int fromidx, int toidx);
|
||||
LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *isnum);
|
||||
LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *isnum);
|
||||
|
||||
/* From Lua 5.3. */
|
||||
LUA_API int lua_isyieldable (lua_State *L);
|
||||
|
||||
|
||||
struct lua_Debug {
|
||||
int event;
|
||||
const char *name; /* (n) */
|
||||
const char *namewhat; /* (n) `global', `local', `field', `method' */
|
||||
const char *what; /* (S) `Lua', `C', `main', `tail' */
|
||||
const char *source; /* (S) */
|
||||
int currentline; /* (l) */
|
||||
int nups; /* (u) number of upvalues */
|
||||
int linedefined; /* (S) */
|
||||
int lastlinedefined; /* (S) */
|
||||
char short_src[LUA_IDSIZE]; /* (S) */
|
||||
/* private part */
|
||||
int i_ci; /* active function */
|
||||
};
|
||||
|
||||
/* }====================================================================== */
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#endif
|
154
vendor/luajit/include/luaconf.h
vendored
154
vendor/luajit/include/luaconf.h
vendored
@ -1,154 +0,0 @@
|
||||
/*
|
||||
** Configuration header.
|
||||
** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
|
||||
*/
|
||||
|
||||
#ifndef luaconf_h
|
||||
#define luaconf_h
|
||||
|
||||
#ifndef WINVER
|
||||
#define WINVER 0x0501
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
|
||||
/* Default path for loading Lua and C modules with require(). */
|
||||
#if defined(_WIN32)
|
||||
/*
|
||||
** In Windows, any exclamation mark ('!') in the path is replaced by the
|
||||
** path of the directory of the executable file of the current process.
|
||||
*/
|
||||
#define LUA_LDIR "!\\lua\\"
|
||||
#define LUA_CDIR "!\\"
|
||||
#define LUA_PATH_DEFAULT \
|
||||
".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;"
|
||||
#define LUA_CPATH_DEFAULT \
|
||||
".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll"
|
||||
#else
|
||||
/*
|
||||
** Note to distribution maintainers: do NOT patch the following lines!
|
||||
** Please read ../doc/install.html#distro and pass PREFIX=/usr instead.
|
||||
*/
|
||||
#ifndef LUA_MULTILIB
|
||||
#define LUA_MULTILIB "lib"
|
||||
#endif
|
||||
#ifndef LUA_LMULTILIB
|
||||
#define LUA_LMULTILIB "lib"
|
||||
#endif
|
||||
#define LUA_LROOT "/usr/local"
|
||||
#define LUA_LUADIR "/lua/5.1/"
|
||||
|
||||
#ifdef LUA_ROOT
|
||||
#define LUA_JROOT LUA_ROOT
|
||||
#define LUA_RLDIR LUA_ROOT "/share" LUA_LUADIR
|
||||
#define LUA_RCDIR LUA_ROOT "/" LUA_MULTILIB LUA_LUADIR
|
||||
#define LUA_RLPATH ";" LUA_RLDIR "?.lua;" LUA_RLDIR "?/init.lua"
|
||||
#define LUA_RCPATH ";" LUA_RCDIR "?.so"
|
||||
#else
|
||||
#define LUA_JROOT LUA_LROOT
|
||||
#define LUA_RLPATH
|
||||
#define LUA_RCPATH
|
||||
#endif
|
||||
|
||||
#ifndef LUA_LJDIR
|
||||
#define LUA_LJDIR LUA_JROOT "/share/luajit-2.1"
|
||||
#endif
|
||||
|
||||
#define LUA_JPATH ";" LUA_LJDIR "/?.lua"
|
||||
#define LUA_LLDIR LUA_LROOT "/share" LUA_LUADIR
|
||||
#define LUA_LCDIR LUA_LROOT "/" LUA_LMULTILIB LUA_LUADIR
|
||||
#define LUA_LLPATH ";" LUA_LLDIR "?.lua;" LUA_LLDIR "?/init.lua"
|
||||
#define LUA_LCPATH1 ";" LUA_LCDIR "?.so"
|
||||
#define LUA_LCPATH2 ";" LUA_LCDIR "loadall.so"
|
||||
|
||||
#define LUA_PATH_DEFAULT "./?.lua" LUA_JPATH LUA_LLPATH LUA_RLPATH
|
||||
#define LUA_CPATH_DEFAULT "./?.so" LUA_LCPATH1 LUA_RCPATH LUA_LCPATH2
|
||||
#endif
|
||||
|
||||
/* Environment variable names for path overrides and initialization code. */
|
||||
#define LUA_PATH "LUA_PATH"
|
||||
#define LUA_CPATH "LUA_CPATH"
|
||||
#define LUA_INIT "LUA_INIT"
|
||||
|
||||
/* Special file system characters. */
|
||||
#if defined(_WIN32)
|
||||
#define LUA_DIRSEP "\\"
|
||||
#else
|
||||
#define LUA_DIRSEP "/"
|
||||
#endif
|
||||
#define LUA_PATHSEP ";"
|
||||
#define LUA_PATH_MARK "?"
|
||||
#define LUA_EXECDIR "!"
|
||||
#define LUA_IGMARK "-"
|
||||
#define LUA_PATH_CONFIG \
|
||||
LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" \
|
||||
LUA_EXECDIR "\n" LUA_IGMARK "\n"
|
||||
|
||||
/* Quoting in error messages. */
|
||||
#define LUA_QL(x) "'" x "'"
|
||||
#define LUA_QS LUA_QL("%s")
|
||||
|
||||
/* Various tunables. */
|
||||
#define LUAI_MAXSTACK 65500 /* Max. # of stack slots for a thread (<64K). */
|
||||
#define LUAI_MAXCSTACK 8000 /* Max. # of stack slots for a C func (<10K). */
|
||||
#define LUAI_GCPAUSE 200 /* Pause GC until memory is at 200%. */
|
||||
#define LUAI_GCMUL 200 /* Run GC at 200% of allocation speed. */
|
||||
#define LUA_MAXCAPTURES 32 /* Max. pattern captures. */
|
||||
|
||||
/* Configuration for the frontend (the luajit executable). */
|
||||
#if defined(luajit_c)
|
||||
#define LUA_PROGNAME "luajit" /* Fallback frontend name. */
|
||||
#define LUA_PROMPT "> " /* Interactive prompt. */
|
||||
#define LUA_PROMPT2 ">> " /* Continuation prompt. */
|
||||
#define LUA_MAXINPUT 512 /* Max. input line length. */
|
||||
#endif
|
||||
|
||||
/* Note: changing the following defines breaks the Lua 5.1 ABI. */
|
||||
#define LUA_INTEGER ptrdiff_t
|
||||
#define LUA_IDSIZE 60 /* Size of lua_Debug.short_src. */
|
||||
/*
|
||||
** Size of lauxlib and io.* on-stack buffers. Weird workaround to avoid using
|
||||
** unreasonable amounts of stack space, but still retain ABI compatibility.
|
||||
** Blame Lua for depending on BUFSIZ in the ABI, blame **** for wrecking it.
|
||||
*/
|
||||
#define LUAL_BUFFERSIZE (BUFSIZ > 16384 ? 8192 : BUFSIZ)
|
||||
|
||||
/* The following defines are here only for compatibility with luaconf.h
|
||||
** from the standard Lua distribution. They must not be changed for LuaJIT.
|
||||
*/
|
||||
#define LUA_NUMBER_DOUBLE
|
||||
#define LUA_NUMBER double
|
||||
#define LUAI_UACNUMBER double
|
||||
#define LUA_NUMBER_SCAN "%lf"
|
||||
#define LUA_NUMBER_FMT "%.14g"
|
||||
#define lua_number2str(s, n) sprintf((s), LUA_NUMBER_FMT, (n))
|
||||
#define LUAI_MAXNUMBER2STR 32
|
||||
#define LUA_INTFRMLEN "l"
|
||||
#define LUA_INTFRM_T long
|
||||
|
||||
/* Linkage of public API functions. */
|
||||
#if defined(LUA_BUILD_AS_DLL)
|
||||
#if defined(LUA_CORE) || defined(LUA_LIB)
|
||||
#define LUA_API __declspec(dllexport)
|
||||
#else
|
||||
#define LUA_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define LUA_API extern
|
||||
#endif
|
||||
|
||||
#define LUALIB_API LUA_API
|
||||
|
||||
/* Compatibility support for assertions. */
|
||||
#if defined(LUA_USE_ASSERT) || defined(LUA_USE_APICHECK)
|
||||
#include <assert.h>
|
||||
#endif
|
||||
#ifdef LUA_USE_ASSERT
|
||||
#define lua_assert(x) assert(x)
|
||||
#endif
|
||||
#ifdef LUA_USE_APICHECK
|
||||
#define luai_apicheck(L, o) { (void)L; assert(o); }
|
||||
#else
|
||||
#define luai_apicheck(L, o) { (void)L; }
|
||||
#endif
|
||||
|
||||
#endif
|
79
vendor/luajit/include/luajit.h
vendored
79
vendor/luajit/include/luajit.h
vendored
@ -1,79 +0,0 @@
|
||||
/*
|
||||
** LuaJIT -- a Just-In-Time Compiler for Lua. https://luajit.org/
|
||||
**
|
||||
** Copyright (C) 2005-2025 Mike Pall. All rights reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining
|
||||
** a copy of this software and associated documentation files (the
|
||||
** "Software"), to deal in the Software without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Software, and to
|
||||
** permit persons to whom the Software is furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** [ MIT license: https://www.opensource.org/licenses/mit-license.php ]
|
||||
*/
|
||||
|
||||
#ifndef _LUAJIT_H
|
||||
#define _LUAJIT_H
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#define LUAJIT_VERSION "LuaJIT 2.1.1736781742"
|
||||
#define LUAJIT_VERSION_NUM 20199 /* Deprecated. */
|
||||
#define LUAJIT_VERSION_SYM luaJIT_version_2_1_1736781742
|
||||
#define LUAJIT_COPYRIGHT "Copyright (C) 2005-2025 Mike Pall"
|
||||
#define LUAJIT_URL "https://luajit.org/"
|
||||
|
||||
/* Modes for luaJIT_setmode. */
|
||||
#define LUAJIT_MODE_MASK 0x00ff
|
||||
|
||||
enum {
|
||||
LUAJIT_MODE_ENGINE, /* Set mode for whole JIT engine. */
|
||||
LUAJIT_MODE_DEBUG, /* Set debug mode (idx = level). */
|
||||
|
||||
LUAJIT_MODE_FUNC, /* Change mode for a function. */
|
||||
LUAJIT_MODE_ALLFUNC, /* Recurse into subroutine protos. */
|
||||
LUAJIT_MODE_ALLSUBFUNC, /* Change only the subroutines. */
|
||||
|
||||
LUAJIT_MODE_TRACE, /* Flush a compiled trace. */
|
||||
|
||||
LUAJIT_MODE_WRAPCFUNC = 0x10, /* Set wrapper mode for C function calls. */
|
||||
|
||||
LUAJIT_MODE_MAX
|
||||
};
|
||||
|
||||
/* Flags or'ed in to the mode. */
|
||||
#define LUAJIT_MODE_OFF 0x0000 /* Turn feature off. */
|
||||
#define LUAJIT_MODE_ON 0x0100 /* Turn feature on. */
|
||||
#define LUAJIT_MODE_FLUSH 0x0200 /* Flush JIT-compiled code. */
|
||||
|
||||
/* LuaJIT public C API. */
|
||||
|
||||
/* Control the JIT engine. */
|
||||
LUA_API int luaJIT_setmode(lua_State *L, int idx, int mode);
|
||||
|
||||
/* Low-overhead profiling API. */
|
||||
typedef void (*luaJIT_profile_callback)(void *data, lua_State *L,
|
||||
int samples, int vmstate);
|
||||
LUA_API void luaJIT_profile_start(lua_State *L, const char *mode,
|
||||
luaJIT_profile_callback cb, void *data);
|
||||
LUA_API void luaJIT_profile_stop(lua_State *L);
|
||||
LUA_API const char *luaJIT_profile_dumpstack(lua_State *L, const char *fmt,
|
||||
int depth, size_t *len);
|
||||
|
||||
/* Enforce (dynamic) linker error for version mismatches. Call from main. */
|
||||
LUA_API void LUAJIT_VERSION_SYM(void);
|
||||
|
||||
#endif
|
44
vendor/luajit/include/lualib.h
vendored
44
vendor/luajit/include/lualib.h
vendored
@ -1,44 +0,0 @@
|
||||
/*
|
||||
** Standard library header.
|
||||
** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
|
||||
*/
|
||||
|
||||
#ifndef _LUALIB_H
|
||||
#define _LUALIB_H
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#define LUA_FILEHANDLE "FILE*"
|
||||
|
||||
#define LUA_COLIBNAME "coroutine"
|
||||
#define LUA_MATHLIBNAME "math"
|
||||
#define LUA_STRLIBNAME "string"
|
||||
#define LUA_TABLIBNAME "table"
|
||||
#define LUA_IOLIBNAME "io"
|
||||
#define LUA_OSLIBNAME "os"
|
||||
#define LUA_LOADLIBNAME "package"
|
||||
#define LUA_DBLIBNAME "debug"
|
||||
#define LUA_BITLIBNAME "bit"
|
||||
#define LUA_JITLIBNAME "jit"
|
||||
#define LUA_FFILIBNAME "ffi"
|
||||
|
||||
LUALIB_API int luaopen_base(lua_State *L);
|
||||
LUALIB_API int luaopen_math(lua_State *L);
|
||||
LUALIB_API int luaopen_string(lua_State *L);
|
||||
LUALIB_API int luaopen_table(lua_State *L);
|
||||
LUALIB_API int luaopen_io(lua_State *L);
|
||||
LUALIB_API int luaopen_os(lua_State *L);
|
||||
LUALIB_API int luaopen_package(lua_State *L);
|
||||
LUALIB_API int luaopen_debug(lua_State *L);
|
||||
LUALIB_API int luaopen_bit(lua_State *L);
|
||||
LUALIB_API int luaopen_jit(lua_State *L);
|
||||
LUALIB_API int luaopen_ffi(lua_State *L);
|
||||
LUALIB_API int luaopen_string_buffer(lua_State *L);
|
||||
|
||||
LUALIB_API void luaL_openlibs(lua_State *L);
|
||||
|
||||
#ifndef lua_assert
|
||||
#define lua_assert(x) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif
|
BIN
vendor/luajit/linux/libluajit.a
vendored
BIN
vendor/luajit/linux/libluajit.a
vendored
Binary file not shown.
BIN
vendor/luajit/windows/libluajit.a
vendored
BIN
vendor/luajit/windows/libluajit.a
vendored
Binary file not shown.
884
wrapper.go
884
wrapper.go
@ -1,61 +1,110 @@
|
||||
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
|
||||
#cgo !windows pkg-config: --static luajit
|
||||
#cgo windows CFLAGS: -I/usr/local/include/luajit-2.1
|
||||
#cgo windows LDFLAGS: -lluajit-5.1 -static
|
||||
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int do_string(lua_State *L, const char *s) {
|
||||
int status = luaL_loadstring(L, s);
|
||||
if (status) return status;
|
||||
return lua_pcall(L, 0, LUA_MULTRET, 0);
|
||||
int status = luaL_loadstring(L, s);
|
||||
if (status == 0) {
|
||||
status = lua_pcall(L, 0, 0, 0);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int do_file(lua_State *L, const char *filename) {
|
||||
int status = luaL_loadfile(L, filename);
|
||||
if (status) return status;
|
||||
return lua_pcall(L, 0, LUA_MULTRET, 0);
|
||||
int status = luaL_loadfile(L, filename);
|
||||
if (status == 0) {
|
||||
status = lua_pcall(L, 0, 0, 0);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int execute_with_results(lua_State *L, const char *code, int store_results) {
|
||||
int status = luaL_loadstring(L, code);
|
||||
if (status != 0) return status;
|
||||
return lua_pcall(L, 0, store_results ? LUA_MULTRET : 0, 0);
|
||||
}
|
||||
|
||||
static size_t get_table_length(lua_State *L, int index) {
|
||||
return lua_objlen(L, index);
|
||||
}
|
||||
|
||||
static int is_integer(lua_State *L, int index) {
|
||||
if (!lua_isnumber(L, index)) return 0;
|
||||
lua_Number n = lua_tonumber(L, index);
|
||||
return n == (lua_Number)(lua_Integer)n;
|
||||
}
|
||||
|
||||
static int sample_array_type(lua_State *L, int index, int count) {
|
||||
int all_numbers = 1;
|
||||
int all_integers = 1;
|
||||
int all_strings = 1;
|
||||
int all_bools = 1;
|
||||
|
||||
for (int i = 1; i <= count && i <= 5; i++) {
|
||||
lua_pushnumber(L, i);
|
||||
lua_gettable(L, index);
|
||||
|
||||
int type = lua_type(L, -1);
|
||||
if (type != LUA_TNUMBER) all_numbers = all_integers = 0;
|
||||
if (type != LUA_TSTRING) all_strings = 0;
|
||||
if (type != LUA_TBOOLEAN) all_bools = 0;
|
||||
|
||||
if (all_numbers && !is_integer(L, -1)) all_integers = 0;
|
||||
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (!all_numbers && !all_strings && !all_bools) break;
|
||||
}
|
||||
|
||||
if (all_integers) return 1;
|
||||
if (all_numbers) return 2;
|
||||
if (all_strings) return 3;
|
||||
if (all_bools) return 4;
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// State represents a Lua state with configurable stack safety
|
||||
// Stack management constants
|
||||
const (
|
||||
LUA_MINSTACK = 20
|
||||
LUA_MAXSTACK = 1000000
|
||||
LUA_REGISTRYINDEX = -10000
|
||||
LUA_GLOBALSINDEX = -10002
|
||||
)
|
||||
|
||||
type State struct {
|
||||
L *C.lua_State
|
||||
safeStack bool
|
||||
L *C.lua_State
|
||||
}
|
||||
|
||||
// NewSafe creates a new Lua state with full stack safety guarantees
|
||||
func NewSafe() *State {
|
||||
func New(openLibs ...bool) *State {
|
||||
L := C.luaL_newstate()
|
||||
if L == nil {
|
||||
return nil
|
||||
}
|
||||
C.luaL_openlibs(L)
|
||||
return &State{L: L, safeStack: true}
|
||||
}
|
||||
|
||||
// New creates a new Lua state with minimal stack checking
|
||||
func New() *State {
|
||||
L := C.luaL_newstate()
|
||||
if L == nil {
|
||||
return nil
|
||||
if len(openLibs) == 0 || openLibs[0] {
|
||||
C.luaL_openlibs(L)
|
||||
}
|
||||
C.luaL_openlibs(L)
|
||||
return &State{L: L, safeStack: false}
|
||||
|
||||
return &State{L: L}
|
||||
}
|
||||
|
||||
// Close closes the Lua state
|
||||
func (s *State) Close() {
|
||||
if s.L != nil {
|
||||
C.lua_close(s.L)
|
||||
@ -63,157 +112,12 @@ func (s *State) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// DoString executes a Lua string with appropriate stack management
|
||||
func (s *State) DoString(str string) error {
|
||||
cstr := C.CString(str)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
|
||||
if s.safeStack {
|
||||
return stackGuardErr(s, func() error {
|
||||
return s.safeCall(func() C.int {
|
||||
return C.do_string(s.L, cstr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
status := C.do_string(s.L, cstr)
|
||||
if status != 0 {
|
||||
return &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushValue pushes a Go value onto the stack
|
||||
func (s *State) PushValue(v interface{}) error {
|
||||
if s.safeStack {
|
||||
return stackGuardErr(s, func() error {
|
||||
if err := s.checkStack(1); err != nil {
|
||||
return fmt.Errorf("pushing value: %w", err)
|
||||
}
|
||||
return s.pushValueUnsafe(v)
|
||||
})
|
||||
}
|
||||
return s.pushValueUnsafe(v)
|
||||
}
|
||||
|
||||
func (s *State) pushValueSafe(v interface{}) error {
|
||||
if err := s.checkStack(1); err != nil {
|
||||
return fmt.Errorf("pushing value: %w", err)
|
||||
}
|
||||
return s.pushValueUnsafe(v)
|
||||
}
|
||||
|
||||
func (s *State) pushValueUnsafe(v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
s.PushNil()
|
||||
case bool:
|
||||
s.PushBoolean(v)
|
||||
case float64:
|
||||
s.PushNumber(v)
|
||||
case int:
|
||||
s.PushNumber(float64(v))
|
||||
case string:
|
||||
s.PushString(v)
|
||||
case map[string]interface{}:
|
||||
// Special case: handle array stored in map
|
||||
if arr, ok := v[""].([]float64); ok {
|
||||
s.NewTable()
|
||||
for i, elem := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
s.PushNumber(elem)
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return s.pushTableUnsafe(v)
|
||||
case []float64:
|
||||
s.NewTable()
|
||||
for i, elem := range v {
|
||||
s.PushNumber(float64(i + 1))
|
||||
s.PushNumber(elem)
|
||||
s.SetTable(-3)
|
||||
}
|
||||
case []interface{}:
|
||||
s.NewTable()
|
||||
for i, elem := range v {
|
||||
s.PushNumber(float64(i + 1))
|
||||
if err := s.pushValueUnsafe(elem); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetTable(-3)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %T", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToValue converts a Lua value to a Go value
|
||||
func (s *State) ToValue(index int) (interface{}, error) {
|
||||
if s.safeStack {
|
||||
return stackGuardValue[interface{}](s, func() (interface{}, error) {
|
||||
return s.toValueUnsafe(index)
|
||||
})
|
||||
}
|
||||
return s.toValueUnsafe(index)
|
||||
}
|
||||
|
||||
func (s *State) toValueUnsafe(index int) (interface{}, error) {
|
||||
switch s.GetType(index) {
|
||||
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:
|
||||
if !s.IsTable(index) {
|
||||
return nil, fmt.Errorf("not a table at index %d", index)
|
||||
}
|
||||
return s.toTableUnsafe(index)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %s", s.GetType(index))
|
||||
}
|
||||
}
|
||||
|
||||
// Simple operations remain unchanged as they don't need stack protection
|
||||
|
||||
func (s *State) GetType(index int) LuaType { return LuaType(C.lua_type(s.L, C.int(index))) }
|
||||
func (s *State) IsFunction(index int) bool { return s.GetType(index) == TypeFunction }
|
||||
func (s *State) IsTable(index int) bool { return s.GetType(index) == TypeTable }
|
||||
func (s *State) ToBoolean(index int) bool { return C.lua_toboolean(s.L, C.int(index)) != 0 }
|
||||
func (s *State) ToNumber(index int) float64 { return float64(C.lua_tonumber(s.L, C.int(index))) }
|
||||
func (s *State) ToString(index int) string {
|
||||
return C.GoString(C.lua_tolstring(s.L, C.int(index), nil))
|
||||
}
|
||||
func (s *State) GetTop() int { return int(C.lua_gettop(s.L)) }
|
||||
func (s *State) Pop(n int) { C.lua_settop(s.L, C.int(-n-1)) }
|
||||
func (s *State) SetTop(index int) { C.lua_settop(s.L, C.int(index)) }
|
||||
|
||||
// Push operations
|
||||
|
||||
func (s *State) PushNil() { C.lua_pushnil(s.L) }
|
||||
func (s *State) PushBoolean(b bool) { C.lua_pushboolean(s.L, C.int(bool2int(b))) }
|
||||
func (s *State) PushNumber(n float64) { C.lua_pushnumber(s.L, C.double(n)) }
|
||||
func (s *State) PushString(str string) {
|
||||
cstr := C.CString(str)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.lua_pushstring(s.L, cstr)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func bool2int(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// Stack operations
|
||||
func (s *State) GetTop() int { return int(C.lua_gettop(s.L)) }
|
||||
func (s *State) SetTop(index int) { C.lua_settop(s.L, C.int(index)) }
|
||||
func (s *State) PushCopy(index int) { C.lua_pushvalue(s.L, C.int(index)) }
|
||||
func (s *State) Pop(n int) { C.lua_settop(s.L, C.int(-n-1)) }
|
||||
func (s *State) Remove(index int) { C.lua_remove(s.L, C.int(index)) }
|
||||
|
||||
func (s *State) absIndex(index int) int {
|
||||
if index > 0 || index <= LUA_REGISTRYINDEX {
|
||||
@ -222,102 +126,602 @@ func (s *State) absIndex(index int) int {
|
||||
return s.GetTop() + index + 1
|
||||
}
|
||||
|
||||
// SetField sets a field in a table at the given index with cached absolute index
|
||||
func (s *State) SetField(index int, key string) {
|
||||
absIdx := index
|
||||
if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
|
||||
absIdx = s.GetTop() + index + 1
|
||||
}
|
||||
// Type checking
|
||||
func (s *State) GetType(index int) LuaType { return LuaType(C.lua_type(s.L, C.int(index))) }
|
||||
func (s *State) IsNil(index int) bool { return s.GetType(index) == TypeNil }
|
||||
func (s *State) IsBoolean(index int) bool { return s.GetType(index) == TypeBoolean }
|
||||
func (s *State) IsNumber(index int) bool { return C.lua_isnumber(s.L, C.int(index)) != 0 }
|
||||
func (s *State) IsString(index int) bool { return C.lua_isstring(s.L, C.int(index)) != 0 }
|
||||
func (s *State) IsTable(index int) bool { return s.GetType(index) == TypeTable }
|
||||
func (s *State) IsFunction(index int) bool { return s.GetType(index) == TypeFunction }
|
||||
|
||||
cstr := C.CString(key)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.lua_setfield(s.L, C.int(absIdx), cstr)
|
||||
// Value conversion
|
||||
func (s *State) ToBoolean(index int) bool { return C.lua_toboolean(s.L, C.int(index)) != 0 }
|
||||
func (s *State) ToNumber(index int) float64 { return float64(C.lua_tonumber(s.L, C.int(index))) }
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// GetField gets a field from a table with cached absolute index
|
||||
// Push methods
|
||||
func (s *State) PushNil() { C.lua_pushnil(s.L) }
|
||||
func (s *State) PushBoolean(b bool) { C.lua_pushboolean(s.L, boolToInt(b)) }
|
||||
func (s *State) PushNumber(n float64) { C.lua_pushnumber(s.L, C.lua_Number(n)) }
|
||||
|
||||
func (s *State) PushString(str string) {
|
||||
if len(str) < 128 {
|
||||
cstr := C.CString(str)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.lua_pushlstring(s.L, cstr, C.size_t(len(str)))
|
||||
} else {
|
||||
header := (*struct {
|
||||
p unsafe.Pointer
|
||||
len int
|
||||
cap int
|
||||
})(unsafe.Pointer(&str))
|
||||
C.lua_pushlstring(s.L, (*C.char)(header.p), C.size_t(len(str)))
|
||||
}
|
||||
}
|
||||
|
||||
// Table operations
|
||||
func (s *State) CreateTable(narr, nrec int) { C.lua_createtable(s.L, C.int(narr), C.int(nrec)) }
|
||||
func (s *State) NewTable() { C.lua_createtable(s.L, 0, 0) }
|
||||
func (s *State) GetTable(index int) { C.lua_gettable(s.L, C.int(index)) }
|
||||
func (s *State) SetTable(index int) { C.lua_settable(s.L, C.int(index)) }
|
||||
func (s *State) Next(index int) bool { return C.lua_next(s.L, C.int(index)) != 0 }
|
||||
|
||||
func (s *State) GetField(index int, key string) {
|
||||
absIdx := index
|
||||
if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
|
||||
absIdx = s.GetTop() + index + 1
|
||||
ckey := C.CString(key)
|
||||
defer C.free(unsafe.Pointer(ckey))
|
||||
C.lua_getfield(s.L, C.int(index), ckey)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *State) GetTableLength(index int) int {
|
||||
return int(C.get_table_length(s.L, C.int(index)))
|
||||
}
|
||||
|
||||
// Enhanced PushValue with comprehensive type support
|
||||
func (s *State) PushValue(v any) error {
|
||||
switch val := v.(type) {
|
||||
case nil:
|
||||
s.PushNil()
|
||||
case bool:
|
||||
s.PushBoolean(val)
|
||||
case int:
|
||||
s.PushNumber(float64(val))
|
||||
case int64:
|
||||
s.PushNumber(float64(val))
|
||||
case float64:
|
||||
s.PushNumber(val)
|
||||
case string:
|
||||
s.PushString(val)
|
||||
case []int:
|
||||
return s.pushIntSlice(val)
|
||||
case []string:
|
||||
return s.pushStringSlice(val)
|
||||
case []bool:
|
||||
return s.pushBoolSlice(val)
|
||||
case []float64:
|
||||
return s.pushFloatSlice(val)
|
||||
case []any:
|
||||
return s.pushAnySlice(val)
|
||||
case []map[string]any:
|
||||
return s.pushMapSlice(val)
|
||||
case map[string]string:
|
||||
return s.pushStringMap(val)
|
||||
case map[string]int:
|
||||
return s.pushIntMap(val)
|
||||
case map[int]any:
|
||||
return s.pushIntKeyMap(val)
|
||||
case map[string]any:
|
||||
return s.pushAnyMap(val)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %T", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushIntSlice(arr []int) error {
|
||||
s.CreateTable(len(arr), 0)
|
||||
for i, v := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
s.PushNumber(float64(v))
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushStringSlice(arr []string) error {
|
||||
s.CreateTable(len(arr), 0)
|
||||
for i, v := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
s.PushString(v)
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushBoolSlice(arr []bool) error {
|
||||
s.CreateTable(len(arr), 0)
|
||||
for i, v := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
s.PushBoolean(v)
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushFloatSlice(arr []float64) error {
|
||||
s.CreateTable(len(arr), 0)
|
||||
for i, v := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
s.PushNumber(v)
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushAnySlice(arr []any) error {
|
||||
s.CreateTable(len(arr), 0)
|
||||
for i, v := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
if err := s.PushValue(v); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushStringMap(m map[string]string) error {
|
||||
s.CreateTable(0, len(m))
|
||||
for k, v := range m {
|
||||
s.PushString(k)
|
||||
s.PushString(v)
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushIntMap(m map[string]int) error {
|
||||
s.CreateTable(0, len(m))
|
||||
for k, v := range m {
|
||||
s.PushString(k)
|
||||
s.PushNumber(float64(v))
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushIntKeyMap(m map[int]any) error {
|
||||
s.CreateTable(0, len(m))
|
||||
for k, v := range m {
|
||||
s.PushNumber(float64(k))
|
||||
if err := s.PushValue(v); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) pushAnyMap(m map[string]any) error {
|
||||
s.CreateTable(0, len(m))
|
||||
for k, v := range m {
|
||||
s.PushString(k)
|
||||
if err := s.PushValue(v); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enhanced ToValue with automatic type detection
|
||||
func (s *State) ToValue(index int) (any, error) {
|
||||
switch s.GetType(index) {
|
||||
case TypeNil:
|
||||
return nil, nil
|
||||
case TypeBoolean:
|
||||
return s.ToBoolean(index), nil
|
||||
case TypeNumber:
|
||||
num := s.ToNumber(index)
|
||||
if num == float64(int(num)) && num >= -2147483648 && num <= 2147483647 {
|
||||
return int(num), nil
|
||||
}
|
||||
return num, nil
|
||||
case TypeString:
|
||||
return s.ToString(index), nil
|
||||
case TypeTable:
|
||||
return s.ToTable(index)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %s", s.GetType(index))
|
||||
}
|
||||
}
|
||||
|
||||
// ToTable converts a Lua table to optimal Go type
|
||||
func (s *State) ToTable(index int) (any, error) {
|
||||
absIdx := s.absIndex(index)
|
||||
if !s.IsTable(absIdx) {
|
||||
return nil, fmt.Errorf("value at index %d is not a table", index)
|
||||
}
|
||||
|
||||
if s.safeStack {
|
||||
if err := s.checkStack(1); err != nil {
|
||||
s.PushNil()
|
||||
return
|
||||
length := s.GetTableLength(absIdx)
|
||||
|
||||
if length > 0 {
|
||||
arrayType := int(C.sample_array_type(s.L, C.int(absIdx), C.int(length)))
|
||||
switch arrayType {
|
||||
case 1: // int array
|
||||
return s.extractIntArray(absIdx, length), nil
|
||||
case 2: // float array
|
||||
return s.extractFloatArray(absIdx, length), nil
|
||||
case 3: // string array
|
||||
return s.extractStringArray(absIdx, length), nil
|
||||
case 4: // bool array
|
||||
return s.extractBoolArray(absIdx, length), nil
|
||||
default: // mixed array
|
||||
return s.extractAnyArray(absIdx, length), nil
|
||||
}
|
||||
}
|
||||
|
||||
cstr := C.CString(key)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.lua_getfield(s.L, C.int(absIdx), cstr)
|
||||
return s.extractAnyMap(absIdx)
|
||||
}
|
||||
|
||||
// GetGlobal gets a global variable and pushes it onto the stack
|
||||
func (s *State) GetGlobal(name string) {
|
||||
if s.safeStack {
|
||||
if err := s.checkStack(1); err != nil {
|
||||
s.PushNil()
|
||||
return
|
||||
func (s *State) extractIntArray(index, length int) []int {
|
||||
result := make([]int, length)
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(index)
|
||||
result[i-1] = int(s.ToNumber(-1))
|
||||
s.Pop(1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *State) extractFloatArray(index, length int) []float64 {
|
||||
result := make([]float64, length)
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(index)
|
||||
result[i-1] = s.ToNumber(-1)
|
||||
s.Pop(1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *State) extractStringArray(index, length int) []string {
|
||||
result := make([]string, length)
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(index)
|
||||
result[i-1] = s.ToString(-1)
|
||||
s.Pop(1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *State) extractBoolArray(index, length int) []bool {
|
||||
result := make([]bool, length)
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(index)
|
||||
result[i-1] = s.ToBoolean(-1)
|
||||
s.Pop(1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *State) extractAnyArray(index, length int) []any {
|
||||
result := make([]any, length)
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(index)
|
||||
if val, err := s.ToValue(-1); err == nil {
|
||||
result[i-1] = val
|
||||
}
|
||||
s.Pop(1)
|
||||
}
|
||||
cstr := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.lua_getfield(s.L, C.LUA_GLOBALSINDEX, cstr)
|
||||
return result
|
||||
}
|
||||
|
||||
// SetGlobal sets a global variable from the value at the top of the stack
|
||||
func (s *State) SetGlobal(name string) {
|
||||
// SetGlobal doesn't need stack space checking as it pops the value
|
||||
cstr := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.lua_setfield(s.L, C.LUA_GLOBALSINDEX, cstr)
|
||||
}
|
||||
func (s *State) extractAnyMap(index int) (map[string]any, error) {
|
||||
result := make(map[string]any)
|
||||
s.PushNil()
|
||||
for s.Next(index) {
|
||||
var key string
|
||||
switch s.GetType(-2) {
|
||||
case TypeString:
|
||||
key = s.ToString(-2)
|
||||
case TypeNumber:
|
||||
key = strconv.FormatFloat(s.ToNumber(-2), 'g', -1, 64)
|
||||
default:
|
||||
s.Pop(1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove removes element with cached absolute index
|
||||
func (s *State) Remove(index int) {
|
||||
absIdx := index
|
||||
if s.safeStack && (index < 0 && index > LUA_REGISTRYINDEX) {
|
||||
absIdx = s.GetTop() + index + 1
|
||||
if value, err := s.ToValue(-1); err == nil {
|
||||
result[key] = value
|
||||
}
|
||||
s.Pop(1)
|
||||
}
|
||||
C.lua_remove(s.L, C.int(absIdx))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Global operations
|
||||
func (s *State) GetGlobal(name string) { s.GetField(LUA_GLOBALSINDEX, name) }
|
||||
func (s *State) SetGlobal(name string) { s.SetField(LUA_GLOBALSINDEX, name) }
|
||||
|
||||
// Code execution
|
||||
func (s *State) LoadString(code string) error {
|
||||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
|
||||
status := C.luaL_loadstring(s.L, ccode)
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), "LoadString")
|
||||
s.Pop(1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) LoadFile(filename string) error {
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
|
||||
status := C.luaL_loadfile(s.L, cfilename)
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("LoadFile(%s)", filename))
|
||||
s.Pop(1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) Call(nargs, nresults int) error {
|
||||
status := C.lua_pcall(s.L, C.int(nargs), C.int(nresults), 0)
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("Call(%d,%d)", nargs, nresults))
|
||||
s.Pop(1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) DoString(code string) error {
|
||||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
|
||||
status := C.do_string(s.L, ccode)
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), "DoString")
|
||||
s.Pop(1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoFile executes a Lua file with appropriate stack management
|
||||
func (s *State) DoFile(filename string) error {
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
|
||||
if s.safeStack {
|
||||
return stackGuardErr(s, func() error {
|
||||
return s.safeCall(func() C.int {
|
||||
return C.do_file(s.L, cfilename)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
status := C.do_file(s.L, cfilename)
|
||||
if status != 0 {
|
||||
return &LuaError{
|
||||
Code: int(status),
|
||||
Message: s.ToString(-1),
|
||||
}
|
||||
err := s.CreateLuaError(int(status), fmt.Sprintf("DoFile(%s)", filename))
|
||||
s.Pop(1)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) SetPackagePath(path string) error {
|
||||
path = filepath.ToSlash(path)
|
||||
if err := s.DoString(fmt.Sprintf(`package.path = %q`, path)); err != nil {
|
||||
return fmt.Errorf("setting package.path: %w", err)
|
||||
func (s *State) Execute(code string) (int, error) {
|
||||
baseTop := s.GetTop()
|
||||
ccode := C.CString(code)
|
||||
defer C.free(unsafe.Pointer(ccode))
|
||||
|
||||
status := C.execute_with_results(s.L, ccode, 1)
|
||||
if status != 0 {
|
||||
err := s.CreateLuaError(int(status), "Execute")
|
||||
s.Pop(1)
|
||||
return 0, err
|
||||
}
|
||||
return nil
|
||||
|
||||
return s.GetTop() - baseTop, nil
|
||||
}
|
||||
|
||||
func (s *State) ExecuteWithResult(code string) (any, error) {
|
||||
top := s.GetTop()
|
||||
defer s.SetTop(top)
|
||||
|
||||
nresults, err := s.Execute(code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nresults == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return s.ToValue(-nresults)
|
||||
}
|
||||
|
||||
func (s *State) BatchExecute(statements []string) error {
|
||||
return s.DoString(strings.Join(statements, "; "))
|
||||
}
|
||||
|
||||
// Package path operations
|
||||
func (s *State) SetPackagePath(path string) error {
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
return s.DoString(fmt.Sprintf(`package.path = %q`, path))
|
||||
}
|
||||
|
||||
func (s *State) AddPackagePath(path string) error {
|
||||
path = filepath.ToSlash(path)
|
||||
if err := s.DoString(fmt.Sprintf(`package.path = package.path .. ";%s"`, path)); err != nil {
|
||||
return fmt.Errorf("adding to package.path: %w", err)
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
return s.DoString(fmt.Sprintf(`package.path = package.path .. ";%s"`, path))
|
||||
}
|
||||
|
||||
// Metatable operations
|
||||
func (s *State) SetMetatable(index int) { C.lua_setmetatable(s.L, C.int(index)) }
|
||||
func (s *State) GetMetatable(index int) bool { return C.lua_getmetatable(s.L, C.int(index)) != 0 }
|
||||
|
||||
// Helper functions
|
||||
func boolToInt(b bool) C.int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetFieldString gets a string field from a table with default
|
||||
func (s *State) GetFieldString(index int, key string, defaultVal string) string {
|
||||
s.GetField(index, key)
|
||||
defer s.Pop(1)
|
||||
if s.IsString(-1) {
|
||||
return s.ToString(-1)
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// GetFieldNumber gets a number field from a table with default
|
||||
func (s *State) GetFieldNumber(index int, key string, defaultVal float64) float64 {
|
||||
s.GetField(index, key)
|
||||
defer s.Pop(1)
|
||||
if s.IsNumber(-1) {
|
||||
return s.ToNumber(-1)
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// GetFieldBool gets a boolean field from a table with default
|
||||
func (s *State) GetFieldBool(index int, key string, defaultVal bool) bool {
|
||||
s.GetField(index, key)
|
||||
defer s.Pop(1)
|
||||
if s.IsBoolean(-1) {
|
||||
return s.ToBoolean(-1)
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// GetFieldTable gets a table field from a table
|
||||
func (s *State) GetFieldTable(index int, key string) (any, bool) {
|
||||
s.GetField(index, key)
|
||||
defer s.Pop(1)
|
||||
if s.IsTable(-1) {
|
||||
val, err := s.ToTable(-1)
|
||||
return val, err == nil
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ForEachTableKV iterates over string key-value pairs in a table
|
||||
func (s *State) ForEachTableKV(index int, fn func(key, value string) bool) {
|
||||
absIdx := s.absIndex(index)
|
||||
s.PushNil()
|
||||
for s.Next(absIdx) {
|
||||
if s.IsString(-2) && s.IsString(-1) {
|
||||
if !fn(s.ToString(-2), s.ToString(-1)) {
|
||||
s.Pop(2)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.Pop(1)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachArray iterates over array elements
|
||||
func (s *State) ForEachArray(index int, fn func(i int, state *State) bool) {
|
||||
absIdx := s.absIndex(index)
|
||||
length := s.GetTableLength(absIdx)
|
||||
for i := 1; i <= length; i++ {
|
||||
s.PushNumber(float64(i))
|
||||
s.GetTable(absIdx)
|
||||
if !fn(i, s) {
|
||||
s.Pop(1)
|
||||
return
|
||||
}
|
||||
s.Pop(1)
|
||||
}
|
||||
}
|
||||
|
||||
// SafeToString safely converts value to string with error
|
||||
func (s *State) SafeToString(index int) (string, error) {
|
||||
if !s.IsString(index) && !s.IsNumber(index) {
|
||||
return "", fmt.Errorf("value at index %d is not a string", index)
|
||||
}
|
||||
return s.ToString(index), nil
|
||||
}
|
||||
|
||||
// SafeToNumber safely converts value to number with error
|
||||
func (s *State) SafeToNumber(index int) (float64, error) {
|
||||
if !s.IsNumber(index) {
|
||||
return 0, fmt.Errorf("value at index %d is not a number", index)
|
||||
}
|
||||
return s.ToNumber(index), nil
|
||||
}
|
||||
|
||||
// SafeToTable safely converts value to table with error
|
||||
func (s *State) SafeToTable(index int) (any, error) {
|
||||
if !s.IsTable(index) {
|
||||
return nil, fmt.Errorf("value at index %d is not a table", index)
|
||||
}
|
||||
return s.ToTable(index)
|
||||
}
|
||||
|
||||
// CallGlobal calls a global function with arguments
|
||||
func (s *State) CallGlobal(name string, args ...any) ([]any, error) {
|
||||
s.GetGlobal(name)
|
||||
if !s.IsFunction(-1) {
|
||||
s.Pop(1)
|
||||
return nil, fmt.Errorf("global '%s' is not a function", name)
|
||||
}
|
||||
|
||||
for i, arg := range args {
|
||||
if err := s.PushValue(arg); err != nil {
|
||||
s.Pop(i + 1)
|
||||
return nil, fmt.Errorf("failed to push argument %d: %w", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
baseTop := s.GetTop() - len(args) - 1
|
||||
if err := s.Call(len(args), C.LUA_MULTRET); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newTop := s.GetTop()
|
||||
nresults := newTop - baseTop
|
||||
|
||||
results := make([]any, nresults)
|
||||
for i := 0; i < nresults; i++ {
|
||||
val, err := s.ToValue(baseTop + i + 1)
|
||||
if err != nil {
|
||||
results[i] = nil
|
||||
} else {
|
||||
results[i] = val
|
||||
}
|
||||
}
|
||||
|
||||
s.SetTop(baseTop)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s *State) pushMapSlice(arr []map[string]any) error {
|
||||
s.CreateTable(len(arr), 0)
|
||||
for i, m := range arr {
|
||||
s.PushNumber(float64(i + 1))
|
||||
if err := s.PushValue(m); err != nil {
|
||||
return err
|
||||
}
|
||||
s.SetTable(-3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
276
wrapper_test.go
276
wrapper_test.go
@ -1,276 +0,0 @@
|
||||
package luajit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type stateFactory struct {
|
||||
name string
|
||||
new func() *State
|
||||
}
|
||||
|
||||
var factories = []stateFactory{
|
||||
{"unsafe", New},
|
||||
{"safe", NewSafe},
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
for _, f := range factories {
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
wantErr bool
|
||||
}{
|
||||
{"simple addition", "return 1 + 1", false},
|
||||
{"set global", "test = 42", false},
|
||||
{"syntax error", "this is not valid lua", true},
|
||||
{"runtime error", "error('test error')", true},
|
||||
}
|
||||
|
||||
for _, f := range factories {
|
||||
for _, tt := range tests {
|
||||
t.Run(f.name+"/"+tt.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
err := L.DoString(tt.code)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DoString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushAndGetValues(t *testing.T) {
|
||||
values := []struct {
|
||||
name string
|
||||
push func(*State)
|
||||
check func(*State) error
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
push: func(L *State) { L.PushString("hello") },
|
||||
check: func(L *State) error {
|
||||
if got := L.ToString(-1); got != "hello" {
|
||||
return fmt.Errorf("got %q, want %q", got, "hello")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "number",
|
||||
push: func(L *State) { L.PushNumber(42.5) },
|
||||
check: func(L *State) error {
|
||||
if got := L.ToNumber(-1); got != 42.5 {
|
||||
return fmt.Errorf("got %f, want %f", got, 42.5)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "boolean",
|
||||
push: func(L *State) { L.PushBoolean(true) },
|
||||
check: func(L *State) error {
|
||||
if got := L.ToBoolean(-1); !got {
|
||||
return fmt.Errorf("got %v, want true", got)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
push: func(L *State) { L.PushNil() },
|
||||
check: func(L *State) error {
|
||||
if typ := L.GetType(-1); typ != TypeNil {
|
||||
return fmt.Errorf("got type %v, want TypeNil", typ)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range factories {
|
||||
for _, v := range values {
|
||||
t.Run(f.name+"/"+v.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
v.push(L)
|
||||
if err := v.check(L); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackManipulation(t *testing.T) {
|
||||
for _, f := range factories {
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
// Push values
|
||||
values := []string{"first", "second", "third"}
|
||||
for _, v := range values {
|
||||
L.PushString(v)
|
||||
}
|
||||
|
||||
// Check size
|
||||
if top := L.GetTop(); top != len(values) {
|
||||
t.Errorf("stack size = %d, want %d", top, len(values))
|
||||
}
|
||||
|
||||
// Pop one value
|
||||
L.Pop(1)
|
||||
|
||||
// Check new top
|
||||
if str := L.ToString(-1); str != "second" {
|
||||
t.Errorf("top value = %q, want 'second'", str)
|
||||
}
|
||||
|
||||
// Check new size
|
||||
if top := L.GetTop(); top != len(values)-1 {
|
||||
t.Errorf("stack size after pop = %d, want %d", top, len(values)-1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobals(t *testing.T) {
|
||||
for _, f := range factories {
|
||||
t.Run(f.name, func(t *testing.T) {
|
||||
L := f.new()
|
||||
if L == nil {
|
||||
t.Fatal("Failed to create Lua state")
|
||||
}
|
||||
defer L.Close()
|
||||
|
||||
// Test via Lua
|
||||
if err := L.DoString(`globalVar = "test"`); err != nil {
|
||||
t.Fatalf("DoString error: %v", err)
|
||||
}
|
||||
|
||||
// Get the global
|
||||
L.GetGlobal("globalVar")
|
||||
if str := L.ToString(-1); str != "test" {
|
||||
t.Errorf("global value = %q, want 'test'", str)
|
||||
}
|
||||
L.Pop(1)
|
||||
|
||||
// Set and get via API
|
||||
L.PushNumber(42)
|
||||
L.SetGlobal("testNum")
|
||||
|
||||
L.GetGlobal("testNum")
|
||||
if num := L.ToNumber(-1); num != 42 {
|
||||
t.Errorf("global number = %f, want 42", num)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoFile(t *testing.T) {
|
||||
L := NewSafe()
|
||||
defer L.Close()
|
||||
|
||||
// Create test file
|
||||
content := []byte(`
|
||||
function add(a, b)
|
||||
return a + b
|
||||
end
|
||||
result = add(40, 2)
|
||||
`)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
filename := filepath.Join(tmpDir, "test.lua")
|
||||
if err := os.WriteFile(filename, content, 0644); err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
if err := L.DoFile(filename); err != nil {
|
||||
t.Fatalf("DoFile failed: %v", err)
|
||||
}
|
||||
|
||||
L.GetGlobal("result")
|
||||
if result := L.ToNumber(-1); result != 42 {
|
||||
t.Errorf("Expected result=42, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequireAndPackagePath(t *testing.T) {
|
||||
L := NewSafe()
|
||||
defer L.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create module file
|
||||
moduleContent := []byte(`
|
||||
local M = {}
|
||||
function M.multiply(a, b)
|
||||
return a * b
|
||||
end
|
||||
return M
|
||||
`)
|
||||
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, "mathmod.lua"), moduleContent, 0644); err != nil {
|
||||
t.Fatalf("Failed to create module file: %v", err)
|
||||
}
|
||||
|
||||
// Add module path and test require
|
||||
if err := L.AddPackagePath(filepath.Join(tmpDir, "?.lua")); err != nil {
|
||||
t.Fatalf("AddPackagePath failed: %v", err)
|
||||
}
|
||||
|
||||
if err := L.DoString(`
|
||||
local math = require("mathmod")
|
||||
result = math.multiply(6, 7)
|
||||
`); err != nil {
|
||||
t.Fatalf("Failed to require module: %v", err)
|
||||
}
|
||||
|
||||
L.GetGlobal("result")
|
||||
if result := L.ToNumber(-1); result != 42 {
|
||||
t.Errorf("Expected result=42, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPackagePath(t *testing.T) {
|
||||
L := NewSafe()
|
||||
defer L.Close()
|
||||
|
||||
customPath := "./custom/?.lua"
|
||||
if err := L.SetPackagePath(customPath); err != nil {
|
||||
t.Fatalf("SetPackagePath failed: %v", err)
|
||||
}
|
||||
|
||||
L.GetGlobal("package")
|
||||
L.GetField(-1, "path")
|
||||
if path := L.ToString(-1); path != customPath {
|
||||
t.Errorf("Expected package.path=%q, got %q", customPath, path)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user