diff --git a/README.md b/README.md index 0830fa3..d5e6837 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ 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.New() // pass false to not load standard libs @@ -36,25 +34,21 @@ 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.LoadAndRunBytecode(bytecode, "calc") + err := L.LoadAndRunBytecode(bytecode, "calc") } // Or do both at once err := L.CompileAndRun(`return "hello"`, "greeting") ``` -### When to Use Bytecode - -Bytecode execution is consistently faster than direct execution: - ``` Benchmark Ops/sec Comparison ---------------------------------------------------------------------------- @@ -70,21 +64,15 @@ 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? 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) @@ -95,22 +83,62 @@ 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]any{ - "name": "Arthur Dent", - "age": 30, - "items": []float64{1, 2, 3}, + "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 ([]int, map[string]string, etc.) +``` + +### 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 @@ -118,34 +146,31 @@ result, err := L.ToTable(-1) 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("Error: %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) + } } ``` ## Memory Management -The wrapper uses a custom table pooling system to reduce GC pressure when handling many tables: +The wrapper uses bytecode buffer pooling to reduce allocations: ```go -// Tables are pooled and reused internally for better performance +// Bytecode buffers are pooled and reused internally for i := 0; i < 1000; i++ { - L.GetGlobal("table") - table, _ := L.ToTable(-1) - // Use table... - L.Pop(1) - // Table is automatically returned to pool + bytecode, _ := L.CompileBytecode(code, "test") + // Buffer automatically returned to pool } ``` -The sandbox also manages its environment efficiently: +Function pointers are managed safely: ```go -// Environment objects are pooled and reused -for i := 0; i < 1000; i++ { - result, _ := sandbox.Run("return i + 1") -} +// Functions are registered in a thread-safe registry +L.RegisterGoFunction("myFunc", myGoFunc) +defer L.Cleanup() // Cleans up all registered functions ``` ## Best Practices @@ -160,7 +185,12 @@ for i := 0; i < 1000; i++ { ### 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 +- 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 @@ -186,11 +216,11 @@ Bytecode properly preserves closures and upvalues: ```go code := ` - local counter = 0 - return function() - counter = counter + 1 - return counter - end + local counter = 0 + return function() + counter = counter + 1 + return counter + end ` bytecode, _ := L.CompileBytecode(code, "counter") @@ -198,14 +228,53 @@ L.LoadAndRunBytecodeWithResults(bytecode, "counter", 1) L.SetGlobal("increment") // Later... -L.GetGlobal("increment") -L.Call(0, 1) // Returns 1 -L.Pop(1) - -L.GetGlobal("increment") -L.Call(0, 1) // Returns 2 +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? Check out the tests in the repository - they're full of examples. If you're stuck, open an issue! We're here to help.