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 :: 0

Value tags. FALSE and TRUE are separate tags so truthiness is one compare.

LUA_TFALSE :: 1
LUA_TTRUE :: 2
LUA_TNUMBER :: 3
LUA_TSTRING :: 4
LUA_TTABLE :: 5
LUA_TFUNCTION :: 6
LUA_TUSERDATA :: 7
LUA_TTHREAD :: 8
LUA_TLIGHTUSERDATA :: 9
LUA_TBOX :: 10
LUA_OK :: 0

Status codes returned by calls and loads.

LUA_YIELD :: 1
LUA_ERRRUN :: 2
LUA_ERRSYNTAX :: 3
LUA_ERRMEM :: 4
LUA_ERRERR :: 5
LUA_ERRFILE :: 6
LUA_NATIVE_ERROR :: -1

A native returns its result count, or one of these.

LUA_NATIVE_YIELD :: -2
LUA_MULTRET :: -1
LuaValue :: struct { n: float64; h: s32; t: u8; }
LuaNative :: #type (L: *LuaState) -> s32

Called 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) -> bool
LuaFinalizer :: #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 :: () -> LuaLimits
LuaState :: 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 :: -1

Type codes returned by LuaType, matching the C API.

LUA_TYPE_NIL :: 0
LUA_TYPE_BOOLEAN :: 1
LUA_TYPE_LIGHTUSERDATA :: 2
LUA_TYPE_NUMBER :: 3
LUA_TYPE_STRING :: 4
LUA_TYPE_TABLE :: 5
LUA_TYPE_FUNCTION :: 6
LUA_TYPE_USERDATA :: 7
LUA_TYPE_THREAD :: 8
LUA_REGISTRYINDEX :: -10000

Pseudo-indices.

LUA_ENVIRONINDEX :: -10001
LUA_GLOBALSINDEX :: -10002
LUA_REFNIL :: -1

LuaRef result for nil.

LUA_NOREF :: -2
LuaUpvalueIndex :: (i: s32) -> s32
LuaOpen :: (L: *LuaState) -> bool

Initialize a state with the standard libraries. Returns false when the record was already open.

TagMethodNames :: () -> [20]string
LuaClose :: (L: *LuaState)

Run pending __gc finalizers and host finalizers, then free everything.

LuaSetLimits :: (L: *LuaState, limits: LuaLimits)
LuaGetLimits :: (L: *LuaState) -> LuaLimits
LuaSetHook :: (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) -> *void
LuaCollectGarbage :: (L: *LuaState)
LuaMemoryUsed :: (L: *LuaState) -> s64

Estimated live heap bytes.

LuaGetTop :: (L: *LuaState) -> s32
LuaSetTop :: (L: *LuaState, index: s32)
LuaPop :: (L: *LuaState, n: s32)
LuaCheckStack :: (L: *LuaState, extra: s32) -> bool
LuaAbsIndex :: (L: *LuaState, index: s32) -> s32

A 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) -> s32
LuaTypeName :: (L: *LuaState, type: s32) -> string
LuaIsNone :: (L: *LuaState, index: s32) -> bool
LuaIsNil :: (L: *LuaState, index: s32) -> bool
LuaIsNoneOrNil :: (L: *LuaState, index: s32) -> bool
LuaIsBoolean :: (L: *LuaState, index: s32) -> bool
LuaIsTable :: (L: *LuaState, index: s32) -> bool
LuaIsFunction :: (L: *LuaState, index: s32) -> bool
LuaIsUserdata :: (L: *LuaState, index: s32) -> bool
LuaIsNative :: (L: *LuaState, index: s32) -> bool
LuaIsNumber :: (L: *LuaState, index: s32) -> bool

Numbers and strings convertible to numbers.

LuaIsString :: (L: *LuaState, index: s32) -> bool

Strings and numbers.

LuaToNumber :: (L: *LuaState, index: s32) -> float64
LuaToNumberChecked :: (L: *LuaState, index: s32) -> (float64, bool)
LuaToInteger :: (L: *LuaState, index: s32) -> s64

The number truncated toward zero; 0 when it is not a number.

LuaToBoolean :: (L: *LuaState, index: s32) -> bool
LuaToString :: (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) -> s64

Raw length: string bytes, table border, 0 otherwise.

LuaToUserdata :: (L: *LuaState, index: s32) -> *void
LuaUserdataTag :: (L: *LuaState, index: s32) -> s64
LuaRawEqual :: (L: *LuaState, a: s32, b: s32) -> bool
LuaEqual :: (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) -> bool
LuaCreateTable :: (L: *LuaState, narr: s32, nrec: s32)
LuaNewTable :: (L: *LuaState)
LuaGetTable :: (L: *LuaState, index: s32) -> bool

t[k] with metamethods: pops the key, pushes the value.

LuaGetField :: (L: *LuaState, index: s32, name: string) -> bool
LuaGetGlobal :: (L: *LuaState, name: string) -> bool
LuaSetTable :: (L: *LuaState, index: s32) -> bool

t[k] = v with metamethods: pops the value and the key.

LuaSetField :: (L: *LuaState, index: s32, name: string) -> bool

t[name] = v: pops the value.

LuaSetGlobal :: (L: *LuaState, name: string) -> bool
LuaRawGet :: (L: *LuaState, index: s32)
LuaRawGetI :: (L: *LuaState, index: s32, n: s64)
LuaRawSet :: (L: *LuaState, index: s32) -> bool

Raw 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) -> s32

lua_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) -> bool

Push the metatable of a value; false (nothing pushed) when it has none.

LuaSetMetatable :: (L: *LuaState, index: s32) -> bool

Pop a table (or nil) and make it the value's metatable.

LuaNewMetatable :: (L: *LuaState, name: string) -> bool

luaL_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) -> *void

The 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) -> bool

Pop a table and make it the environment of the value at index.

LuaCall :: (L: *LuaState, nargs: s32, want: s32) -> bool

Call 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) -> s32

Protected 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) -> s32

Compile a chunk and push it as a function (or push the error message).

LuaLoadString :: (L: *LuaState, text: string) -> s32
LuaDoString :: (L: *LuaState, text: string, chunkname: string) -> s32

Load 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) -> s32

LuaPCall with debug.traceback as the error handler.

LuaDoFile :: (L: *LuaState, path: string) -> s32

Read 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) -> s32
LuaRaiseError :: (L: *LuaState) -> s32

Raise the value on top of the stack. Return this from a native.

LuaErrorText :: (L: *LuaState, message: string) -> s32

Raise "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) -> bool
LuaCheckAny :: (L: *LuaState, n: s32) -> bool
LuaTraceback :: (L: *LuaState, message: string, level: s32)

Push "message\nstack traceback:..." for the running thread.

LuaRef :: (L: *LuaState) -> s32

Pop 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) -> s32

Yield the top `n` values from a native: `return LuaYield(L, n)`. The resume arguments become the native call's results.

Source ↗

Documents