const std = @import("std"); const builtin = @import("builtin"); const testing = std.testing; const luau = @import("luau"); const AllocFn = luau.AllocFn; const StringBuffer = luau.StringBuffer; const DebugInfo = luau.DebugInfo; const State = luau.VM.lua.State; const expect = testing.expect; const expectEqual = testing.expectEqual; const expectEqualStrings = testing.expectEqualStrings; const expectError = testing.expectError; const EXCEPTIONS_ENABLED = !builtin.cpu.arch.isWasm(); fn expectStringContains(actual: []const u8, expected_contains: []const u8) !void { if (std.mem.indexOf(u8, actual, expected_contains) == null) return; return error.TestExpectedStringContains; } fn alloc(data: ?*anyopaque, ptr: ?*anyopaque, osize: usize, nsize: usize) callconv(.c) ?*anyopaque { _ = data; const alignment = @alignOf(std.c.max_align_t); if (@as(?[*]align(alignment) u8, @ptrCast(@alignCast(ptr)))) |prev_ptr| { const prev_slice = prev_ptr[0..osize]; if (nsize == 0) { testing.allocator.free(prev_slice); return null; } const new_ptr = testing.allocator.realloc(prev_slice, nsize) catch return null; return new_ptr.ptr; } else if (nsize == 0) { return null; } else { const new_ptr = testing.allocator.alignedAlloc(u8, .fromByteUnits(alignment), nsize) catch return null; return new_ptr.ptr; } } fn failing_alloc(data: ?*anyopaque, ptr: ?*anyopaque, osize: usize, nsize: usize) callconv(.c) ?*anyopaque { _ = data; _ = ptr; _ = osize; _ = nsize; return null; } test { std.testing.refAllDecls(@This()); } test "initialization" { // initialize the Zig wrapper var lua = try luau.init(&testing.allocator); try expectEqual(luau.VM.lua.Status.Ok, lua.status()); lua.deinit(); // attempt to initialize the Zig wrapper with no memory try expectError(error.OutOfMemory, luau.init(&testing.failing_allocator)); // use the library directly lua = try luau.VM.lstate.newstate(alloc, null); lua.close(); // use the library with a bad AllocFn try expectError(error.OutOfMemory, luau.VM.lstate.newstate(failing_alloc, null)); // use the auxiliary library (uses libc realloc and cannot be checked for leaks!) lua = try luau.VM.lstate.Lnewstate(); lua.close(); } test "alloc functions" { var lua = try luau.VM.lstate.newstate(alloc, null); defer lua.deinit(); // get default allocator var data: ?*anyopaque = undefined; try expectEqual(alloc, lua.getallocf(&data)); } test "Zig allocator access" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const inner = struct { fn inner(l: *State) i32 { const allocator = luau.getallocator(l); const num = l.tointeger(1) orelse @panic("expected integer"); // Use the allocator const nums = allocator.alloc(i32, @intCast(num)) catch unreachable; defer allocator.free(nums); // Do something pointless to use the slice var sum: i32 = 0; for (nums, 0..) |*n, i| n.* = @intCast(i); for (nums) |n| sum += n; l.pushinteger(sum); return 1; } }.inner; try lua.Zpushfunction(inner, "test"); lua.pushinteger(10); _ = lua.pcall(1, 1, 0); try expectEqual(45, lua.tointeger(-1).?); } test "standard library loading" { // open all standard libraries { var lua = try luau.init(&testing.allocator); defer lua.deinit(); try lua.Lopenlibs(); } // open all standard libraries with individual functions // these functions are only useful if you want to load the standard // packages into a non-standard table { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); lua.openstring(); lua.opentable(); lua.openmath(); lua.openos(); lua.opendebug(); lua.opencoroutine(); lua.openutf8(); lua.openbit32(); lua.openbuffer(); lua.openvector(); } } test "number conversion success and failure" { const lua = try luau.init(&testing.allocator); defer lua.deinit(); _ = try lua.pushstring("1234.5678"); try expectEqual(1234.5678, lua.tonumber(-1) orelse return error.InvalidType); _ = try lua.pushstring("1234"); try expectEqual(1234, lua.tointeger(-1) orelse return error.InvalidType); lua.pushnil(); try expectError(error.Fail, lua.tonumber(-1) orelse error.Fail); try expectError(error.Fail, lua.tointeger(-1) orelse error.Fail); _ = try lua.pushstring("fail"); try expectError(error.Fail, lua.tonumber(-1) orelse error.Fail); try expectError(error.Fail, lua.tointeger(-1) orelse error.Fail); } test "compare" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.pushnumber(1); lua.pushnumber(2); try testing.expect(!try lua.equal(1, 2)); try testing.expect(try lua.lessthan(1, 2)); lua.pushinteger(2); try testing.expect(try lua.equal(2, 3)); } const add = struct { fn addInner(l: *State) i32 { const a = l.tointeger(1) orelse 0; const b = l.tointeger(2) orelse 0; l.pushinteger(a + b); return 1; } }.addInner; test "type of and getting values" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.pushnil(); try expect(lua.isnil(1)); try expect(lua.isnoneornil(1)); try expect(lua.isnoneornil(2)); try expect(lua.isnone(2)); try expectEqual(.Nil, lua.typeOf(1)); lua.pushboolean(true); try expectEqual(.Boolean, lua.typeOf(-1)); try expect(lua.isboolean(-1)); try lua.newtable(); try expectEqual(.Table, lua.typeOf(-1)); try expect(lua.istable(-1)); lua.pushinteger(1); try expectEqual(.Number, lua.typeOf(-1)); try expect(lua.isnumber(-1)); try expectEqual(1, lua.tointeger(-1) orelse @panic("bad")); try expectEqualStrings("number", lua.Ltypename(-1)); lua.pushunsigned(4); try expectEqual(.Number, lua.typeOf(-1)); try expect(lua.isnumber(-1)); try expectEqual(4, lua.tounsigned(-1) orelse @panic("bad")); try expectEqualStrings("number", lua.Ltypename(-1)); var value: i32 = 0; lua.pushlightuserdata(&value); try expectEqual(.LightUserdata, lua.typeOf(-1)); try expect(lua.islightuserdata(-1)); try expect(lua.isuserdata(-1)); lua.pushnumber(0.1); try expectEqual(.Number, lua.typeOf(-1)); try expect(lua.isnumber(-1)); try expectEqual(0.1, lua.tonumber(-1) orelse @panic("bad")); _ = lua.pushthread(); try expectEqual(.Thread, lua.typeOf(-1)); try expect(lua.isthread(-1)); try expectEqual(lua, lua.tothread(-1) orelse @panic("bad")); try lua.pushstring("all your codebase are belong to us"); try expectEqualStrings("all your codebase are belong to us", lua.tolstring(-1) orelse @panic("bad")); try expectEqual(.String, lua.typeOf(-1)); try expect(lua.isstring(-1)); try lua.Zpushfunction(add, "func"); try expectEqual(.Function, lua.typeOf(-1)); try expect(lua.iscfunction(-1)); try expect(lua.isfunction(-1)); try expectEqual(luau.VM.zapi.toCFn(add), lua.tocfunction(-1).?); try lua.pushstring("hello world"); try expectEqualStrings("hello world", lua.tostring(-1) orelse @panic("bad")); try expectEqual(.String, lua.typeOf(-1)); try expect(lua.isstring(-1)); try lua.pushfstring("{s} {s} {d}", .{ "hello", "world", @as(i32, 10) }); try expectEqual(.String, lua.typeOf(-1)); try expect(lua.isstring(-1)); try expectEqualStrings("hello world 10", lua.tostring(-1) orelse @panic("bad")); // Comptime known try lua.pushfstring("{s} {s} {d}", .{ "hello", "world", @as(i32, 10) }); try expectEqual(.String, lua.typeOf(-1)); try expect(lua.isstring(-1)); try expectEqualStrings("hello world 10", lua.tostring(-1) orelse @panic("bad")); // Runtime known const arg1 = try std.testing.allocator.dupe(u8, "Hello"); defer std.testing.allocator.free(arg1); const arg2 = try std.testing.allocator.dupe(u8, "World"); defer std.testing.allocator.free(arg2); try lua.pushfstring("{s} {s} {d}", .{ arg1, arg2, @as(i32, 10) }); try expectEqual(.String, lua.typeOf(-1)); try expect(lua.isstring(-1)); try expectEqualStrings("Hello World 10", lua.tostring(-1) orelse @panic("bad")); lua.pushvalue(2); try expectEqual(.Boolean, lua.typeOf(-1)); try expect(lua.isboolean(-1)); } test "typenames" { try expectEqualStrings("no value", luau.VM.lapi.typename(.None)); try expectEqualStrings("nil", luau.VM.lapi.typename(.Nil)); try expectEqualStrings("boolean", luau.VM.lapi.typename(.Boolean)); try expectEqualStrings("userdata", luau.VM.lapi.typename(.LightUserdata)); try expectEqualStrings("number", luau.VM.lapi.typename(.Number)); try expectEqualStrings("string", luau.VM.lapi.typename(.String)); try expectEqualStrings("table", luau.VM.lapi.typename(.Table)); try expectEqualStrings("function", luau.VM.lapi.typename(.Function)); try expectEqualStrings("userdata", luau.VM.lapi.typename(.Userdata)); try expectEqualStrings("thread", luau.VM.lapi.typename(.Thread)); try expectEqualStrings("vector", luau.VM.lapi.typename(.Vector)); try expectEqualStrings("buffer", luau.VM.lapi.typename(.Buffer)); } // test "executing string contents" { // var lua = try luau.init(&testing.allocator); // defer lua.deinit(); // lua.Lopenlibs(); // try lua.loadString("f = function(x) return x + 10 end"); // _ = lua.pcall(0, 0, 0); // try lua.loadString("a = f(2)"); // _ = lua.pcall(0, 0, 0); // try expectEqual(.number, try lua.getglobal("a")); // try expectEqual(12, try lua.toInteger(1)); // try expectError(error.Fail, lua.loadString("bad syntax")); // try lua.loadString("a = g()"); // try expectError(error.Runtime, lua.pcall(0, 0, 0)); // } test "filling and checking the stack" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); try expectEqual(0, lua.gettop()); // We want to push 30 values onto the stack // this should work without fail try expectEqual(true, lua.checkstack(30)); var count: i32 = 0; while (count < 30) : (count += 1) { lua.pushnil(); } try expectEqual(30, lua.gettop()); // this should fail (beyond max stack size) try expectEqual(false, lua.checkstack(1_000_000)); // this is small enough it won't fail (would raise an error if it did) try lua.Lcheckstack(40, null); while (count < 40) : (count += 1) { lua.pushnil(); } try expectEqual(40, lua.gettop()); } test "stack manipulation" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); var num: i32 = 1; while (num <= 10) : (num += 1) { lua.pushinteger(num); } try expectEqual(10, lua.gettop()); lua.settop(12); try expectEqual(12, lua.gettop()); try expect(lua.isnil(-1)); lua.remove(1); try expect(lua.isnil(-1)); lua.insert(1); try expect(lua.isnil(1)); lua.settop(0); try expectEqual(0, lua.gettop()); } test "calling a function" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); try lua.Zsetglobalfn("zigadd", add); _ = try lua.getglobal("zigadd"); lua.pushinteger(10); lua.pushinteger(32); // pcall is preferred, but we might as well test call when we know it is safe lua.call(2, 1); try expectEqual(42, lua.tointeger(1).?); } // test "string buffers" { // var lua = try luau.init(&testing.allocator); // defer lua.deinit(); // var buffer: StringBuffer = undefined; // buffer.init(lua); // buffer.addChar('z'); // buffer.addString("igl"); // var str = buffer.prep(); // str[0] = 'u'; // str[1] = 'a'; // str[2] = 'u'; // buffer.addSize(3); // buffer.addString(" api "); // lua.pushnumber(5.1); // buffer.addValue(); // buffer.pushResult(); // try expectEqualStrings("zigluau api 5.1", try lua.toString(-1)); // // now test a small buffer // buffer.init(lua); // var b = buffer.prep(); // b[0] = 'a'; // b[1] = 'b'; // b[2] = 'c'; // buffer.addSize(3); // b = buffer.prep(); // @memcpy(b[0..23], "defghijklmnopqrstuvwxyz"); // buffer.addSize(23); // buffer.pushResult(); // try expectEqualStrings("abcdefghijklmnopqrstuvwxyz", try lua.toString(-1)); // lua.pop(1); // buffer.init(lua); // b = buffer.prep(); // @memcpy(b[0..3], "abc"); // buffer.pushResultSize(3); // try expectEqualStrings("abc", try lua.toString(-1)); // lua.pop(1); // } const sub = struct { fn subInner(l: *State) i32 { const a = l.toInteger(1) catch 0; const b = l.toInteger(2) catch 0; l.pushinteger(a - b); return 1; } }.subInner; test "function registration" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const funcs = [_]luau.VM.laux.Reg{ .{ .name = "add", .func = luau.VM.zapi.toCFn(add) }, }; try lua.newtable(); try lua.Lregister(null, &funcs); _ = try lua.getfield(-1, "add"); lua.pushinteger(1); lua.pushinteger(2); _ = lua.pcall(2, 1, 0); try expectEqual(3, lua.tointeger(-1).?); lua.settop(0); // register functions as globals in a library table try lua.Lregister("testlib", &funcs); // testlib.add(1, 2) _ = try lua.getglobal("testlib"); _ = try lua.getfield(-1, "add"); lua.pushinteger(1); lua.pushinteger(2); _ = lua.pcall(2, 1, 0); try expectEqual(3, lua.tointeger(-1).?); } test "warn fn" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const warnFn = struct { fn inner(L: *State) void { const msg = L.tostring(1) orelse @panic("bad"); if (!std.mem.eql(u8, msg, "this will be caught by the warnFn")) std.debug.panic("test failed", .{}); } }.inner; try lua.Zpushfunction(warnFn, "newWarn"); lua.pushvalue(-1); try lua.setfield(luau.VM.lua.GLOBALSINDEX, "warn"); try lua.pushstring("this will be caught by the warnFn"); lua.call(1, 0); } test "string literal" { const allocator = testing.allocator; var lua = try luau.init(&allocator); defer lua.deinit(); const zbytes = [_:0]u8{ 'H', 'e', 'l', 'l', 'o', ' ', 0, 'W', 'o', 'r', 'l', 'd' }; try testing.expectEqual(zbytes.len, 12); try lua.pushstring(&zbytes); const str1 = lua.tostring(-1) orelse @panic("bad"); try testing.expectEqual(6, str1.len); try testing.expectEqualStrings("Hello ", str1); try lua.pushlstring(&zbytes); const str2 = lua.tolstring(-1) orelse @panic("bad"); try testing.expectEqual(12, str2.len); try testing.expectEqualStrings(&zbytes, str2); } test "concat" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); _ = try lua.pushstring("hello "); lua.pushnumber(10); _ = try lua.pushstring(" wow!"); try lua.concat(3); try expectEqualStrings("hello 10 wow!", lua.tostring(-1) orelse @panic("bad")); } test "garbage collector" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); // because the garbage collector is an opaque, unmanaged // thing, it is hard to test, so just run each function _ = lua.gc(.Stop, 0); _ = lua.gc(.Collect, 0); _ = lua.gc(.Restart, 0); _ = lua.gc(.Count, 0); _ = lua.gc(.CountB, 0); _ = lua.gc(.IsRunning, 0); _ = lua.gc(.Step, 0); _ = lua.gc(.SetGoal, 10); _ = lua.gc(.SetStepMul, 2); _ = lua.gc(.SetStepSize, 1); } test "threads" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); var new_thread = try lua.newthread(); try expectEqual(1, lua.gettop()); try expectEqual(0, new_thread.gettop()); lua.pushinteger(10); lua.pushnil(); try expectEqual(3, lua.gettop()); lua.xmove(new_thread, 2); try expectEqual(2, new_thread.gettop()); try expectEqual(1, lua.gettop()); var new_thread2 = try lua.newthread(); try expectEqual(2, lua.gettop()); try expectEqual(0, new_thread2.gettop()); lua.pushnil(); lua.xpush(new_thread2, -1); try expectEqual(3, lua.gettop()); try expectEqual(1, new_thread2.gettop()); try expectEqual(.Nil, new_thread2.typeOf(1)); } test "userdata and uservalues" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const Data = struct { val: i32, code: [4]u8, }; // create a Luau-owned pointer to a Data with 2 associated user values var data = try lua.newuserdata(Data); data.val = 1; @memcpy(&data.code, "abcd"); try expectEqual(data, lua.touserdata(Data, 1) orelse @panic("bad")); try expectEqual(@as(*const anyopaque, @ptrCast(data)), lua.topointer(1) orelse @panic("bad")); } test "upvalues" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); // counter from PIL const counter = struct { fn inner(l: *State) i32 { var counter = l.tointeger(luau.VM.lua.upvalueindex(1)) orelse 0; counter += 1; l.pushinteger(counter); l.pushinteger(counter); l.replace(luau.VM.lua.upvalueindex(1)); return 1; } }.inner; // Initialize the counter at 0 lua.pushinteger(0); try lua.pushcclosure(luau.VM.zapi.toCFn(counter), "counter", 1); try lua.setglobal("counter"); // call the function repeatedly, each time ensuring the result increases by one var expected: i32 = 1; while (expected <= 10) : (expected += 1) { _ = try lua.getglobal("counter"); lua.call(0, 1); try expectEqual(expected, lua.tointeger(-1).?); lua.pop(1); } } test "raise error" { if (!EXCEPTIONS_ENABLED) return error.SkipZigTest; var lua = try luau.init(&testing.allocator); defer lua.deinit(); const makeError = struct { fn inner(l: *State) !i32 { _ = try l.pushstring("makeError made an error"); l.raiseerror(); return 0; } }.inner; try lua.Zpushfunction(makeError, "func"); lua.pushinteger(1256); try expectError(error.Runtime, lua.pcall(1, 0, 0).check()); try expectEqualStrings("makeError made an error", lua.tostring(-1).?); } fn continuation(l: *State, status: luau.Status, ctx: isize) i32 { _ = status; if (ctx == 5) { _ = l.pushstring("done"); return 1; } else { // yield the current context value l.pushinteger(ctx); return l.yieldCont(1, ctx + 1, luau.wrap(continuation)); } } fn continuation52(l: *State) i32 { const ctxOrNull = l.getContext() catch unreachable; const ctx = ctxOrNull orelse 0; if (ctx == 5) { _ = l.pushstring("done"); return 1; } else { // yield the current context value l.pushinteger(ctx); return l.yieldCont(1, ctx + 1, luau.wrap(continuation52)); } } test "yielding no continuation" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const thread = try lua.newthread(); const func = struct { fn inner(l: *State) !i32 { l.pushinteger(1); return try l.yield(1); } }.inner; try thread.Zpushfunction(func, "func"); try expectEqual(.Suspended, lua.costatus(thread)); _ = try thread.resumethread(null, 0).check(); try expectEqual(.Suspended, lua.costatus(thread)); try expectEqual(1, thread.tointeger(-1).?); try thread.resetthread(); try expect(thread.isthreadreset()); try expectEqual(.Finished, lua.costatus(thread)); } test "aux check functions" { if (!EXCEPTIONS_ENABLED) return error.SkipZigTest; var lua = try luau.init(&testing.allocator); defer lua.deinit(); const function = struct { fn inner(l: *State) !i32 { try l.Lcheckany(1); _ = l.Lcheckinteger(2); _ = l.Lchecknumber(3); _ = l.Lcheckstring(4); l.Lchecktype(5, .Boolean); return 0; } }.inner; try lua.Zpushfunction(function, "func"); _ = lua.pcall(0, 0, 0).check() catch { try expectStringContains("argument #1", lua.tostring(-1) orelse @panic("bad")); lua.pop(-1); }; try lua.Zpushfunction(function, "func"); lua.pushnil(); _ = lua.pcall(1, 0, 0).check() catch { try expectStringContains("number expected", lua.tostring(-1) orelse @panic("bad")); lua.pop(-1); }; try lua.Zpushfunction(function, "func"); lua.pushnil(); lua.pushinteger(3); _ = lua.pcall(2, 0, 0).check() catch { try expectStringContains("string expected", lua.tostring(-1) orelse @panic("bad")); lua.pop(-1); }; try lua.Zpushfunction(function, "func"); lua.pushnil(); lua.pushinteger(3); lua.pushnumber(4); _ = lua.pcall(3, 0, 0).check() catch { try expectStringContains("string expected", lua.tostring(-1) orelse @panic("bad")); lua.pop(-1); }; try lua.Zpushfunction(function, "func"); lua.pushnil(); lua.pushinteger(3); lua.pushnumber(4); _ = try lua.pushstring("hello world"); _ = lua.pcall(4, 0, 0).check() catch { try expectStringContains("boolean expected", lua.tostring(-1) orelse @panic("bad")); lua.pop(-1); }; try lua.Zpushfunction(function, "func"); // test pushFail here (currently acts the same as pushnil) lua.pushnil(); lua.pushinteger(3); lua.pushnumber(4); _ = try lua.pushstring("hello world"); lua.pushboolean(true); _ = lua.pcall(5, 0, 0); } test "aux opt functions" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const function = struct { fn inner(l: *State) i32 { expectEqual(10, l.Loptinteger(1, 10)) catch unreachable; expectEqualStrings("zig", l.Loptstring(2, "zig")) catch unreachable; expectEqual(1.23, l.Loptnumber(3, 1.23)) catch unreachable; expectEqualStrings("lang", l.Loptstring(4, "lang")) catch unreachable; return 0; } }.inner; try lua.Zpushfunction(function, "func"); _ = lua.pcall(0, 0, 0); try lua.Zpushfunction(function, "func"); lua.pushinteger(10); _ = try lua.pushstring("zig"); lua.pushnumber(1.23); _ = try lua.pushstring("lang"); _ = lua.pcall(4, 0, 0); } // test "checkOption" { // if (!EXCEPTIONS_ENABLED) // return error.SkipZigTest; // var lua = try luau.init(&testing.allocator); // defer lua.deinit(); // const Variant = enum { // one, // two, // three, // }; // const function = struct { // fn inner(l: *lua) i32 { // const option = l.checkOption(Variant, 1, .one); // l.pushinteger(switch (option) { // .one => 1, // .two => 2, // .three => 3, // }); // return 1; // } // }.inner; // lua.Zpushfunction(function, "func"); // _ = lua.pushstring("one"); // _ = lua.pcall(1, 1, 0); // try expectEqual(1, try lua.toInteger(-1)); // lua.pop(1); // lua.Zpushfunction(function, "func"); // _ = lua.pushstring("two"); // _ = lua.pcall(1, 1, 0); // try expectEqual(2, try lua.toInteger(-1)); // lua.pop(1); // lua.Zpushfunction(function, "func"); // _ = lua.pushstring("three"); // _ = lua.pcall(1, 1, 0); // try expectEqual(3, try lua.toInteger(-1)); // lua.pop(1); // // try the default now // lua.Zpushfunction(function, "func"); // _ = lua.pcall(0, 1, 0); // try expectEqual(1, try lua.toInteger(-1)); // lua.pop(1); // // check the raised error // lua.Zpushfunction(function, "func"); // _ = lua.pushstring("unknown"); // try expectError(error.Runtime, lua.pcall(1, 1, 0)); // try expectStringContains("(invalid option 'unknown')", try lua.toString(-1)); // } test "ref luau" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.pushnil(); try expectEqual(null, lua.ref(1)); try expectEqual(1, lua.gettop()); // In luau lua.ref does not pop the item from the stack // and the data is stored in the REGISTRYINDEX by default _ = try lua.pushstring("Hello there"); const ref = try lua.ref(2) orelse @panic("bad"); _ = lua.rawgeti(luau.VM.lua.REGISTRYINDEX, ref); try expectEqualStrings("Hello there", lua.tostring(-1) orelse @panic("bad")); lua.unref(ref); } test "args and errors" { if (!EXCEPTIONS_ENABLED) return error.SkipZigTest; var lua = try luau.init(&testing.allocator); defer lua.deinit(); const argCheck = struct { fn inner(l: *State) !i32 { try l.Largcheck(false, 1, "error!"); return 0; } }.inner; try lua.Zpushfunction(argCheck, "ArgCheck"); try expectError(error.Runtime, lua.pcall(0, 0, 0).check()); const raisesError = struct { fn inner(l: *State) !i32 { try l.LerrorL("some error {s}!", .{"zig"}); unreachable; } }.inner; try lua.Zpushfunction(raisesError, "Error"); try expectError(error.Runtime, lua.pcall(0, 0, 0).check()); try expectEqualStrings("some error zig!", lua.tostring(-1) orelse @panic("bad")); const raisesFmtError = struct { fn inner(l: *State) !i32 { try l.LerrorL("some fmt error {s}!", .{"zig"}); unreachable; } }.inner; try lua.Zpushfunction(raisesFmtError, "ErrorFmt"); try expectError(error.Runtime, lua.pcall(0, 0, 0).check()); try expectEqualStrings("some fmt error zig!", lua.tostring(-1) orelse @panic("bad")); const FmtError = struct { fn inner(l: *State) !i32 { return l.Zerrorf("some err fmt error {s}!", .{"zig"}); } }.inner; try lua.Zpushfunction(FmtError, "ErrorFmt"); try expectError(error.Runtime, lua.pcall(0, 0, 0).check()); try expectEqualStrings("some err fmt error zig!", lua.tostring(-1) orelse @panic("bad")); const Error = struct { fn inner(l: *State) !i32 { return l.Zerror("some error"); } }.inner; try lua.Zpushfunction(Error, "Error"); try expectError(error.Runtime, lua.pcall(0, 0, 0).check()); try expectEqualStrings("some error", lua.tostring(-1) orelse @panic("bad")); } test "objectLen" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); _ = try lua.pushstring("lua"); try testing.expectEqual(3, lua.objlen(-1)); } test "compile and run bytecode" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); try lua.Lopenlibs(); // Load bytecode const src = "return 133"; const bc = try luau.compile(testing.allocator, src, luau.CompileOptions{}); defer testing.allocator.free(bc); try lua.load("...", bc, 0); _ = lua.pcall(0, 1, 0); const v = lua.tointeger(-1) orelse @panic("bad"); try expectEqual(133, v); // Try mutable globals. Calls to mutable globals should produce longer bytecode. const src2 = "Foo.print()\nBar.print()"; const bc1 = try luau.compile(testing.allocator, src2, luau.CompileOptions{}); defer testing.allocator.free(bc1); const options = luau.CompileOptions{ .mutableGlobals = &[_:null]?[*:0]const u8{ "Foo", "Bar" }, }; const bc2 = try luau.compile(testing.allocator, src2, options); defer testing.allocator.free(bc2); // A really crude check for changed bytecode. Better would be to match // produced bytecode in text format, but the API doesn't support it. try expect(bc1.len < bc2.len); } const DataDtor = struct { gc_hits_ptr: *i32, pub fn dtor(self: *DataDtor) void { self.gc_hits_ptr.* = self.gc_hits_ptr.* + 1; } }; test "userdata dtor" { var gc_hits: i32 = 0; // create a Luau-owned pointer to a Data, configure Data with a destructor. { var lua = try luau.init(&testing.allocator); defer lua.deinit(); var data = try lua.newuserdatadtor(DataDtor, DataDtor.dtor); data.gc_hits_ptr = &gc_hits; try expectEqual(@as(*anyopaque, @ptrCast(data)), lua.topointer(1) orelse @panic("bad")); try expectEqual(0, gc_hits); lua.pop(1); // don't let the stack hold a ref to the user data _ = lua.gc(.Collect, 0); try expectEqual(1, gc_hits); _ = lua.gc(.Collect, 0); try expectEqual(1, gc_hits); } } fn vectorCtor(l: *State) i32 { const x = l.tonumber(1) orelse 0; const y = l.tonumber(2) orelse 0; const z = l.tonumber(3) orelse 0; if (luau.luau_vector_size == 4) { const w = l.optNumber(4, 0); l.pushVector(@floatCast(x), @floatCast(y), @floatCast(z), @floatCast(w)); } else { l.pushVector(@floatCast(x), @floatCast(y), @floatCast(z)); } return 1; } fn foo(a: i32, b: i32) i32 { return a + b; } fn bar(a: i32, b: i32) !i32 { if (a > b) return error.wrong; return a + b; } test "debug stacktrace" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const stackTrace = struct { fn inner(l: *State) !i32 { try l.pushstring(l.debugtrace()); return 1; } }.inner; try lua.Zpushfunction(stackTrace, "test"); _ = lua.pcall(0, 1, 0); try expectEqualStrings("[C] function test\n", lua.tostring(-1) orelse @panic("bad")); } test "debug stacktrace luau" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const src = \\function MyFunction() \\ return stack() \\end \\ \\return MyFunction() \\ ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, }); defer testing.allocator.free(bc); const stackTrace = struct { fn inner(l: *State) !i32 { try l.pushstring(l.debugtrace()); return 1; } }.inner; try lua.Zpushfunction(stackTrace, "stack"); try lua.setglobal("stack"); try lua.load("module", bc, 0); _ = lua.pcall(0, 1, 0); // CALL main() try expectEqualStrings( \\[C] function stack \\[string "module"]:2 function MyFunction \\[string "module"]:5 \\ , lua.tostring(-1) orelse @panic("bad")); } test "buffers" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); lua.openbuffer(); const buf = try lua.newbuffer(12); try lua.Zpushbuffer("Hello, world 2"); try expectEqual(12, buf.len); @memcpy(buf, "Hello, world"); try expect(lua.isbuffer(-1)); try expectEqualStrings("Hello, world", buf); try expectEqualStrings("Hello, world", lua.tobuffer(-2) orelse @panic("bad")); try expectEqualStrings("Hello, world 2", lua.tobuffer(-1) orelse @panic("bad")); const src = \\function MyFunction(buf, buf2) \\ assert(buffer.tostring(buf) == "Hello, world") \\ assert(buffer.tostring(buf2) == "Hello, world 2") \\ local newBuf = buffer.create(4); \\ buffer.writeu8(newBuf, 0, 82) \\ buffer.writeu8(newBuf, 1, 101) \\ buffer.writeu8(newBuf, 2, 115) \\ buffer.writeu8(newBuf, 3, 116) \\ return newBuf \\end \\ \\return MyFunction \\ ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, }); defer testing.allocator.free(bc); try lua.load("module", bc, 0); _ = lua.pcall(0, 1, 0); // CALL main() lua.pushvalue(-3); lua.pushvalue(-3); _ = lua.pcall(2, 1, 0); // CALL MyFunction(buf) const newBuf = lua.Lcheckbuffer(-1); try expectEqual(4, newBuf.len); try expectEqualStrings("Rest", newBuf); } test "Set Api" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); lua.openstring(); const vectorFn = struct { fn inner(l: *State) i32 { const x: f32 = @floatCast(l.Loptnumber(1, 0.0)); const y: f32 = @floatCast(l.Loptnumber(2, 0.0)); const z: f32 = @floatCast(l.Loptnumber(3, 0.0)); if (luau.VECTOR_SIZE == 3) { l.pushvector(x, y, z, null); } else { const w: f32 = @floatCast(l.Loptnumber(4, 0.0)); l.pushvector(x, y, z, w); } return 1; } }.inner; try lua.Zsetglobalfn("vector", vectorFn); const src = \\function MyFunction(api) \\ assert(type(api.a) == "function"); api.a() \\ assert(type(api.b) == "boolean" and api.b == true); \\ assert(type(api.c) == "number" and api.c == 1.1); \\ assert(type(api.d) == "number" and api.d == 2); \\ assert(type(api.e) == "string" and api.e == "Api"); \\ assert(type(api.f) == "string" and api.f == string.char(65, 0, 66) and api.f ~= "AB" and #api.f == 3); \\ assert(type(api.pos) == "vector" and api.pos.X == 1 and api.pos.Y == 2 and api.pos.Z == 3); \\ \\ assert(type(_a) == "function"); _a() \\ assert(type(_b) == "boolean" and _b == true); \\ assert(type(_c) == "number" and _c == 1.1); \\ assert(type(_d) == "number" and _d == 2); \\ assert(type(_e) == "string" and _e == "Api"); \\ assert(type(_f) == "string" and _f == string.char(65, 0, 66) and _f ~= "AB" and #_f == 3); \\ assert(type(_pos) == "vector" and _pos.X == 1 and _pos.Y == 2 and _pos.Z == 3); \\ \\ assert(type(gl_a) == "function"); gl_a() \\ assert(type(gl_b) == "boolean" and gl_b == true); \\ assert(type(gl_c) == "number" and gl_c == 1.1); \\ assert(type(gl_d) == "number" and gl_d == 2); \\ assert(type(gl_e) == "string" and gl_e == "Api"); \\ assert(type(gl_f) == "string" and gl_f == string.char(65, 0, 66) and gl_f ~= "AB" and #gl_f == 3); \\ assert(type(gl_pos) == "vector" and gl_pos.X == 1 and gl_pos.Y == 2 and gl_pos.Z == 3); \\end \\ \\return MyFunction \\ ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 0, .vectorCtor = "vector", .vectorType = "vector", }); defer testing.allocator.free(bc); const tempFn = struct { fn inner(l: *State) !i32 { _ = try l.getglobal("count"); l.pushinteger((l.tointeger(-1) orelse 0) + 1); try l.setglobal("count"); return 0; } }.inner; try lua.newtable(); try lua.Zsetfieldfn(-1, "a", tempFn); try lua.Zsetfield(-1, "b", true); try lua.Zsetfield(-1, "c", 1.1); try lua.Zsetfield(-1, "d", 2); try lua.Zsetfield(-1, "e", "Api"); try lua.Zsetfield(-1, "f", &[_]u8{ 'A', 0, 'B' }); if (luau.VECTOR_SIZE == 3) { try lua.Zsetfield(-1, "pos", @Vector(3, f32){ 1.0, 2.0, 3.0 }); } else { try lua.Zsetfield(-1, "pos", @Vector(4, f32){ 1.0, 2.0, 3.0, 4.0 }); } try lua.Zsetfieldfn(luau.VM.lua.GLOBALSINDEX, "_a", tempFn); try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_b", true); try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_c", @as(f64, 1.1)); try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_d", @as(i32, 2)); try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_e", "Api"); try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_f", &[_]u8{ 'A', 0, 'B' }); if (luau.VECTOR_SIZE == 3) { try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_pos", @Vector(3, f32){ 1.0, 2.0, 3.0 }); } else { try lua.Zsetfield(luau.VM.lua.GLOBALSINDEX, "_pos", @Vector(4, f32){ 1.0, 2.0, 3.0, 4.0 }); } try lua.Zsetglobalfn("gl_a", tempFn); try lua.Zsetglobal("gl_b", true); try lua.Zsetglobal("gl_c", 1.1); try lua.Zsetglobal("gl_d", 2); try lua.Zsetglobal("gl_e", "Api"); try lua.Zsetglobal("gl_f", &[_]u8{ 'A', 0, 'B' }); if (luau.VECTOR_SIZE == 3) { try lua.Zsetglobal("gl_pos", @Vector(3, f32){ 1.0, 2.0, 3.0 }); } else { try lua.Zsetglobal("gl_pos", @Vector(4, f32){ 1.0, 2.0, 3.0, 4.0 }); } try lua.load("module", bc, 0); _ = lua.pcall(0, 1, 0); // CALL main() lua.pushvalue(-2); switch (try lua.pcall(1, 1, 0).check()) { .Ok => {}, .Yield => std.debug.panic("unexpected yield\n", .{}), .Break => std.debug.panic("unexpected break\n", .{}), else => unreachable, } _ = try lua.getglobal("count"); try expectEqual(3, lua.tointeger(-1) orelse @panic("bad")); } test "Vectors" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); lua.openstring(); lua.openmath(); const vectorFn = struct { fn inner(l: *State) i32 { const x: f32 = @floatCast(l.Loptnumber(1, 0.0)); const y: f32 = @floatCast(l.Loptnumber(2, 0.0)); const z: f32 = @floatCast(l.Loptnumber(3, 0.0)); if (luau.VECTOR_SIZE == 3) { l.pushvector(x, y, z, null); } else { const w: f32 = @floatCast(l.Loptnumber(4, 0.0)); l.pushvector(x, y, z, w); } return 1; } }.inner; const src = \\function MyFunction() \\ local vec = vector(0, 1.1, 2.2); \\ assert(type(vec) == "vector") \\ assert(vec.X == 0); \\ assert(math.round(vec.Y*100)/100 == 1.1); -- 1.100000023841858 \\ assert(math.round(vec.Z*100)/100 == 2.2); -- 2.200000047683716 \\ return vec \\end \\ \\return MyFunction() \\ ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 0, .vectorCtor = "vector", .vectorType = "vector", }); defer testing.allocator.free(bc); try lua.Zsetglobalfn("vector", vectorFn); try lua.load("module", bc, 0); _ = lua.pcall(0, 1, 0); // CALL main() try expect(lua.isvector(-1)); const vec = lua.tovector(-1) orelse @panic("bad"); try expectEqual(luau.VECTOR_SIZE, vec.len); try expectEqual(0.0, vec[0]); try expectEqual(1.1, vec[1]); try expectEqual(2.2, vec[2]); if (luau.VECTOR_SIZE == 4) { try expectEqual(0.0, vec[3]); } if (luau.VECTOR_SIZE == 3) { lua.pushvector(0.0, 1.0, 0.0, null); } else { lua.pushvector(0.0, 1.0, 0.0, 0.0); } const vec2 = lua.Lcheckvector(-1); try expectEqual(luau.VECTOR_SIZE, vec2.len); try expectEqual(0.0, vec2[0]); try expectEqual(1.0, vec2[1]); try expectEqual(0.0, vec2[2]); if (luau.VECTOR_SIZE == 4) { try expectEqual(0.0, vec2[3]); } } test "Luau JIT/CodeGen" { // Skip this test if the Luau NCG is not supported on machine if (!luau.CodeGen.Supported()) return error.SkipZigTest; var lua = try luau.init(&std.testing.allocator); defer lua.deinit(); luau.CodeGen.Create(lua); lua.openbase(); try lua.Zsetglobalfn("test", struct { fn inner(L: *State) !i32 { L.pushboolean(L.Gisnative(@intCast(L.Loptinteger(1, 0)))); return 1; } }.inner); const src = \\ \\local function func(): () \\ assert(native == test()) \\ return \\end \\ \\pcall(func) \\ ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 2, }); defer testing.allocator.free(bc); try lua.load("module", bc, 0); luau.CodeGen.Compile(lua, -1); _ = lua.pcall(0, 0, 0); // CALL main() } test "Luau JIT/CodeGen compileLoad" { // Skip this test if the Luau NCG is not supported on machine if (!luau.CodeGen.Supported()) return error.SkipZigTest; var lua = try luau.init(&std.testing.allocator); defer lua.deinit(); luau.CodeGen.Create(lua); lua.openbase(); try lua.Zsetglobalfn("test", struct { fn inner(L: *State) !i32 { L.pushboolean(L.Gisnative(@intCast(L.Loptinteger(1, 0)))); return 1; } }.inner); const src = \\ \\local function func(): () \\ assert(native == test()) \\ return \\end \\ \\pcall(func) \\ ; try luau.Compiler.Compiler.compileLoad(lua, "module", src, .{ .debugLevel = 2, .optimizationLevel = 2, }, 0); luau.CodeGen.Compile(lua, -1); _ = lua.pcall(0, 0, 0); // CALL main() } test "Luau JIT/CodeGen ParseResult" { // Skip this test if the Luau NCG is not supported on machine if (!luau.CodeGen.Supported()) return error.SkipZigTest; var lua = try luau.init(&std.testing.allocator); defer lua.deinit(); luau.CodeGen.Create(lua); lua.openbase(); try lua.Zsetglobalfn("test", struct { fn inner(L: *State) !i32 { L.pushboolean(L.Gisnative(@intCast(L.Loptinteger(1, 0)))); return 1; } }.inner); const src = \\ \\local function func(): () \\ assert(native == test()) \\ return \\end \\ \\pcall(func) \\ ; const luau_allocator = luau.Ast.Allocator.init(); defer luau_allocator.deinit(); const astNameTable = luau.Ast.Lexer.AstNameTable.init(luau_allocator); defer astNameTable.deinit(); const parseResult = luau.Ast.Parser.parse(src, astNameTable, luau_allocator, .{}); defer parseResult.deinit(); try luau.Compiler.Compiler.compileLoadParseResult(lua, "module", parseResult, astNameTable, .{ .debugLevel = 2, .optimizationLevel = 2, }, 0); luau.CodeGen.Compile(lua, -1); _ = lua.pcall(0, 0, 0); // CALL main() } test "Readonly table" { if (!EXCEPTIONS_ENABLED) return error.SkipZigTest; var lua = try luau.init(&testing.allocator); defer lua.deinit(); try lua.newtable(); lua.setreadonly(-1, true); try lua.setglobal("List"); const src = \\List[1] = "test" ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 2, }); defer testing.allocator.free(bc); try lua.load("module", bc, 0); try expectError(error.Runtime, lua.pcall(0, 0, 0).check()); // CALL main() } test "Metamethods" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); try lua.Lopenlibs(); _ = try lua.Lnewmetatable("MyMetatable"); try lua.Zsetfieldfn(-1, luau.Metamethods.index, struct { fn inner(l: *State) !i32 { l.Lchecktype(1, .Table); const key = l.tostring(2) orelse unreachable; expectEqualStrings("test", key) catch unreachable; try l.pushstring("Hello, world"); return 1; } }.inner); try lua.Zsetfieldfn(-1, luau.Metamethods.tostring, struct { fn inner(l: *State) !i32 { l.Lchecktype(1, .Table); try l.pushstring("MyMetatable"); return 1; } }.inner); try lua.newtable(); lua.pushvalue(-2); _ = try lua.setmetatable(-2); try expectEqual(.String, lua.getfield(-1, "test")); try expectEqualStrings("Hello, world", lua.tostring(-1) orelse @panic("bad")); lua.pop(1); try expectEqual(.Function, lua.getglobal("tostring")); lua.pushvalue(-2); _ = lua.pcall(1, 1, 0); try expectEqualStrings("MyMetatable", lua.tostring(-1) orelse @panic("bad")); lua.pop(1); } test "Zig Error Fn Lua Handled" { if (!EXCEPTIONS_ENABLED) return error.SkipZigTest; var lua = try luau.init(&testing.allocator); defer lua.deinit(); const zigEFn = struct { fn inner(_: *State) !i32 { return error.Fail; } }.inner; try lua.Zpushfunction(zigEFn, "zigEFn"); try expectEqual(error.Runtime, lua.pcall(0, 0, 0).check()); try expectEqualStrings("Fail", lua.tostring(-1).?); } test "getfieldObject" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); try lua.Lopenlibs(); try lua.newtable(); try lua.Zsetfield(-1, "test", true); try lua.Zsetglobal("some", "Value"); // switch (try lua.getfieldObj(-1, "test")) { // .boolean => |b| try expectEqual(true, b), // else => @panic("Failed"), // } // switch (try lua.getglobalObj("some")) { // .string => |s| try expectEqualStrings("Value", s), // else => @panic("Failed"), // } // _ = lua.newbuffer(2); // lua.pushnil(); // switch (try lua.typeOfObj(-2)) { // .buffer => |buf| try expectEqualStrings(&[_]u8{ 0, 0 }, buf), // else => @panic("Failed"), // } // lua.pop(1); // switch (try lua.typeOfObj(-1)) { // .nil => {}, // else => @panic("Failed"), // } // try expectEqual(.nil, lua.typeOf(-1)); // should not be consumed // try expectEqual(.nil, lua.typeOf(-2)); // should not be consumed // try expectEqual(.buffer, lua.typeOf(-3)); // lua.pop(2); // lua.pushnumber(1.2); // switch (try lua.typeOfObj(-1)) { // .number => |n| { // // can leak if not handled, stack grows // try expectEqual(1.2, n); // }, // else => @panic("Failed"), // } // try expectEqual(.number, lua.typeOf(-1)); // should not be consumed // try expectEqual(.number, lua.typeOf(-2)); // should not be consumed // try expectEqual(.buffer, lua.typeOf(-3)); // switch (try lua.typeOfObjConsumed(-1)) { // .number => |n| { // // pops automatically with value // try expectEqual(1.2, n); // }, // else => @panic("Failed"), // } // // should be consumed // try expectEqual(.number, lua.typeOf(-1)); // should not be consumed // try expectEqual(.number, lua.typeOf(-2)); // should not be consumed // try expectEqual(.buffer, lua.typeOf(-3)); // lua.pop(2); // const res = try lua.typeOfObj(-1); // if (res == .buffer) { // try expectEqualStrings(&[_]u8{ 0, 0 }, res.buffer); // } else @panic("Failed"); } test "FFlags" { try expectError(error.UnknownFlag, luau.FFlags.SetByName(bool, "someunknownflag", true)); try expectError(error.UnknownFlag, luau.FFlags.SetByName(i32, "someunknownflag", 1)); try expectError(error.UnknownFlag, luau.FFlags.SetByName(c_int, "someunknownflag", 1)); try expectEqual(null, luau.FFlags.GetByName(bool, "someunknownflag")); try expectEqual(null, luau.FFlags.GetByName(i32, "someunknownflag")); try expectEqual(null, luau.FFlags.GetByName(c_int, "someunknownflag")); var bool_flags = luau.FFlags.Get(bool).iterator(); while (bool_flags.next()) |flag| { const name: []const u8 = std.mem.span(flag.name); try expect(name.len > 0); const current = flag.value; flag.value = !current; try expectEqual(!current, luau.FFlags.GetByName(bool, name).?.value); flag.value = current; try expectEqual(current, luau.FFlags.GetByName(bool, name).?.value); } var int_flags = luau.FFlags.Get(i32).iterator(); while (int_flags.next()) |flag| { const name: []const u8 = std.mem.span(flag.name); try expect(name.len > 0); const current = flag.value; flag.value = current - 1; try expectEqual(current - 1, luau.FFlags.GetByName(i32, name).?.value); flag.value = current; try expectEqual(current, luau.FFlags.GetByName(i32, name).?.value); } } test "State getInfo" { var lua = try luau.init(&std.testing.allocator); defer lua.deinit(); lua.openbase(); const src = \\function MyFunction() \\ func() \\end \\ \\MyFunction() ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 2, }); defer testing.allocator.free(bc); try lua.Zsetglobalfn("func", struct { fn inner(L: *State) !i32 { var ar: luau.VM.lua.Debug = .{ .ssbuf = undefined }; try expect(L.getinfo(1, "snl", &ar)); try expect(ar.what == .lua); try std.testing.expectEqualSentinel(u8, 0, "MyFunction", ar.name orelse @panic("Failed")); try std.testing.expectEqualStrings("[string \"module\"]", ar.short_src orelse @panic("Failed")); try expect(ar.linedefined.? == 1); return 1; } }.inner); try lua.load("module", bc, 0); _ = lua.pcall(0, 1, 0); // CALL main() } test "yielding error" { { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); lua.opencoroutine(); const src = \\local ok, res = pcall(foo) \\assert(not ok) \\assert(res == "error") ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 2, }); defer testing.allocator.free(bc); try lua.Zsetglobalfn("foo", struct { fn inner(L: *State) !i32 { return L.yield(0); } }.inner); try lua.load("module", bc, 0); try expectEqual(.Yield, lua.resumethread(lua, 0)); try lua.pushstring("error"); try expectEqual(.Ok, lua.resumeerror(lua)); } { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); lua.opencoroutine(); const src = \\local ok, res = pcall(foo) \\assert(not ok) \\assert(res == "fmt error 10") ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 2, .optimizationLevel = 2, }); defer testing.allocator.free(bc); try lua.Zsetglobalfn("foo", struct { fn inner(L: *State) !i32 { return L.yield(0); } }.inner); try lua.load("module", bc, 0); try expectEqual(.Yield, lua.resumethread(lua, 0)); try expectEqual(.Ok, lua.Zresumeferror(lua, "fmt error {d}", .{10})); } } test "Ast/Parser - HotComments" { const src = \\--!HotComments \\--!optimize 2 ; const luau_allocator = luau.Ast.Allocator.init(); defer luau_allocator.deinit(); const names = luau.Ast.Lexer.AstNameTable.init(luau_allocator); defer names.deinit(); var result = luau.Ast.Parser.parse(src, names, luau_allocator, .{}); defer result.deinit(); try testing.expectEqual(2, result.hotcomments.size()); { try expectEqualStrings("HotComments", result.hotcomments.at(0).content.slice()); try expectEqualStrings("optimize 2", result.hotcomments.at(1).content.slice()); } } test "Thread Data" { const Sample = struct { a: i32, b: i32, }; var lua = try luau.init(&testing.allocator); defer lua.deinit(); const zigFn = struct { fn inner(L: *State) !i32 { const data = L.getthreaddata(*Sample); // should exists try expectEqual(10, data.a); try expectEqual(20, data.b); return 0; } }.inner; var data = Sample{ .a = 10, .b = 20 }; lua.setthreaddata(*Sample, &data); try lua.Zpushfunction(zigFn, "zigFn"); try expectEqual(.Ok, lua.pcall(0, 0, 0)); } test "Alloc (P)" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); inline for (0..20) |_| { try lua.createtable(0, 100); } lua.pop(20); } test "Alloc Ref (P)" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); const noopFn = struct { fn inner(_: *State) callconv(.c) i32 { return 0; } }.inner; inline for (0..15) |_| { try lua.createtable(0, 100); try lua.pushcclosure(noopFn, "test", 0); try lua.rawsetfield(-2, "test"); } lua.pop(15); inline for (0..15) |_| { try lua.createtable(0, 100); inline for (0..5) |_| try lua.createtable(0, 100); try lua.pushcclosure(noopFn, "test", 5); try lua.rawsetfield(-2, "test"); } lua.pop(15); { const T = try lua.newthread(); const src = \\local a = foo(); \\local dead = (function() \\ foo() \\end); \\foo() \\dead = nil; \\for _ = 0, 10 do \\ (function() \\ foo(a) \\ end)() \\end \\a = 1 ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 0, .optimizationLevel = 1, }); defer testing.allocator.free(bc); try T.Zsetglobalfn("foo", struct { fn inner(L: *State) !i32 { inline for (0..15) |_| { try L.createtable(0, 40); } return 1; } }.inner); try T.load("module", bc, 0); T.call(0, 0); } lua.pop(1); { const T = try lua.newthread(); const src = \\local a = foo(); \\local dead = (function() \\ foo() \\end); \\foo() \\dead = nil; \\for _ = 0, 10 do \\ (function() \\ foo(a) \\ end)() \\end \\a = 1 ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 0, .optimizationLevel = 1, }); defer testing.allocator.free(bc); try T.Zsetglobalfn("foo", struct { fn inner(L: *State) !i32 { inline for (0..15) |_| { try L.createtable(0, 40); } return 1; } }.inner); try T.load("module", bc, 0); T.call(0, 0); } lua.pop(1); inline for (0..15) |_| { try lua.createtable(0, 100); try lua.pushcclosure(noopFn, "test", 0); try lua.rawsetfield(-2, "test"); } lua.pop(15); } test "String (S)" { var lua = try luau.init(&testing.allocator); defer lua.deinit(); lua.openbase(); { const T = try lua.newthread(); errdefer std.debug.print("error: {s}\n", .{T.tostring(-1).?}); const src = \\assert(foo() == `Hello, world`) ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 0, .optimizationLevel = 1, }); defer testing.allocator.free(bc); try T.Zsetglobalfn("foo", struct { fn inner(L: *State) !i32 { try L.pushlstring("Hello, world"); return 1; } }.inner); try T.load("module", bc, 0); _ = try T.pcall(0, 0, 0).check(); } if (EXCEPTIONS_ENABLED) { const T = try lua.newthread(); errdefer std.debug.print("error: {s}\n", .{T.tostring(-1).?}); const src = \\local ok, res = pcall(foo) \\assert(res == `foo bar baz, a long string, luau`) \\assert(not ok) ; const bc = try luau.compile(testing.allocator, src, .{ .debugLevel = 1, .optimizationLevel = 2, }); defer testing.allocator.free(bc); try T.Zsetglobalfn("foo", struct { fn inner(L: *State) !i32 { return L.Zerror("foo bar baz, a long string, luau"); } }.inner); try T.load("module", bc, 0); _ = try T.pcall(0, 0, 0).check(); } }