This commit is contained in:
2026-05-29 09:13:38 +12:00
commit f96df2aed7
6 changed files with 677 additions and 0 deletions

231
src/main.zig Normal file
View File

@@ -0,0 +1,231 @@
const std = @import("std");
const Io = std.Io;
const Iface = @import("zig_interface_testing");
const HelperIMPL = struct {
const Circle = struct {
const Self = @This();
radius: f32,
pub fn init(r: f32) Self {
return Self{
.radius = r,
};
}
pub fn perimeter(self: Self) f32 {
return std.math.pi * self.radius * 2;
}
pub fn area(self: Self) f32 {
return std.math.pi * self.radius * self.radius;
}
pub fn format(
self: Self,
writer: *std.Io.Writer,
) !void {
try writer.print("Circle : {}", .{self.radius});
}
};
const Rect = struct {
const Self = @This();
w: f32,
h: f32,
pub fn init(w: f32, h: f32) Self {
return Self{
.w = w,
.h = h,
};
}
pub fn perimeter(self: Rect) f32 {
return self.h * 2 + self.w * 2;
}
pub fn area(self: Self) f32 {
return self.h * self.w;
}
pub fn format(
self: Self,
writer: *std.Io.Writer,
) !void {
try writer.print("sqaure : {}, {}", .{ self.h, self.w });
}
};
const Shape = struct {
const Self = @This();
data: *anyopaque,
vtable: *const Vtable,
const Vtable = struct {
perimeter_fn: *const Iface.VtableFn(fn (*anyopaque) f32), // -> *const fn (tuple) returnType
area_fn: *const Iface.VtableFn(fn (*anyopaque) f32),
format_fn: *const Iface.VtableFn(fn (self: *anyopaque, writer: *std.Io.Writer) anyerror!void),
};
pub fn init(shape: anytype) Self {
std.debug.print("shape : {*}\n", .{shape});
const self = Self{
.data = @ptrCast(shape),
.vtable = &Vtable{
.perimeter_fn = Iface.ToVtableFn(@field(@TypeOf(shape.*), "perimeter")),
.area_fn = Iface.ToVtableFn(@field(@TypeOf(shape.*), "area")),
.format_fn = Iface.ToVtableFn(@field(@TypeOf(shape.*), "format")),
},
};
return self;
}
pub fn perimeter(self: Self) f32 {
return self.vtable.perimeter_fn(.{self.data});
}
pub fn area(self: Self) f32 {
return self.vtable.area_fn(.{self.data});
}
pub fn format(
self: Self,
writer: *std.Io.Writer,
) !void {
self.vtable.format_fn(.{ self.data, writer }) catch {
return std.Io.Writer.Error.WriteFailed;
};
}
};
};
const NormalIMPL = struct {
const Circle = struct {
const Self = @This();
radius: f32,
pub fn init(r: f32) Self {
return Self{
.radius = r,
};
}
pub fn perimeter(self: *anyopaque) f32 {
const cric: *Self = @ptrCast(@alignCast(self));
return std.math.pi * cric.radius * 2;
}
pub fn area(self: *anyopaque) f32 {
const cric: *Self = @ptrCast(@alignCast(self));
return std.math.pi * cric.radius * cric.radius;
}
pub fn format(
self: *anyopaque,
writer: *std.Io.Writer,
) !void {
const cric: *Self = @ptrCast(@alignCast(self));
try writer.print("Circle : {}", .{cric.radius});
}
};
const Rect = struct {
const Self = @This();
w: f32,
h: f32,
pub fn init(w: f32, h: f32) Self {
return Self{
.w = w,
.h = h,
};
}
pub fn perimeter(self: *anyopaque) f32 {
const rect: *Self = @ptrCast(@alignCast(self));
return rect.h * 2 + rect.w * 2;
}
pub fn area(self: *anyopaque) f32 {
const rect: *Self = @ptrCast(@alignCast(self));
return rect.h * rect.w;
}
pub fn format(
self: *anyopaque,
writer: *std.Io.Writer,
) !void {
const rect: *Self = @ptrCast(@alignCast(self));
try writer.print("sqaure : {}, {}", .{ rect.h, rect.w });
}
};
const Shape = struct {
const Self = @This();
data: *anyopaque,
vtable: *const Vtable,
const Vtable = struct {
perimeter_fn: *const fn (*anyopaque) f32, // -> *const fn (tuple) returnType
area_fn: *const fn (*anyopaque) f32,
format_fn: *const fn (self: *anyopaque, writer: *std.Io.Writer) anyerror!void,
};
pub fn init(shape: anytype) Self {
std.debug.print("shape : {*}\n", .{shape});
const self = Self{
.data = @ptrCast(shape),
.vtable = &Vtable{
.perimeter_fn = @field(@TypeOf(shape.*), "perimeter"),
.area_fn = @field(@TypeOf(shape.*), "area"),
.format_fn = @field(@TypeOf(shape.*), "format"),
},
};
return self;
}
pub fn perimeter(self: Self) f32 {
return self.vtable.perimeter_fn(self.data);
}
pub fn area(self: Self) f32 {
return self.vtable.area_fn(self.data);
}
pub fn format(
self: Self,
writer: *std.Io.Writer,
) !void {
self.vtable.format_fn(self.data, writer) catch {
return std.Io.Writer.Error.WriteFailed;
};
}
};
};
pub fn main(init: std.process.Init) !void {
_ = init;
var rect = HelperIMPL.Rect{
.h = 5,
.w = 10,
};
var circle = HelperIMPL.Circle{
.radius = 10,
};
var rect_shape = HelperIMPL.Shape.init(&rect);
var circle_shape = HelperIMPL.Shape.init(&circle);
std.debug.print("rect : area {}, prim : {}, {any} {*}.\n", .{ rect.area(), rect.perimeter(), rect, &rect });
std.debug.print("rect_shape : area {}, prim : {}, {f}.\n", .{ rect_shape.area(), rect_shape.perimeter(), rect_shape });
std.debug.print("circle : area {}, prim : {}, {any} {*}.\n", .{ circle.area(), circle.perimeter(), circle, &circle });
std.debug.print("circle_shape : area {}, prim : {}, {f}.\n", .{ circle_shape.area(), circle_shape.perimeter(), circle_shape });
}

149
src/root.zig Normal file
View File

@@ -0,0 +1,149 @@
//! This a is a meta library that implements 2 functions:
//! - VtableFn which simply converts a given function like `fn(arg1: u32, arg2: u8) f32` -> `fn(args : struct{u32, u8}) f32`
//! - ToVtableFn which wraps the passed function to be the form of VtableFn
//! using these you can implement interface with less boilerplate
//!
//!
//! an example can be seen below:
//! ```zig
//!
//! const A = struct {
//! f: u8,
//!
//! pub fn z(a: A) u8 {
//! return a + 2;
//! }
//! };
//!
//! const B = struct {
//! f: u8,
//!
//! pub fn z(b: B) u8 {
//! return b + 3;
//! }
//! };
//!
//! const C = struct {
//! data: *anyopaque,
//! vtable: *const VTable,
//!
//! const VTable = struct {
//! z_fn: *const VtableFn(fn (*anyopaque) u8),
//! };
//!
//! pub fn init(letter: anytype) C {
//! return C{
//! .data = @ptrCast(letter),
//! .vtable = VTable{
//! .z_fn = ToVtableFn(@field(@TypeOf(letter.*), "z")),
//! },
//! };
//! }
//!
//! pub fn z(c: C) u8 {
//! return c.vtable.z_fn(.{c.data});
//! }
//! };
//!
//!
//! fn usage() void {
//! var a = A{
//! .f = 2;
//! }
//!
//! var b = B{
//! .f = 2;
//! }
//! // yes this has to be a pointer
//! var c : C = C.init(&a);
//! _ = c.z();
//! }
//!
//! ```
//!
//! both structs A & B implementing a function z
//! C is an interface for A & B that has some pointer to the data (data) and a vtable
//! to hold the function pointers. Within the vtable you call VtableFn on the function
//! signature you plan to implement.
//! within the init function, first the given pointer to the letter is cast to the anyopaque
//! yes this value must be passed in to the function as a pointer if you dont want to deal with @constcast
//! next the `z` function is gotten from the namespace of the letter `@field(@TypeOf(letter.*), "z")`
//! this gotten `z` function is now passed to `ToVtableFn` which returns the wrapped function.
//!
//! finally to call this function use the signature `{interface}.vtable.{function_name}(.{function args})`
//! where the all args for the function must be passed through as a tuple.
//!
//!
//! how `@field(@TypeOf(letter.*), "z")` works:
//! - `letter.*` gets the plain type not the pointer to it
//! - `@TypeOf(_)` get the type that the defernced letter is
//! - `@field(_. "z")` finally gets the function (no the pointer to the function the function itself)
//!
const std = @import("std");
/// all this function
pub fn VtableFn(function: anytype) type {
return @Fn(
&.{std.meta.ArgsTuple(function)},
&.{.{}},
@typeInfo(function).@"fn".return_type orelse void,
.{
.@"callconv" = @typeInfo(function).@"fn".calling_convention,
.varargs = @typeInfo(function).@"fn".is_var_args,
},
);
}
fn VtableFnArgs(function: anytype) []const type {
var new_tuple_type: []const type = &.{
*anyopaque,
};
// @compileLog(@TypeOf(function));
inline for (@typeInfo(std.meta.ArgsTuple(function)).@"struct".fields, 0..) |field, i| {
if (i == 0) continue; // skip the first one
new_tuple_type = new_tuple_type ++ .{field.type};
}
return new_tuple_type;
}
fn ToVtableFnType(function: anytype) type {
// still have to swap all args to *anyopaque
return @Fn(
&.{@Tuple(VtableFnArgs(function))},
&.{.{}},
@typeInfo(function).@"fn".return_type orelse void,
.{
.@"callconv" = @typeInfo(function).@"fn".calling_convention,
.varargs = @typeInfo(function).@"fn".is_var_args,
},
);
}
/// wraps a given function to be in the form of `fn(tuple) return type` where the tuple will always have the first field being of type `*anyopaque`
/// this wrapper internally then converts the type back to that as in the orignal function and calls it
pub fn ToVtableFn(comptime function: anytype) fn (@Tuple(VtableFnArgs(@TypeOf(function)))) (@typeInfo(@TypeOf(function)).@"fn".return_type orelse void) {
// @compileLog(@Tuple(VtableFnArgs(@TypeOf(function))));
return struct { // | these args are the ones that replace the @this with *anyopaque
pub fn functionWrapper(args: @Tuple(VtableFnArgs(@TypeOf(function)))) (@typeInfo(@TypeOf(function)).@"fn".return_type orelse void) {
const orignal_type = @typeInfo(@TypeOf(function)).@"fn".params[0].type orelse @compileError("function need at least one type");
var type_casted_args: std.meta.ArgsTuple(@TypeOf(function)) = undefined;
// yeah i have to do this as args isnt a pointer according to zig (even though it is)
type_casted_args[0] = @as(**orignal_type, @ptrCast(@alignCast(@constCast(&args[0])))).*.*;
inline for (args, 0..) |arg, i| {
if (i == 0) continue; // skip the first one
type_casted_args[i] = arg;
}
// | these args are the ones where its converted back to the original type
return @call(.auto, function, type_casted_args);
}
}.functionWrapper;
}