diff --git a/src/lib.zig b/src/lib.zig index 53df4c1..14adbdb 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -2594,6 +2594,19 @@ pub const Lua = opaque { c.lua_toclose(@ptrCast(lua), index); } + /// Converts the Lua value at the given `index` to a numeric type; + /// if T is an integer type, the Lua value is converted to an integer. + /// + /// * Pops: `0` + /// * Pushes: `0` + /// * Errors: `error.Overflow` if `T` is an integer type and the value at index doesn't fit + pub fn toNumeric(lua: *Lua, comptime T: type, index: i32) !T { + if (@typeInfo(T) == .int) { + return std.math.cast(T, try lua.toInteger(index)) orelse error.Overflow; + } + return @floatCast(try lua.toNumber(index)); + } + /// Converts the Lua value at the given `index` to a signed integer /// The Lua value must be an integer, or a number, or a string convertible to an integer /// Returns an error if the conversion failed @@ -3342,6 +3355,32 @@ pub const Lua = opaque { c.luaL_checkany(@ptrCast(lua), arg); } + /// Checks whether the function argument `arg` is a numeric type and converts it to type T + /// + /// Raises a Lua error if the argument is an integer type but std.math.cast fails + /// + /// * Pops: `0` + /// * Pushes: `0` + /// * Errors: `explained in text / on purpose` + pub fn checkNumeric(lua: *Lua, comptime T: type, arg: i32) T { + if (comptime @typeInfo(T) != .int) return @floatCast(lua.checkNumber(arg)); + return std.math.cast(T, lua.checkInteger(arg)) orelse { + const error_msg = comptime msg: { + var buf: [1024]u8 = undefined; + const info = @typeInfo(T).int; + const signedness = switch (info.signedness) { + .unsigned => "u", + .signed => "i", + }; + const output = std.fmt.bufPrintZ(&buf, "integer argument doesn't fit inside {s}{d} range [{d}, {d}]", .{ + signedness, info.bits, std.math.minInt(T), std.math.maxInt(T), + }) catch unreachable; + break :msg output[0..output.len :0].*; + }; + lua.argError(arg, &error_msg); + }; + } + /// Checks whether the function argument `arg` is a number and returns this number cast to an i32 /// /// Not available in Lua 5.3 and 5.4 diff --git a/src/tests.zig b/src/tests.zig index 21cf279..44349ba 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3055,3 +3055,41 @@ test "error union for CFn" { try expectEqualStrings("MissingInteger", try lua.toString(-1)); }; } + +test "checkNumeric and toNumeric" { + const error_msg = "integer argument doesn't fit inside u8 range [0, 255]"; + + const lua: *Lua = try .init(testing.allocator); + defer lua.deinit(); + + lua.pushFunction(zlua.wrap(struct { + fn f(l: *Lua) i32 { + _ = l.checkNumeric(u8, 1); + return 1; + } + }.f)); + const idx = lua.getTop(); + + lua.pushValue(idx); + lua.pushInteger(128); + try lua.protectedCall(.{ + .args = 1, + .results = 1, + }); + const val = lua.toNumeric(u8, lua.getTop()); + try std.testing.expectEqual(128, val); + + lua.pushValue(idx); + lua.pushInteger(256); + if (lua.protectedCall(.{ + .args = 1, + .results = 0, + })) |_| { + return error.ExpectedError; + } else |_| { + const string = try lua.toString(lua.getTop()); + errdefer std.log.err("expected error message to contain: {s}", .{error_msg}); + errdefer std.log.err("error message: {s}", .{string}); + _ = std.mem.indexOf(u8, string, error_msg) orelse return error.BadErrorMessage; + } +}