vm

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package vm executes the bytecode produced by compiler/bytecode. The model is a conventional Lua 5.4 stack VM: every value lives on a single operand stack, locals are slots inside the current frame, upvalues capture enclosing-function locals, and globals live in a single _G table.

Lua values map to Go types as follows:

nil       → Lua nil
bool      → Lua boolean        (see boolean.go)
int64     → Lua integer        (see integer.go)
float64   → Lua float          (see float.go)
string    → Lua string         (see string.go)
*Table    → Lua table          (see table.go)
*Closure  → Lua-defined fn     (see closure.go)
*GoFunc   → Go-defined fn      (see closure.go)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Equal

func Equal(a, b Value) bool

Equal implements Lua-level == for two values. Numbers cross-compare across int/float subtype; everything else compares within type. Tables/functions compare by reference. Metatable __eq is layered on top of this in vm.go.

func FloatArg

func FloatArg(name string, n int, args []Value) float64

FloatArg coerces to float64 (integer → float promotion is fine).

func IntArg

func IntArg(name string, n int, args []Value) int64

IntArg coerces to int64. Floats with a fractional part are rejected.

func IsTruthy

func IsTruthy(v Value) bool

IsTruthy reports whether v is treated as true by Lua control-flow rules. Only nil and false are falsy; every other value (including 0 and "") is truthy.

func NumArg

func NumArg(name string, n int, args []Value) (int64, float64, bool, bool)

NumArg accepts any value coercible to a Lua number (int or float). The (i, f, isInt, ok) tuple lets callers preserve integer-vs-float subtype. `ok` is always true on return — a non-coercible arg panics directly.

func OptInt

func OptInt(name string, n int, args []Value, dflt int64) int64

OptInt returns the integer arg at position n or `dflt` if the arg is absent or nil. A present, non-numeric, non-nil value raises.

func OptString

func OptString(name string, n int, args []Value, dflt string) string

OptString returns the string arg at position n or `dflt` if the arg is absent or nil. A present, non-string, non-nil value raises.

func PatternGSub

func PatternGSub(
	s, pat string, repl Value, maxN int,
	callFn func(fn Value, args []Value) []Value,
) (string, int)

PatternGSub implements string.gsub. repl is the substitution source (string, *Table, or *GoFunc/*Closure); the function form is called back through callFn (the caller injects vm.CallValue to keep this file VM-API-free). maxN <= 0 means "unlimited".

func PatternHasSpecials

func PatternHasSpecials(pat string) bool

PatternHasSpecials reports whether pat contains any pattern-magic characters. string.find's `plain` flag short-circuits when this is false, falling back to strings.Index without engaging the matcher.

func RegisterPreload

func RegisterPreload(v *VM, name string, loader func(*VM, []Value) []Value)

RegisterPreload installs a host loader under `package.preload[name]`. The first `require(name)` invokes `loader` and caches the result in `package.loaded`; later requires return the cache.

Every native module went through the same 12-line dance of fetching package / preload / creating a GoFunc — this helper centralizes it so each module's Register* function is a one-liner.

If `package` is missing (the loader subsystem hasn't been wired into this VM), the call is a silent no-op — matching the pre-existing behavior of each native module's Register*Preload.

func StringArg

func StringArg(name string, n int, args []Value) string

StringArg accepts strings, and (per Lua) coerces numbers via the standard integer/float formatter.

func ToFloat

func ToFloat(v Value) (float64, bool)

ToFloat coerces v to a float64 by Lua rules.

func ToInteger

func ToInteger(v Value) (int64, bool)

ToInteger coerces v to an int64 by Lua rules. Floats only convert if they are mathematically integer-valued and fit in int64.

func ToNumber

func ToNumber(v Value) (intVal int64, floatVal float64, isInt bool, ok bool)

ToNumber attempts to coerce v to a Lua number. It reports the value's concrete Lua subtype: an int64 is an integer, a float64 is a float — even when the float is mathematically integral (e.g. 2.0 stays a float, so `2.0 + 3` is the float 5.0, per Lua 5.4's int/float subtype rules). Returns the value, an "is integer" flag, and an "ok" flag (false if v cannot be a number). Strings follow tonumber's rules: "2" parses to an integer, "2.0" to a float.

func ToString

func ToString(v Value) string

ToString renders v in Lua's print/tostring format. Per-type formatters live in their respective per-type files.

func ToStringMM

func ToStringMM(v *VM, val Value) string

ToStringMM renders val to its string form, honouring the `__tostring` metamethod on tables and other values that have one. Numeric/string/bool/ nil values skip the lookup and route to ToString directly — they cannot carry metatables in current.lsc. If `__tostring` is defined but does not return a string, Lua's rule applies: it is an error. Here we surface that as a panic with a Lua-style message so it lands as a runtime error.

Callers must pass a non-nil *VM. A nil VM falls back to ToString (used by disassembly / debug paths that shouldn't trigger Lua calls).

func TypeName

func TypeName(v Value) string

TypeName returns the Lua-level type tag of v ("nil", "boolean", "number", "string", "table", "function").

Types

type CallFrame

type CallFrame struct {
	Closure  *Closure
	IP       int
	Base     int // Stack index where this frame's locals begin
	Top      int // Stack index just past this frame's last allocated local
	NResults int // -1 means "all"
	Varargs  []Value
	// Deferred holds the closures registered by `defer` in this activation.
	// They run in last-in-first-out order when the frame unwinds (see
	// VM.runDeferred). nil for the overwhelmingly common frame that defers
	// nothing, so the slice costs nothing until a `defer` actually fires.
	Deferred []*Closure
	// contains filtered or unexported fields
}

CallFrame is one activation record. Base is the absolute index of the frame's first local slot on the operand stack; locals 0..NumLocals-1 live at Stack[Base..Base+NumLocals]. The returnTo state is captured by the caller frame; nresults says how many values the caller wants when the callee returns.

type Closure

type Closure struct {
	Proto    *bytecode.InstructionSet
	Upvalues []*Upvalue
}

Closure is a Lua-defined function: a function prototype plus the upvalues captured at the moment the closure was created.

func ClosureArg

func ClosureArg(name string, n int, args []Value) *Closure

ClosureArg insists on a Lua *Closure. (Use FunctionArg if you also want to accept host *GoFunc; this helper is strict, matching Lua's `coroutine.create` / `coroutine.wrap` which require a real closure.)

type Coroutine

type Coroutine struct {
	// contains filtered or unexported fields
}

Coroutine wraps a closure plus the channel handshake needed to drive it. `status` follows Lua's vocabulary: "suspended" (waiting for resume), "running" (currently executing), "normal" (suspended because it resumed another coroutine — not modeled in v1), and "dead" (returned or errored).

func CoroutineArg

func CoroutineArg(name string, n int, args []Value) *Coroutine

CoroutineArg insists on a *Coroutine.

type GMatchIter

type GMatchIter struct {
	// contains filtered or unexported fields
}

PatternGMatchNext drives one step of a gmatch iterator. `state` is the (string, pattern, position) iterator state stored as a Lua table; the caller advances `pos` between calls.

func NewGMatchIter

func NewGMatchIter(s, pat string, init int) *GMatchIter

NewGMatchIter sets up a string.gmatch iterator state. `init` is the Lua 5.4 optional third argument: a 1-based start position (negative counts from the end, clamped to [1, #s+1]).

func (*GMatchIter) Next

func (g *GMatchIter) Next() []Value

Next produces the next match's captures (or whole-match), or nil when exhausted. Per Lua 5.4 (`e != lastmatch` in gmatch_aux), an empty match abutting the previous match's end is rejected and the scan resumes one byte later — that both avoids infinite loops on patterns like "a*" and stops a spurious empty match right after a non-empty one. The matchState (and its capture buffer) is reused across calls, so a gmatch loop allocates per match only for the returned values.

type GoFunc

type GoFunc struct {
	Name string
	Fn   func(vm *VM, args []Value) []Value
}

GoFunc is a host-provided function callable from Lua. Args is read-only; the function returns its results as a slice (may be empty).

type LuaError

type LuaError string

LuaError is a typed string used when the VM raises a runtime error. It is recovered by pcall().

func Errorf

func Errorf(format string, args ...any) LuaError

func (LuaError) Error

func (e LuaError) Error() string

type Table

type Table struct {
	// contains filtered or unexported fields
}

Table is a Lua table, split into an array part keyed 1..n and a hash part for everything else. Hash iteration is insertion-ordered (Lua doesn't guarantee an order, but determinism makes pairs() predictable for tests).

The metatable field, if non-nil, gives this table its metamethods (see vm/metatable.go). Use Metatable / SetMetatable for external access.

func NewTable

func NewTable(arrHint, hashHint int) *Table

NewTable returns an empty table. The hints are used only when non-zero — a {} literal that ends up empty (a common pattern for "carrier" objects) stays at zero allocations. Set() and the array/hash growth paths handle nil-slice/map promotion lazily.

func NilOrTableArg

func NilOrTableArg(name string, n int, args []Value) *Table

NilOrTableArg accepts nil (returned as Go nil *Table) or a *Table. Used by setmetatable's second argument.

func TableArg

func TableArg(name string, n int, args []Value) *Table

TableArg insists on a *Table.

func TableOrStringArg

func TableOrStringArg(name string, n int, args []Value) (s string, t *Table, isString bool)

TableOrStringArg accepts either a string or a *Table — used by rawlen. Exactly one of the returned `s`/`t` will be meaningful; the other is the zero value. Callers should branch on `isString`.

func (*Table) Append

func (t *Table) Append(v Value)

Append pushes v onto the end of the array part. Used by SetList to bulk-fill.

func (*Table) EntryCount

func (t *Table) EntryCount() int64

EntryCount returns the total number of key/value pairs stored in the table: the non-nil entries in the array part plus every live entry in the hash part. Unlike Len it counts past array holes and includes non-integer keys, so it reflects the table's actual occupancy rather than its Lua border length.

func (*Table) Get

func (t *Table) Get(key Value) Value

Get returns t[key]. Missing or nil keys yield nil.

func (*Table) Len

func (t *Table) Len() int64

Len returns the table's array-part length. This is Lua's "border" length for the common case where the array part has no holes.

func (*Table) Metatable

func (t *Table) Metatable() *Table

Metatable returns the table's metatable, or nil.

func (*Table) Next

func (t *Table) Next(key Value) (Value, Value)

Next returns the (k, v) pair that follows key in iteration order. Calling with key==nil starts iteration. When iteration is exhausted both returns are nil. Iteration first walks the array part, then the hash part in insertion order.

func (*Table) Set

func (t *Table) Set(key, value Value)

Set assigns t[key]=value. A nil value removes the binding. Setting a nil or NaN key panics with a Lua-style error.

func (*Table) SetMetatable

func (t *Table) SetMetatable(mt *Table)

SetMetatable assigns mt as the metatable. Pass nil to clear.

type Thread

type Thread struct {
	Stack    []Value
	Frames   []*CallFrame
	OpenUpvs []*Upvalue
	// CallMarks is the pending MarkArgs stack. A thread can legitimately
	// suspend between a MarkArgs and its matching Call (any
	// `f(coroutine.yield())` shape), so the marks are per-thread state —
	// leaving them on the VM would let another thread pop them against the
	// wrong stack.
	CallMarks []int
}

Thread is the per-coroutine stack/frame state. The active thread's fields always match the VM's live Stack/frames/openUpvs; on every resume/yield boundary we save the live state into the outgoing thread and load the incoming thread's state into the live fields.

type Upvalue

type Upvalue struct {
	Stack  *[]Value // non-nil while open
	Index  int      // stack slot while open
	Closed Value    // value while closed (Stack==nil)
}

Upvalue is a captured local. While the enclosing function is still on the stack the upvalue is "open" and reads/writes the live stack slot through `Stack[Index]`. When the enclosing function returns, the VM closes the upvalue: it copies the value into Closed and clears Stack. Subsequent accesses then go through Closed.

Closing happens inside VM.closeUpvaluesAbove, called on every Return whose frame contains open upvalues.

func (*Upvalue) Get

func (u *Upvalue) Get() Value

Get reads the upvalue's current value.

func (*Upvalue) Set

func (u *Upvalue) Set(v Value)

Set stores v into the upvalue.

type VM

type VM struct {
	Stack   []Value
	Globals *Table
	// contains filtered or unexported fields
}

VM is the top-level interpreter state. It owns the operand stack, the frame stack, the globals table, and the open-upvalue list.

When coroutines are in use, the VM's live Stack/frames/openUpvs always belong to the *currently executing* coroutine (or the main thread). On every resume/yield boundary the outgoing thread's state is saved into its Thread snapshot and the incoming thread's snapshot is loaded; see vm/coroutine.go.

func New

func New() *VM

New creates a fresh VM with an empty globals table.

func (*VM) AddScriptDir

func (v *VM) AddScriptDir(dir string)

AddScriptDir prepends search rules rooted at `dir` to package.path so `require` can find modules sitting next to the script being run, no matter what the process's current working directory is. The CLI calls this once it knows the main script's location; the REPL (no script) and the bundled .exe (script lives in the binary, not on disk) never do.

Entries are prepended so a module next to the script shadows a same-named one in the cwd — "modules near me win", the least-surprising default. A no-op if the loader/package table is missing or dir is empty.

func (*VM) CallFrames

func (v *VM) CallFrames() []*CallFrame

CallFrames returns the live call-frame stack, outermost first. The slice shares backing storage with the VM — callers must not mutate or retain it past the current callback. Exposed for the `debug` native module so it can implement traceback / getinfo without living inside this package.

func (*VM) CallValue

func (v *VM) CallValue(fn Value, args []Value, nresults int) []Value

CallValue invokes `fn` with `args` from Go code. Useful for embedding and for stdlib helpers that need to call back into Lua (e.g. ipairs internals).

func (*VM) Run

func (v *VM) Run(main *bytecode.InstructionSet) (err error)

Run loads `main` as the top-level chunk and executes it. The chunk runs with no arguments and discards results. Any runtime error surfaces as a non-nil error.

func (*VM) RunMainChunkWithResults

func (v *VM) RunMainChunkWithResults(main *bytecode.InstructionSet) (results []Value, err error)

RunMainChunkWithResults is like Run but keeps every value the chunk returned and hands them back to the caller. The REPL uses it to print the value of bare expressions like Lua's interactive `lua` does.

func (*VM) SafeCall

func (v *VM) SafeCall(fn Value, args []Value) (rs []Value, errVal Value, failed bool)

SafeCall invokes fn with args, recovering any Lua error or runtime panic and fully unwinding the VM (frames, stack, and any open upvalues the failing call created) so the VM stays usable afterwards. It returns the call results, the Lua error value (valid only when failed is true), and whether the call failed. Hosts that run user callbacks outside a pcall — e.g. the http server's per-request handler dispatch — should route through this so one bad callback can't corrupt the shared VM.

func (*VM) SetGlobal

func (v *VM) SetGlobal(name string, val Value)

SetGlobal is a convenience helper for embedding code: register `name` in the globals table without going through bytecode.

type Value

type Value = any

Value is any Lua runtime value. The valid concrete types are documented at the top of this file.

func AnyArg

func AnyArg(name string, n int, args []Value) Value

AnyArg simply asserts presence — for "value expected" sites where the callee handles every Value type itself (type(), pcall(), error()).

func PatternFind

func PatternFind(s, pat string, init int) (int, int, []Value, bool)

PatternFind searches s for pat starting from init (1-based, like Lua; 0/negative values clamp). Returns (startByte, endByte, captures, ok). startByte / endByte are 1-based byte offsets, matching Lua's string.find return shape.

func PatternMatch

func PatternMatch(s, pat string, init int) []Value

PatternMatch is like PatternFind but only returns the captures (or the whole match if there are no explicit captures), matching string.match's contract.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL