Add the dependency
In your project’s ziran.toml; pin ref to a commit for reproducible builds.
[dependencies.lua]
git = "https://github.com/ziranlang/lua.git"
ref = "master"Overview
The embedding API follows the Lua C API: a stack, natives as plain Ziran procedures, userdata with metatables, registry references, coroutines a native can yield from, and limits on memory, call depth and instruction count for running untrusted mods. File and environment access is off until the host grants it.
Use it
ziran add https://github.com/ziranlang/lua.git
#import "lua/vm"
state: LuaState;
Add :: (L: *LuaState) -> s32 {
a, ok1 := LuaCheckNumber(L, 1)
if !ok1 { return LUA_NATIVE_ERROR }
b, ok2 := LuaCheckNumber(L, 2)
if !ok2 { return LUA_NATIVE_ERROR }
LuaPushNumber(L, a + b)
return 1
}
Run :: () -> bool {
L := *state
LuaOpen(L)
LuaRegister(L, "add", Add)
status := LuaDoString(L, "print(add(1, 2))", "=example")
if status != LUA_OK {
message, ok := LuaToString(L, -1)
print("%\n", message)
}
LuaClose(L)
return status == LUA_OK
}
docs/API.md describes the whole API and what each library supports.
Check it
make test
This runs the API unit tests, the Lua scripts in tests/lua, and loads
every .lua file of the Katalis mods (set KATALIS_MODS to another
folder; it is skipped when missing). make wasm (part of make test) builds
the VM for wasm32 with Emscripten and runs it in a private headless Chromium;
set EMCC and CHROMIUM if they are elsewhere (it is skipped without them).
lua/src/vm.zi
vm
A Lua 5.1 virtual machine in Ziran, following the LuaJIT dialect that Luanti mods use: lexer, parser, register compiler, interpreter, garbage collector, the standard libraries (base, coroutine, string, table, math, a sandboxed os/io subset, bit, debug) and an embedding API modeled on the C API. docs/API.md documents the embedding API.
Import with #import "lua/vm".
LUA_TNIL :: 0Value tags. FALSE and TRUE are separate tags so truthiness is one compare.
LUA_TFALSE :: 1LUA_TTRUE :: 2LUA_TNUMBER :: 3LUA_TSTRING :: 4LUA_TTABLE :: 5LUA_TFUNCTION :: 6LUA_TUSERDATA :: 7LUA_TTHREAD :: 8LUA_TLIGHTUSERDATA :: 9LUA_TBOX :: 10LUA_OK :: 0Status codes returned by calls and loads.
LUA_YIELD :: 1LUA_ERRRUN :: 2LUA_ERRSYNTAX :: 3LUA_ERRMEM :: 4LUA_ERRERR :: 5LUA_ERRFILE :: 6LUA_NATIVE_ERROR :: -1A native returns its result count, or one of these.
LUA_NATIVE_YIELD :: -2LUA_MULTRET :: -1LuaValue :: struct { n: float64; h: s32; t: u8; }LuaNative :: #type (L: *LuaState) -> s32Called every `count` VM checkpoints (calls and backward jumps). Return true to continue, false to raise an error (the message is the value on
LuaHook :: #type (L: *LuaState) -> boolLuaFinalizer :: #type (L: *LuaState, data: *void, tag: s64)LuaPrintSink :: #type (context: *void, text: string)LuaLimits :: struct { max_memory: s64; max_native_depth: s32; max_frames: s32; }LuaDefaultLimits :: () -> LuaLimitsLuaState :: struct { strings: Vec(LuaString); string_free: Vec(s32); intern: Vec(s32); intern_used: s64; tables: Vec(LuaTable); table_free: Vec(s32); closures: Vec(LuaClosure); closure_free: Vec(s32); protos: Vec(LuaProto); proto_free: Vec(s32); boxes: Vec(LuaBox); box_free: Vec(s32); userdata: Vec(LuaUserdata); userdata_free: Vec(s32); threads: Vec(LuaThread); thread_free: Vec(s32); cur: s32; main: s32; globals: s32; registry: s32; type_meta: [11]s32; tm_names: [20]s32; name_n: s32; name_goto: s32; name_self: s32; name_loaded: s32; next_fn: s32; inext_fn: s32; handler_slack: s32; weak: Vec(s32); error: LuaValue; error_traced: bool; ref_free: Vec(s32); allocated: s64; threshold: s64; gray: Vec(LuaValue); pending_finalize: Vec(s32); collecting: bool; resumers: Vec(s32); native_depth: s32; limits: LuaLimits; open: bool; hook: LuaHook; hook_set: bool; hook_count: s64; hook_left: s64; print_sink: LuaPrintSink; print_sink_set: bool; print_context: *void; file_root: Vec(u8); random_state: u64; clock_start: s64; env_allow: Vec(u8); file_meta: s32; scratch: Vec(u8); scratch2: Vec(u8); host: *void; }One Lua universe. The host owns this record (a global or a long-lived local) and passes its address to every API call; LuaOpen initializes it and LuaClose releases everything it holds.
LUA_TYPE_NONE :: -1Type codes returned by LuaType, matching the C API.
LUA_TYPE_NIL :: 0LUA_TYPE_BOOLEAN :: 1LUA_TYPE_LIGHTUSERDATA :: 2LUA_TYPE_NUMBER :: 3LUA_TYPE_STRING :: 4LUA_TYPE_TABLE :: 5LUA_TYPE_FUNCTION :: 6LUA_TYPE_USERDATA :: 7LUA_TYPE_THREAD :: 8LUA_REGISTRYINDEX :: -10000Pseudo-indices.
LUA_ENVIRONINDEX :: -10001LUA_GLOBALSINDEX :: -10002LUA_REFNIL :: -1LuaRef result for nil.
LUA_NOREF :: -2LuaUpvalueIndex :: (i: s32) -> s32LuaOpen :: (L: *LuaState) -> boolInitialize a state with the standard libraries. Returns false when the record was already open.
TagMethodNames :: () -> [20]stringLuaClose :: (L: *LuaState)Run pending __gc finalizers and host finalizers, then free everything.
LuaSetLimits :: (L: *LuaState, limits: LuaLimits)LuaGetLimits :: (L: *LuaState) -> LuaLimitsLuaSetHook :: (L: *LuaState, hook: LuaHook, count: s64)Call `hook` every `count` checkpoints (Lua calls and loop back-edges). A hook returning false raises an error in the running Lua code; set its message with LuaSetHookMessage first.
LuaClearHook :: (L: *LuaState)LuaSetHookMessage :: (L: *LuaState, message: string)LuaSetPrintSink :: (L: *LuaState, sink: LuaPrintSink, context: *void)LuaAllowEnvironment :: (L: *LuaState, prefix: string)Let os.getenv read variables whose names start with `prefix` (none by default, so os.getenv returns nil).
LuaSetFileRoot :: (L: *LuaState, path: string)Directory that dofile/loadfile/io.open may read below; empty (the default) disables Lua-side file access.
LuaSetHost :: (L: *LuaState, host: *void)LuaHost :: (L: *LuaState) -> *voidLuaCollectGarbage :: (L: *LuaState)LuaMemoryUsed :: (L: *LuaState) -> s64Estimated live heap bytes.
LuaGetTop :: (L: *LuaState) -> s32LuaSetTop :: (L: *LuaState, index: s32)LuaPop :: (L: *LuaState, n: s32)LuaCheckStack :: (L: *LuaState, extra: s32) -> boolLuaAbsIndex :: (L: *LuaState, index: s32) -> s32A positive index for `index`, the same pseudo-index otherwise.
LuaPushValue :: (L: *LuaState, index: s32)LuaRemove :: (L: *LuaState, index: s32)LuaInsert :: (L: *LuaState, index: s32)LuaReplace :: (L: *LuaState, index: s32)Move the top value into `index` (resolved before the pop, as in C).
LuaType :: (L: *LuaState, index: s32) -> s32LuaTypeName :: (L: *LuaState, type: s32) -> stringLuaIsNone :: (L: *LuaState, index: s32) -> boolLuaIsNil :: (L: *LuaState, index: s32) -> boolLuaIsNoneOrNil :: (L: *LuaState, index: s32) -> boolLuaIsBoolean :: (L: *LuaState, index: s32) -> boolLuaIsTable :: (L: *LuaState, index: s32) -> boolLuaIsFunction :: (L: *LuaState, index: s32) -> boolLuaIsUserdata :: (L: *LuaState, index: s32) -> boolLuaIsNative :: (L: *LuaState, index: s32) -> boolLuaIsNumber :: (L: *LuaState, index: s32) -> boolNumbers and strings convertible to numbers.
LuaIsString :: (L: *LuaState, index: s32) -> boolStrings and numbers.
LuaToNumber :: (L: *LuaState, index: s32) -> float64LuaToNumberChecked :: (L: *LuaState, index: s32) -> (float64, bool)LuaToInteger :: (L: *LuaState, index: s32) -> s64The number truncated toward zero; 0 when it is not a number.
LuaToBoolean :: (L: *LuaState, index: s32) -> boolLuaToString :: (L: *LuaState, index: s32) -> (string, bool)String bytes of a string or number value (numbers are converted in place, as lua_tolstring does). The view lasts while the value stays reachable.
LuaObjLen :: (L: *LuaState, index: s32) -> s64Raw length: string bytes, table border, 0 otherwise.
LuaToUserdata :: (L: *LuaState, index: s32) -> *voidLuaUserdataTag :: (L: *LuaState, index: s32) -> s64LuaRawEqual :: (L: *LuaState, a: s32, b: s32) -> boolLuaEqual :: (L: *LuaState, a: s32, b: s32) -> (bool, bool)a == b with __eq; ok=false when a metamethod raised an error.
LuaLessThan :: (L: *LuaState, a: s32, b: s32) -> (bool, bool)LuaPushNil :: (L: *LuaState)LuaPushNumber :: (L: *LuaState, n: float64)LuaPushInteger :: (L: *LuaState, n: s64)LuaPushBoolean :: (L: *LuaState, b: bool)LuaPushString :: (L: *LuaState, text: string)LuaPushLightUserdata :: (L: *LuaState, data: *void)LuaPushNative :: (L: *LuaState, fn: LuaNative, name: string)Push a native function; `name` appears in error messages and tracebacks.
LuaPushNativeClosure :: (L: *LuaState, fn: LuaNative, count: s32, name: string)Push a native closure taking the top `count` values as upvalues, read back with LuaUpvalueIndex(1..count).
LuaNewUserdata :: (L: *LuaState, data: *void, tag: s64)Push a full userdata wrapping host data. `tag` identifies the host type.
LuaNewUserdataFinalized :: (L: *LuaState, data: *void, tag: s64, finalizer: LuaFinalizer)LuaNewUserdata with `finalizer` run when the userdata is collected or the state closes (after any __gc metamethod).
LuaPushThreadValue :: (L: *LuaState) -> boolLuaCreateTable :: (L: *LuaState, narr: s32, nrec: s32)LuaNewTable :: (L: *LuaState)LuaGetTable :: (L: *LuaState, index: s32) -> boolt[k] with metamethods: pops the key, pushes the value.
LuaGetField :: (L: *LuaState, index: s32, name: string) -> boolLuaGetGlobal :: (L: *LuaState, name: string) -> boolLuaSetTable :: (L: *LuaState, index: s32) -> boolt[k] = v with metamethods: pops the value and the key.
LuaSetField :: (L: *LuaState, index: s32, name: string) -> boolt[name] = v: pops the value.
LuaSetGlobal :: (L: *LuaState, name: string) -> boolLuaRawGet :: (L: *LuaState, index: s32)LuaRawGetI :: (L: *LuaState, index: s32, n: s64)LuaRawSet :: (L: *LuaState, index: s32) -> boolRaw t[k] = v: pops value and key; false for a nil/NaN key or non-table.
LuaRawSetI :: (L: *LuaState, index: s32, n: s64)LuaNext :: (L: *LuaState, index: s32) -> s32lua_next: pops a key and pushes the next key and value. Returns 1 for a pair, 0 at the end, -1 for an invalid key (error pending).
LuaGetMetatable :: (L: *LuaState, index: s32) -> boolPush the metatable of a value; false (nothing pushed) when it has none.
LuaSetMetatable :: (L: *LuaState, index: s32) -> boolPop a table (or nil) and make it the value's metatable.
LuaNewMetatable :: (L: *LuaState, name: string) -> boolluaL_newmetatable: registry[name] = new table (pushed); false when it already existed (the existing one is pushed).
LuaGetRegistryMetatable :: (L: *LuaState, name: string)LuaTestUserdata :: (L: *LuaState, index: s32, name: string) -> *voidThe data of a userdata whose metatable is registry[name], else null.
LuaGetFenv :: (L: *LuaState, index: s32)Push the environment table of a function, userdata or thread.
LuaSetFenv :: (L: *LuaState, index: s32) -> boolPop a table and make it the environment of the value at index.
LuaCall :: (L: *LuaState, nargs: s32, want: s32) -> boolCall the function below `nargs` arguments, leaving `want` results (LUA_MULTRET for all). Unprotected: inside a native, return LUA_NATIVE_ERROR when this fails.
LuaPCall :: (L: *LuaState, nargs: s32, want: s32, handler: s32) -> s32Protected call. `handler` is a stack index of an error function (0 for none). On error the message replaces the function and arguments.
LuaLoadBuffer :: (L: *LuaState, text: string, chunkname: string) -> s32Compile a chunk and push it as a function (or push the error message).
LuaLoadString :: (L: *LuaState, text: string) -> s32LuaDoString :: (L: *LuaState, text: string, chunkname: string) -> s32Load and run a chunk; errors carry a traceback. The message stays on the stack when the status is not LUA_OK.
CallWithTraceback :: (L: *LuaState, nargs: s32, want: s32) -> s32LuaPCall with debug.traceback as the error handler.
LuaDoFile :: (L: *LuaState, path: string) -> s32Read and run a file from the host file system (not limited by the file root, which only applies to Lua's dofile/loadfile).
LuaLoadFile :: (L: *LuaState, path: string) -> s32LuaRaiseError :: (L: *LuaState) -> s32Raise the value on top of the stack. Return this from a native.
LuaErrorText :: (L: *LuaState, message: string) -> s32Raise "chunk:line: message" positioned at the Lua caller of the native.
LuaArgError :: (L: *LuaState, n: s32, message: string) -> s32"bad argument #n to 'name' (message)".
LuaTypeError :: (L: *LuaState, n: s32, expected: string) -> s32"bad argument #n to 'name' (expected expected, got actual)".
LuaCheckNumber :: (L: *LuaState, n: s32) -> (float64, bool)Argument checks for natives: ok=false means an error is pending and the native should return LUA_NATIVE_ERROR.
LuaCheckInteger :: (L: *LuaState, n: s32) -> (s64, bool)LuaOptNumber :: (L: *LuaState, n: s32, fallback: float64) -> (float64, bool)LuaOptInteger :: (L: *LuaState, n: s32, fallback: s64) -> (s64, bool)LuaCheckString :: (L: *LuaState, n: s32) -> (string, bool)LuaOptString :: (L: *LuaState, n: s32, fallback: string) -> (string, bool)LuaCheckTable :: (L: *LuaState, n: s32) -> boolLuaCheckAny :: (L: *LuaState, n: s32) -> boolLuaTraceback :: (L: *LuaState, message: string, level: s32)Push "message\nstack traceback:..." for the running thread.
LuaRef :: (L: *LuaState) -> s32Pop the top value into the registry and return a handle for it.
LuaUnref :: (L: *LuaState, ref: s32)LuaPushRef :: (L: *LuaState, ref: s32)LuaRegister :: (L: *LuaState, name: string, fn: LuaNative)globals[name] = native.
LuaSetNative :: (L: *LuaState, index: s32, name: string, fn: LuaNative)t[name] = native for the table at index.
LuaNewThread :: (L: *LuaState)Push a new coroutine running the function on top of the stack (popped).
LuaResume :: (L: *LuaState, index: s32, nargs: s32) -> (s32, s32)Resume the coroutine at `index` with the top `nargs` values; its results (or error message) replace them. Returns LUA_OK when it finished, LUA_YIELD when it yielded, an error status otherwise.
LuaYield :: (L: *LuaState, n: s32) -> s32Yield the top `n` values from a native: `return LuaYield(L, n)`. The resume arguments become the native call's results.