luapure is a faithful Go port of PUC-Lua 5.4.8: the language, instruction set, standard libraries, error messages, and edge cases are matched against the reference C sources. This document is the single source of truth for where it differs from PUC and why — and for the Go-native facilities it adds on top.
It has four parts:
- Structural divergences — implementation choices that keep the same observable behavior.
- Library-surface differences — standard-library functions that differ or are absent.
- Behavioral won't-fix — semantics that cannot match because of the Go-native design.
- Go-native additions — embedding API that PUC has no equivalent for.
These are deliberate implementation choices. Observable semantics, error messages, and edge cases are still matched against the PUC sources.
- GC is delegated to the Go runtime (no
lmem/ incremental collector). Weak tables useweak.Pointer(Go 1.24);__gcusesruntime.SetFinalizerplus a main-thread drain queue.collectgarbagedrivesruntime.GC(). Valueis a tagged struct (tag + inline scalar + GC pointer), not NaN-boxing — Go's precise GC must be able to follow pointers.- Errors unwind via
panic/recoverinstead oflongjmp. - Coroutines are goroutines handing off over channels, so a yield can cross a Go/"C" frame (e.g. inside a metamethod). Pure-Lua coroutines run stacklessly and never spawn a goroutine.
- Binary chunks use PUC-Lua 5.4's exact precompiled-chunk format (a port of
ldump.c/lundump.c): a dump is byte-identical toluac 5.4.8on a 64-bit little-endian host, and luapure loadsluacoutput and vice versa. (Internally luapure keeps absolute line numbers; dump recompresses them to PUC'slineinfo/abslineinfoand undump restores them.) - Memory-limit caps (configurable
MaxTableArraySize,MaxLexElement, constant count): Go cannot turn an allocation failure into a catchable error (OOM is a fatal runtime throw), so these size checks stand in for PUC's malloc-failure path. Default off; the conformance runner sets them.
The standard-library surface otherwise equals a stock PUC 5.4.8 build
(including the LUA_COMPAT_5_3 defaults). The exceptions:
io.popen— absent. Spawning a process and piping to it depends on the host process/OS, outside a portable pure-Go VM's scope. (os.executeis present.)- Dynamic C library loading — absent.
requirecannot load.so/.dllmodules, andpackage.loadlibalways returns the"absent"failure — which is itself a valid PUC build (one compiled withoutLUA_DL). Register Go-native modules instead withRequiref/Preload. - Already at parity (formerly missing): the deprecated
LUA_COMPAT_MATHLIBfunctionsmath.pow,math.sinh,math.cosh,math.tanh,math.log10,math.frexp,math.ldexp,math.atan2, and the deprecated no-opdebug.setcstacklimit, are present (pinned by_glue5.4-tests/deprecated-compat.lua).io.tmpfile,file:setvbuf, andos.tmpnameare present.
Consequences of being a Go-native, embeddable VM — not gaps to close:
collectgarbage("count"/"step")accounting. The functions and modes exist, but exact byte accounting and the incremental step state cannot match: the Go runtime owns the heap.collectgarbage("collect")drives a realruntime.GC();"stop"/"restart"are tracked forisrunningbut do not actually pause the Go collector.- Conformance ceiling = 30/33. Every official 5.4 test file that can pass,
passes. The three that cannot:
gc.lua— thecollectgarbageaccounting above.files.lua(process-dependent tail) —os.execute/io.popenand seekable-stdin assumptions depend on the host process/OS.all.lua/main.lua— driver scripts, not behavioural fixtures.
PUC has no equivalent for these; they are the Go embedding surface. See the API
reference (make doc / make doc-web) for signatures.
- State construction options —
NewState(opts...):WithOpenLibs,WithSandbox,WithContext, and per-State limit overridesWithMaxStack/WithMaxCCalls/WithMaxTableArraySize(validated at construction). PUC bakes these limits in at compile time; luapure lets a pool give different States different limits in one process. - Cancellation —
SetContext(ctx)/Context(): cooperative cancellation checked between instructions, so a deadline or abort stops even a tight loop. A Go callback readsContext()to make its own blocking work cancellable. - Instruction budget —
SetInstructionLimit(n)/InstructionCount(): a Go-only cap on executed instructions (runaway-CPU guard), orthogonal to the wall-clock context and not reachable fromdebug.sethook. - Protected mode —
WithRecoverGoPanics()/SetRecoverGoPanics(bool): opt in to recovering a non-LuaError Go panic from a callback into a catchable*GoPanicError(with the VM unwound so the State stays reusable). Off by default, which re-raises — PUC-faithful. - Sandboxing —
NewSandbox()(safe libraries only) andRunWith(env, src, name)/CallProtoEnv(p, env, n): run a chunk under a custom_ENV, so a fresh env per call confines globals (the 5.4_ENVsandbox) without recompiling. - Host modules —
Requiref(name, open, glb)andPreload(name, open): register a Go-built module sorequire(name)resolves it (the embedding-side analog ofluaL_requiref/package.preload). - Go ⇄ Lua data —
ToValue/FromValueconversion, full userdata with metatables and uservalues (NewUserData/NewMetatable/CheckUserData), structured errors (*LuaErrorwithValue()/Traceback()). A callback raises errors withArgError/TypeError,RaiseError(format, …)(PUCluaL_error, position-prefixed) orRaiseValue(v)(PUClua_error, an arbitrary value). - Debugging — a Go-native debug hook (
SetGoHook) and aDebugger/Sessionwith breakpoints/stepping, exposed over the Model Context Protocol (debugmcp) and the Debug Adapter Protocol (debugdap).
See also: README.md · ROADMAP.md ·
FILEMAP.md. Lua is © 1994–2025 Lua.org, PUC-Rio (MIT); luapure
is a derivative work (MIT) — see LICENSE / LICENSE-Lua.