Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added comptime convenience functions #55

Merged
merged 11 commits into from
Feb 7, 2024
259 changes: 258 additions & 1 deletion src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,19 @@ pub const Lua = struct {
/// Returns the acceptable index index converted into an equivalent absolute index
/// See https://www.lua.org/manual/5.4/manual.html#lua_absindex
pub fn absIndex(lua: *Lua, index: i32) i32 {
return c.lua_absindex(lua.state, index);
switch (lang) {
.lua51, .luajit => {
if (index > 0 or index <= registry_index) {
return index;
} else {
const result = lua.getTop() + 1 + index;
return @intCast(result);
}
},
else => {
return c.lua_absindex(lua.state, index);
},
}
}

/// Performs an arithmetic or bitwise operation over the value(s) at the top of the stack,
Expand Down Expand Up @@ -3015,6 +3027,251 @@ pub const Lua = struct {
pub fn openBit32(lua: *Lua) void {
_ = c.luaopen_bit32(lua.state);
}

/// Pushes any valid zig value onto the stack,
/// Works with ints, floats, booleans, structs,
/// optionals, and strings
pub fn pushAny(lua: *Lua, value: anytype) void {
switch (@typeInfo(@TypeOf(value))) {
.Int, .ComptimeInt => {
//const casted: Integer = @intCast(value);
VisenDev marked this conversation as resolved.
Show resolved Hide resolved
lua.pushInteger(@intCast(value));
},
.Float, .ComptimeFloat => {
//const casted: Number = @floatCast(value);
lua.pushNumber(@floatCast(value));
},
.Pointer => |info| {
switch (info.size) {
.One => {
if (@typeInfo(info.child) == .Array) {
if (@typeInfo(info.child).Array.child != u8) {
@compileError("only u8 arrays can be pushed");
}
_ = lua.pushString(&(value.*));
} else {
if (info.is_const) {
@compileLog(value);
@compileError("Pointer must not be const");
}
lua.pushLightUserdata(@ptrCast(value));
}
},
.C, .Many, .Slice => {
if (info.child != u8) {
@compileError("Only u8 slices (strings) are valid slice types");
}
if (info.sentinel) |sentinel| {
const casted: *info.child = @ptrCast(@constCast(sentinel));
if (casted.* != 0) {
@compileError("Sentinel of slice must be a null terminator");
}
_ = lua.pushString(value);
} else {
var null_terminated = lua.allocator().dupeZ(u8, value) catch unreachable;
VisenDev marked this conversation as resolved.
Show resolved Hide resolved
defer lua.allocator().free(null_terminated);
_ = &null_terminated; //required to override unused var error
VisenDev marked this conversation as resolved.
Show resolved Hide resolved
_ = lua.pushString(null_terminated);
}
},
}
},
.Bool => {
lua.pushBoolean(value);
},
.Optional, .Null => {
if (value == null) {
lua.pushNil();
} else {
lua.pushAny(value.?);
}
},
.Struct => |info| {
lua.createTable(0, 0);
inline for (info.fields) |field| {
lua.pushAny(field.name);
lua.pushAny(@field(value, field.name));
lua.setTable(-3);
}
},
.Fn => {
lua.autoPushFunction(value);
},
.Void => {},
else => {
@compileLog(value);
@compileError("Invalid type given");
},
}
}

/// Converts the specified index of the lua stack to the specified
/// type if possible and returns it
pub fn toAny(lua: *Lua, comptime T: type, index: i32) !T {
switch (@typeInfo(T)) {
.Int => {
switch (comptime lang) {
.lua51, .luajit => {
const result = lua.toInteger(index);
return @as(T, @intCast(result));
},
else => {
const result = try lua.toInteger(index);
return @as(T, @intCast(result));
},
}
},
.Float => {
switch (comptime lang) {
.lua51, .luajit => {
const result = lua.toNumber(index);
return @as(T, @floatCast(result));
},
else => {
const result = try lua.toNumber(index);
return @as(T, @floatCast(result));
},
}
},
.Pointer => |param_info| {
switch (param_info.size) {
.Slice, .Many => {
if (param_info.child != u8) {
@compileError("Only u8 arrays (strings) may be parameters");
}
if (!param_info.is_const) {
@compileError("Slice must be a const slice");
}
const string: [*:0]const u8 = try lua.toString(index);
const end = std.mem.indexOfSentinel(u8, 0, string);

if (param_info.sentinel == null) {
return string[0..end];
} else {
return string[0..end :0];
}
},
else => {
return try lua.toUserdata(param_info.child, index);
},
}
},
.Bool => {
return lua.toBoolean(index);
},
.Struct => {
return try lua.toStruct(T, index);
},
.Optional => {
if (lua.isNil(index)) {
lua.pop(1);
return null;
} else {
return try lua.toAny(@typeInfo(T).Optional.child, index);
}
},
else => {
@compileError("Invalid parameter type");
},
}
}

/// Converts value at given index to a zig struct if possible
fn toStruct(lua: *Lua, comptime T: type, raw_index: i32) !T {
const index = lua.absIndex(raw_index);

if (!lua.isTable(index)) {
std.log.err("Parsing lua table failed because value at index {} is not a table", .{raw_index});
VisenDev marked this conversation as resolved.
Show resolved Hide resolved
return error.ValueNotATable;
}
std.debug.assert(lua.typeOf(index) == .table);

var result: T = undefined;
inline for (@typeInfo(T).Struct.fields) |field| {
const field_name = comptime field.name ++ "";
_ = lua.pushString(field_name);
std.debug.assert(lua.typeOf(index) == .table);
const lua_field_type = lua.getTable(index);
if (lua_field_type == .nil) {
if (field.default_value) |default_value| {
@field(result, field.name) = @as(*const field.type, @ptrCast(@alignCast(default_value))).*;
} else {
std.log.err("Parsing lua table failed because field {s} is missing", .{field_name});
return error.LuaTableMissingValue;
}
} else {
@field(result, field.name) = try lua.toAny(field.type, -1);
}
}

return result;
}

///automatically calls a lua function with the given arguments
pub fn autoCall(lua: *Lua, comptime ReturnType: type, func_name: [:0]const u8, args: anytype) !ReturnType {
if (try lua.getGlobal(func_name) != LuaType.function) return error.InvalidFunctionName;

inline for (args) |arg| {
lua.pushAny(arg);
}

const num_results = if (ReturnType == void) 0 else 1;
try lua.protectedCall(args.len, num_results, 0);
defer lua.setTop(0);

return lua.toAny(ReturnType, -1);
}

//automatically generates a wrapper function
fn GenerateInterface(comptime function: anytype) type {
const info = @typeInfo(@TypeOf(function));
if (info != .Fn) {
@compileLog(info);
@compileLog(function);
@compileError("function pointer must be passed");
}
return struct {
pub fn interface(lua: *Lua) i32 {
var parameters: std.meta.ArgsTuple(@TypeOf(function)) = undefined;

inline for (info.Fn.params, 0..) |param, i| {
parameters[i] = lua.toAny(param.type.?, (i + 1)) catch |err| {
lua.raiseErrorStr(@errorName(err), .{});
};
}

if (@typeInfo(info.Fn.return_type.?) == .ErrorUnion) {
const result = @call(.auto, function, parameters) catch |err| {
lua.raiseErrorStr(@errorName(err), .{});
};
lua.pushAny(result);
} else {
const result = @call(.auto, function, parameters);
lua.pushAny(result);
}

return 1;
}
};
}

///generates the interface for and pushes a function to the stack
pub fn autoPushFunction(lua: *Lua, function: anytype) void {
const Interface = GenerateInterface(function);
lua.pushFunction(wrap(Interface.interface));
}

///get any lua global
pub fn get(lua: *Lua, comptime ReturnType: type, name: [:0]const u8) !ReturnType {
_ = try lua.getGlobal(name);
return try lua.toAny(ReturnType, -1);
}

///set any lua global
pub fn set(lua: *Lua, name: [:0]const u8, value: anytype) void {
lua.pushAny(value);
lua.setGlobal(name);
}
};

/// A string buffer allowing for Zig code to build Lua strings piecemeal
Expand Down
Loading
Loading