1950 lines
55 KiB
Zig
1950 lines
55 KiB
Zig
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();
|
|
}
|
|
}
|