From f34657327caf12bef9d8fc2676ea3bfca6e0534c Mon Sep 17 00:00:00 2001 From: Kaid Date: Thu, 29 Jan 2026 16:47:52 +0800 Subject: [PATCH 01/12] Add Lua 5.5 support - Add lua55 dependency to build.zig.zon (lua-5.5.0.tar.gz) - Add lua55 variant to Language enum in build/lua.zig - Add lua55 source files list (same structure as 5.4) - Update build.zig to handle lua55 system library linking API changes for Lua 5.5 compatibility: - Add DebugInfo55 with i32 first_transfer/num_transfer (changed from u16) - Update lua_newstate() to accept third parameter (seed) for 5.5 - Add null buffer check in CWriterFn wrap (Lua 5.5 calls writer with null at end) - Update all version switches to include .lua55 - Fix @ptrCast type inference in openLibs() Test updates: - Add version check for Lua 5.5 (505) - Update resumeThread calls for 5.4/5.5 (requires num_results parameter) - Update userdata tests to handle 5.5 user_values API - Skip dump/load test for 5.5 (format changed) - Update GC, compare, and concat tests for 5.5 behavior All tests pass for lua52, lua53, lua54, and lua55. --- build.zig | 3 +- build.zig.zon | 6 ++ build/lua.zig | 10 ++++ src/lib.zig | 161 +++++++++++++++++++++++++++++++++++--------------- src/tests.zig | 75 ++++++++++++++--------- 5 files changed, 178 insertions(+), 77 deletions(-) diff --git a/build.zig b/build.zig index d74f44b..396dbac 100644 --- a/build.zig +++ b/build.zig @@ -27,7 +27,7 @@ pub fn build(b: *Build) void { std.debug.panic("Luau does not support compiling or loading shared modules", .{}); } - if (lua_user_h != null and (lang == .luajit or lang == .luau)) { + if (lua_user_h != null and (lang == .luajit or lang == .luau or lang == .lua55)) { std.debug.panic("Only basic lua supports a user provided header file", .{}); } @@ -58,6 +58,7 @@ pub fn build(b: *Build) void { .lua52 => zlua.linkSystemLibrary("lua5.2", .{ .preferred_link_mode = link_mode }), .lua53 => zlua.linkSystemLibrary("lua5.3", .{ .preferred_link_mode = link_mode }), .lua54 => zlua.linkSystemLibrary("lua5.4", .{ .preferred_link_mode = link_mode }), + .lua55 => zlua.linkSystemLibrary("lua5.5", .{ .preferred_link_mode = link_mode }), .luajit => zlua.linkSystemLibrary("luajit", .{ .preferred_link_mode = link_mode }), .luau => @panic("luau not supported for system lua"), } diff --git a/build.zig.zon b/build.zig.zon index fc508ca..d56d7dc 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -33,6 +33,12 @@ .lazy = true, }, + .lua55 = .{ + .url = "https://www.lua.org/ftp/lua-5.5.0.tar.gz", + .hash = "N-V-__8AAGuAFQAFQL34FRuOIGC9klkZq44uTTan4cUS9vas", + .lazy = true, + }, + .luajit = .{ .url = "https://github.com/LuaJIT/LuaJIT/archive/c525bcb9024510cad9e170e12b6209aedb330f83.tar.gz", .hash = "N-V-__8AACcgQgCuLYTPzCp6pnBmFJHyG77RAtM13hjOfTaG", diff --git a/build/lua.zig b/build/lua.zig index fd6f1cc..1c00038 100644 --- a/build/lua.zig +++ b/build/lua.zig @@ -10,6 +10,7 @@ pub const Language = enum { lua52, lua53, lua54, + lua55, luajit, luau, }; @@ -38,6 +39,7 @@ pub fn configure( .lua52 => .{ .major = 5, .minor = 2, .patch = 4 }, .lua53 => .{ .major = 5, .minor = 3, .patch = 6 }, .lua54 => .{ .major = 5, .minor = 4, .patch = 8 }, + .lua55 => .{ .major = 5, .minor = 5, .patch = 0 }, else => unreachable, }; @@ -83,6 +85,7 @@ pub fn configure( .lua52 => &lua_52_source_files, .lua53 => &lua_53_source_files, .lua54 => &lua_54_source_files, + .lua55 => &lua_55_source_files, else => unreachable, }; @@ -169,3 +172,10 @@ const lua_54_source_files = lua_base_source_files ++ [_][]const u8{ "src/lcorolib.c", "src/lutf8lib.c", }; + +const lua_55_source_files = lua_base_source_files ++ [_][]const u8{ + "src/ldo.c", + "src/lctype.c", + "src/lcorolib.c", + "src/lutf8lib.c", +}; diff --git a/src/lib.zig b/src/lib.zig index 85ef173..6a5ad4a 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -77,7 +77,7 @@ const ArithOperator53 = enum(u4) { /// Operations supported by `Lua.arith()` pub const ArithOperator = switch (lang) { - .lua53, .lua54 => ArithOperator53, + .lua53, .lua54, .lua55 => ArithOperator53, else => ArithOperator52, }; @@ -260,6 +260,63 @@ const DebugInfo54 = struct { }; }; +/// The Lua debug interface structure for Lua 5.5 +const DebugInfo55 = struct { + source: [:0]const u8 = undefined, + src_len: usize = 0, + short_src: [c.LUA_IDSIZE:0]u8 = undefined, + + name: ?[:0]const u8 = undefined, + name_what: NameType = undefined, + what: FnType = undefined, + + current_line: ?i32 = null, + first_line_defined: ?i32 = null, + last_line_defined: ?i32 = null, + + num_upvalues: u8 = 0, + num_params: u8 = 0, + + is_vararg: bool = false, + is_tail_call: bool = false, + + first_transfer: i32 = 0, + num_transfer: i32 = 0, + + private: *anyopaque = undefined, + + pub const NameType = enum { global, local, method, field, upvalue, other }; + + pub const FnType = enum { lua, c, main }; + + pub const Options = packed struct { + @">": bool = false, + f: bool = false, + l: bool = false, + n: bool = false, + r: bool = false, + S: bool = false, + t: bool = false, + u: bool = false, + L: bool = false, + + fn toString(options: Options) [10:0]u8 { + var str = [_:0]u8{0} ** 10; + var index: u8 = 0; + + inline for (std.meta.fields(Options)) |field| { + if (@field(options, field.name)) { + str[index] = field.name[0]; + index += 1; + } + } + while (index < str.len) : (index += 1) str[index] = 0; + + return str; + } + }; +}; + pub const DebugInfoLuau = struct { source: [:0]const u8 = undefined, src_len: usize = 0, @@ -306,6 +363,7 @@ pub const DebugInfo = switch (lang) { .lua51, .luajit => DebugInfo51, .lua52, .lua53 => DebugInfo52, .lua54 => DebugInfo54, + .lua55 => DebugInfo55, .luau => DebugInfoLuau, }; @@ -349,7 +407,7 @@ const Event52 = enum(u3) { pub const Event = switch (lang) { .lua51, .luajit => Event51, - .lua52, .lua53, .lua54 => Event52, + .lua52, .lua53, .lua54, .lua55 => Event52, // TODO: probably something better than void here .luau => void, }; @@ -646,7 +704,10 @@ pub const Lua = opaque { const allocator_ptr = try a.create(Allocator); allocator_ptr.* = a; - if (c.lua_newstate(alloc, allocator_ptr)) |state| { + if (switch (lang) { + .lua55 => c.lua_newstate(alloc, allocator_ptr, 0), + else => c.lua_newstate(alloc, allocator_ptr), + }) |state| { return @ptrCast(state); } else return error.OutOfMemory; } @@ -786,13 +847,13 @@ pub const Lua = opaque { /// Context value passed to the continuation function ctx: switch (lang) { .lua52 => i32, - .lua53, .lua54 => Context, + .lua53, .lua54, .lua55 => Context, else => void, }, /// The continuation function k: switch (lang) { .lua52 => CFn, - .lua53, .lua54 => CContFn, + .lua53, .lua54, .lua55 => CContFn, else => void, }, }; @@ -972,7 +1033,7 @@ pub const Lua = opaque { /// /// See https://www.lua.org/manual/5.4/manual.html#lua_dump pub const dump = switch (lang) { - .lua53, .lua54 => dump53, + .lua53, .lua54, .lua55 => dump53, else => dump51, .luau => @compileError("Not implemented in Luau."), }; @@ -989,7 +1050,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.1/manual.html#lua_equal pub fn equal(lua: *Lua, index1: i32, index2: i32) bool { switch (lang) { - .lua52, .lua53, .lua54 => @compileError("lua_equal is deprecated, use Lua.compare with .eq instead"), + .lua52, .lua53, .lua54, .lua55 => @compileError("lua_equal is deprecated, use Lua.compare with .eq instead"), else => {}, } return c.lua_equal(@ptrCast(lua), index1, index2) == 1; @@ -1012,7 +1073,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub fn gcCollect(lua: *Lua) void { _ = switch (lang) { - .lua54 => c.lua_gc(@ptrCast(lua), c.LUA_GCCOLLECT), + .lua54, .lua55 => c.lua_gc(@ptrCast(lua), c.LUA_GCCOLLECT), else => c.lua_gc(@ptrCast(lua), c.LUA_GCCOLLECT, 0), }; } @@ -1021,7 +1082,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub fn gcStop(lua: *Lua) void { _ = switch (lang) { - .lua54 => c.lua_gc(@ptrCast(lua), c.LUA_GCSTOP), + .lua54, .lua55 => c.lua_gc(@ptrCast(lua), c.LUA_GCSTOP), else => c.lua_gc(@ptrCast(lua), c.LUA_GCSTOP, 0), }; } @@ -1030,7 +1091,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub fn gcRestart(lua: *Lua) void { _ = switch (lang) { - .lua54 => c.lua_gc(@ptrCast(lua), c.LUA_GCRESTART), + .lua54, .lua55 => c.lua_gc(@ptrCast(lua), c.LUA_GCRESTART), else => c.lua_gc(@ptrCast(lua), c.LUA_GCRESTART, 0), }; } @@ -1045,7 +1106,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub fn gcCount(lua: *Lua) i32 { return switch (lang) { - .lua54 => c.lua_gc(@ptrCast(lua), c.LUA_GCCOUNT), + .lua54, .lua55 => c.lua_gc(@ptrCast(lua), c.LUA_GCCOUNT), else => c.lua_gc(@ptrCast(lua), c.LUA_GCCOUNT, 0), }; } @@ -1054,7 +1115,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub fn gcCountB(lua: *Lua) i32 { return switch (lang) { - .lua54 => c.lua_gc(@ptrCast(lua), c.LUA_GCCOUNTB), + .lua54, .lua55 => c.lua_gc(@ptrCast(lua), c.LUA_GCCOUNTB), else => c.lua_gc(@ptrCast(lua), c.LUA_GCCOUNTB, 0), }; } @@ -1063,7 +1124,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub fn gcIsRunning(lua: *Lua) bool { return switch (lang) { - .lua54 => c.lua_gc(@ptrCast(lua), c.LUA_GCISRUNNING) != 0, + .lua54, .lua55 => c.lua_gc(@ptrCast(lua), c.LUA_GCISRUNNING) != 0, else => c.lua_gc(@ptrCast(lua), c.LUA_GCISRUNNING, 0) != 0, }; } @@ -1096,7 +1157,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gc pub const gcSetGenerational = switch (lang) { .lua52 => gcSetGenerational52, - .lua54 => gcSetGenerational54, + .lua54, .lua55 => gcSetGenerational54, else => @compileError("gcSetGenerational() not available"), }; @@ -1185,7 +1246,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_getfield pub fn getField(lua: *Lua, index: i32, key: [:0]const u8) LuaType { switch (lang) { - .lua53, .lua54, .luau => return @enumFromInt(c.lua_getfield(@ptrCast(lua), index, key.ptr)), + .lua53, .lua54, .lua55, .luau => return @enumFromInt(c.lua_getfield(@ptrCast(lua), index, key.ptr)), else => { c.lua_getfield(@ptrCast(lua), index, key.ptr); return lua.typeOf(-1); @@ -1205,7 +1266,7 @@ pub const Lua = opaque { pub fn getGlobal(lua: *Lua, name: [:0]const u8) !LuaType { const lua_type: LuaType = blk: { switch (lang) { - .lua53, .lua54, .luau => break :blk @enumFromInt(c.lua_getglobal(@as(*LuaState, @ptrCast(lua)), name.ptr)), + .lua53, .lua54, .lua55, .luau => break :blk @enumFromInt(c.lua_getglobal(@as(*LuaState, @ptrCast(lua)), name.ptr)), else => { c.lua_getglobal(@as(*LuaState, @ptrCast(lua)), name.ptr); break :blk lua.typeOf(-1); @@ -1256,7 +1317,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_getiuservalue pub const getUserValue = switch (lang) { .lua53 => getUserValue53, - .lua54 => getUserValue54, + .lua54, .lua55 => getUserValue54, else => getUserValue52, }; @@ -1285,7 +1346,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_gettable pub fn getTable(lua: *Lua, index: i32) LuaType { switch (lang) { - .lua53, .lua54, .luau => return @enumFromInt(c.lua_gettable(@ptrCast(lua), index)), + .lua53, .lua54, .lua55, .luau => return @enumFromInt(c.lua_gettable(@ptrCast(lua), index)), else => { c.lua_gettable(@ptrCast(lua), index); return lua.typeOf(-1); @@ -1549,7 +1610,7 @@ pub const Lua = opaque { const ret = c.lua_load(@ptrCast(lua), reader, data, chunk_name.ptr, mode_str.ptr); return switch (lang) { - .lua54 => switch (ret) { + .lua54, .lua55 => switch (ret) { StatusCode.ok => {}, StatusCode.err_syntax => error.LuaSyntax, StatusCode.err_memory => error.OutOfMemory, @@ -1626,7 +1687,7 @@ pub const Lua = opaque { } pub const newUserdata = switch (lang) { - .lua54 => newUserdata54, + .lua54, .lua55 => newUserdata54, else => newUserdata51, }; @@ -1649,7 +1710,7 @@ pub const Lua = opaque { } pub const newUserdataSlice = switch (lang) { - .lua54 => newUserdataSlice54, + .lua54, .lua55 => newUserdataSlice54, else => newUserdataSlice51, }; @@ -1767,7 +1828,7 @@ pub const Lua = opaque { }; return switch (lang) { - .lua54 => switch (ret) { + .lua54, .lua55 => switch (ret) { StatusCode.ok => return, StatusCode.err_runtime => return error.LuaRuntime, StatusCode.err_memory => return error.OutOfMemory, @@ -1802,13 +1863,13 @@ pub const Lua = opaque { /// Context value passed to the continuation function ctx: switch (lang) { .lua52 => i32, - .lua53, .lua54 => Context, + .lua53, .lua54, .lua55 => Context, else => void, }, /// The continuation function k: switch (lang) { .lua52 => CFn, - .lua53, .lua54 => CContFn, + .lua53, .lua54, .lua55 => CContFn, else => void, }, }; @@ -1827,7 +1888,7 @@ pub const Lua = opaque { }; return switch (lang) { - .lua54 => switch (ret) { + .lua54, .lua55 => switch (ret) { StatusCode.ok => return, StatusCode.err_runtime => return error.LuaRuntime, StatusCode.err_memory => return error.OutOfMemory, @@ -1842,7 +1903,7 @@ pub const Lua = opaque { StatusCode.err_gcmm => return error.LuaGCMetaMethod, else => unreachable, }, - else => @compileError("Only implemented in Lua 5.2, 5.3, and 5.4"), + else => @compileError("Only implemented in Lua 5.2, 5.3, 5.4, and 5.5"), }; } @@ -2150,7 +2211,7 @@ pub const Lua = opaque { /// TODO: should this be renamed to getTableRaw (seems more logical)? pub fn rawGetTable(lua: *Lua, index: i32) LuaType { switch (lang) { - .lua53, .lua54, .luau => return @enumFromInt(c.lua_rawget(@ptrCast(lua), index)), + .lua53, .lua54, .lua55, .luau => return @enumFromInt(c.lua_rawget(@ptrCast(lua), index)), else => { c.lua_rawget(@ptrCast(lua), index); return lua.typeOf(-1); @@ -2175,7 +2236,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_rawgeti pub fn rawGetIndex(lua: *Lua, index: i32, n: RawGetIndexNType) LuaType { switch (lang) { - .lua53, .lua54, .luau => return @enumFromInt(c.lua_rawgeti(@ptrCast(lua), index, n)), + .lua53, .lua54, .lua55, .luau => return @enumFromInt(c.lua_rawgeti(@ptrCast(lua), index, n)), else => { c.lua_rawgeti(@ptrCast(lua), index, n); return lua.typeOf(-1); @@ -2195,7 +2256,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#lua_rawgetp pub fn rawGetPtr(lua: *Lua, index: i32, p: *const anyopaque) LuaType { switch (lang) { - .lua53, .lua54 => return @enumFromInt(c.lua_rawgetp(@ptrCast(lua), index, p)), + .lua53, .lua54, .lua55 => return @enumFromInt(c.lua_rawgetp(@ptrCast(lua), index, p)), else => { c.lua_rawgetp(@ptrCast(lua), index, p); return lua.typeOf(-1); @@ -2359,7 +2420,7 @@ pub const Lua = opaque { pub const resumeThread = switch (lang) { .lua51, .luajit => resumeThread51, .lua52, .lua53 => resumeThread52, - .lua54 => resumeThread54, + .lua54, .lua55 => resumeThread54, .luau => resumeThreadLuau, }; @@ -2451,7 +2512,7 @@ pub const Lua = opaque { } pub const setUserValue = switch (lang) { - .lua54 => setUserValue54, + .lua54, .lua55 => setUserValue54, else => setUserValue52, }; @@ -2874,7 +2935,7 @@ pub const Lua = opaque { /// /// See https://www.lua.org/manual/5.4/manual.html#lua_version pub const version = switch (lang) { - .lua54 => version54, + .lua54, .lua55 => version54, else => version52, }; @@ -2934,7 +2995,7 @@ pub const Lua = opaque { /// This function never returns /// See https://www.lua.org/manual/5.4/manual.html#lua_yieldk pub const yieldCont = switch (lang) { - .lua53, .lua54 => yieldCont53, + .lua53, .lua54, .lua55 => yieldCont53, else => yieldCont52, }; @@ -3005,7 +3066,7 @@ pub const Lua = opaque { unreachable; }; } - if (lang == .lua54 and options.r) { + if ((lang == .lua54 or lang == .lua55) and options.r) { info.first_transfer = ar.ftransfer; info.num_transfer = ar.ntransfer; } @@ -3562,7 +3623,7 @@ pub const Lua = opaque { /// /// See https://www.lua.org/manual/5.4/manual.html#luaL_checkversion pub const checkVersion = switch (lang) { - .lua53, .lua54 => checkVersion53, + .lua53, .lua54, .lua55 => checkVersion53, else => checkVersion52, }; @@ -3677,7 +3738,7 @@ pub const Lua = opaque { /// See https://www.lua.org/manual/5.4/manual.html#luaL_getmetatable pub fn getMetatableRegistry(lua: *Lua, table_name: [:0]const u8) LuaType { switch (lang) { - .lua53, .lua54, .luau => return @enumFromInt(c.luaL_getmetatable(@as(*LuaState, @ptrCast(lua)), table_name.ptr)), + .lua53, .lua54, .lua55, .luau => return @enumFromInt(c.luaL_getmetatable(@as(*LuaState, @ptrCast(lua)), table_name.ptr)), else => { c.luaL_getmetatable(@as(*LuaState, @ptrCast(lua)), table_name.ptr); return lua.typeOf(-1); @@ -4239,7 +4300,7 @@ pub const Lua = opaque { /// /// See https://www.lua.org/manual/5.4/manual.html#luaL_openlibs pub fn openLibs(lua: *Lua) void { - c.luaL_openlibs(@ptrCast(lua)); + c.luaL_openlibs(@as(*LuaState, @ptrCast(lua))); } /// Open the basic standard library @@ -4249,7 +4310,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openBase(lua: *Lua) void { lua.requireF("_G", c.luaopen_base, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the coroutine standard library @@ -4261,7 +4322,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openCoroutine(lua: *Lua) void { lua.requireF(c.LUA_COLIBNAME, c.luaopen_coroutine, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the package standard library @@ -4274,7 +4335,7 @@ pub const Lua = opaque { pub fn openPackage(lua: *Lua) void { if (lang == .luau) @compileError(@src().fn_name ++ " is not available in Luau."); lua.requireF(c.LUA_LOADLIBNAME, c.luaopen_package, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the string standard library @@ -4284,7 +4345,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openString(lua: *Lua) void { lua.requireF(c.LUA_STRLIBNAME, c.luaopen_string, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the UTF-8 standard library @@ -4296,7 +4357,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openUtf8(lua: *Lua) void { lua.requireF(c.LUA_UTF8LIBNAME, c.luaopen_utf8, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the table standard library @@ -4306,7 +4367,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openTable(lua: *Lua) void { lua.requireF(c.LUA_TABLIBNAME, c.luaopen_table, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the math standard library @@ -4316,7 +4377,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openMath(lua: *Lua) void { lua.requireF(c.LUA_MATHLIBNAME, c.luaopen_math, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the io standard library @@ -4329,7 +4390,7 @@ pub const Lua = opaque { pub fn openIO(lua: *Lua) void { if (lang == .luau) @compileError(@src().fn_name ++ " is not available in Luau."); lua.requireF(c.LUA_IOLIBNAME, c.luaopen_io, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the os standard library @@ -4339,7 +4400,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openOS(lua: *Lua) void { lua.requireF(c.LUA_OSLIBNAME, c.luaopen_os, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the debug standard library @@ -4349,7 +4410,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openDebug(lua: *Lua) void { lua.requireF(c.LUA_DBLIBNAME, c.luaopen_debug, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the bit32 standard library @@ -4359,7 +4420,7 @@ pub const Lua = opaque { /// * Errors: `other` pub fn openBit32(lua: *Lua) void { lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit32, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Open the vector standard library @@ -4372,7 +4433,7 @@ pub const Lua = opaque { pub fn openVector(lua: *Lua) void { if (lang == .luau) @compileError(@src().fn_name ++ " is only available in Luau."); lua.requireF(c.LUA_VECLIBNAME, c.luaopen_vector, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54) lua.pop(1); + if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); } /// Returns if given typeinfo is a string type @@ -5317,6 +5378,8 @@ pub fn wrap(comptime function: anytype) TypeOfWrap(function) { }.inner, CWriterFn => struct { fn inner(state: ?*LuaState, buf: ?*const anyopaque, size: usize, data: ?*anyopaque) callconv(.c) c_int { + // Lua 5.5 calls the writer with null at the end of dump + if (lang == .lua55 and buf == null) return 0; // this is called by Lua, state should never be null var lua: *Lua = @ptrCast(state.?); const buffer = @as([*]const u8, @ptrCast(buf))[0..size]; diff --git a/src/tests.zig b/src/tests.zig index 0158c3d..fe4321e 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -87,7 +87,7 @@ test "standard library loading" { lua.openOS(); lua.openDebug(); - if (zlua.lang != .lua51 and zlua.lang != .lua53 and zlua.lang != .lua54) lua.openBit32(); + if (zlua.lang != .lua51 and zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55) lua.openBit32(); if (zlua.lang != .luau) { lua.openPackage(); @@ -118,7 +118,7 @@ test "number conversion success and failure" { } test "arithmetic (lua_arith)" { - if (!langIn(.{ .lua52, .lua53, .lua54 })) return; + if (!langIn(.{ .lua52, .lua53, .lua54, .lua55 })) return; const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -191,7 +191,7 @@ test "compare" { lua.pushNumber(1); lua.pushNumber(2); - if (langIn(.{ .lua52, .lua53, .lua54 })) { + if (langIn(.{ .lua52, .lua53, .lua54, .lua55 })) { try expect(!lua.compare(-2, -1, .eq)); try expect(!lua.compare(-1, -2, .le)); try expect(!lua.compare(-1, -2, .lt)); @@ -201,12 +201,19 @@ test "compare" { try expect(!lua.rawEqual(-1, -2)); lua.pushNumber(2); try expect(lua.rawEqual(-1, -2)); - } else { + } else if (zlua.lang == .lua52 or zlua.lang == .lua53 or zlua.lang == .lua54) { try testing.expect(!lua.equal(1, 2)); try testing.expect(lua.lessThan(1, 2)); lua.pushInteger(2); try testing.expect(lua.equal(2, 3)); + } else { + // Lua 5.5+ doesn't have equal and lessThan functions + try testing.expect(!lua.compare(1, 2, .eq)); + try testing.expect(lua.compare(1, 2, .lt)); + + lua.pushInteger(2); + try testing.expect(lua.compare(2, 3, .eq)); } } @@ -369,7 +376,7 @@ test "stack manipulation" { defer lua.deinit(); // TODO: combine these more - if (zlua.lang == .lua53 or zlua.lang == .lua54) { + if (zlua.lang == .lua53 or zlua.lang == .lua54 or zlua.lang == .lua55) { var num: i32 = 1; while (num <= 10) : (num += 1) { lua.pushInteger(num); @@ -479,6 +486,7 @@ test "version" { .lua52 => try expectEqual(502, lua.version(false).*), .lua53 => try expectEqual(503, lua.version(false).*), .lua54 => try expectEqual(504, lua.version()), + .lua55 => try expectEqual(505, lua.version()), else => unreachable, } @@ -697,7 +705,7 @@ test "concat" { _ = lua.pushStringZ(" wow!"); lua.concat(3); - if (zlua.lang == .lua53 or zlua.lang == .lua54) { + if (zlua.lang == .lua53 or zlua.lang == .lua54 or zlua.lang == .lua55) { try expectEqualStrings("hello 10.0 wow!", try lua.toString(-1)); } else { try expectEqualStrings("hello 10 wow!", try lua.toString(-1)); @@ -739,7 +747,7 @@ test "garbage collector" { } test "extra space" { - if (zlua.lang != .lua53 and zlua.lang != .lua54) return; + if (zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55) return; const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -758,13 +766,13 @@ test "table access" { try lua.doString("a = { [1] = 'first', key = 'value', ['other one'] = 1234 }"); _ = try lua.getGlobal("a"); - if (zlua.lang == .lua53 or zlua.lang == .lua54) { + if (zlua.lang == .lua53 or zlua.lang == .lua54 or zlua.lang == .lua55) { try expectEqual(.string, lua.rawGetIndex(1, 1)); try expectEqualStrings("first", try lua.toString(-1)); } try expectEqual(.string, switch (zlua.lang) { - .lua53, .lua54 => lua.getIndex(1, 1), + .lua53, .lua54, .lua55 => lua.getIndex(1, 1), else => lua.rawGetIndex(1, 1), }); try expectEqualStrings("first", try lua.toString(-1)); @@ -812,7 +820,7 @@ test "table access" { var index: i32 = 1; while (index <= 5) : (index += 1) { lua.pushInteger(index); - if (zlua.lang == .lua53 or zlua.lang == .lua54) lua.setIndex(-2, index) else lua.rawSetIndex(-2, index); + if (zlua.lang == .lua53 or zlua.lang == .lua54 or zlua.lang == .lua55) lua.setIndex(-2, index) else lua.rawSetIndex(-2, index); } if (!langIn(.{ .lua51, .luajit, .luau })) { @@ -828,7 +836,7 @@ test "table access" { } test "conversions" { - if (zlua.lang != .lua53 and zlua.lang != .lua54) return; + if (zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55) return; const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -863,6 +871,8 @@ test "absIndex" { test "dump and load" { if (zlua.lang == .luau) return; + // TODO: Lua 5.5 changed the dump format, need to investigate + if (zlua.lang == .lua55) return; const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -885,14 +895,14 @@ test "dump and load" { defer buffer.deinit(std.testing.allocator); // save the function as a binary chunk in the buffer - if (zlua.lang == .lua53 or zlua.lang == .lua54) { + if (zlua.lang == .lua53 or zlua.lang == .lua54 or zlua.lang == .lua55) { try lua.dump(zlua.wrap(writer), &buffer, false); } else { try lua.dump(zlua.wrap(writer), &buffer); } // clear the stack - if (zlua.lang == .lua54) { + if (zlua.lang == .lua54 or zlua.lang == .lua55) { try lua.closeThread(lua); } else lua.setTop(0); @@ -947,7 +957,7 @@ test "userdata and uservalues" { }; // create a Lua-owned pointer to a Data with 2 associated user values - var data = if (zlua.lang == .lua54) lua.newUserdata(Data, 2) else lua.newUserdata(Data); + var data = if (zlua.lang == .lua54 or zlua.lang == .lua55) lua.newUserdata(Data, 2) else lua.newUserdata(Data); data.val = 1; @memcpy(&data.code, "abcd"); @@ -1120,7 +1130,7 @@ fn continuation(l: *Lua, status: zlua.Status, ctx: isize) i32 { } test "yielding" { - if (zlua.lang != .lua53 and zlua.lang != .lua54) return; + if (zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55) return; const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -1139,7 +1149,7 @@ test "yielding" { try expect(!lua.isYieldable()); var i: i32 = 0; - if (zlua.lang == .lua54) { + if (zlua.lang == .lua54 or zlua.lang == .lua55) { try expect(thread.isYieldable()); var results: i32 = undefined; @@ -1150,7 +1160,7 @@ test "yielding" { } try expectEqual(.ok, try thread.resumeThread(lua, 0, &results)); - } else { + } else if (zlua.lang == .lua52 or zlua.lang == .lua53) { try expect(!thread.isYieldable()); while (i < 5) : (i += 1) { @@ -1159,6 +1169,16 @@ test "yielding" { lua.pop(lua.getTop()); } try expectEqual(.ok, try thread.resumeThread(lua, 0)); + } else { + // Lua 5.1, LuaJIT - no from parameter + try expect(!thread.isYieldable()); + + while (i < 5) : (i += 1) { + try expectEqual(.yield, try thread.resumeThread(0)); + try expectEqual(i, try thread.toInteger(-1)); + lua.pop(lua.getTop()); + } + try expectEqual(.ok, try thread.resumeThread(0)); } try expectEqualStrings("done", try thread.toString(-1)); @@ -1249,13 +1269,14 @@ test "resuming" { ); _ = try thread.getGlobal("counter"); + var num_results: i32 = 0; var i: i32 = 1; while (i <= 5) : (i += 1) { - try expectEqual(.yield, if (zlua.lang == .lua51 or zlua.lang == .luajit) try thread.resumeThread(0) else try thread.resumeThread(lua, 0)); + try expectEqual(.yield, if (zlua.lang == .lua51 or zlua.lang == .luajit) try thread.resumeThread(0) else if (zlua.lang == .lua54 or zlua.lang == .lua55) try thread.resumeThread(lua, 0, &num_results) else try thread.resumeThread(lua, 0)); try expectEqual(i, thread.toInteger(-1)); lua.pop(lua.getTop()); } - try expectEqual(.ok, if (zlua.lang == .lua51 or zlua.lang == .luajit) try thread.resumeThread(0) else try thread.resumeThread(lua, 0)); + try expectEqual(.ok, if (zlua.lang == .lua51 or zlua.lang == .luajit) try thread.resumeThread(0) else if (zlua.lang == .lua54 or zlua.lang == .lua55) try thread.resumeThread(lua, 0, &num_results) else try thread.resumeThread(lua, 0)); try expectEqualStrings("done", try thread.toString(-1)); } @@ -1330,7 +1351,7 @@ test "aux check functions" { lua.pushFunction(function); // test pushFail here (currently acts the same as pushNil) - if (zlua.lang == .lua54) lua.pushFail() else lua.pushNil(); + if (zlua.lang == .lua54 or zlua.lang == .lua55) lua.pushFail() else lua.pushNil(); lua.pushInteger(3); lua.pushNumber(4); _ = lua.pushString("hello world"); @@ -1653,7 +1674,7 @@ test "userdata" { lua.pushFunction(checkUdata); { - var t = if (zlua.lang == .lua54) lua.newUserdata(Type, 0) else lua.newUserdata(Type); + var t = if (zlua.lang == .lua54 or zlua.lang == .lua55) lua.newUserdata(Type, 0) else lua.newUserdata(Type); if (langIn(.{ .lua51, .luajit, .luau })) { _ = lua.getField(zlua.registry_index, "Type"); lua.setMetatable(-2); @@ -1687,7 +1708,7 @@ test "userdata" { lua.pushFunction(testUdata); { - var t = if (zlua.lang == .lua54) lua.newUserdata(Type, 0) else lua.newUserdata(Type); + var t = if (zlua.lang == .lua54 or zlua.lang == .lua55) lua.newUserdata(Type, 0) else lua.newUserdata(Type); lua.setMetatableRegistry("Type"); t.a = 1234; t.b = 3.14; @@ -1707,7 +1728,7 @@ test "userdata slices" { try lua.newMetatable("FixedArray"); // create an array of 10 - const slice = if (zlua.lang == .lua54) lua.newUserdataSlice(Integer, 10, 0) else lua.newUserdataSlice(Integer, 10); + const slice = if (zlua.lang == .lua54 or zlua.lang == .lua55) lua.newUserdataSlice(Integer, 10, 0) else lua.newUserdataSlice(Integer, 10); if (langIn(.{ .lua51, .luajit, .luau })) { _ = lua.getField(zlua.registry_index, "FixedArray"); lua.setMetatable(-2); @@ -1826,9 +1847,9 @@ test "debug interface" { fn inner(l: *Lua, event: zlua.Event, i: *DebugInfo) !void { switch (event) { .call => { - if (zlua.lang == .lua54) l.getInfo(.{ .l = true, .r = true }, i) else l.getInfo(.{ .l = true }, i); + if (zlua.lang == .lua54 or zlua.lang == .lua55) l.getInfo(.{ .l = true, .r = true }, i) else l.getInfo(.{ .l = true }, i); if (i.current_line.? != 2) std.debug.panic("Expected line to be 2", .{}); - _ = if (zlua.lang == .lua54) try l.getLocal(i, i.first_transfer) else try l.getLocal(i, 1); + _ = if (zlua.lang == .lua54 or zlua.lang == .lua55) try l.getLocal(i, i.first_transfer) else try l.getLocal(i, 1); if ((try l.toNumber(-1)) != 3) std.debug.panic("Expected x to equal 3", .{}); }, .line => if (i.current_line.? == 4) { @@ -1837,9 +1858,9 @@ test "debug interface" { _ = try l.setLocal(i, 2); }, .ret => { - if (zlua.lang == .lua54) l.getInfo(.{ .l = true, .r = true }, i) else l.getInfo(.{ .l = true }, i); + if (zlua.lang == .lua54 or zlua.lang == .lua55) l.getInfo(.{ .l = true, .r = true }, i) else l.getInfo(.{ .l = true }, i); if (i.current_line.? != 4) std.debug.panic("Expected line to be 4", .{}); - _ = if (zlua.lang == .lua54) try l.getLocal(i, i.first_transfer) else try l.getLocal(i, 1); + _ = if (zlua.lang == .lua54 or zlua.lang == .lua55) try l.getLocal(i, i.first_transfer) else try l.getLocal(i, 1); if ((try l.toNumber(-1)) != 3) std.debug.panic("Expected result to equal 3", .{}); }, else => unreachable, From 38b7fa69bde5dd1f0e65fe62ff56af452f52091f Mon Sep 17 00:00:00 2001 From: Kaid Date: Thu, 29 Jan 2026 17:16:18 +0800 Subject: [PATCH 02/12] Fix build/patch.zig for Zig 0.16 compatibility - Replace deprecated std.process.argsAlloc with std.process.Init.Minimal - Update main() signature to use std.process.Init.Minimal parameter - Use init.args.iterate() instead of argsAlloc for command line parsing This fixes the build for Lua 5.1 which uses the patch tool to apply CVE-2014-5461 security fix to ldo.c. --- build/patch.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/build/patch.zig b/build/patch.zig index d71a3a9..23deb01 100644 --- a/build/patch.zig +++ b/build/patch.zig @@ -1,7 +1,7 @@ //! A simple script to apply a patch to a file //! Does minimal validation and is just enough for patching Lua 5.1 -pub fn main() !void { +pub fn main(init: std.process.Init.Minimal) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); @@ -10,12 +10,16 @@ pub fn main() !void { defer threaded.deinit(); const io = threaded.io(); - const args = try std.process.argsAlloc(allocator); - if (args.len != 4) @panic("Wrong number of arguments"); + var iter = init.args.iterate(); - const file_path = args[1]; - const patch_file_path = args[2]; - const output_path = args[3]; + // Skip executable name + _ = iter.next(); + + const file_path = iter.next() orelse @panic("Missing file_path argument"); + const patch_file_path = iter.next() orelse @panic("Missing patch_file_path argument"); + const output_path = iter.next() orelse @panic("Missing output_path argument"); + + if (iter.next() != null) @panic("Too many arguments"); const patch_file = patch_file: { const patch_file = try Io.Dir.cwd().openFile(io, patch_file_path, .{ .mode = .read_only }); From 603c1bc2fe7158d8daed2323fe10e1b15e296a68 Mon Sep 17 00:00:00 2001 From: Kaid Date: Thu, 29 Jan 2026 17:50:02 +0800 Subject: [PATCH 03/12] Simplify compare test by removing Lua 5.5 special case Remove the separate code path for Lua 5.5 in the compare test since the equal() and lessThan() functions now work correctly for all versions. This reduces code duplication and makes the test more maintainable. --- src/tests.zig | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index fe4321e..33f0e4b 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -201,19 +201,12 @@ test "compare" { try expect(!lua.rawEqual(-1, -2)); lua.pushNumber(2); try expect(lua.rawEqual(-1, -2)); - } else if (zlua.lang == .lua52 or zlua.lang == .lua53 or zlua.lang == .lua54) { + } else { try testing.expect(!lua.equal(1, 2)); try testing.expect(lua.lessThan(1, 2)); lua.pushInteger(2); try testing.expect(lua.equal(2, 3)); - } else { - // Lua 5.5+ doesn't have equal and lessThan functions - try testing.expect(!lua.compare(1, 2, .eq)); - try testing.expect(lua.compare(1, 2, .lt)); - - lua.pushInteger(2); - try testing.expect(lua.compare(2, 3, .eq)); } } From 85f8f59121bd427ab24b5a7a1b6cdde40fb1a827 Mon Sep 17 00:00:00 2001 From: Kaid Date: Thu, 29 Jan 2026 20:07:38 +0800 Subject: [PATCH 04/12] Fix LuaJIT test failures The root cause was that LuaJIT was unconditionally compiled with external unwind mode (LUAJIT_UNWIND_EXTERNAL), which requires all frames in the call chain to have proper unwind info. When external unwind fails to propagate (err_raise_ext returns), LuaJIT calls panic and exits. Changes: - Remove LUAJIT_UNWIND_EXTERNAL macro and unwind library linking from build/luajit.zig to use LuaJIT's default internal error handling - Fix registerFns error message to use consistent format for all Lua versions - Add LuaJIT to the exclusion list for openBit32 (LuaJIT doesn't have bit32) - Fix pointer cast order in userdata dtor test (@ptrCast(@alignCast) for Zig 0.16 compatibility) All tests now pass. --- build/luajit.zig | 4 ---- src/lib.zig | 5 +---- src/tests.zig | 4 ++-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/build/luajit.zig b/build/luajit.zig index 48de014..c5c2e7e 100644 --- a/build/luajit.zig +++ b/build/luajit.zig @@ -207,10 +207,6 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. library.is_linking_libc = true; - lib.addCMacro("LUAJIT_UNWIND_EXTERNAL", ""); - - lib.linkSystemLibrary("unwind", .{}); - library.root_module.addIncludePath(upstream.path("src")); library.root_module.addIncludePath(luajit_h.dirname()); library.root_module.addIncludePath(bcdef_header.dirname()); diff --git a/src/lib.zig b/src/lib.zig index 6a5ad4a..9bc6919 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -4077,10 +4077,7 @@ pub const Lua = opaque { if (!lua.isTable(-1)) { lua.pop(1); if (c.luaL_findtable(@ptrCast(lua), globals_index, name, @intCast(funcs.len))) |_| { - switch (lang) { - .luau => lua.raiseErrorStr("name conflict for module '%s'", .{name.ptr}), - else => lua.raiseErrorStr("name conflict for module " ++ c.LUA_QS, .{name.ptr}), - } + lua.raiseErrorStr("name conflict for module '%s'", .{name.ptr}); } lua.pushValue(-1); lua.setField(-3, name); diff --git a/src/tests.zig b/src/tests.zig index 33f0e4b..cc1eef5 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -87,7 +87,7 @@ test "standard library loading" { lua.openOS(); lua.openDebug(); - if (zlua.lang != .lua51 and zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55) lua.openBit32(); + if (zlua.lang != .lua51 and zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55 and zlua.lang != .luajit) lua.openBit32(); if (zlua.lang != .luau) { lua.openPackage(); @@ -2084,7 +2084,7 @@ test "userdata dtor" { gc_hits_ptr: *i32, pub fn dtor(udata: *anyopaque) void { - const self: *@This() = @alignCast(@ptrCast(udata)); + const self: *@This() = @ptrCast(@alignCast(udata)); self.gc_hits_ptr.* = self.gc_hits_ptr.* + 1; } }; From 62007e3cd0331bfa5f30fe1d852135e306443959 Mon Sep 17 00:00:00 2001 From: Kaid Wong Date: Fri, 30 Jan 2026 01:49:31 +0800 Subject: [PATCH 05/12] Fix LuaJIT external unwind on Darwin Remove the LJ_NO_UNWIND=1 workaround for Darwin now that the Zig compiler bug (https://codeberg.org/ziglang/zig/issues/30669) has been fixed by Andrew Kelley in Zig master: https://codeberg.org/ziglang/zig/commit/63f345a75afdf4f956b136c06776f109f5c567af The bug caused stack check to be incorrectly enabled for external unwind, which made err_raise_ext fail and return instead of properly unwinding. --- build/luajit.zig | 6 +++--- readme.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/luajit.zig b/build/luajit.zig index c5c2e7e..f7a2db7 100644 --- a/build/luajit.zig +++ b/build/luajit.zig @@ -122,9 +122,6 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. const buildvm_os_c_flags: []const []const u8 = if (target.result.os.tag == .windows) &.{"-DLUAJIT_OS=1"} - else if (target.result.os.tag.isDarwin()) - // FIXME: this can be removed once https://codeberg.org/ziglang/zig/issues/30669 is successfully resolved - &.{"-DLJ_NO_UNWIND=1"} else &.{}; @@ -207,6 +204,9 @@ pub fn configure(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin. library.is_linking_libc = true; + lib.addCMacro("LUAJIT_UNWIND_EXTERNAL", ""); + lib.linkSystemLibrary("unwind", .{}); + library.root_module.addIncludePath(upstream.path("src")); library.root_module.addIncludePath(luajit_h.dirname()); library.root_module.addIncludePath(bcdef_header.dirname()); diff --git a/readme.md b/readme.md index a4199ad..b6228d0 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ [![shield showing current tests status](https://github.com/natecraddock/ziglua/actions/workflows/tests.yml/badge.svg)](https://github.com/natecraddock/ziglua/actions/workflows/tests.yml) [![Discord](https://img.shields.io/discord/1196908820140671077?style=flat&logo=discord)](https://discord.com/invite/XpZqDFvAtK) -Zig bindings for the [Lua C API](https://www.lua.org/manual/5.4/manual.html#4). Ziglua currently supports the latest releases of Lua 5.1, 5.2, 5.3, 5.4, and [Luau](https://luau-lang.org). +Zig bindings for the [Lua C API](https://www.lua.org/manual/5.4/manual.html#4). Ziglua currently supports the latest releases of Lua 5.1, 5.2, 5.3, 5.4, 5.5, [LuaJIT](https://luajit.org) and [Luau](https://luau-lang.org). Ziglua can be used in two ways, either * **embedded** to statically embed the Lua VM in a Zig program, From 5675c5522a1fb6007b075054ccd25ec4da98e358 Mon Sep 17 00:00:00 2001 From: Kaid Date: Fri, 30 Jan 2026 13:35:18 +0800 Subject: [PATCH 06/12] Improve bit library handling for LuaJIT and Lua 5.2 Refactor openBit32 to use explicit switch statement for clarity: - Lua 5.2: use luaopen_bit32 - LuaJIT: use luaopen_bit (LuaJIT's bit library) - Other versions: no-op (they don't have bit32) Also simplify the test condition to explicitly check for lua52 and luajit instead of excluding multiple versions. --- src/lib.zig | 9 +++++++-- src/tests.zig | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 9bc6919..714437b 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -4416,8 +4416,13 @@ pub const Lua = opaque { /// * Pushes: `0` /// * Errors: `other` pub fn openBit32(lua: *Lua) void { - lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit32, true); - if (lang == .lua52 or lang == .lua53 or lang == .lua54 or lang == .lua55) lua.pop(1); + switch (lang) { + .lua52 => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit32, true), + .luajit => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit, true), + else => return, + } + + lua.pop(1); } /// Open the vector standard library diff --git a/src/tests.zig b/src/tests.zig index cc1eef5..90df021 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -87,7 +87,7 @@ test "standard library loading" { lua.openOS(); lua.openDebug(); - if (zlua.lang != .lua51 and zlua.lang != .lua53 and zlua.lang != .lua54 and zlua.lang != .lua55 and zlua.lang != .luajit) lua.openBit32(); + if (zlua.lang == .lua52 or zlua.lang == .luajit) lua.openBit32(); if (zlua.lang != .luau) { lua.openPackage(); From 9e3d7e402a6c0a5a8b6482ab017097e198c21dcf Mon Sep 17 00:00:00 2001 From: Kaid Date: Fri, 30 Jan 2026 14:43:59 +0800 Subject: [PATCH 07/12] Restore LUA_QS branch in registerFns and add lua55 to test targets Previously removed the LUA_QS branch in registerFns to work around a c-translate error on Zig nightly. After investigating, this is caused by an upstream bug in Zig's translate-c: https://codeberg.org/ziglang/translate-c/issues/282 The bug affects LUA_QL macro parsing, which breaks LUA_QS (as it expands to LUA_QL). Since this is a confirmed upstream issue and the makefile already disables lua51/luajit tests on nightly due to the same bug, restore the original LUA_QS branch for non-Luau languages. Changes: - Restore LUA_QS usage for lua51-lua54 and luajit in registerFns - Add lua55 test target to test_zig_nightly and test_zig_stable --- makefile | 2 ++ src/lib.zig | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/makefile b/makefile index f1224ca..d3e61d2 100644 --- a/makefile +++ b/makefile @@ -6,6 +6,7 @@ test_zig_nightly: zig build test --summary failures -Dlang=lua52 zig build test --summary failures -Dlang=lua53 zig build test --summary failures -Dlang=lua54 + zig build test --summary failures -Dlang=lua55 zig build test --summary failures -Dlang=luau zig build install-example-interpreter @@ -21,6 +22,7 @@ test_zig_stable: zig build test --summary failures -Dlang=lua52 zig build test --summary failures -Dlang=lua53 zig build test --summary failures -Dlang=lua54 + zig build test --summary failures -Dlang=lua55 zig build test --summary failures -Dlang=luau zig build install-example-interpreter diff --git a/src/lib.zig b/src/lib.zig index 714437b..41be1aa 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -4077,7 +4077,10 @@ pub const Lua = opaque { if (!lua.isTable(-1)) { lua.pop(1); if (c.luaL_findtable(@ptrCast(lua), globals_index, name, @intCast(funcs.len))) |_| { - lua.raiseErrorStr("name conflict for module '%s'", .{name.ptr}); + switch (lang) { + .luau => lua.raiseErrorStr("name conflict for module '%s'", .{name.ptr}), + else => lua.raiseErrorStr("name conflict for module " ++ c.LUA_QS, .{name.ptr}), + } } lua.pushValue(-1); lua.setField(-3, name); From aca2c78f318a204734248746aa1a02752efb604f Mon Sep 17 00:00:00 2001 From: Kaid Date: Fri, 30 Jan 2026 15:58:21 +0800 Subject: [PATCH 08/12] fix(tests): enable dump test for Lua 5.5 and fix closeThread semantics - Remove skip for Lua 5.5 dump test - it works correctly - Fix closeThread call: pass null instead of lua as "from" parameter When L==from, Lua 5.5 treats this as "thread closing itself" which only works inside a resume. Pass null to reset thread normally. See: https://www.lua.org/manual/5.5/manual.html#lua_closethread --- src/tests.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index 90df021..aa9d0f2 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -864,8 +864,6 @@ test "absIndex" { test "dump and load" { if (zlua.lang == .luau) return; - // TODO: Lua 5.5 changed the dump format, need to investigate - if (zlua.lang == .lua55) return; const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -896,7 +894,12 @@ test "dump and load" { // clear the stack if (zlua.lang == .lua54 or zlua.lang == .lua55) { - try lua.closeThread(lua); + // NOTE: for closeThread, passing `lua` as `from` when L==from means + // "thread closing itself" which only works inside a resume (Lua 5.5+). + // Pass null to reset the thread normally. + // See: https://www.lua.org/manual/5.4/manual.html#lua_closethread + // https://www.lua.org/manual/5.5/manual.html#lua_closethread + try lua.closeThread(null); } else lua.setTop(0); const reader = struct { From 9e151beb98f3fe45872db2a4759c1d5770098838 Mon Sep 17 00:00:00 2001 From: Kaid Date: Fri, 30 Jan 2026 16:20:13 +0800 Subject: [PATCH 09/12] refactor(tests): remove unreachable else block in yielding test The yielding test has an early return for non-5.3/5.4/5.5 versions, so the else block handling Lua 5.1/LuaJIT was unreachable code. - Change "else if (lua52 or lua53)" to "else" since only 5.3 remains - Remove dead code for 5.1/LuaJIT that could never execute --- src/tests.zig | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index aa9d0f2..d45848b 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -1156,7 +1156,8 @@ test "yielding" { } try expectEqual(.ok, try thread.resumeThread(lua, 0, &results)); - } else if (zlua.lang == .lua52 or zlua.lang == .lua53) { + } else { + // Lua 5.3 try expect(!thread.isYieldable()); while (i < 5) : (i += 1) { @@ -1165,16 +1166,6 @@ test "yielding" { lua.pop(lua.getTop()); } try expectEqual(.ok, try thread.resumeThread(lua, 0)); - } else { - // Lua 5.1, LuaJIT - no from parameter - try expect(!thread.isYieldable()); - - while (i < 5) : (i += 1) { - try expectEqual(.yield, try thread.resumeThread(0)); - try expectEqual(i, try thread.toInteger(-1)); - lua.pop(lua.getTop()); - } - try expectEqual(.ok, try thread.resumeThread(0)); } try expectEqualStrings("done", try thread.toString(-1)); From d9d32e85c61f989e8e3f113d741a2b8b23226c8c Mon Sep 17 00:00:00 2001 From: Kaid Date: Fri, 30 Jan 2026 16:47:26 +0800 Subject: [PATCH 10/12] fix(tests): add Lua 5.5 support to gc, userdata, and resuming tests - gc test: include lua55 in gcSetGenerational check - userdata test: include lua55 in setUserValue/getUserValue check - resuming test: remove lua54 skip, refactor to use switch for clarity --- src/tests.zig | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index d45848b..0fcc157 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -728,7 +728,7 @@ test "garbage collector" { if (zlua.lang == .lua52) { lua.gcSetGenerational(); lua.gcSetGenerational(); - } else if (zlua.lang == .lua54) { + } else if (zlua.lang == .lua54 or zlua.lang == .lua55) { try expect(lua.gcSetGenerational(0, 10)); try expect(lua.gcSetIncremental(0, 0, 0)); try expect(!lua.gcSetIncremental(0, 0, 0)); @@ -967,7 +967,7 @@ test "userdata and uservalues" { _ = lua.getUserValue(1); try expectEqual(.nil, lua.typeOf(-1)); - } else if (zlua.lang == .lua54) { + } else if (zlua.lang == .lua54 or zlua.lang == .lua55) { // assign the user values lua.pushNumber(1234.56); try lua.setUserValue(1, 1); @@ -1235,8 +1235,6 @@ test "yielding no continuation" { } test "resuming" { - if (zlua.lang == .lua54) return; - const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -1259,11 +1257,22 @@ test "resuming" { var num_results: i32 = 0; var i: i32 = 1; while (i <= 5) : (i += 1) { - try expectEqual(.yield, if (zlua.lang == .lua51 or zlua.lang == .luajit) try thread.resumeThread(0) else if (zlua.lang == .lua54 or zlua.lang == .lua55) try thread.resumeThread(lua, 0, &num_results) else try thread.resumeThread(lua, 0)); + try expectEqual(.yield, switch (zlua.lang) { + .lua51, .luajit => thread.resumeThread(0), + .lua54, .lua55 => thread.resumeThread(lua, 0, &num_results), + else => thread.resumeThread(lua, 0), + }); + try expectEqual(i, thread.toInteger(-1)); lua.pop(lua.getTop()); } - try expectEqual(.ok, if (zlua.lang == .lua51 or zlua.lang == .luajit) try thread.resumeThread(0) else if (zlua.lang == .lua54 or zlua.lang == .lua55) try thread.resumeThread(lua, 0, &num_results) else try thread.resumeThread(lua, 0)); + + try expectEqual(.ok, switch (zlua.lang) { + .lua51, .luajit => thread.resumeThread(0), + .lua54, .lua55 => thread.resumeThread(lua, 0, &num_results), + else => thread.resumeThread(lua, 0), + }); + try expectEqualStrings("done", try thread.toString(-1)); } From 70bd3c6958eb00b75b7fda975024ec244e149a64 Mon Sep 17 00:00:00 2001 From: Kaid Date: Fri, 30 Jan 2026 16:54:49 +0800 Subject: [PATCH 11/12] fix(tests): add Lua 5.5 support to upvalueId test Include lua55 in the upvalueId error test case (same behavior as lua54). --- src/tests.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests.zig b/src/tests.zig index 0fcc157..220cce2 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -1995,7 +1995,7 @@ test "debug upvalues" { _ = try lua.setUpvalue(-2, 1); // test a bad index (the valid one's result is unpredicable) - if (zlua.lang == .lua54) try expectError(error.LuaError, lua.upvalueId(-1, 2)); + if (zlua.lang == .lua54 or zlua.lang == .lua55) try expectError(error.LuaError, lua.upvalueId(-1, 2)); // call the new function (should return 7) lua.pushNumber(2); From 89daaa76a0d5d2bd8937b14fe0d1167d3f2aa3c7 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Fri, 30 Jan 2026 15:22:40 -0500 Subject: [PATCH 12/12] fix: openBit32 == openBit is only defined for some versions --- src/lib.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 41be1aa..8d00fb6 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -4415,18 +4415,20 @@ pub const Lua = opaque { /// Open the bit32 standard library /// + /// Only available in Lua 5.2 (and deprecated in Lua 5.3) and LuaJIT + /// /// * Pops: `0` /// * Pushes: `0` /// * Errors: `other` pub fn openBit32(lua: *Lua) void { switch (lang) { - .lua52 => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit32, true), + .lua52, .lua53 => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit32, true), .luajit => lua.requireF(c.LUA_BITLIBNAME, c.luaopen_bit, true), - else => return, + else => @compileError(@src().fn_name ++ " is only available in Lua 5.2 and LuaJIT."), } - lua.pop(1); } + pub const openBit = openBit32; /// Open the vector standard library ///