Skip to content

Commit 0f0f489

Browse files
authored
Added comptime convenience functions (#55)
Adds several convenience functions that build on top of the lua api using Zig comptime * pushAny * toAny * toStruct * autoCall * autoPushFunction * get * set
1 parent a213c57 commit 0f0f489

File tree

2 files changed

+462
-1
lines changed

2 files changed

+462
-1
lines changed

src/lib.zig

+259-1
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,19 @@ pub const Lua = struct {
647647
/// Returns the acceptable index index converted into an equivalent absolute index
648648
/// See https://www.lua.org/manual/5.4/manual.html#lua_absindex
649649
pub fn absIndex(lua: *Lua, index: i32) i32 {
650-
return c.lua_absindex(lua.state, index);
650+
switch (lang) {
651+
.lua51, .luajit => {
652+
if (index > 0 or index <= registry_index) {
653+
return index;
654+
} else {
655+
const result = lua.getTop() + 1 + index;
656+
return @intCast(result);
657+
}
658+
},
659+
else => {
660+
return c.lua_absindex(lua.state, index);
661+
},
662+
}
651663
}
652664

653665
/// Performs an arithmetic or bitwise operation over the value(s) at the top of the stack,
@@ -3013,6 +3025,252 @@ pub const Lua = struct {
30133025
pub fn openBit32(lua: *Lua) void {
30143026
_ = c.luaopen_bit32(lua.state);
30153027
}
3028+
3029+
/// Pushes any valid zig value onto the stack,
3030+
/// Works with ints, floats, booleans, structs,
3031+
/// optionals, and strings
3032+
pub fn pushAny(lua: *Lua, value: anytype) !void {
3033+
switch (@typeInfo(@TypeOf(value))) {
3034+
.Int, .ComptimeInt => {
3035+
lua.pushInteger(@intCast(value));
3036+
},
3037+
.Float, .ComptimeFloat => {
3038+
lua.pushNumber(@floatCast(value));
3039+
},
3040+
.Pointer => |info| {
3041+
switch (info.size) {
3042+
.One => {
3043+
if (@typeInfo(info.child) == .Array) {
3044+
if (@typeInfo(info.child).Array.child != u8) {
3045+
@compileError("only u8 arrays can be pushed");
3046+
}
3047+
_ = lua.pushString(&(value.*));
3048+
} else {
3049+
if (info.is_const) {
3050+
@compileLog(value);
3051+
@compileError("Pointer must not be const");
3052+
}
3053+
lua.pushLightUserdata(@ptrCast(value));
3054+
}
3055+
},
3056+
.C, .Many, .Slice => {
3057+
if (info.child != u8) {
3058+
@compileError("Only u8 slices (strings) are valid slice types");
3059+
}
3060+
if (info.sentinel) |sentinel| {
3061+
const casted: *info.child = @ptrCast(@constCast(sentinel));
3062+
if (casted.* != 0) {
3063+
@compileError("Sentinel of slice must be a null terminator");
3064+
}
3065+
_ = lua.pushString(value);
3066+
} else {
3067+
const null_terminated = try lua.allocator().dupeZ(u8, value);
3068+
defer lua.allocator().free(null_terminated);
3069+
_ = lua.pushString(null_terminated);
3070+
}
3071+
},
3072+
}
3073+
},
3074+
.Bool => {
3075+
lua.pushBoolean(value);
3076+
},
3077+
.Optional, .Null => {
3078+
if (value == null) {
3079+
lua.pushNil();
3080+
} else {
3081+
try lua.pushAny(value.?);
3082+
}
3083+
},
3084+
.Struct => |info| {
3085+
lua.createTable(0, 0);
3086+
inline for (info.fields) |field| {
3087+
try lua.pushAny(field.name);
3088+
try lua.pushAny(@field(value, field.name));
3089+
lua.setTable(-3);
3090+
}
3091+
},
3092+
.Fn => {
3093+
lua.autoPushFunction(value);
3094+
},
3095+
.Void => {},
3096+
else => {
3097+
@compileLog(value);
3098+
@compileError("Invalid type given");
3099+
},
3100+
}
3101+
}
3102+
3103+
/// Converts the specified index of the lua stack to the specified
3104+
/// type if possible and returns it
3105+
pub fn toAny(lua: *Lua, comptime T: type, index: i32) !T {
3106+
3107+
//TODO implement enums
3108+
switch (@typeInfo(T)) {
3109+
.Int => {
3110+
switch (comptime lang) {
3111+
.lua51, .luajit => {
3112+
const result = lua.toInteger(index);
3113+
return @as(T, @intCast(result));
3114+
},
3115+
else => {
3116+
const result = try lua.toInteger(index);
3117+
return @as(T, @intCast(result));
3118+
},
3119+
}
3120+
},
3121+
.Float => {
3122+
switch (comptime lang) {
3123+
.lua51, .luajit => {
3124+
const result = lua.toNumber(index);
3125+
return @as(T, @floatCast(result));
3126+
},
3127+
else => {
3128+
const result = try lua.toNumber(index);
3129+
return @as(T, @floatCast(result));
3130+
},
3131+
}
3132+
},
3133+
.Pointer => |param_info| {
3134+
switch (param_info.size) {
3135+
.Slice, .Many => {
3136+
if (param_info.child != u8) {
3137+
@compileError("Only u8 arrays (strings) may be parameters");
3138+
}
3139+
if (!param_info.is_const) {
3140+
@compileError("Slice must be a const slice");
3141+
}
3142+
const string: [*:0]const u8 = try lua.toString(index);
3143+
const end = std.mem.indexOfSentinel(u8, 0, string);
3144+
3145+
if (param_info.sentinel == null) {
3146+
return string[0..end];
3147+
} else {
3148+
return string[0..end :0];
3149+
}
3150+
},
3151+
else => {
3152+
return try lua.toUserdata(param_info.child, index);
3153+
},
3154+
}
3155+
},
3156+
.Bool => {
3157+
return lua.toBoolean(index);
3158+
},
3159+
.Struct => {
3160+
return try lua.toStruct(T, index);
3161+
},
3162+
.Optional => {
3163+
if (lua.isNil(index)) {
3164+
lua.pop(1);
3165+
return null;
3166+
} else {
3167+
return try lua.toAny(@typeInfo(T).Optional.child, index);
3168+
}
3169+
},
3170+
else => {
3171+
@compileError("Invalid parameter type");
3172+
},
3173+
}
3174+
}
3175+
3176+
/// Converts value at given index to a zig struct if possible
3177+
fn toStruct(lua: *Lua, comptime T: type, raw_index: i32) !T {
3178+
const index = lua.absIndex(raw_index);
3179+
3180+
if (!lua.isTable(index)) {
3181+
return error.ValueNotATable;
3182+
}
3183+
std.debug.assert(lua.typeOf(index) == .table);
3184+
3185+
var result: T = undefined;
3186+
inline for (@typeInfo(T).Struct.fields) |field| {
3187+
const field_name = comptime field.name ++ "";
3188+
_ = lua.pushString(field_name);
3189+
std.debug.assert(lua.typeOf(index) == .table);
3190+
const lua_field_type = lua.getTable(index);
3191+
if (lua_field_type == .nil) {
3192+
if (field.default_value) |default_value| {
3193+
@field(result, field.name) = @as(*const field.type, @ptrCast(@alignCast(default_value))).*;
3194+
} else {
3195+
return error.LuaTableMissingValue;
3196+
}
3197+
} else {
3198+
@field(result, field.name) = try lua.toAny(field.type, -1);
3199+
}
3200+
}
3201+
3202+
return result;
3203+
}
3204+
3205+
///automatically calls a lua function with the given arguments
3206+
pub fn autoCall(lua: *Lua, comptime ReturnType: type, func_name: [:0]const u8, args: anytype) !ReturnType {
3207+
if (try lua.getGlobal(func_name) != LuaType.function) return error.InvalidFunctionName;
3208+
3209+
inline for (args) |arg| {
3210+
try lua.pushAny(arg);
3211+
}
3212+
3213+
const num_results = if (ReturnType == void) 0 else 1;
3214+
try lua.protectedCall(args.len, num_results, 0);
3215+
defer lua.setTop(0);
3216+
3217+
return lua.toAny(ReturnType, -1);
3218+
}
3219+
3220+
//automatically generates a wrapper function
3221+
fn GenerateInterface(comptime function: anytype) type {
3222+
const info = @typeInfo(@TypeOf(function));
3223+
if (info != .Fn) {
3224+
@compileLog(info);
3225+
@compileLog(function);
3226+
@compileError("function pointer must be passed");
3227+
}
3228+
return struct {
3229+
pub fn interface(lua: *Lua) i32 {
3230+
var parameters: std.meta.ArgsTuple(@TypeOf(function)) = undefined;
3231+
3232+
inline for (info.Fn.params, 0..) |param, i| {
3233+
parameters[i] = lua.toAny(param.type.?, (i + 1)) catch |err| {
3234+
lua.raiseErrorStr(@errorName(err), .{});
3235+
};
3236+
}
3237+
3238+
if (@typeInfo(info.Fn.return_type.?) == .ErrorUnion) {
3239+
const result = @call(.auto, function, parameters) catch |err| {
3240+
lua.raiseErrorStr(@errorName(err), .{});
3241+
};
3242+
lua.pushAny(result) catch |err| {
3243+
lua.raiseErrorStr(@errorName(err), .{});
3244+
};
3245+
} else {
3246+
const result = @call(.auto, function, parameters);
3247+
lua.pushAny(result) catch |err| {
3248+
lua.raiseErrorStr(@errorName(err), .{});
3249+
};
3250+
}
3251+
3252+
return 1;
3253+
}
3254+
};
3255+
}
3256+
3257+
///generates the interface for and pushes a function to the stack
3258+
pub fn autoPushFunction(lua: *Lua, function: anytype) void {
3259+
const Interface = GenerateInterface(function);
3260+
lua.pushFunction(wrap(Interface.interface));
3261+
}
3262+
3263+
///get any lua global
3264+
pub fn get(lua: *Lua, comptime ReturnType: type, name: [:0]const u8) !ReturnType {
3265+
_ = try lua.getGlobal(name);
3266+
return try lua.toAny(ReturnType, -1);
3267+
}
3268+
3269+
///set any lua global
3270+
pub fn set(lua: *Lua, name: [:0]const u8, value: anytype) !void {
3271+
try lua.pushAny(value);
3272+
lua.setGlobal(name);
3273+
}
30163274
};
30173275

30183276
/// A string buffer allowing for Zig code to build Lua strings piecemeal

0 commit comments

Comments
 (0)