Documentation
¶
Overview ¶
Package glua is a pure-Go Lua 5.4 for small embedded scripts: rules, policies, handlers and configuration. It needs no cgo and no assembly, and supports 64-bit platforms only.
Clients that prefer the shorter name can alias the import:
import lua "github.com/folbricht/glua"
The model ¶
Three types carry it. A Runtime is the host's configuration: libraries, host globals, Go modules, bound Go types and limits. A Script is code loaded into a Runtime: its top level runs once, in Runtime.Load, and everything it built is then frozen. A VM is a private, mutable instance of a Script, for the rare script that must keep state between calls. Runtimes and Scripts never change after they are made, so any goroutine may use them at the same time.
rt := lua.MustNew(lua.Limits{Steps: 100_000})
script, err := rt.Load(ctx, "policy.lua", src)
allow, err := script.Func2[*Request, int64, bool]("allow")
ok, err := allow(ctx, req, 10) // safe from any goroutine
The functions Func0 to Func3 return plain Go functions backed by Lua functions. Their argument and result types are checked when they are bound, not when they are called, and a call allocates nothing in the library. Script.Get reads a frozen global without running anything, and Script.Call and Script.CallN call any function with Values.
Runtime.Expr compiles an expression over the fields of a Go struct into a Rule:
rule, err := rt.Expr[Order, bool]("gold", `total > 100 and customer.tier == "gold"`)
ok, err := rule.Eval(ctx, &order)
Free names are the struct's fields, then the Runtime's globals; a misspelt name is an error from Expr, not from Eval. In a chunk, fields tagged rw can be assigned and come back to Go. Eval runs a chunk on a default Runtime, for one-off use.
Freezing ¶
After the top level runs, Load freezes every table reachable from the globals and from what the chunk returned, their metatables included, and every captured variable that closures can write. Writing one later raises an ordinary Lua error, "attempt to modify a frozen table". A call can still build and change tables of its own, which die with the call. Load fails if the state reaches something that cannot be shared: a coroutine, a library function with hidden state such as a gmatch iterator, a file, or a Go value a script can change, itself or through its fields: one of a type with writable fields or a __newindex metamethod, or that the host marked Mutable. State that must outlive a call belongs in a host function or in a VM.
Names that the host or the libraries define are reserved: a script cannot assign them, although it can shadow them with a local.
Host functions ¶
GoFunc is the one native signature for Go functions called from Lua. Call gives a GoFunc its arguments, with Call.Arg and Call.Opt converting them, and takes its results with Call.Return. The typed adapters Fn1, Fn2E, Do1 and their relatives wrap plain Go functions without reflection; Reflect wraps any function by reflection, and is never applied implicitly. A first parameter of type context.Context receives the call's context and takes no Lua argument. Host functions see their arguments the Lua way: a numeric string is a number. Results going back to Go are strict.
Go values in Lua ¶
Scalars and strings convert directly; time.Duration is a number of seconds. A pointer to a struct is a userdata whose fields Lua reads by their snake_case names, or the name a lua struct tag gives; fields are read-only unless tagged rw. Nested structs, slices and maps are live views of the Go value. Type adds methods and metamethods and renames, hides or opens fields; Convert gives a type a scalar or string form instead. Values, tables and GoFuncs pass through. ValueOf and Value.As convert by the same rules.
Limits ¶
Every entry from Go runs under Limits: a step budget, an allocation budget, a call depth and a depth of nesting between Go and Lua. A zero field takes its default, so a Runtime made with no options is bounded. Running out of steps or bytes, nesting too deep and a cancelled context are terminal errors: pcall cannot catch them, and the entry returns them as an *Error that wraps ErrSteps, ErrAlloc, ErrHostDepth or the context's cause. A host function ends the entry the same way by returning an error made with Terminate. A set of fixed caps holds even with NoLimit. Hardened is the profile for untrusted scripts. An entry that runs long and waits on its host can have its budgets renewed after each wait, with Call.Renew.
The documents under docs/ in the repository describe the sandbox and limits in full, the ways glua differs from Lua 5.4, and how common tasks look in GopherLua and in glua.
Errors ¶
A runtime error is an *Error: Error returns one line such as "policy.lua:4: boom", Error.Traceback the frames it passed through, and Unwrap the Go error behind it. A Go error returned by a host function is a string inside Lua; if that same string leaves Lua, errors.Is and errors.As still find the original, as they find the error of a module that require could not load. Code that does not compile gives a *SyntaxError, and a binding that cannot work a *BindError, before anything runs.
Example (Config) ¶
package main
import (
"context"
"fmt"
"slices"
lua "github.com/folbricht/glua"
)
var ctx = context.Background()
type Config struct {
Port int
Hosts []string
}
func main() {
rt := lua.MustNew()
script, err := rt.Load(ctx, "config.lua", `config = { port = 8080, hosts = {"a", "b"} }
count = 0
function bump() count = count + 1 return count end`)
if err != nil {
panic(err)
}
cfg, err := script.Get[Config]("config") // decoded from the frozen globals; runs nothing
fmt.Printf("%+v %v\n", cfg, err)
t, _ := script.Get[*lua.Table]("config") // or walk it, from any goroutine
port, _ := t.Field[int]("port")
fmt.Println(port)
var keys []string
for k := range t.All() {
keys = append(keys, k.String())
}
slices.Sort(keys)
fmt.Println(keys)
vm, err := script.NewVM(ctx) // runs the top level again in a private, unfrozen environment
if err != nil {
panic(err)
}
err = vm.Set("verbose", true) // state persists between calls; one goroutine at a time
bump, _ := vm.Func0[int]("bump")
bump(ctx)
n, _ := bump(ctx)
fmt.Println(err, n)
shared, _ := script.Func0[int]("bump")
_, err = shared(ctx)
fmt.Println(err)
}
Output: {Port:8080 Hosts:[a b]} <nil> 8080 [hosts port] <nil> 2 config.lua:3: attempt to modify a frozen table
Example (Errors) ¶
6. Errors.
package main
import (
"context"
"errors"
"fmt"
lua "github.com/folbricht/glua"
)
var ctx = context.Background()
func main() {
_, err := lua.Eval[int64](ctx, `local function check(n) if n > 1 then error("boom") end end
check(2)`)
if le, ok := errors.AsType[*lua.Error](err); ok {
fmt.Println(le) // one line
for _, f := range le.Traceback() {
fmt.Printf("%s:%d: in %s\n", f.Source, f.Line, f.Function)
}
}
rt := lua.MustNew(lua.Limits{Steps: 1000})
_, err = rt.Load(ctx, "spin.lua", `while true do end`)
if errors.Is(err, lua.ErrSteps) || errors.Is(err, context.DeadlineExceeded) {
fmt.Println("ran too long:", err)
}
_, err = rt.Load(ctx, "bad.lua", "x = = 1")
if se, ok := errors.AsType[*lua.SyntaxError](err); ok {
fmt.Println(se.Line, se.Msg)
}
}
Output: eval:1: boom eval:1: in function 'check' eval:2: in main chunk ran too long: spin.lua:1: glua: step limit exceeded 1 unexpected symbol near '='
Example (Handlers) ¶
package main
import (
"context"
"errors"
"fmt"
"strings"
"sync"
lua "github.com/folbricht/glua"
)
var ctx = context.Background()
type Request struct {
Method string
Path string
hdr map[string]string
}
func (r *Request) Header(name string) string { return r.hdr[name] }
const policySrc = `
local prefix = "/api/"
function allow(req, limit)
if req.method ~= "GET" then return false end
local cost = tonumber(req:header("x-cost")) or 0
return req.path:sub(1, #prefix) == prefix and cost <= limit
end
`
func main() {
rt, err := lua.New(
lua.Limits{Steps: 100_000, AllocBytes: 1 << 20},
lua.Global("env", "prod"),
lua.Global("upper", lua.Fn1(strings.ToUpper)),
lua.Type[Request](lua.Method("header", lua.Fn2((*Request).Header))),
)
if err != nil {
panic(err)
}
script, err := rt.Load(ctx, "policy.lua", policySrc) // compiles, runs the top level once, freezes the result
if err != nil {
panic(err)
}
allow, err := script.Func2[*Request, int64, bool]("allow")
if err != nil {
panic(err)
}
reqs := []*Request{
{Method: "GET", Path: "/api/users", hdr: map[string]string{"x-cost": "3"}},
{Method: "GET", Path: "/api/report", hdr: map[string]string{"x-cost": "50"}},
{Method: "POST", Path: "/api/users"},
{Method: "GET", Path: "/static/app.js"},
}
results := make([]bool, len(reqs))
var wg sync.WaitGroup
for i, req := range reqs {
wg.Go(func() {
results[i], _ = allow(ctx, req, 10) // a plain func(context.Context, *Request, int64) (bool, error)
})
}
wg.Wait()
fmt.Println(results)
// optional hooks can be probed
if _, err := script.Func1[*Request, struct{}]("on_deny"); errors.Is(err, lua.ErrUndefined) {
fmt.Println("no on_deny hook")
}
}
Output: [true false false false] no on_deny hook
Example (HostFunctions) ¶
package main
import (
"context"
"errors"
"fmt"
"os"
"strings"
lua "github.com/folbricht/glua"
)
var ctx = context.Background()
type Request struct {
Method string
Path string
hdr map[string]string
}
func (r *Request) Header(name string) string { return r.hdr[name] }
var store = map[string]string{"user:42": "ann"}
func lookup(ctx context.Context, key string) (string, error) {
if v, ok := store[key]; ok {
return v, nil
}
return "", errors.New("not found")
}
func main() {
clamp := lua.GoFunc(func(c *lua.Call) error {
x, lo := c.Arg[float64](0), c.Arg[float64](1)
hi := c.Opt[float64](2, 1)
return c.Return(min(max(x, lo), hi))
})
split := lua.GoFunc(func(c *lua.Call) error {
s, sep := c.Arg[string](0), c.Arg[string](1)
head, tail, _ := strings.Cut(s, sep)
return c.Return2(head, tail)
})
each := lua.GoFunc(func(c *lua.Call) error {
t := c.Arg[*lua.Table](0)
for _, v := range t.Items() {
if _, err := c.Invoke(c.Value(1), v); err != nil {
return err
}
}
return nil
})
rt := lua.MustNew(
lua.Output(os.Stdout), // where print goes; the default discards it
lua.Global("upper", lua.Fn1(strings.ToUpper)),
lua.Global("clamp", clamp),
lua.Global("log_warn", lua.Do1(func(msg string) { fmt.Println("warning:", msg) })),
lua.Module("kv", lua.Members{
"get": lua.Fn2E(lookup), // func(context.Context, string) (string, error)
"split": split,
"each": each,
"version": 3,
}),
lua.Type[Request](lua.Method("header", lua.Fn2((*Request).Header))),
)
script, err := rt.Load(ctx, "host.lua", `
function run(req)
print(upper("hello")) -- global
print(clamp(1.7, 0)) -- third argument left out, so c.Opt supplies 1
print(kv.get("user:42")) -- module member; the Go side also receives the context
local head, tail = kv.split("a=b", "=") -- two results
print(head, tail)
print(req:header("x-cost")) -- method, req goes in as the first argument
print(pcall(kv.get, "")) -- a Go error is a Lua error, a string here
local total = 0
kv.each({1, 2, 3}, function(n) total = total + n end) -- Go calls back into Lua
print(total)
print(require("kv") == kv, kv.version) -- the same table as the global kv
log_warn("done")
end`)
if err != nil {
panic(err)
}
run, _ := script.Func1[*Request, struct{}]("run")
if _, err := run(ctx, &Request{hdr: map[string]string{"x-cost": "7"}}); err != nil {
fmt.Println(err)
}
}
Output: HELLO 1.0 ann a b 7 false not found 6 true 3 warning: done
Example (OneLiner) ¶
1. A one-liner.
package main
import (
"context"
"fmt"
lua "github.com/folbricht/glua"
)
var ctx = context.Background()
func main() {
n, err := lua.Eval[int64](ctx, `return 6 * 7`)
fmt.Println(n, err)
}
Output: 42 <nil>
Example (Rule) ¶
package main
import (
"context"
"fmt"
lua "github.com/folbricht/glua"
)
var ctx = context.Background()
type Customer struct {
Tier string
}
type Order struct {
Total float64
Items int
Customer *Customer
Discount float64 `lua:"discount,rw"`
}
func main() {
rt := lua.MustNew()
rule, err := rt.Expr[Order, bool]("gold-discount", `total > 100 and customer.tier == "gold"`)
if err != nil {
panic(err)
}
order := Order{Total: 120, Customer: &Customer{Tier: "gold"}}
ok, err := rule.Eval(ctx, &order) // safe from any goroutine; order stays on the caller's stack
fmt.Println(ok, err)
// a typo fails when the rule is made, not in production
_, err = rt.Expr[Order, bool]("gold-discount", `totl > 100`)
fmt.Println(err)
// in a chunk, a field tagged rw is an output
tiers, _ := rt.Expr[Order, struct{}]("tiers", `
if total > 100 then discount = 0.1 end
if items >= 10 then discount = discount + 0.05 end`)
order = Order{Total: 150, Items: 12}
tiers.Eval(ctx, &order)
fmt.Printf("%.2f\n", order.Discount)
}
Output: true <nil> gold-discount:1: unknown name 'totl' 0.15
Index ¶
- Constants
- Variables
- func Eval[R any](ctx context.Context, src string) (R, error)
- func SetHashSeed(seed uint64)
- func Terminate(err error) error
- type BindError
- type Call
- func (c *Call) Arg[T any](i int) T
- func (c *Call) ArgError(i int, msg string) error
- func (c *Call) Charge(n int) error
- func (c *Call) Context() context.Context
- func (c *Call) Errorf(format string, args ...any) error
- func (c *Call) Invoke(fn Value, args ...Value) (Value, error)
- func (c *Call) InvokeN(dst []Value, fn Value, args ...Value) ([]Value, error)
- func (c *Call) Len() int
- func (c *Call) Opt[T any](i int, def T) T
- func (c *Call) Renew()
- func (c *Call) Return[T any](v T) error
- func (c *Call) Return2[T, U any](v T, u U) error
- func (c *Call) ReturnValues(vs ...Value) error
- func (c *Call) Value(i int) Value
- type CallStats
- type Chunk
- type Error
- type Frame
- type GoFunc
- func Do0(f func()) GoFunc
- func Do0E(f func() error) GoFunc
- func Do1[A any](f func(A)) GoFunc
- func Do1E[A any](f func(A) error) GoFunc
- func Do2[A, B any](f func(A, B)) GoFunc
- func Do2E[A, B any](f func(A, B) error) GoFunc
- func Do3[A, B, C any](f func(A, B, C)) GoFunc
- func Do3E[A, B, C any](f func(A, B, C) error) GoFunc
- func Fn0[R any](f func() R) GoFunc
- func Fn0E[R any](f func() (R, error)) GoFunc
- func Fn1[A, R any](f func(A) R) GoFunc
- func Fn1E[A, R any](f func(A) (R, error)) GoFunc
- func Fn2[A, B, R any](f func(A, B) R) GoFunc
- func Fn2E[A, B, R any](f func(A, B) (R, error)) GoFunc
- func Fn3[A, B, C, R any](f func(A, B, C) R) GoFunc
- func Fn3E[A, B, C, R any](f func(A, B, C) (R, error)) GoFunc
- func Reflect(fn any) GoFunc
- type Kind
- type Lib
- type Limits
- type Members
- type Option
- func AutoBind(on bool) Option
- func ChunkLoader(find func(ctx context.Context, name string) (*Chunk, error)) Option
- func Clock(now func() time.Time) Option
- func Convert[T any](enc func(T) Value, dec func(Value) (T, error)) Option
- func Global(name string, v any) Option
- func Hardened() Option
- func Loader(find func(name string) (src string, err error)) Option
- func Module(name string, members Members) Option
- func Observe(f func(CallStats)) Option
- func Output(w io.Writer) Option
- func OutputFunc(f func(ctx context.Context, s string) error) Option
- func RandSeed(seed uint64) Option
- func RecoverPanics() Option
- func SourceModule(name, src string) Option
- func StrictGlobals() Option
- func Type[T any](opts ...TypeOption) Option
- func Without(names ...string) Option
- type Pair
- type PanicError
- type Rule
- type Runtime
- type Script
- func (s *Script) Call(ctx context.Context, fn Value, args ...Value) (Value, error)
- func (s *Script) CallN(ctx context.Context, dst []Value, fn Value, args ...Value) ([]Value, error)
- func (s *Script) Func0[R any](name string) (func(context.Context) (R, error), error)
- func (s *Script) Func1[A, R any](name string) (func(context.Context, A) (R, error), error)
- func (s *Script) Func2[A, B, R any](name string) (func(context.Context, A, B) (R, error), error)
- func (s *Script) Func3[A, B, C, R any](name string) (func(context.Context, A, B, C) (R, error), error)
- func (s *Script) Get[T any](name string) (T, error)
- func (s *Script) Globals() *Table
- func (s *Script) NewVM(ctx context.Context) (*VM, error)
- func (s *Script) Result() Value
- func (s *Script) WithLimits(l Limits) (*Script, error)
- type SyntaxError
- type Table
- func (t *Table) All() iter.Seq2[Value, Value]
- func (t *Table) Field[T any](name string) (T, error)
- func (t *Table) Freeze() error
- func (t *Table) Frozen() bool
- func (t *Table) Get(key Value) Value
- func (t *Table) Items() iter.Seq2[int, Value]
- func (t *Table) Len() int
- func (t *Table) Next(key Value) (k, v Value, ok bool)
- func (t *Table) Set(key, val Value)
- func (t *Table) SetField[T any](name string, v T)
- type TypeOption
- type VM
- func (vm *VM) Call(ctx context.Context, fn Value, args ...Value) (Value, error)
- func (vm *VM) CallN(ctx context.Context, dst []Value, fn Value, args ...Value) ([]Value, error)
- func (vm *VM) Func0[R any](name string) (func(context.Context) (R, error), error)
- func (vm *VM) Func1[A, R any](name string) (func(context.Context, A) (R, error), error)
- func (vm *VM) Func2[A, B, R any](name string) (func(context.Context, A, B) (R, error), error)
- func (vm *VM) Func3[A, B, C, R any](name string) (func(context.Context, A, B, C) (R, error), error)
- func (vm *VM) Function(ch *Chunk) Value
- func (vm *VM) Get[T any](name string) (T, error)
- func (vm *VM) Globals() *Table
- func (vm *VM) Set[T any](name string, v T) error
- type Value
Examples ¶
Constants ¶
const NoLimit = -1
NoLimit switches a limit off. The fixed caps still hold.
Variables ¶
var ( ErrSteps = errors.New("glua: step limit exceeded") // Limits.Steps ran out ErrAlloc = errors.New("glua: allocation limit exceeded") // Limits.AllocBytes ran out, or one allocation was over 1 GiB ErrHostDepth = errors.New("glua: too many nested calls between Go and Lua") // Limits.HostDepth was reached ErrUndefined = errors.New("glua: name is not defined") // FuncN or Get named nothing ErrNotFunction = errors.New("glua: value is not a function") // FuncN, Call or CallN got something else )
Errors that errors.Is finds. The first three are terminal: pcall cannot catch them, and an *Error that wraps them carries the script's position and traceback.
Functions ¶
func Eval ¶
Eval loads src on a default Runtime (LibSafe, default limits) and decodes the chunk's first result, or, for a Pair, its first two.
func SetHashSeed ¶
func SetHashSeed(seed uint64)
SetHashSeed fixes the process-wide hash seed, and with it the order of pairs and the values tostring shows in place of addresses, for tests and replay. It must be called before any other use of the package; afterwards it panics.
func Terminate ¶ added in v0.3.0
Terminate wraps err so that a host function returning it ends the whole entry, as running out of steps does: pcall, xpcall and coroutine.resume do not see it, __close handlers run on the grace budget, and the entry returns an *Error that unwraps to err. It must be returned as it is: wrapped again, as by fmt.Errorf with %w, it is an ordinary error. It is for a host that finds the script must not go on, for a permission it lacks or a quota it spent. A terminal error that already stands wins. Terminate(nil) is nil.
Types ¶
type BindError ¶
BindError is a binding that cannot work: a Go value or type that cannot be converted, a duplicate or reserved name, a missing field. New, Load, Expr and FuncN return it, before anything runs.
type Call ¶
type Call struct {
// contains filtered or unexported fields
}
Call is the view a host function has of its invocation. It holds indices, never addresses, so it survives stack growth. It is valid only until the function returns, and only on the goroutine that received it.
func (*Call) Arg ¶
Arg returns argument i, counting from 0, converted to T. A missing or unconvertible argument raises "bad argument #1 to 'f' (number expected, got nil)", which pcall can catch. It unwinds by panic, so it must run on the goroutine that received c. c.Value(i).As[T]() is the form that returns an error instead.
func (*Call) ArgError ¶ added in v0.4.0
ArgError returns the error for a bad argument i, counting from 0 as Arg does, as C Lua's luaL_argerror makes it: "bad argument #1 to 'f' (msg)", the function named as the calling code names it, or by its own name when Go code made the call, a method call not counting self, with the position Errorf gives.
func (*Call) Charge ¶
Charge counts work a host function does for the script against the entry's budgets: n bytes against AllocBytes and a step per 64 of them, as library functions charge for what they make. Call it before allocating. Running out is a terminal error, which the function should return. A function that calls Charge is taken to charge for everything it makes, so its results are not charged again when it returns.
func (*Call) Context ¶
Context returns the context of the outermost entry from Go. It carries what is left of the entry's budgets: a Script called with it from the host function shares them.
func (*Call) Errorf ¶
Errorf returns an error with a formatted message, for a host function to return. As C Lua's luaL_error does, the message Lua sees starts with the position of the Lua code that called the function ("policy.lua:4: no such user"), and has none when Go code, such as pcall, made the call. %w wraps an error as fmt.Errorf does, for errors.Is and errors.As, and the error may itself be wrapped: the position starts the whole message.
func (*Call) Invoke ¶
Invoke calls fn with args from inside a host function and returns its first result. It shares the budgets of the outermost entry.
func (*Call) InvokeN ¶
InvokeN calls fn with args from inside a host function and appends its results to dst[:0]. It shares the budgets of the outermost entry.
func (*Call) Renew ¶ added in v0.3.0
func (c *Call) Renew()
Renew gives the entry its step and allocation budgets afresh, as they were when it started. It is for an entry that runs long and waits on its host, such as a session that reads input or a worker that waits for events: a host function that renews after each wait bounds the work between waits rather than over the entry's whole life. Renew only after a real wait: a script that loops on a function that renews at once is bounded by its context alone. A limit already exceeded stays exceeded. Renew does nothing in an entry made with another entry's Context, which shares that entry's budgets, nor in closers running after a terminal error.
func (*Call) Return ¶
Return sets the function's single result. The last call wins. It reports only a value that cannot be converted, so `return c.Return(x)` reads well.
func (*Call) ReturnValues ¶
ReturnValues sets the function's results. The last call wins.
type Chunk ¶ added in v0.3.0
type Chunk struct {
// contains filtered or unexported fields
}
Chunk is compiled Lua source, as Compile makes it; the zero Chunk is not one. It never changes, so one Chunk can serve any number of Runtimes and goroutines; each environment that requires it runs it afresh, and VM.Function makes it a function of a VM.
type Error ¶
type Error struct {
Value Value // what was raised: for VM errors a message with its position; a string for Go errors
// contains filtered or unexported fields
}
Error is a runtime error raised by a script or by a host function it called.
type Frame ¶
type Frame struct {
Source string // "" for Go code
Line int
Function string // "main chunk", "function 'f'", "function <policy.lua:12>", or "Go function"
}
Frame is one level of a traceback.
type GoFunc ¶
GoFunc is the one native signature for host functions. Everything else adapts to it.
func Reflect ¶
Reflect adapts any Go function by reflection. Convenient and slow; it is never applied implicitly. A first context.Context parameter receives the call's context, a final error result becomes a Lua error, and a variadic function takes the remaining arguments. It panics if fn is not a function or has a type that cannot be converted.
type Kind ¶
type Kind uint8
Kind is the type of a Value as Lua sees it.
const ( KindNil Kind = iota // nil KindBool // boolean KindInt // integer number KindFloat // float number KindString // string KindTable // table KindFunction // a Lua or Go function KindUserdata // a Go pointer KindThread // a coroutine )
The kinds of Value. Lua's number type is split into KindInt and KindFloat.
type Lib ¶
type Lib uint
Lib options are OR-ed together. With none given, LibSafe is used.
const ( LibBase Lib = 1 << iota // the base library, without dofile and loadfile; also require and package LibString // string, with pack and unpack LibTable // table LibMath // math LibUTF8 // utf8 LibCoroutine // coroutine LibOS // os: clock, time, date and difftime only LibIO // io, for trusted scripts only LibLoad // load: compile text at run time LibNone Lib = 1 << 31 // an empty environment; with other flags, only those LibSafe = LibBase | LibString | LibTable | LibMath | LibUTF8 // the default )
type Limits ¶
type Limits struct {
Steps int64 // default 10M. Back-edges, calls, and library work in proportion to its size
AllocBytes int64 // default 64 MiB. Charged before each allocation made for the script
CallDepth int // default 10,000 Lua frames. Catchable "stack overflow"
HostDepth int // default 100 nested Go re-entries
InitSteps int64 // defaults to Steps. Used by Load and NewVM for the top level
InitAllocBytes int64 // defaults to AllocBytes
}
Limits apply to one entry from Go, including everything it re-enters, until a host function renews them with Call.Renew. A zero field takes the default. NoLimit switches a limit off.
type Members ¶
Members holds the contents of a module: GoFuncs, scalars, strings, nested Members, pointers to bound types.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option is implemented by Lib, Limits and the results of the functions below.
func AutoBind ¶
AutoBind chooses whether Go types statically reachable from a binding are bound with the default rules. Without it, only types registered with Type are. It is on by default, and off under Hardened.
func ChunkLoader ¶ added in v0.3.0
ChunkLoader is Loader for a host that compiles modules itself: find receives the context of the entry that requires name, so it can choose the module by who asks, and returns it compiled, so it can name it in messages as it likes and give the same Chunk again rather than have each environment compile it. Like Loader, it runs from any goroutine that loads a Script or makes a VM, so it must be safe for concurrent use. An error from find says the module was not found, or did not compile when it is or wraps a *SyntaxError; so does a nil Chunk. A Chunk's source is not charged to the script, as a SourceModule's is not. Loader and ChunkLoader cannot both be given.
func Convert ¶
Convert gives T a scalar or string form instead of a userdata: netip.Addr, uuid.UUID, decimals. Conversions are process-wide, like type IDs; registering a second conversion for the same type fails.
func Global ¶
Global defines a global that every script of the Runtime sees, under a reserved name. Tables given here are frozen.
func Hardened ¶
func Hardened() Option
Hardened is the profile for untrusted scripts: the bounds-checked loop, AutoBind(false), no LibLoad, LibCoroutine, LibOS or LibIO, no NoLimit, and maps and slices passed by copy.
func Loader ¶
Loader gives require the source of modules not defined by Module or SourceModule. It runs only while a script loads, or in a VM. An error it returns, or the *SyntaxError of its source, is behind the error of require, for errors.Is and errors.As.
func Module ¶
Module defines a module of Go members. It is a global under its name, and require returns the same table.
func Output ¶
Output is where print and warn write; the default discards. Writes are serialised, so w need not be safe for concurrent use. Of Output and OutputFunc, the last given wins.
func OutputFunc ¶ added in v0.3.0
OutputFunc is Output for a host that needs to know which entry wrote: f receives what Output's writer would, one call for each print, warn or io.write, with the entry's context, so it can tag the text with the request or tenant behind it. Calls are not serialised: f must be safe for concurrent use. An error it returns is raised in the script, as a write error is.
func RandSeed ¶
RandSeed seeds math.random at the start of every entry, for reproducible runs. It also makes math.randomseed() without arguments reproducible: it takes its seed from the generator.
func RecoverPanics ¶
func RecoverPanics() Option
RecoverPanics turns a panic in a host function into a *PanicError returned by the entry, instead of letting it continue.
func SourceModule ¶
SourceModule defines a module written in Lua, which require runs in the environment that loads it.
func StrictGlobals ¶
func StrictGlobals() Option
StrictGlobals makes reading an undefined global an error, instead of nil.
func Type ¶
func Type[T any](opts ...TypeOption) Option
Type customises how *T appears in Lua: methods, metamethods, and which fields are visible, writable and under what name. With AutoBind(false), only types registered here are bound.
func Without ¶ added in v0.3.0
Without leaves out names the libraries define: globals such as "collectgarbage" or "require", or library members such as "string.rep". A global left out is free, so the host may define its own under it with Global or Module, and a script may assign it. Strings' methods are the string library's, so leaving out "string.rep" takes s:rep too, while leaving out "string" takes only the global. Naming something the libraries do not define is an error from New.
type Pair ¶
type Pair[A, B any] struct { First A Second B }
Pair as a result type decodes the first two results, as in the idiom return nil, "message".
type PanicError ¶
PanicError is a panic recovered at the entry: one inside the VM itself, which the checked loop of Hardened turns bugs into, or, with RecoverPanics, one in a host function.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
Error returns "glua: panic: " and the panic value.
type Rule ¶
type Rule[E, R any] struct { // contains filtered or unexported fields }
Rule is a compiled Expr. It is a concrete generic type with a method, so a call to Eval is static and escape analysis can see that env does not leak: a func value would force every pointer argument to the heap. Share freely.
func (*Rule[E, R]) Eval ¶
Eval copies *env into a slot of the borrowed executor, runs, and on success copies it back, which carries the writable fields out: the others are read-only to the script. env itself does not escape, so a caller-local struct stays on the stack. What its pointers and strings refer to is copied to the heap slot and must live on the heap.
type Runtime ¶
type Runtime struct {
// contains filtered or unexported fields
}
Runtime is the host side of the sandbox: libraries, globals, modules, bound Go types and limits. It never changes after New, so it is safe to share between goroutines.
func New ¶
New makes a Runtime. It reports bad options: a value that cannot be converted, a duplicate name, a syntax error in a source module, both Loader and ChunkLoader.
func (*Runtime) Expr ¶
Expr compiles an expression, or a chunk, whose free names are the fields of E and then the Runtime's globals. The source is parsed as `return <src>` first, then as a chunk; in a chunk, fields that are writable can be assigned. Unknown names, assignments to read-only fields or to globals, and a field that shadows a global are errors here, not at run time.
type Script ¶
type Script struct {
// contains filtered or unexported fields
}
Script is a loaded script: verified code plus the frozen state its top level built. Share freely.
func (*Script) Call ¶
Call calls fn, which must be a function of the same Runtime, and returns its first result.
func (*Script) Func0 ¶
Func0 returns a Go function backed by the Lua function name: a global, a dotted path ("handlers.allow"), or "" for the function the chunk returned. Safe for concurrent use.
func (*Script) Func3 ¶
func (s *Script) Func3[A, B, C, R any](name string) (func(context.Context, A, B, C) (R, error), error)
Func3 is Func0 for a function of three arguments.
func (*Script) Get ¶
Get reads a global, or a dotted path, from the frozen globals and converts it to T. It runs nothing.
func (*Script) NewVM ¶
NewVM runs the top level again in a private environment that is not frozen, under the init limits. Lua modules it requires run again too, and host calls the top level makes are repeated.
type SyntaxError ¶
SyntaxError is a chunk that does not compile.
func (*SyntaxError) Error ¶
func (e *SyntaxError) Error() string
Error returns "source:line: message", as C Lua reports a syntax error.
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table is a Lua table: an array part for the keys 1..n, which may hold nils, and an open-addressing hash part of our own. Go maps are not used: they rehash the key on every access and cannot support next() without side tables. Table methods are raw: they never run metamethods.
func (*Table) Freeze ¶
Freeze makes t and everything reachable from it read-only, so that any goroutine may read it. It fails, freezing nothing, if t reaches a coroutine, a library function with hidden state, such as a gmatch iterator, or a Go value a script can change.
func (*Table) Len ¶
Len returns a border of t, as C Lua's luaH_getn does: an n such that t[n] is not nil and t[n+1] is nil, or 0 if t[1] is nil. It does not run metamethods.
func (*Table) Next ¶
Next returns the key and value after key in traversal order: the array part, then the hash part by node index. A nil key starts the traversal; a nil result key ends it. Setting existing fields, also to nil, during a traversal is allowed. ok is false for a key that is not in the table.
type TypeOption ¶
type TypeOption interface {
// contains filtered or unexported methods
}
TypeOption customises how a Go type appears in Lua, in Type.
func Meta ¶
func Meta(event string, f GoFunc) TypeOption
Meta adds a metamethod, such as "__eq", "__lt", "__tostring" or "__len".
func Method ¶
func Method(name string, f GoFunc) TypeOption
Method adds a method: obj:name(...) calls f with obj as the first argument.
func Mutable ¶ added in v0.4.2
func Mutable() TypeOption
Mutable marks a type whose methods or metamethods change its values, such as a __call that counts, so that a shared script cannot hold one. glua cannot tell a function that writes from one that reads. A type with a __newindex metamethod is mutable without it.
func Rename ¶
func Rename(field, luaName string) TypeOption
Rename gives the Go field a Lua name of its own.
func Writable ¶
func Writable(fields ...string) TypeOption
Writable makes the named Go fields writable from Lua.
type VM ¶
type VM struct {
// contains filtered or unexported fields
}
VM is a private, mutable instance of a Script: its top level run again in an environment of its own, which is not frozen, so state persists between calls. The Runtime's layer, the libraries, host globals and Go modules, is still shared and frozen. One goroutine at a time; values from a VM are mutable and follow that rule wherever they go.
func (*VM) Func0 ¶
Func0 returns a Go function backed by the Lua function name, as Script.Func0 does, running on this VM: not safe for concurrent use. A struct passed by value is copied to the heap.
func (*VM) Function ¶ added in v0.4.0
Function returns ch as a function of this VM, as Lua's load gives one: a Value for Call or CallN, which runs ch's top level in the VM's environment, with the call's arguments as its ..., and returns what the chunk returns. Unlike require, each call runs the chunk again. Its _ENV is the function's, as with load: a chunk that assigns it changes it for later calls. A nil or zero Chunk gives nil.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is any Lua value. It is 16 bytes, the size of a Go interface, and is passed by value. The zero Value is nil. Values cannot be compared with == or used as map keys; use Equal.
Scalars are identified by p, objects by n:
nil p == nil n == 0 boolean p == &tags[1] n is 0 or 1 integer p == &tags[2] n holds the int64 bits float p == &tags[3] n holds the float64 bits string first byte, or emptyStr n is kindString<<56 | length table *Table n is kindTable<<56 function *closure or *goFunc n is kindLuaFn<<56 or kindGoFn<<56 thread *coroutine n is kindThread<<56 userdata the Go pointer itself n is kindUser<<56 | flags<<48 | type ID
Every constructor keeps four invariants, which checkValue asserts:
- p == nil exactly when the Value is {nil, 0}.
- Every empty string is {&emptyStr[0], kindString<<56}.
- p points inside its allocation, never one past the end.
- Type IDs are process-wide and never reused.
func ValueOf ¶
ValueOf converts v to a Value by the built-in rules and registered conversions. It panics on a type that cannot be converted, which is a programming error.
func (Value) Equal ¶
Equal reports whether v and w are equal in Lua without metamethods (rawequal). 1 equals 1.0, 0.0 equals -0.0, and NaN equals nothing.
func (Value) Int ¶
Int returns v as an integer if v is an integer, or a float with an exact integer value. Strings are not converted.
func (Value) Str ¶
Str returns the contents of v if v is a string. Numbers are not converted. The result shares memory with v and must not be modified through unsafe means.
Source Files
¶
- adapters.go
- arith.go
- bind.go
- checked.go
- checked_gen.go
- compile.go
- conv.go
- convert.go
- coroutine.go
- debug.go
- doc.go
- errors.go
- eval.go
- exec.go
- fast.go
- fastcall.go
- freeze.go
- hash.go
- host.go
- lib.go
- lib_base.go
- lib_io.go
- lib_math.go
- lib_os.go
- lib_pack.go
- lib_string.go
- lib_table.go
- lib_utf8.go
- load.go
- opcode.go
- ops.go
- pattern.go
- pcall.go
- pow.go
- proto.go
- rule.go
- runtime.go
- script.go
- stackstress_off.go
- table.go
- typed.go
- value.go
- verify.go
- vm.go
Directories
¶
| Path | Synopsis |
|---|---|
|
bench
|
|
|
asmgate
command
Command asmgate checks the machine code of the interpreter's hot loop, (*executor).fast.
|
Command asmgate checks the machine code of the interpreter's hot loop, (*executor).fast. |
|
compare
command
Command compare times the spike's VM benchmarks against C Lua in interleaved rounds and reports medians, spread and ratios as a Markdown table.
|
Command compare times the spike's VM benchmarks against C Lua in interleaved rounds and reports medians, spread and ratios as a Markdown table. |
|
internal
|
|
|
cmd/genloop
command
Command genloop generates the checked interpreter loop, checked_gen.go, from the fast loop in fast.go, so that the two can never drift apart.
|
Command genloop generates the checked interpreter loop, checked_gen.go, from the fast loop in fast.go, so that the two can never drift apart. |
|
syntax
Package syntax turns Lua 5.4 source text into an abstract syntax tree.
|
Package syntax turns Lua 5.4 source text into an abstract syntax tree. |
|
Package luajson converts between glua values and JSON, straight from tables to JSON text and back without an intermediate tree, and gives scripts a json module.
|
Package luajson converts between glua values and JSON, straight from tables to JSON text and back without an intermediate tree, and gives scripts a json module. |