luapure

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package luapure is a pure-Go implementation of PUC-Lua 5.4: its instruction set, single-pass compiler, virtual machine, standard libraries, and semantics are ported from the reference Lua sources. It is written from scratch against the PUC 5.4 sources; it is a derivative work of Lua.

Lua is Copyright (C) 1994-2025 Lua.org, PUC-Rio, distributed under the MIT license; that notice is retained in LICENSE-Lua as the license requires. See README.md for the PUC-source-to-file map and the Go-native adaptations.

Package luapure is a ground-up implementation of the Lua 5.4 virtual machine (instruction set, compiler, and interpreter), distinct from the package's historical 5.1-derived engine. This file ports PUC-Lua 5.4.8's lopcodes.h and lopcodes.c verbatim: the 32-bit instruction encoding, the opcode set, and the per-opcode mode table.

Instruction formats (bit 0 is least significant):

iABC   C(8) | B(8) | k(1) | A(8) | Op(7)
iABx        Bx(17)        | A(8) | Op(7)
iAsBx      sBx(17,signed) | A(8) | Op(7)
iAx              Ax(25)          | Op(7)
isJ          sJ(25,signed)       | Op(7)
Example (Register)

Register a Go function that validates its arguments with CheckString, exactly like the standard library functions do.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	L.Register("greet", func(L *luapure.LState) int {
		who := L.CheckString(1)
		L.Push(luapure.MkString("hello, " + who))
		return 1
	})

	res, _ := L.DoString(`return greet("world")`, "=embed")
	fmt.Println(res[0].Str())
}
Output:
hello, world
Example (Sandbox)

A sandbox state omits the dangerous libraries and the code-loading globals.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewSandbox()
	res, _ := L.DoString(`return load == nil, io == nil, (1 + 2)`, "=embed")
	fmt.Println(res[0].AsBool(), res[1].AsBool(), res[2].AsInt())
}
Output:
true true 3
Example (Session)

Example_session shows the front-end loop: a breakpoint stops the program, the controller reads a variable, then resumes. Source is fetched by id, never held by the controller.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	db := map[string]string{
		"demo": "local x = 21\nlocal y = x * 2\nreturn y\n",
	}
	L := luapure.NewState()
	L.OpenLibs()
	sess := luapure.NewSession(L, func(id string) (string, bool) { s, ok := db[id]; return s, ok })
	sess.SetBreakpoints("demo", []int{2}) // stop at `local y = x * 2`

	done := sess.Start(db["demo"], "=demo")
	for {
		select {
		case st := <-sess.Stops():
			x, _ := sess.Eval(0, "x")
			fmt.Printf("stopped at %s:%d, x=%s\n", st.Source, st.Line, x)
			sess.Continue()
		case r := <-done:
			if r.Err != nil {
				fmt.Println("error:", r.Err)
				return
			}
			fmt.Printf("result=%d\n", r.Values[0].AsInt())

			return
		}
	}
}
Output:
stopped at demo:2, x=21
result=42
Example (TableExchange)

Build a table in Go, hand it to a script as a global, and read the results back — the core embedding round-trip.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	cfg := luapure.NewTable()
	cfg.SetStr("name", luapure.MkString("luapure"))
	cfg.SetStr("level", luapure.Int(5))
	L.SetGlobal("config", cfg.Value())

	res, err := L.DoString(`return config.name, config.level * 2`, "=embed")
	if err != nil {
		panic(err)
	}
	fmt.Println(res[0].Str(), res[1].AsInt())
}
Output:
luapure 10
Example (Userdata)

Bind a Go type into Lua as userdata: register a named metatable once, give it a method table, then hand scripts a userdata value they call methods on. The method recovers the Go value type-checked with CheckUserData.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	type counter struct{ n int64 }

	L := luapure.NewState()
	L.OpenLibs()

	mt, _ := L.NewMetatable("Counter")
	methods := luapure.NewTable()
	methods.SetStr("inc", luapure.NewGoFunc("inc", func(L *luapure.LState) int {
		c := L.CheckUserData(1, "Counter").(*counter)
		c.n += L.OptInt(2, 1)
		L.Push(luapure.Int(c.n))
		return 1
	}))
	mt.SetStr("__index", methods.Value())

	L.SetGlobal("c", L.NewUserData(&counter{}, mt))

	res, err := L.DoString(`c:inc(); c:inc(10); return c:inc()`, "=embed")
	if err != nil {
		panic(err)
	}
	fmt.Println(res[0].AsInt())
}
Output:
12
Example (Uservalue)

A uservalue keeps a Lua value associated with (and reachable from) a userdatum. Here a script stashes a label on the object and reads it back; the host sees the same slot through UserValue.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	mt, _ := L.NewMetatable("Box")
	ud := L.NewUserDataUV(struct{}{}, 1, mt)
	L.SetGlobal("box", ud)

	if _, err := L.DoString(`debug.setuservalue(box, "hello", 1)`, "=embed"); err != nil {
		panic(err)
	}
	v, _ := ud.UserValue(1)
	fmt.Println(v.Str())
}
Output:
hello

Index

Examples

Constants

View Source
const (
	TM_INDEX = iota
	TM_NEWINDEX
	TM_GC
	TM_MODE
	TM_LEN
	TM_EQ
	TM_ADD
	TM_SUB
	TM_MUL
	TM_MOD
	TM_POW
	TM_DIV
	TM_IDIV
	TM_BAND
	TM_BOR
	TM_BXOR
	TM_SHL
	TM_SHR
	TM_UNM
	TM_BNOT
	TM_LT
	TM_LE
	TM_CONCAT
	TM_CALL
	TM_CLOSE
	TM_N
)

--- tag-method codes (ltm.h TMS, ORDER TM) ---

Only the arithmetic/bitwise events are used by the compiler (stamped into the MMBIN family so the VM can find the right metamethod); the full enum is defined here for the VM to reuse.

View Source
const (
	VVOID     expkind = iota // empty expression (no value)
	VNIL                     // constant nil
	VTRUE                    // constant true
	VFALSE                   // constant false
	VK                       // constant in 'k'; info = index
	VKFLT                    // float constant; nval
	VKINT                    // integer constant; ival
	VKSTR                    // string constant; strval
	VNONRELOC                // value in a fixed register; info = register
	VLOCAL                   // local variable; vr.ridx register, vr.vidx actvar slot
	VUPVAL                   // upvalue; info = upvalue index
	VCONST                   // compile-time <const>; info = actvar slot
	VINDEXED                 // t[k]: ind.t table register, ind.idx key register
	VINDEXUP                 // upvalue[k]: ind.t table upvalue, ind.idx key K index
	VINDEXI                  // t[i]: ind.t table register, ind.idx integer key
	VINDEXSTR                // t.field: ind.t table register, ind.idx key K index
	VJMP                     // test/comparison; info = pc of the jump
	VRELOC                   // result can go in any register; info = instruction pc
	VCALL                    // function call; info = instruction pc
	VVARARG                  // vararg; info = instruction pc
)
View Source
const (
	TypeNil           = 0
	TypeBoolean       = 1
	TypeLightUserData = 2
	TypeNumber        = 3
	TypeString        = 4
	TypeTable         = 5
	TypeFunction      = 6
	TypeUserData      = 7
	TypeThread        = 8
)

Basic Lua types (lua.h LUA_T*), as reported by Value.Type() and `type()`.

View Source
const (
	VarKindReg     uint8 = iota // regular variable
	VarKindConst                // <const>
	VarKindToClose              // <close> (to-be-closed)
	VarKindCTConst              // compile-time constant
)

Upvalue kinds (PUC lparser.h VDKREG / RDKCONST / RDKTOCLOSE / RDKCTC).

View Source
const (
	SizeC  = 8
	SizeB  = 8
	SizeBx = SizeC + SizeB + 1 // 17
	SizeA  = 8
	SizeAx = SizeBx + SizeA // 25
	SizeJ  = SizeBx + SizeA // 25 (sJ)
	SizeOp = 7
)

Field sizes (bits) — lopcodes.h SIZE_*.

View Source
const (
	PosOp = 0
	PosA  = PosOp + SizeOp // 7
	PosK  = PosA + SizeA   // 15
	PosB  = PosK + 1       // 16
	PosC  = PosB + SizeB   // 24
	PosBx = PosK           // 15
	PosAx = PosA           // 7
	PosJ  = PosA           // 7 (sJ)
)

Field positions (bit offsets) — lopcodes.h POS_*.

View Source
const (
	MaxArgBx  = (1 << SizeBx) - 1 // 131071
	OffsetsBx = MaxArgBx >> 1     // 65535
	MaxArgAx  = (1 << SizeAx) - 1 // 33554431
	MaxArgJ   = (1 << SizeJ) - 1  // 33554431
	OffsetsJ  = MaxArgJ >> 1      // 16777215
	MaxArgA   = (1 << SizeA) - 1  // 255
	MaxArgB   = (1 << SizeB) - 1  // 255
	MaxArgC   = (1 << SizeC) - 1  // 255
	OffsetsC  = MaxArgC >> 1      // 127
)

Argument limits — lopcodes.h MAXARG_* / OFFSET_*.

View Source
const (
	LuaVersion = "Lua 5.4" // == _VERSION; PUC LUA_VERSION (DO NOT CHANGE)
	LuaRelease = "5.4.8"   // PUC patch level this engine tracks
	Version    = "0.1.2"   // luapure implementation version
)

Version identifiers. LuaVersion is the language version exposed to Lua as the global _VERSION; it mirrors PUC's LUA_VERSION exactly ("Lua 5.4") and must never carry an implementation suffix, since scripts and libraries compare it for exact equality (the PUC test suite's all.lua bails out on _VERSION ~= "Lua 5.4"). The luapure implementation version is exposed separately, both here and to Lua via the _LUAPURE_VERSION global.

View Source
const (
	OpAdd  = iota // +
	OpSub         // -
	OpMul         // *
	OpMod         // %
	OpPow         // ^
	OpDiv         // /
	OpIDiv        // //
	OpBAnd        // &
	OpBOr         // |
	OpBXor        // ~ (binary)
	OpShl         // <<
	OpShr         // >>
	OpUnm         // unary -
	OpBNot        // ~ (unary)
)

Arithmetic operation codes, matching lua.h LUA_OP* (ORDER OP). The values are significant: BinOpr/UnOpr map onto this range by a fixed offset (see lcode.go binopr2op / constfolding).

View Source
const LFieldsPerFlush = 50

LFieldsPerFlush is the number of list items accumulated before a SETLIST.

View Source
const NoReg = MaxArgA

NoReg is an invalid register that still fits in 8 bits (lopcodes.h NO_REG).

View Source
const NumOpCodes = int(OP_EXTRAARG) + 1

NumOpCodes is the count of opcodes (lopcodes.h NUM_OPCODES).

Variables

View Source
var (
	True  = Value{/* contains filtered or unexported fields */}
	False = Value{/* contains filtered or unexported fields */}
)

True and False are the boolean values.

View Source
var (
	// MaxStack bounds the value stack (PUC LUAI_MAXSTACK), so unbounded
	// recursion raises "stack overflow" instead of exhausting memory.
	MaxStack = 1000000

	// ErrorStackReserve is slack above MaxStack (PUC ERRORSTACKSIZE) kept free
	// so that, after a "stack overflow" is raised, the message handler and any
	// to-be-closed variables still have room to run.
	ErrorStackReserve = 200

	// MaxCCalls bounds nested Go-level calls (metamethods, pcall, hooks, native
	// callbacks), matching PUC's LUAI_MAXCCALLS. Exceeding it raises a catchable
	// error rather than letting Go recursion overflow the real stack and abort.
	MaxCCalls = 200

	// LoadBufferSize is the disk read-block size for streaming file loads (PUC
	// getF reads BUFSIZ at a time); a file is fed to the compiler one block at a
	// time so a large source is lexed incrementally and never held whole.
	LoadBufferSize = 8192

	// IDSize is the length to which chunk source names are truncated in
	// messages (PUC LUA_IDSIZE), via luaO_chunkid / shortSrc.
	IDSize = 60

	// MaxTagLoop limits __index/__newindex metamethod chains (lvm.c MAXTAGLOOP)
	// before raising "'__index' chain too long; possible loop".
	MaxTagLoop = 2000

	// MaxTableArraySize, when > 0, caps how far a table's array part may grow
	// before extending it raises a catchable "not enough memory" error. Go
	// cannot turn a real allocation failure into a recoverable error (OOM is a
	// fatal runtime throw), so a program that fills a table without bound (Lua's
	// heavy.lua toomanyidx: `for i=1,math.huge do a[i]=i end`) would crash the
	// host process instead of erroring. This ceiling stands in for PUC's
	// malloc-failure path. 0 (the default) preserves unlimited growth.
	MaxTableArraySize int

	// MaxLexElement, when > 0, caps the length of a single lexical element (an
	// identifier, string, or number token). PUC's save raises "lexical element
	// too long" when the token buffer would grow past MAX_SIZE; on a 64-bit
	// build that is effectively unreachable, so an unbounded token (heavy.lua's
	// hugeid, a reader feeding one endless identifier) OOMs the host instead.
	// This ceiling raises the catchable error first. 0 (the default) keeps the
	// unbounded behavior.
	MaxLexElement int
)
View Source
var Nil = Value{/* contains filtered or unexported fields */}

Nil is the nil value.

Functions

func Disasm

func Disasm(p *Proto) string

Disasm renders a Proto's bytecode as a numbered listing, one instruction per line: "<pc> [<line>] NAME operands".

func DisasmInst

func DisasmInst(i Instruction) string

DisasmInst renders a single instruction as "NAME operands", decoding operands according to the opcode's format (lopcodes.h getOpMode). The k flag, when set, is appended as "k". This is a compact, deterministic form for tests and debugging — not the exact `luac -l` listing.

func FromValue

func FromValue(v Value) any

FromValue converts a Lua Value into a Go value: nil, bool, int64, float64, string, or — for a table — a map[any]any whose keys and values are themselves converted (shared/cyclic tables map to the same Go map). Functions, threads, and userdata are returned as their Value unchanged.

Example

FromValue converts a Lua table to a map[any]any.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	v := L.ToValue(map[string]any{"x": 42})
	m := luapure.FromValue(v).(map[any]any)
	fmt.Println(m["x"])
}
Output:
42

func GetArgA

func GetArgA(i Instruction) int

func GetArgAx

func GetArgAx(i Instruction) int

func GetArgB

func GetArgB(i Instruction) int

func GetArgBx

func GetArgBx(i Instruction) int

func GetArgC

func GetArgC(i Instruction) int

func GetArgk

func GetArgk(i Instruction) int

func GetArgsB

func GetArgsB(i Instruction) int

func GetArgsBx

func GetArgsBx(i Instruction) int

func GetArgsC

func GetArgsC(i Instruction) int

func GetArgsJ

func GetArgsJ(i Instruction) int

func Int2sC

func Int2sC(i int) int

int2sC / sC2int convert between a signed-C value and its excess-K encoding.

func LuacInst

func LuacInst(i Instruction) string

LuacInst renders one instruction with exactly the operand columns `luac -l` prints (lopcodes/luac.c PrintCode), minus the trailing "; ..." comment. This lets the codegen tests diff our output against the reference compiler. The k flag is shown as luac does: a "k" suffix on the last operand for the store/self/return family, or a separate 0/1 column for the test family.

func LuacListing

func LuacListing(p *Proto) []string

LuacListing renders a prototype tree as a flat list of luac-style instruction strings in luac's preorder (each function followed by its nested functions), suitable for diffing against parsed `luac -l` output.

func SC2int

func SC2int(i int) int

func SetArgA

func SetArgA(i *Instruction, v int)

func SetArgAx

func SetArgAx(i *Instruction, v int)

func SetArgB

func SetArgB(i *Instruction, v int)

func SetArgBx

func SetArgBx(i *Instruction, v int)

func SetArgC

func SetArgC(i *Instruction, v int)

func SetArgk

func SetArgk(i *Instruction, v int)

func SetArgsBx

func SetArgsBx(i *Instruction, v int)

func SetArgsJ

func SetArgsJ(i *Instruction, v int)

func SetOpCode

func SetOpCode(i *Instruction, o OpCode)

func Testk

func Testk(i Instruction) bool

func VersionString

func VersionString() string

VersionString returns the full implementation banner, e.g. "luapure 0.1.2 (Lua 5.4.8)". It is the value exposed to Lua as the global _LUAPURE_VERSION.

Types

type BinOpr

type BinOpr int

BinOpr enumerates binary operators in the order lcode.c expects (ORDER OPR); the layout lets binopr2op/binopr2TM convert to opcodes/tag methods by offset.

const (
	OPR_ADD BinOpr = iota
	OPR_SUB
	OPR_MUL
	OPR_MOD
	OPR_POW
	OPR_DIV
	OPR_IDIV
	OPR_BAND
	OPR_BOR
	OPR_BXOR
	OPR_SHL
	OPR_SHR
	OPR_CONCAT
	OPR_EQ
	OPR_LT
	OPR_LE
	OPR_NE
	OPR_GT
	OPR_GE
	OPR_AND
	OPR_OR
	OPR_NOBINOPR
)

type Closure

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

Closure is a callable value (Value.tag == tagFunction).

type CompileError

type CompileError struct {
	Msg  string
	Line int
}

CompileError is raised when a program exceeds a hard compiler limit (too many registers, control structure too long, …). The top-level compile recovers it.

func (*CompileError) Error

func (e *CompileError) Error() string

type Debugger

type Debugger struct {
	L *LState
	// contains filtered or unexported fields
}

Debugger is a breakpoint/step controller built on the Go debug hook (SetGoHook). It is the substrate a front end — a DAP server, a CLI console — drives: set breakpoints, run, and on each stop inspect frames and choose how to resume.

Control flow and goroutines. The hook fires on the goroutine running the VM. When a stop condition is met (a breakpoint, a completed step, or an async Pause) the Debugger invokes the OnStop handler *synchronously on that goroutine* and blocks the VM there until the handler returns. Inside the handler the program is frozen, so frames (Frames / the LState's Frame) can be read safely; the handler chooses the next action with Continue / StepInto / StepOver / StepOut and returns to resume. A front end running on another goroutine (reading a socket) coordinates by having the handler block on a channel it feeds — the action is still issued on the VM goroutine, which keeps frame access race-free.

Methods that configure the session (SetBreakpoints, AddBreakpoint, Pause) are safe to call from another goroutine while the VM runs. The resume actions and frame inspection are meant to be used from within the OnStop handler.

func NewDebugger

func NewDebugger(L *LState) *Debugger

NewDebugger attaches a debugger to L and installs its line hook. The session starts in run mode (stops only at breakpoints or Pause). Call Detach to remove the hook.

func (*Debugger) AddBreakpoint

func (d *Debugger) AddBreakpoint(source string, line int)

AddBreakpoint adds a single source:line breakpoint.

func (*Debugger) ClearBreakpoints

func (d *Debugger) ClearBreakpoints()

ClearBreakpoints removes every breakpoint.

func (*Debugger) Continue

func (d *Debugger) Continue()

Continue resumes free execution until the next breakpoint or Pause.

func (*Debugger) Detach

func (d *Debugger) Detach()

Detach removes the debug hook; the program then runs unobserved.

func (*Debugger) Frames

func (d *Debugger) Frames() []Frame

Frames returns the live call frames, innermost first (level 0 .. depth-1). The returned frames are valid only until the handler resumes.

func (*Debugger) OnStop

func (d *Debugger) OnStop(fn func(d *Debugger, reason StopReason))

OnStop sets the handler invoked (on the VM goroutine, blocking) each time the debugger stops. The handler inspects state and calls one resume action.

func (*Debugger) Pause

func (d *Debugger) Pause()

Pause requests that the debugger stop at the next line it executes. It is asynchronous: call it from another goroutine while the VM runs.

func (*Debugger) RemoveBreakpoint

func (d *Debugger) RemoveBreakpoint(source string, line int)

RemoveBreakpoint removes a single source:line breakpoint.

func (*Debugger) SetBreakpoints

func (d *Debugger) SetBreakpoints(source string, lines []int)

SetBreakpoints replaces the breakpoint set for source with lines. A breakpoint matches a frame whose raw chunk name or short name equals source, so "@f.lua", "f.lua" and a "=name" chunk are all addressable.

func (*Debugger) StepInto

func (d *Debugger) StepInto()

StepInto resumes until the next source line in any frame (descending into calls).

func (*Debugger) StepOut

func (d *Debugger) StepOut()

StepOut resumes until execution returns to a shallower frame than the current one.

func (*Debugger) StepOver

func (d *Debugger) StepOver()

StepOver resumes until the next source line at the current depth or shallower (calls made from this line run to completion without stopping).

type Frame

type Frame struct {
	L *LState
	// contains filtered or unexported fields
}

Frame is a handle to a live call frame. It is valid only transiently — while the VM is paused inside a hook the frame belongs to — because the underlying call-info and stack slots are reused once execution resumes. Do not retain a Frame past the hook that produced it.

func (Frame) CurrentLine

func (f Frame) CurrentLine() int

CurrentLine returns the source line the frame is stopped on, or -1 for a native frame or one with stripped line info.

func (Frame) Eval

func (f Frame) Eval(expr string) ([]Value, error)

Eval compiles and runs a Lua expression in the scope of the frame, the way a debugger console's "print" or a watch expression does: a bare name resolves to the frame's locals first, then its upvalues, then globals, and an assignment writes back to whichever it found (or to a new global). The expression's results are returned; a statement (e.g. "x = 1") is accepted too.

Eval is for use while the program is paused at the frame (from a Debugger OnStop handler). The debug hook is suppressed for the duration, so evaluating does not itself trip breakpoints or recurse into the handler.

func (Frame) Func

func (f Frame) Func() Value

Func returns the function value running in the frame.

func (Frame) FuncName

func (f Frame) FuncName() (name, what string)

FuncName returns the frame function's name and namewhat as debug.getinfo's "n" option computes them from the call site (e.g. "f", "method"); both are empty when no name can be inferred.

func (Frame) IsLua

func (f Frame) IsLua() bool

IsLua reports whether the frame runs a Lua function (false for a native one).

func (Frame) IsTailCall

func (f Frame) IsTailCall() bool

IsTailCall reports whether the frame was entered by a tail call (so its caller frame was replaced).

func (Frame) IsVararg

func (f Frame) IsVararg() bool

IsVararg reports whether a Lua frame's function takes varargs.

func (Frame) Level

func (f Frame) Level() int

Level is the frame's stack level (0 = innermost), as passed to LState.Frame.

func (Frame) Local

func (f Frame) Local(n int) (name string, v Value, ok bool)

Local returns the frame's n-th local variable (1-based), the lua_getlocal form: its source name (or "(temporary)"/"(vararg)") and current value. ok is false when n is out of range.

func (Frame) Locals

func (f Frame) Locals() []Local

Locals enumerates the frame's active local variables in declaration order (the indices Local/SetLocal accept). Unnamed in-range slots appear as "(temporary)"; callers wanting only source-named locals can skip names that begin with "(".

func (Frame) NumParams

func (f Frame) NumParams() int

NumParams returns the number of fixed parameters of a Lua frame's function (0 for a native frame).

func (Frame) NumUpvalues

func (f Frame) NumUpvalues() int

NumUpvalues returns the count of upvalues of the frame's function.

func (Frame) SetLocal

func (f Frame) SetLocal(n int, v Value) bool

SetLocal assigns the frame's n-th local variable (1-based), the lua_setlocal form. It reports false when n is out of range.

func (Frame) SetUpvalue

func (f Frame) SetUpvalue(n int, v Value) bool

SetUpvalue assigns the frame function's n-th upvalue (1-based). It reports false when n is out of range.

func (Frame) ShortSource

func (f Frame) ShortSource() string

ShortSource returns the chunk name formatted for display (shortSrc), matching the short_src field of debug.getinfo.

func (Frame) Source

func (f Frame) Source() string

Source returns the frame function's chunk name in PUC's raw form ("@file", "=name", or the source string); "=[C]" for a native frame.

func (Frame) Upvalue

func (f Frame) Upvalue(n int) (name string, v Value, ok bool)

Upvalue returns the frame function's n-th upvalue (1-based): its name (empty for a native closure's upvalues, "(no name)" for a stripped Lua chunk) and value. ok is false when n is out of range.

func (Frame) Vararg

func (f Frame) Vararg(n int) (v Value, ok bool)

Vararg returns the frame's n-th extra (vararg) argument (1-based), or ok=false when the frame is not vararg or n is out of range.

type FrameInfo

type FrameInfo struct {
	Level  int
	Source string // chunk id (short name)
	Line   int
	Func   string
	What   string // namewhat: "local", "global", "method", "" ...
}

FrameInfo is a snapshot of one call frame (Session.Stack).

type FuncState

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

FuncState holds the state needed to generate code for one function. The fields here are those the lcode machinery touches; codegen.go adds the parser-side bookkeeping (blocks, active variables, upvalue resolution) it needs.

type GoFunc

type GoFunc func(L *LState) int

GoFunc is a native (C-style) function: it reads its arguments from and pushes its results onto L's stack, returning the number of results, exactly like a lua_CFunction.

type GoHook

type GoHook func(L *LState, event HookEvent, line int)

GoHook is a debug callback. It runs inline on the goroutine executing the VM, with no extra Lua frame pushed, so it can inspect the current frame (via L.Frame) directly. For a line event, line is the source line; otherwise it is -1. The hook may call back into the VM (e.g. L.Call to evaluate an expression) — nested events do not re-enter the hook while it runs.

type GoPanicError

type GoPanicError struct{ Value any }

GoPanicError wraps a non-LuaError Go panic that the protected call recovered in protected mode (SetRecoverGoPanics / WithRecoverGoPanics). Value is the recovered panic value. It surfaces from Call/DoString via errors.As; inside Lua the same error appears as its Error() string.

func (*GoPanicError) Error

func (e *GoPanicError) Error() string

type HookEvent

type HookEvent uint8

HookEvent identifies why a GoHook fired, mirroring PUC's LUA_HOOK* events.

const (
	HookCall     HookEvent = iota // entering a Lua function
	HookReturn                    // a Lua function is about to return
	HookLine                      // execution reached a new source line
	HookCount                     // the instruction-count quantum elapsed
	HookTailCall                  // entering a function by a tail call
)

func (HookEvent) String

func (e HookEvent) String() string

String renders the event the way the Lua hook's first argument spells it.

type HookMask

type HookMask int

HookMask selects which events fire a GoHook; OR the constants together. It mirrors PUC's LUA_MASK* bits.

const (
	MaskCall   HookMask = maskCall
	MaskReturn HookMask = maskRet
	MaskLine   HookMask = maskLine
	MaskCount  HookMask = maskCount
)

type Instruction

type Instruction uint32

Instruction is a single 32-bit Lua 5.4 VM instruction.

func CreateABCk

func CreateABCk(o OpCode, a, b, c, k int) Instruction

func CreateABx

func CreateABx(o OpCode, a, bx int) Instruction

func CreateAx

func CreateAx(o OpCode, a int) Instruction

func CreatesJ

func CreatesJ(o OpCode, j, k int) Instruction

type LState

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

LState is a Lua 5.4 execution state (one thread). It owns the value stack, the chain of CallInfo frames, the open-upvalue and to-be-closed lists, and a reference to the shared globals. Coroutines (multiple threads sharing a global_State) are out of scope for this first VM; LState fuses the per-thread and global state into one object.

func NewSandbox

func NewSandbox() *LState

NewSandbox returns a state with only the safe standard libraries open: base, string, table, math, utf8, and coroutine. The io, os, debug, and package libraries are not opened, and the base globals that load or run arbitrary code or files (load, loadfile, dofile) are removed. collectgarbage is kept. For per-call confinement of a specific chunk, also use RunWith with a custom _ENV; for a time limit, SetContext.

NewSandbox() is shorthand for NewState(WithSandbox()); use the option form to combine it with other options, e.g. NewState(WithSandbox(), WithContext(ctx)).

func NewState

func NewState(opts ...Option) *LState

NewState builds a fresh state with an empty globals table and registry.

With no options it opens no libraries and takes its limits from the package globals (luaconf.go) — identical to earlier releases; call OpenLibs yourself. Options layer on top: WithOpenLibs/WithSandbox open libraries, WithContext sets a cancellation context, and WithMaxStack/WithMaxCCalls/ WithMaxTableArraySize override those limits for this State only.

Example (Options)

NewState options fold setup into one call: open the standard libraries and cap a limit for this state only. WithMaxStack/WithMaxCCalls/ WithMaxTableArraySize override the package globals for this state, leaving other states on the process-wide defaults.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState(luapure.WithOpenLibs(), luapure.WithMaxStack(50000))
	res, err := L.DoString(`return math.max(2, 7)`, "=opts")
	if err != nil {
		panic(err)
	}
	fmt.Println(res[0].AsInt())
}
Output:
7

func (*LState) Arg

func (L *LState) Arg(n int) Value

Arg returns the n-th argument (1-based), or Nil if absent.

func (*LState) ArgError

func (L *LState) ArgError(n int, msg string)

ArgError raises a "bad argument #n to '<func>' (<msg>)" error for the current native call (luaL_argerror).

func (*LState) Call

func (L *LState) Call(fn Value, args []Value, nresults int) ([]Value, error)

Call invokes fn with the given arguments under a protected call (pcall semantics), returning up to nresults results (multRet for all). A Lua error is returned as a *LuaError instead of propagating.

Example

A Lua error from a protected Call comes back as a *LuaError carrying the raised value.

package main

import (
	"errors"
	"fmt"
	"strings"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	res, _ := L.DoString(`return function() error("boom") end`, "=embed")
	fn := res[0]

	_, err := L.Call(fn, nil, 0)
	var le *luapure.LuaError
	fmt.Println(errors.As(err, &le), strings.Contains(le.Error(), "boom"))
}
Output:
true true

func (*LState) CallProto

func (L *LState) CallProto(p *Proto, nresults int) ([]Value, error)

CallProto runs a compiled main-chunk prototype under a protected call, returning up to nresults results (multRet for all).

func (*LState) CallProtoEnv

func (L *LState) CallProtoEnv(p *Proto, env *Table, nresults int) ([]Value, error)

CallProtoEnv runs a compiled chunk with a custom _ENV table, returning up to nresults results (multRet for all). It is RunWith for an already-compiled Proto: the chunk's global accesses resolve through env instead of the state's own globals. A pool can compile once (cache the *Proto) and pass a fresh env per call to confine that call's global writes (the Lua 5.4 _ENV sandbox), without recompiling. Build env as setmetatable({}, {__index = baseline}) to fall back to shared library/globals while keeping per-call writes isolated.

func (*LState) CallValue

func (L *LState) CallValue(fn Value, args []Value, nresults int) []Value

CallValue invokes fn with the given arguments, returning up to nresults results. Intended for use from native functions (e.g. pcall, pairs).

CallValue is unprotected: a Lua error raised by fn propagates as a panic (caught by an enclosing protected call). Embedders calling into Lua from Go should use Call, which runs fn protected and returns the error.

func (*LState) CheckBool

func (L *LState) CheckBool(n int) bool

CheckBool returns argument n when it is a boolean, else raises an error.

func (*LState) CheckInt

func (L *LState) CheckInt(n int) int64

CheckInt returns argument n as an integer, else raises an error. A number with no integer representation is rejected (luaL_checkinteger).

func (*LState) CheckNumber

func (L *LState) CheckNumber(n int) float64

CheckNumber returns argument n as a float, else raises an error (luaL_checknumber).

func (*LState) CheckString

func (L *LState) CheckString(n int) string

CheckString returns argument n as a string, coercing a number, else raises a "bad argument" error (luaL_checkstring).

func (*LState) CheckTable

func (L *LState) CheckTable(n int) *Table

CheckTable returns argument n as a *Table, else raises an error (luaL_checktype with LUA_TTABLE).

func (*LState) CheckUserData

func (L *LState) CheckUserData(n int, name string) any

CheckUserData returns argument n's Go payload after verifying its metatable is the registry metatable named name; otherwise it raises a "bad argument" type error reporting name as the expected type (luaL_checkudata). Use it to safely unwrap the Go value a method received as self.

func (*LState) ClearGoHook

func (L *LState) ClearGoHook()

ClearGoHook removes the Go debug hook.

func (*LState) ClearInstructionLimit

func (L *LState) ClearInstructionLimit()

ClearInstructionLimit removes any instruction cap (and its counter).

func (*LState) Close

func (L *LState) Close()

Close releases OS resources the state holds. It closes any non-standard file the state opened as its default I/O (e.g. a file passed to io.output), then forces a GC cycle and drains pending __gc finalizers so handles no longer referenced by the script are closed too. Because lua-pure delegates collection to the Go runtime, handles still reachable from the state are closed when the state itself becomes unreachable. Close is idempotent; do not run the state afterwards.

Example

Close releases the state's resources and is idempotent.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()
	L.Close()
	L.Close()
	fmt.Println("ok")
}
Output:
ok

func (*LState) Context

func (L *LState) Context() context.Context

Context returns the cancellation context set by SetContext (nil if none). A Go callback uses it to make its own blocking work cancellable — e.g. select on Context().Done(), or pass it to a context-aware API — so a blocking call obeys the same deadline as the VM. Without this, a blocking callback ignores SetContext/ExecTimeout (the VM only checks the context between instructions). Returns the running coroutine's inherited context when called from one.

func (*LState) DoFile

func (L *LState) DoFile(path string) ([]Value, error)

DoFile compiles and runs the file at path as a main chunk under a protected call, returning its results (the dofile convenience).

Example

DoFile loads and runs a script from disk.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	dir, _ := os.MkdirTemp("", "luapure")
	defer os.RemoveAll(dir)
	path := filepath.Join(dir, "s.lua")
	if err := os.WriteFile(path, []byte(`return 1 + 2 + 3`), 0o644); err != nil {
		panic(err)
	}

	res, err := L.DoFile(path)
	if err != nil {
		panic(err)
	}
	fmt.Println(res[0].AsInt())
}
Output:
6

func (*LState) DoString

func (L *LState) DoString(src, name string) ([]Value, error)

DoString compiles and runs src as a main chunk, returning its results.

func (*LState) Frame

func (L *LState) Frame(level int) (f Frame, ok bool)

Frame returns the call frame at the given level: 0 is the currently executing frame, 1 its caller, and so on. ok is false when level is out of range.

func (*LState) GetGlobal

func (L *LState) GetGlobal(name string) Value

GetGlobal returns _ENV[name].

func (*LState) GetMetatable

func (L *LState) GetMetatable(name string) *Table

GetMetatable returns the registry metatable registered under name, or nil when NewMetatable has not created it yet (luaL_getmetatable).

func (*LState) Globals

func (L *LState) Globals() *Table

Globals returns the globals table (_ENV).

func (*LState) Index

func (L *LState) Index(t, key Value) Value

Index returns t[key] honouring the __index metamethod chain (the indexing a Lua program sees). t may be any value; a non-indexable value raises an error.

func (*LState) InstructionCount

func (L *LState) InstructionCount() uint64

InstructionCount returns the approximate number of instructions executed under the current limit (finGCPoll granularity), or 0 if no limit is set.

func (*LState) LoadFile

func (L *LState) LoadFile(path string) (*Proto, error)

LoadFile compiles the file at path into a prototype without running it, streaming it from disk (text or a precompiled binary chunk, like loadfile). A compile or read error is returned; a Lua syntax error comes back as a *LuaError.

func (*LState) NArgs

func (L *LState) NArgs() int

NArgs reports how many arguments the current native call received.

func (*LState) NewMetatable

func (L *LState) NewMetatable(name string) (mt *Table, created bool)

NewMetatable returns the metatable registered under name in the registry, creating an empty one — with its __name set to name — on the first call. created reports whether this call made it, so an embedder installs the methods exactly once (luaL_newmetatable). The name doubles as the type label in "bad argument" errors raised by CheckUserData.

func (*LState) NewUserData

func (L *LState) NewUserData(data any, meta *Table) Value

NewUserData wraps an arbitrary Go value as full userdata carrying meta as its metatable (nil for none). The payload is opaque to Lua scripts, which reach it only through the metatable (e.g. __index methods, __gc). If meta carries a __gc field a finalizer is registered the same way lua_setmetatable does, so the embedder's __gc runs when the object becomes unreachable.

func (*LState) NewUserDataUV

func (L *LState) NewUserDataUV(data any, nuv int, meta *Table) Value

NewUserDataUV is NewUserData with nuv associated Lua-value slots (uservalues), the lua_newuserdatauv form. The slots start nil and are read and written with UserValue / SetUserValue (and the script-facing debug.getuservalue/ setuservalue). Unlike the Go payload, a uservalue is a Lua value the GC tracks — use it to keep a Lua object (a callback, a config table) alive alongside the userdatum and reachable from it without a separate registry entry.

func (*LState) OpenBase

func (L *LState) OpenBase()

OpenBase installs the base library into the globals table.

func (*LState) OpenCoroutine

func (L *LState) OpenCoroutine()

func (*LState) OpenDebug

func (L *LState) OpenDebug()

func (*LState) OpenIO

func (L *LState) OpenIO()

func (*LState) OpenLibs

func (L *LState) OpenLibs()

OpenLibs installs the standard libraries available by default (linit.c). openPackage runs early so the libraries opened after it register themselves in package.loaded.

func (*LState) OpenMath

func (L *LState) OpenMath()

func (*LState) OpenOS

func (L *LState) OpenOS()

func (*LState) OpenString

func (L *LState) OpenString()

OpenString installs the string library and the string metatable.

func (*LState) OpenTable

func (L *LState) OpenTable()

func (*LState) OpenUTF8

func (L *LState) OpenUTF8()

func (*LState) OptInt

func (L *LState) OptInt(n int, def int64) int64

OptInt returns argument n as an integer, or def when it is absent or nil (luaL_optinteger).

func (*LState) OptString

func (L *LState) OptString(n int, def string) string

OptString returns argument n as a string, or def when it is absent or nil (luaL_optstring).

func (*LState) Preload

func (L *LState) Preload(name string, open func(*LState) *Table)

Preload registers open as a lazy loader for module name: open is not called until the first require(name), which then caches the module in package.loaded like any other require. This is the embedding-side analog of setting package.preload[name] to a luaopen_* function in PUC. The package library must be open (OpenLibs); otherwise this is a no-op.

func (*LState) Push

func (L *LState) Push(v Value)

Push appends a value (typically a result) to the stack.

func (*LState) RaiseError

func (L *LState) RaiseError(format string, args ...any) int

RaiseError raises a Lua error from a Go callback with a formatted message, prefixed with the caller's position ("source:line: "), like PUC luaL_error. It does not return (the error unwinds via panic/recover); the int result lets a callback write `return L.RaiseError("bad value %v", v)`, mirroring PUC's `return luaL_error(...)`.

func (*LState) RaiseValue

func (L *LState) RaiseValue(v Value) int

RaiseValue raises v as the error object, like PUC lua_error — use it to raise a non-string error such as a structured error table. As with RaiseError it does not return; the int result is for `return L.RaiseValue(t.Value())`.

func (*LState) Register

func (L *LState) Register(name string, fn GoFunc)

Register installs a native function as a global.

func (*LState) Requiref

func (L *LState) Requiref(name string, open func(*LState) *Table, glb bool) *Table

Requiref opens a host-provided module the way the standard libraries install themselves (the embedding-side analog of PUC's luaL_requiref). It calls open once to build the module table, caches it in package.loaded[name] so a later require(name) returns the same table, and — when glb is true — also binds it to the global name. It is idempotent: an already-loaded module is returned without calling open again. The package library must be open (OpenLibs) for the require/loaded integration; the global binding works regardless. Returns the module table.

L.Requiref("mylib", func(L *luapure.LState) *luapure.Table {
    t := luapure.NewTable()
    t.SetStr("greet", luapure.NewGoFunc("greet", greet))
    return t
}, true) // installs global `mylib`, and require("mylib") returns it

func (*LState) RunWith

func (L *LState) RunWith(env *Table, src, name string) ([]Value, error)

RunWith compiles and runs src as a main chunk whose _ENV is env, under a protected call, returning all results. Expose only the globals you trust in env (build it with NewTable) to confine untrusted code; pair with NewSandbox and SetContext for library and time limits.

Example

RunWith confines a chunk to a custom _ENV: it sees only the globals you put in env.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	env := luapure.NewTable()
	env.SetStr("x", luapure.Int(21))

	res, err := L.RunWith(env, `return x * 2`, "=embed")
	if err != nil {
		panic(err)
	}
	fmt.Println(res[0].AsInt())
}
Output:
42

func (*LState) SetContext

func (L *LState) SetContext(ctx context.Context)

SetContext installs ctx as the cancellation context for this state and the coroutines it spawns. While set, the VM checks ctx.Err() every finGCPoll instructions and raises a catchable error once the context is done, so even a tight infinite loop (`while true do end`) can be interrupted. Pass nil to disable. Cancellation is observed only between VM instructions, not inside a blocking Go call (e.g. a long io read).

Example

A cancelled context interrupts even a tight infinite loop.

package main

import (
	"context"
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	ctx, cancel := context.WithCancel(context.Background())
	cancel() // already cancelled before we run
	L.SetContext(ctx)

	_, err := L.DoString(`while true do end`, "=embed")
	fmt.Println(err != nil)
}
Output:
true

func (*LState) SetGlobal

func (L *LState) SetGlobal(name string, v Value)

SetGlobal sets _ENV[name] = v.

func (*LState) SetGoHook

func (L *LState) SetGoHook(h GoHook, mask HookMask, count int)

SetGoHook installs h as the Go debug hook, firing on the events named by mask; when mask includes MaskCount, count is the instruction quantum between count events. A nil h (or zero mask) removes the hook. It is independent of any Lua debug.sethook hook, which keeps firing alongside it.

func (*LState) SetIndex

func (L *LState) SetIndex(t, key, val Value)

SetIndex assigns t[key] = val honouring the __newindex metamethod chain.

func (*LState) SetInstructionLimit

func (L *LState) SetInstructionLimit(n uint64)

SetInstructionLimit caps how many bytecode instructions this state (and the coroutines it spawns, which share the budget) may execute before the VM raises a catchable "instruction limit exceeded" error. It is a host-only guard against runaway pure-Lua CPU, orthogonal to SetContext's wall-clock cancellation: set ExecTimeout-style deadlines with SetContext and a CPU cap here. n == 0 disables it. Setting a limit resets the counter, so call it once per run (like SetContext). The cap is enforced at the finalizer-poll gate, so its granularity is finGCPoll instructions, not exact.

It is deliberately Go-level only — not exposed to Lua via debug.sethook — because a script could otherwise remove its own count hook and defeat the cap.

func (*LState) SetRecoverGoPanics

func (L *LState) SetRecoverGoPanics(on bool)

SetRecoverGoPanics toggles protected mode: when on, a protected call (Call, DoString, the Lua-level pcall, metamethods) that hits a non-LuaError Go panic — typically a registered Go callback that panicked — recovers it into a catchable *GoPanicError and unwinds the VM to its pre-call state, so the panic does not escape to the host and the State remains reusable. When off (the default) such a panic is re-raised unchanged, which is PUC-faithful (PUC does not catch a C-side abort) and surfaces genuine host bugs. A pool that runs less-trusted callbacks typically turns this on. Inherited by coroutines.

func (*LState) SetUserMetatable

func (L *LState) SetUserMetatable(v Value, meta *Table)

SetUserMetatable replaces a full userdata's metatable (nil to clear it), registering a __gc finalizer if the new metatable carries one — the lua_setmetatable path. It is a no-op for non-userdata values.

func (*LState) StackDepth

func (L *LState) StackDepth() int

StackDepth reports how many active call frames there are (the current frame plus its callers). Frame levels run 0 .. StackDepth()-1.

func (*LState) TestUserData

func (L *LState) TestUserData(n int, name string) any

TestUserData is the non-raising CheckUserData (luaL_testudata): it returns the payload when argument n is full userdata whose metatable is the registry metatable named name, else nil. A nil result is also returned for a userdata whose payload is genuinely nil; when that distinction matters, compare the argument's UserMetatable against GetMetatable directly.

func (*LState) ToValue

func (L *LState) ToValue(x any) Value

ToValue converts a Go value into a Lua Value. Supported: nil, bool, the integer and float kinds, string, []any (a 1-based array table), and map[string]any / map[any]any (a keyed table); nested slices/maps recurse. A Value passes through unchanged. Cyclic Go structures are not supported.

Example

ToValue converts Go data into Lua tables; AsTable reads them back.

package main

import (
	"fmt"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	v := L.ToValue([]any{10, 20, 30})
	t := v.AsTable()
	fmt.Println(t.Len(), t.GetInt(2).AsInt())
}
Output:
3 20

func (*LState) TypeError

func (L *LState) TypeError(n int, want string)

TypeError raises a "bad argument" error reporting that argument n was the wrong type, naming the expected type (luaL_typeerror).

type LocVar

type LocVar struct {
	Name    string
	StartPc int // first pc where the variable is active
	EndPc   int // first pc where it is dead
}

LocVar is a local-variable debug record (PUC lobject.h LocVar): the register holding it is implied by declaration order; StartPc..EndPc is its live range.

type Local

type Local struct {
	Name  string // a source name, or "(temporary)" / "(vararg)" for unnamed slots
	Index int    // 1-based index for Frame.Local / Frame.SetLocal
	Value Value
}

Local pairs a frame-local variable's name with its current value (Frame.Locals).

type LuaError

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

LuaError carries a Lua error value through Go's panic/recover (the longjmp replacement noted in the migration plan). It is the error type returned by the protected entry points (DoString, CallProto, Call), so embedders can type-assert to *LuaError to recover the raised Lua value and traceback.

func (*LuaError) Error

func (e *LuaError) Error() string

func (*LuaError) Traceback

func (e *LuaError) Traceback() string

Traceback returns the captured stack traceback, or "" if none was attached.

func (*LuaError) Unwrap

func (e *LuaError) Unwrap() error

Unwrap exposes a recovered Go panic so callers can errors.As(err, &gpe) for a *GoPanicError; it returns nil for an ordinary Lua error.

func (*LuaError) Value

func (e *LuaError) Value() Value

Value returns the raised Lua error object (commonly a string, but any value can be raised via error()).

type OpCode

type OpCode int

OpCode identifies a Lua 5.4 virtual-machine instruction.

const (
	OP_MOVE OpCode = iota
	OP_LOADI
	OP_LOADF
	OP_LOADK
	OP_LOADKX
	OP_LOADFALSE
	OP_LFALSESKIP
	OP_LOADTRUE
	OP_LOADNIL
	OP_GETUPVAL
	OP_SETUPVAL

	OP_GETTABUP
	OP_GETTABLE
	OP_GETI
	OP_GETFIELD

	OP_SETTABUP
	OP_SETTABLE
	OP_SETI
	OP_SETFIELD

	OP_NEWTABLE

	OP_SELF

	OP_ADDI

	OP_ADDK
	OP_SUBK
	OP_MULK
	OP_MODK
	OP_POWK
	OP_DIVK
	OP_IDIVK

	OP_BANDK
	OP_BORK
	OP_BXORK

	OP_SHRI
	OP_SHLI

	OP_ADD
	OP_SUB
	OP_MUL
	OP_MOD
	OP_POW
	OP_DIV
	OP_IDIV

	OP_BAND
	OP_BOR
	OP_BXOR
	OP_SHL
	OP_SHR

	OP_MMBIN
	OP_MMBINI
	OP_MMBINK

	OP_UNM
	OP_BNOT
	OP_NOT
	OP_LEN

	OP_CONCAT

	OP_CLOSE
	OP_TBC
	OP_JMP
	OP_EQ
	OP_LT
	OP_LE

	OP_EQK
	OP_EQI
	OP_LTI
	OP_LEI
	OP_GTI
	OP_GEI

	OP_TEST
	OP_TESTSET

	OP_CALL
	OP_TAILCALL

	OP_RETURN
	OP_RETURN0
	OP_RETURN1

	OP_FORLOOP
	OP_FORPREP

	OP_TFORPREP
	OP_TFORCALL
	OP_TFORLOOP

	OP_SETLIST

	OP_CLOSURE

	OP_VARARG

	OP_VARARGPREP

	OP_EXTRAARG
)

The opcode set, in PUC order (lopcodes.h). "Grep ORDER OP" if changed.

func GetOpCode

func GetOpCode(i Instruction) OpCode

func (OpCode) IsMM

func (o OpCode) IsMM() bool

IsMM reports whether the opcode invokes a metamethod (MMBIN family).

func (OpCode) IsTest

func (o OpCode) IsTest() bool

IsTest reports whether the opcode is a test (next instruction is a jump).

func (OpCode) Mode

func (o OpCode) Mode() OpMode

Mode returns the instruction format for opcode o.

func (OpCode) Name

func (o OpCode) Name() string

Name returns the opcode's mnemonic, or "?" if out of range.

func (OpCode) SetsA

func (o OpCode) SetsA() bool

SetsA reports whether the opcode assigns register A.

func (OpCode) SetsTopOut

func (o OpCode) SetsTopOut() bool

SetsTopOut reports whether the opcode sets L->top for the next instruction (relevant when C == 0).

func (OpCode) UsesTopIn

func (o OpCode) UsesTopIn() bool

UsesTopIn reports whether the opcode reads L->top set by the previous instruction (relevant when B == 0).

type OpMode

type OpMode int

OpMode is one of the five basic instruction formats.

const (
	IABC OpMode = iota
	IABx
	IAsBx
	IAx
	IsJ
)

type Option

type Option func(*buildOpts)

Option configures a State created by NewState.

func WithContext

func WithContext(ctx context.Context) Option

WithContext installs ctx as the State's cancellation context (equivalent to SetContext); the coroutines it spawns inherit it.

func WithMaxCCalls

func WithMaxCCalls(n int) Option

WithMaxCCalls overrides MaxCCalls for this State only: the nested Go-call depth (metamethods, pcall, hooks, resume) before "C stack overflow".

func WithMaxStack

func WithMaxStack(n int) Option

WithMaxStack overrides MaxStack for this State only: the value-stack ceiling that turns unbounded recursion into a catchable "stack overflow".

func WithMaxTableArraySize

func WithMaxTableArraySize(n int) Option

WithMaxTableArraySize overrides MaxTableArraySize for this State only: the array-part growth ceiling that stands in for PUC's malloc-failure path. 0 means unlimited.

func WithOpenLibs

func WithOpenLibs() Option

WithOpenLibs opens the full standard library (equivalent to calling OpenLibs on the new State).

func WithRecoverGoPanics

func WithRecoverGoPanics() Option

WithRecoverGoPanics turns on protected mode (equivalent to SetRecoverGoPanics): a non-LuaError Go panic from a registered callback is recovered into a catchable *GoPanicError and the VM is unwound cleanly, instead of escaping to the host. Off by default (PUC-faithful re-panic); useful for pools that must survive a panicking callback.

func WithSandbox

func WithSandbox() Option

WithSandbox opens only the safe standard libraries, exactly as NewSandbox does: base, string, table, math, utf8, and coroutine, with load/loadfile/ dofile removed. Pair with WithContext and RunWith to confine untrusted code.

type Proto

type Proto struct {
	NumParams    uint8 // number of fixed (named) parameters
	IsVararg     bool
	MaxStackSize uint8 // registers needed by this function

	Code      []Instruction // opcodes
	Constants []Value       // constant table (k)
	Protos    []*Proto      // functions defined inside this one (p)
	Upvalues  []UpvalDesc   // upvalue descriptors

	// Debug information.
	Source      string   // chunk name
	LineDefined int      // 0 for the main chunk
	LastLineDef int      // last line of the definition
	LineInfo    []int32  // source line per instruction (1:1 with Code)
	LocVars     []LocVar // local-variable debug records
}

Proto is a compiled Lua 5.4 function prototype (PUC lobject.h Proto): the bytecode plus the constants, nested prototypes, upvalue descriptors and debug information a closure is instantiated from.

func CompileReader

func CompileReader(r io.Reader, name string) (*Proto, error)

CompileReader compiles Lua source streamed from r into a prototype, the io.Reader analogue of CompileString. It accepts either text or a precompiled binary chunk and consumes r incrementally rather than buffering it whole.

Example

CompileReader compiles source streamed from any io.Reader.

package main

import (
	"fmt"
	"strings"

	luapure "github.com/htcom-code/lua-pure/lua"
)

func main() {
	L := luapure.NewState()
	L.OpenLibs()

	p, err := luapure.CompileReader(strings.NewReader(`return 6 * 7`), "=embed")
	if err != nil {
		panic(err)
	}
	res, _ := L.CallProto(p, 1)
	fmt.Println(res[0].AsInt())
}
Output:
42

func CompileString

func CompileString(src, name string) (*Proto, error)

CompileString parses and compiles Lua source into a prototype. The chunk name is used for error positions and debug info (PUC's "@file" / "=name" forms).

func (*Proto) AddConstant

func (p *Proto) AddConstant(v Value) int

AddConstant appends v to the constant table and returns its index. (Dedup is the compiler's job; this is the raw append.)

func (*Proto) LineAt

func (p *Proto) LineAt(pc int) int

LineAt returns the source line for instruction pc. A proto with no line info (a stripped chunk) reports -1, matching PUC luaG_getfuncline, so error messages and tracebacks read ":-1:" rather than ":0:".

type RunResult

type RunResult struct {
	Values []Value
	Err    error
}

RunResult carries a finished run's results or error (Session.Start).

type Session

type Session struct {
	L *LState
	// contains filtered or unexported fields
}

Session is a synchronous façade over a Debugger for building a debug front end — an MCP server, a DAP adapter, a CLI console. The Debugger's stop handler runs on the VM goroutine and blocks it; Session lets a controller on another goroutine drive that paused VM with ordinary request/response calls, marshalling each inspection onto the VM goroutine so frame access stays safe.

Source on the server only. A Session never needs the script text to debug: breakpoints and stop locations are keyed by a chunk's *name* (an opaque id such as "script/42") and line, and line numbers come from the compiled prototype. The source text is needed only to *display* where execution stopped, and that is served on demand from wherever the host keeps it (a database, an object store) through the SourceResolver — so a remote client that holds no source can still show context by asking the Session. This mirrors DAP's sourceReference mechanism.

Protocol. Launch with Start and select over Stops() and the returned result channel. On each StopState, inspect with Stack / Variables / Eval, then issue exactly one resume (Continue / StepInto / StepOver / StepOut). Inspection and resume calls are valid only while paused at a stop — between receiving a StopState and resuming. SetBreakpoints and Pause are safe to call anytime.

func NewSession

func NewSession(L *LState, resolve SourceResolver) *Session

NewSession attaches a debug session to L. resolve may be nil (no source serving). It installs the debugger's hook; the program runs only once Start (or the host's own call into the VM) executes a chunk.

func (*Session) Continue

func (s *Session) Continue()

Continue resumes until the next breakpoint or Pause.

func (*Session) Debugger

func (s *Session) Debugger() *Debugger

Debugger exposes the underlying controller (e.g. for ClearBreakpoints/Detach).

func (*Session) Eval

func (s *Session) Eval(level int, expr string) (string, error)

Eval evaluates a Lua expression (or statement) in the scope of the frame at level and returns its results rendered for display (tab-separated).

func (*Session) Pause

func (s *Session) Pause()

Pause asks the program to stop at the next line.

func (*Session) SetBreakpoints

func (s *Session) SetBreakpoints(source string, lines []int)

SetBreakpoints replaces the breakpoints for source (a chunk's short id).

func (*Session) Snippet

func (s *Session) Snippet(id string, line, ctx int) (string, bool)

Snippet renders the lines of chunk id around line (± ctx lines), each prefixed with its number and a marker ("->") on the current line — enough for a front end to show the stop location without ever holding the source itself.

func (*Session) Source

func (s *Session) Source(id string) (text string, ok bool)

Source returns the text of chunk id via the SourceResolver, or ok=false when there is no resolver or the id is unknown. A remote client that holds no source calls this to display where it stopped.

func (*Session) Stack

func (s *Session) Stack() []FrameInfo

Stack returns the call stack at the current stop, innermost first.

func (*Session) Start

func (s *Session) Start(src, chunkName string) <-chan RunResult

Start runs src as a chunk named chunkName on a new goroutine and returns a channel that receives the single result when it finishes. chunkName is the debug identity of the script: prefix it with "=" so its short name (which breakpoints and StopState use) is the bare id, e.g. "=script/42".

func (*Session) StepInto

func (s *Session) StepInto()

StepInto resumes to the next line, descending into calls.

func (*Session) StepOut

func (s *Session) StepOut()

StepOut resumes until execution returns to a shallower frame.

func (*Session) StepOver

func (s *Session) StepOver()

StepOver resumes to the next line at this depth or shallower.

func (*Session) Stops

func (s *Session) Stops() <-chan StopState

Stops delivers a StopState each time the program pauses. Receive from it in the controller loop.

func (*Session) Variables

func (s *Session) Variables(level int) []Var

Variables returns the rendered locals, upvalues and varargs of the frame at level (0 = innermost).

type SourceResolver

type SourceResolver func(id string) (text string, ok bool)

SourceResolver returns the text of the chunk named id (its short name, e.g. "script/42"), or ok=false when unknown. A host backs it with a DB lookup.

type StopReason

type StopReason uint8

StopReason explains why the debugger stopped.

const (
	StopBreakpoint StopReason = iota // a set breakpoint was hit
	StopStep                         // a step (into/over/out) completed
	StopPause                        // an async Pause request landed
)

func (StopReason) String

func (r StopReason) String() string

type StopState

type StopState struct {
	Reason StopReason
	Source string // innermost frame's chunk id (short name); "" for a native frame
	Line   int    // innermost frame's current line (-1 if unavailable)
	Func   string // innermost frame's function name, if known
	Depth  int    // number of active frames
}

StopReason values are reused from the Debugger. StopState describes one stop.

type TMS

type TMS int

TMS is a tag-method selector (ltm.h TMS), ORDER TM.

type Table

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

Table is the Lua 5.4 table object. PUC (ltable.c) uses a hybrid of a packed array part plus an open-addressed node array; this port keeps the same observable semantics with a Go-native split — a 1-based array part for dense integer keys and a map for everything else — which is simpler and equivalent for the VM and the test suite (Lua does not specify hash iteration order).

Keys are normalised exactly as PUC: a float key with an integral value is stored under the equivalent integer key (t[2.0] and t[2] are the same slot), and NaN / nil keys are rejected on assignment.

func NewTable

func NewTable() *Table

NewTable returns a fresh empty table.

func (*Table) Get

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

Get returns t[key] without invoking metamethods (raw access).

func (*Table) GetInt

func (t *Table) GetInt(i int64) Value

GetInt returns t[i] for an integer key.

func (*Table) GetStr

func (t *Table) GetStr(name string) Value

GetStr returns t[name] for a string key.

func (*Table) Len

func (t *Table) Len() int64

Len returns the table's border length (the # operator's raw result).

func (*Table) Next

func (t *Table) Next(key Value) (k, v Value, ok bool)

Next iterates raw key/value pairs the way Lua's next does: pass Nil to start, then pass back the previous key. ok is false once iteration is exhausted. Like next, the traversal order is unspecified and the table must not have keys inserted during iteration (assigning nil to the current key is allowed).

func (*Table) Set

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

Set assigns t[key] = val without invoking metamethods (raw access). Setting a nil value removes the key.

func (*Table) SetInt

func (t *Table) SetInt(i int64, val Value)

SetInt assigns t[i] = val for an integer key.

func (*Table) SetStr

func (t *Table) SetStr(name string, val Value)

SetStr assigns t[name] = val for a string key.

func (*Table) Value

func (t *Table) Value() Value

Value wraps the table as a Lua Value (for SetGlobal, Set, call arguments…).

type UnOpr

type UnOpr int

UnOpr enumerates unary operators (lcode.h UnOpr).

const (
	OPR_MINUS UnOpr = iota
	OPR_BNOT
	OPR_NOT
	OPR_LEN
	OPR_NOUNOPR
)

type UpvalDesc

type UpvalDesc struct {
	Name    string // upvalue name (debug)
	InStack bool   // captured from the enclosing function's registers (vs its upvalues)
	Index   uint8  // register index (InStack) or outer upvalue index
	Kind    uint8  // variable kind: regular / <const> / <close> / compile-time const
}

UpvalDesc describes one upvalue of a Proto (PUC lobject.h Upvaldesc).

type Upvalue

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

Upvalue is a shared variable reference (PUC UpVal). While "open" it aliases a live stack slot (L.stack[idx]); once "closed" it owns the value in `v`. Because the stack is addressed by index rather than by pointer, stack growth never invalidates an open upvalue — no pointer fix-up is needed.

type Value

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

Value is a Lua 5.4 runtime value. It uses a tagged-struct representation: scalars are stored inline so integer/float arithmetic does not box, and GC objects are held through a normal pointer (GC-safe, unlike true NaN-boxing which Go's precise GC cannot follow).

Layout (24 bytes on 64-bit): tag(1) + pad + scalar(8) + gc(8).

func Bool

func Bool(b bool) Value

Bool returns the boolean Value for b.

func Float

func Float(f float64) Value

Float returns a float Value.

func Int

func Int(i int64) Value

Int returns an integer Value (Lua 5.3+ integer subtype).

func MkString

func MkString(s string) Value

MkString returns a string Value backing the given Go string.

func NewGoFunc

func NewGoFunc(name string, fn GoFunc) Value

NewGoFunc wraps a GoFunc as a callable Value.

func (Value) AsBool

func (v Value) AsBool() bool

Bool returns the boolean payload; valid only when IsBool.

func (Value) AsFloat

func (v Value) AsFloat() float64

AsFloat returns the float payload; valid only when IsFloat.

func (Value) AsInt

func (v Value) AsInt() int64

AsInt returns the integer payload; valid only when IsInt.

func (Value) AsTable

func (v Value) AsTable() *Table

AsTable returns the underlying *Table when v is a table, else nil. Pair it with IsTable to distinguish a non-table from an empty table.

func (Value) AsUserData

func (v Value) AsUserData() any

AsUserData returns the Go payload of a full userdata Value, or nil when v is not full userdata (lua_touserdata). Light userdata yields nil — it carries no Go payload. Pair it with IsUserData to tell a nil payload from a non-userdata.

func (Value) IsBool

func (v Value) IsBool() bool

func (Value) IsFalsy

func (v Value) IsFalsy() bool

IsFalsy reports whether v is nil or false (Lua truthiness).

func (Value) IsFloat

func (v Value) IsFloat() bool

func (Value) IsFunction

func (v Value) IsFunction() bool

func (Value) IsInt

func (v Value) IsInt() bool

func (Value) IsNil

func (v Value) IsNil() bool

func (Value) IsNumber

func (v Value) IsNumber() bool

func (Value) IsString

func (v Value) IsString() bool

func (Value) IsTable

func (v Value) IsTable() bool

func (Value) IsThread

func (v Value) IsThread() bool

func (Value) IsUserData

func (v Value) IsUserData() bool

func (Value) NumUserValues

func (v Value) NumUserValues() int

NumUserValues reports how many uservalue slots v was created with, or 0 when v is not full userdata.

func (Value) RawEqual

func (a Value) RawEqual(b Value) bool

RawEqual reports primitive (no-metamethod) equality, matching PUC luaV_equalobj for the cases representable so far. Integer and float compare by mathematical value (1 == 1.0); strings compare by contents.

func (Value) SetUserValue

func (v Value) SetUserValue(n int, val Value) bool

SetUserValue assigns the n-th uservalue slot (1-based) of a full userdata, the lua_setiuservalue form. It reports false (assigning nothing) when v is not userdata or n is out of range.

func (Value) Str

func (v Value) Str() string

Str returns the string payload; valid only when IsString.

func (Value) Type

func (v Value) Type() int

Type returns the basic Lua type tag (lua.h LUA_T*).

func (Value) UserMetatable

func (v Value) UserMetatable() *Table

UserMetatable returns the metatable attached to a full userdata Value, or nil (when v is not userdata or has none).

func (Value) UserValue

func (v Value) UserValue(n int) (val Value, ok bool)

UserValue returns the n-th uservalue slot (1-based) of a full userdata, the lua_getiuservalue form. ok is false when v is not userdata or n is out of range, in which case the value is Nil.

type Var

type Var struct {
	Name  string
	Value string // display rendering of the value
	Kind  string // "local" | "upvalue" | "vararg"
	Raw   Value
}

Var is a rendered variable binding (Session.Variables). Raw carries the underlying value so a front end can render it differently or expand a table into its fields (e.g. a DAP variablesReference tree); it is valid only while the program is paused at the stop the Var was read from.

type ZIO

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

ZIO is a byte stream backed by an optional refill reader (PUC struct Zio). reader returns the next piece and ok=false at end of input; a nil/empty piece also ends the stream, matching PUC's treatment of a NULL/zero-size reader result as EOZ.

Jump to

Keyboard shortcuts

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