update: add delta, util, meta
This commit is contained in:
parent
c4319f5da3
commit
c7d52a0f32
4 changed files with 607 additions and 0 deletions
68
src/delta.zig
Normal file
68
src/delta.zig
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Em time.zig (ou crie um novo arquivo relativedelta.zig e importe)
|
||||||
|
|
||||||
|
pub const RelativeDelta = struct {
|
||||||
|
years: i32 = 0,
|
||||||
|
months: i32 = 0,
|
||||||
|
days: i32 = 0,
|
||||||
|
hours: i32 = 0,
|
||||||
|
minutes: i32 = 0,
|
||||||
|
seconds: i32 = 0,
|
||||||
|
|
||||||
|
pub fn init(fields: struct {
|
||||||
|
years: i32 = 0,
|
||||||
|
months: i32 = 0,
|
||||||
|
days: i32 = 0,
|
||||||
|
hours: i32 = 0,
|
||||||
|
minutes: i32 = 0,
|
||||||
|
seconds: i32 = 0,
|
||||||
|
}) RelativeDelta {
|
||||||
|
return .{
|
||||||
|
.years = fields.years,
|
||||||
|
.months = fields.months,
|
||||||
|
.days = fields.days,
|
||||||
|
.hours = fields.hours,
|
||||||
|
.minutes = fields.minutes,
|
||||||
|
.seconds = fields.seconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers úteis (muito usados depois)
|
||||||
|
pub fn isZero(self: RelativeDelta) bool {
|
||||||
|
return self.years == 0 and
|
||||||
|
self.months == 0 and
|
||||||
|
self.days == 0 and
|
||||||
|
self.hours == 0 and
|
||||||
|
self.minutes == 0 and
|
||||||
|
self.seconds == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize(self: *RelativeDelta) void {
|
||||||
|
// Normaliza meses → anos + meses
|
||||||
|
if (self.months >= 12 or self.months <= -12) {
|
||||||
|
const carry = @divTrunc(self.months, 12);
|
||||||
|
self.years += carry;
|
||||||
|
self.months -= carry * 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normaliza segundos → minutos + segundos
|
||||||
|
if (self.seconds >= 60 or self.seconds <= -60) {
|
||||||
|
const carry = @divTrunc(self.seconds, 60);
|
||||||
|
self.minutes += carry;
|
||||||
|
self.seconds -= carry * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normaliza minutos → horas + minutos
|
||||||
|
if (self.minutes >= 60 or self.minutes <= -60) {
|
||||||
|
const carry = @divTrunc(self.minutes, 60);
|
||||||
|
self.hours += carry;
|
||||||
|
self.minutes -= carry * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normaliza horas → dias + horas
|
||||||
|
if (self.hours >= 24 or self.hours <= -24) {
|
||||||
|
const carry = @divTrunc(self.hours, 24);
|
||||||
|
self.days += carry;
|
||||||
|
self.hours -= carry * 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
182
src/delta_test.zig
Normal file
182
src/delta_test.zig
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
const Time = @import("time.zig").Time;
|
||||||
|
const Context = @import("context.zig").Context;
|
||||||
|
const RelativeDelta = @import("delta.zig").RelativeDelta;
|
||||||
|
|
||||||
|
test "relativedelta rigoroso - meses" {
|
||||||
|
const a = try Time.parse("2025-03-31");
|
||||||
|
const b = try Time.parse("2025-01-31");
|
||||||
|
const delta = a.subRelative(b);
|
||||||
|
try testing.expectEqual(delta.months, 2);
|
||||||
|
try testing.expectEqual(delta.years, 0);
|
||||||
|
try testing.expectEqual(delta.days, 0);
|
||||||
|
|
||||||
|
const rev = b.subRelative(a);
|
||||||
|
try testing.expectEqual(rev.months, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "relativedelta - overflow de dia" {
|
||||||
|
const jan31 = try Time.parse("2023-01-31");
|
||||||
|
const mar01 = try Time.parse("2023-03-01");
|
||||||
|
|
||||||
|
const delta = mar01.subRelative(jan31);
|
||||||
|
// Esperado algo como: +1 mês +1 dia (ou +2 meses -30 dias, mas dateutil prefere o primeiro)
|
||||||
|
try testing.expect(delta.months == 1);
|
||||||
|
try testing.expect(delta.days == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bissexto: 2021-02-28 - 2020-02-29" {
|
||||||
|
const a = try Time.parse("2021-02-28");
|
||||||
|
const b = try Time.parse("2020-02-29");
|
||||||
|
const delta = a.subRelative(b);
|
||||||
|
try testing.expectEqual(delta.years, 1);
|
||||||
|
try testing.expectEqual(delta.months, 0);
|
||||||
|
try testing.expectEqual(delta.days, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bissexto: 2021-03-01 - 2020-02-29" {
|
||||||
|
const a = try Time.parse("2021-03-01");
|
||||||
|
const b = try Time.parse("2020-02-29");
|
||||||
|
const delta = a.subRelative(b);
|
||||||
|
try testing.expectEqual(delta.years, 1);
|
||||||
|
try testing.expectEqual(delta.months, 0);
|
||||||
|
try testing.expectEqual(delta.days, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bissexto: 2021-02-27 - 2020-02-29" {
|
||||||
|
const a = try Time.parse("2021-02-27");
|
||||||
|
const b = try Time.parse("2020-02-29");
|
||||||
|
const delta = a.subRelative(b);
|
||||||
|
try testing.expectEqual(delta.years, 0);
|
||||||
|
try testing.expectEqual(delta.months, 11);
|
||||||
|
try testing.expectEqual(delta.days, 29);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bissexto reverso: 2020-02-29 - 2021-02-28" {
|
||||||
|
const a = try Time.parse("2020-02-29");
|
||||||
|
const b = try Time.parse("2021-02-28");
|
||||||
|
const delta = a.subRelative(b);
|
||||||
|
try testing.expectEqual(delta.years, -1);
|
||||||
|
try testing.expectEqual(delta.months, 0);
|
||||||
|
try testing.expectEqual(delta.days, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: anos normais (não bissexto)" {
|
||||||
|
// 2023 não é bissexto
|
||||||
|
const base = try Time.parse("2023-06-15");
|
||||||
|
const expected = try Time.parse("2026-06-15");
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .years = 3 });
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try expected.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: anos normais com overflow de dia" {
|
||||||
|
// Janeiro 31 + 2 anos → deve ir para 31/jan (2025 não bissexto)
|
||||||
|
const base = try Time.parse("2023-01-31");
|
||||||
|
const expected = try Time.parse("2025-01-31");
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .years = 2 });
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try expected.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: de 29/fev bissexto + 1 ano (vai para 28/fev)" {
|
||||||
|
// 2020 foi bissexto → +1 ano deve ir para 2021-02-28 (não bissexto)
|
||||||
|
const base = try Time.parse("2020-02-29");
|
||||||
|
const expected = try Time.parse("2021-02-28");
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .years = 1 });
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try expected.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: de 29/fev bissexto + 4 anos (permanece 29/fev)" {
|
||||||
|
// 2020 → 2024 (ambos bissextos)
|
||||||
|
const base = try Time.parse("2020-02-29");
|
||||||
|
const expected = try Time.parse("2024-02-29");
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .years = 4 });
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try expected.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: de 29/fev + 1 ano + 1 mês (vai para março)" {
|
||||||
|
// 2020-02-29 + 1 ano → 2021-02-28 + 1 mês → 2021-03-28
|
||||||
|
const base = try Time.parse("2020-02-29");
|
||||||
|
const expected = try Time.parse("2021-03-28");
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .years = 1, .months = 1 });
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try expected.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: meses com overflow (31 → 28/30)" {
|
||||||
|
const cases = [_]struct { base: []const u8, months: i32, expected: []const u8 }{
|
||||||
|
.{ .base = "2023-01-31", .months = 1, .expected = "2023-02-28" }, // não bissexto
|
||||||
|
.{ .base = "2024-01-31", .months = 1, .expected = "2024-02-29" }, // bissexto
|
||||||
|
.{ .base = "2023-03-31", .months = -1, .expected = "2023-02-28" },
|
||||||
|
.{ .base = "2023-01-31", .months = 2, .expected = "2023-03-31" },
|
||||||
|
.{ .base = "2023-08-31", .months = 1, .expected = "2023-09-30" }, // setembro tem 30
|
||||||
|
};
|
||||||
|
|
||||||
|
for (cases) |c| {
|
||||||
|
const base_t = try Time.parse(c.base);
|
||||||
|
const exp_t = try Time.parse(c.expected);
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .months = c.months });
|
||||||
|
const result = base_t.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try exp_t.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: combinação anos + meses + dias (bissexto envolvido)" {
|
||||||
|
// 2024-02-29 + 1 ano + 2 meses + 3 dias
|
||||||
|
// → 2025-02-28 + 2 meses → 2025-04-28 + 3 dias → 2025-05-01
|
||||||
|
const base = try Time.parse("2024-02-29");
|
||||||
|
const expected = try Time.parse("2025-05-01");
|
||||||
|
|
||||||
|
const delta = RelativeDelta.init(.{ .years = 1, .months = 2, .days = 3 });
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try expected.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "addRelative: delta zero não altera data" {
|
||||||
|
const base = try Time.parse("2025-07-20");
|
||||||
|
const delta = RelativeDelta.init(.{});
|
||||||
|
const result = base.addRelative(delta);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
try base.toString( null),
|
||||||
|
try result.toString( null),
|
||||||
|
);
|
||||||
|
}
|
||||||
208
src/meta.zig
Normal file
208
src/meta.zig
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
// https://github.com/cztomsik/tokamak
|
||||||
|
const std = @import("std");
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
// https://github.com/ziglang/zig/issues/19858#issuecomment-2370673253
|
||||||
|
// NOTE: I've tried to make it work with enum / packed struct but I was still
|
||||||
|
// getting weird "operation is runtime due to this operand" here and there
|
||||||
|
// but it should be possible because we do something similar in util.Smol
|
||||||
|
pub const TypeId = *const struct {
|
||||||
|
name: [*:0]const u8,
|
||||||
|
|
||||||
|
pub fn sname(self: *const @This()) []const u8 {
|
||||||
|
// NOTE: we can't switch (invalid record Zig 0.14.1)
|
||||||
|
if (self == tid([]const u8)) return "str";
|
||||||
|
if (self == tid(?[]const u8)) return "?str";
|
||||||
|
return shortName(std.mem.span(self.name), '.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub inline fn tid(comptime T: type) TypeId {
|
||||||
|
const H = struct {
|
||||||
|
const id: Deref(TypeId) = .{ .name = @typeName(T) };
|
||||||
|
};
|
||||||
|
return &H.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tids(comptime types: []const type) []const TypeId {
|
||||||
|
var buf = util.Buf(TypeId).initComptime(types.len);
|
||||||
|
for (types) |T| buf.push(tid(T));
|
||||||
|
return buf.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ptr to a comptime value, wrapped together with its type. We use this to
|
||||||
|
/// pass around values (including a concrete fun types!) during the Bundle
|
||||||
|
/// compilation.
|
||||||
|
pub const ComptimeVal = struct {
|
||||||
|
type: type,
|
||||||
|
ptr: *const anyopaque,
|
||||||
|
|
||||||
|
pub fn wrap(comptime val: anytype) ComptimeVal {
|
||||||
|
return .{ .type = @TypeOf(val), .ptr = @ptrCast(&val) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unwrap(self: ComptimeVal) self.type {
|
||||||
|
return @as(*const self.type, @ptrCast(@alignCast(self.ptr))).*;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn dupe(allocator: std.mem.Allocator, value: anytype) !@TypeOf(value) {
|
||||||
|
return switch (@typeInfo(@TypeOf(value))) {
|
||||||
|
.optional => try dupe(allocator, value orelse return null),
|
||||||
|
.@"struct" => |s| {
|
||||||
|
var res: @TypeOf(value) = undefined;
|
||||||
|
inline for (s.fields) |f| @field(res, f.name) = try dupe(allocator, @field(value, f.name));
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
.pointer => |p| switch (p.size) {
|
||||||
|
.slice => if (p.child == u8) allocator.dupe(p.child, value) else error.NotSupported,
|
||||||
|
else => value,
|
||||||
|
},
|
||||||
|
else => value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(allocator: std.mem.Allocator, value: anytype) void {
|
||||||
|
switch (@typeInfo(@TypeOf(value))) {
|
||||||
|
.optional => if (value) |v| free(allocator, v),
|
||||||
|
.@"struct" => |s| {
|
||||||
|
inline for (s.fields) |f| free(allocator, @field(value, f.name));
|
||||||
|
},
|
||||||
|
.pointer => |p| switch (p.size) {
|
||||||
|
.slice => if (p.child == u8) allocator.free(value),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upcast(context: anytype, comptime T: type) T {
|
||||||
|
return .{
|
||||||
|
.context = context,
|
||||||
|
.vtable = comptime brk: {
|
||||||
|
const Impl = Deref(@TypeOf(context));
|
||||||
|
var vtable: T.VTable = undefined;
|
||||||
|
for (std.meta.fields(T.VTable)) |f| {
|
||||||
|
@field(vtable, f.name) = @ptrCast(&@field(Impl, f.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = vtable;
|
||||||
|
break :brk ©
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Return(comptime fun: anytype) type {
|
||||||
|
return switch (@typeInfo(@TypeOf(fun))) {
|
||||||
|
.@"fn" => |f| f.return_type.?,
|
||||||
|
else => @compileError("Expected a function, got " ++ @typeName(@TypeOf(fun))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Result(comptime fun: anytype) type {
|
||||||
|
const R = Return(fun);
|
||||||
|
|
||||||
|
return switch (@typeInfo(R)) {
|
||||||
|
.error_union => |r| r.payload,
|
||||||
|
else => R,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn LastArg(comptime fun: anytype) type {
|
||||||
|
const params = @typeInfo(@TypeOf(fun)).@"fn".params;
|
||||||
|
return params[params.len - 1].type.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isStruct(comptime T: type) bool {
|
||||||
|
return @typeInfo(T) == .@"struct";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isTuple(comptime T: type) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.@"struct" => |s| s.is_tuple,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isGeneric(comptime fun: anytype) bool {
|
||||||
|
return @typeInfo(@TypeOf(fun)).@"fn".is_generic;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isOptional(comptime T: type) bool {
|
||||||
|
return @typeInfo(T) == .optional;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isOnePtr(comptime T: type) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.pointer => |p| p.size == .one,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isSlice(comptime T: type) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.pointer => |p| p.size == .slice,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn isString(comptime T: type) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.pointer => |ptr| ptr.child == u8 or switch (@typeInfo(ptr.child)) {
|
||||||
|
.array => |arr| arr.child == u8,
|
||||||
|
else => false,
|
||||||
|
},
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Deref(comptime T: type) type {
|
||||||
|
return if (isOnePtr(T)) std.meta.Child(T) else T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Unwrap(comptime T: type) type {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.optional => |o| o.child,
|
||||||
|
else => T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Const(comptime T: type) type {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.pointer => |p| {
|
||||||
|
var info = p;
|
||||||
|
info.is_const = true;
|
||||||
|
return @Type(.{ .pointer = info });
|
||||||
|
},
|
||||||
|
else => T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn hasDecl(comptime T: type, comptime name: []const u8) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.@"struct", .@"union", .@"enum", .@"opaque" => @hasDecl(T, name),
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fieldTypes(comptime T: type) []const type {
|
||||||
|
const fields = std.meta.fields(T);
|
||||||
|
var buf = util.Buf(type).initComptime(fields.len);
|
||||||
|
for (fields) |f| buf.push(f.type);
|
||||||
|
return buf.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fnParams(comptime fun: anytype) []const type {
|
||||||
|
const info = @typeInfo(@TypeOf(fun));
|
||||||
|
if (info != .@"fn") @compileError("Expected a function, got " ++ @typeName(@TypeOf(fun)));
|
||||||
|
|
||||||
|
const params = info.@"fn".params;
|
||||||
|
var buf = util.Buf(type).initComptime(params.len);
|
||||||
|
for (params) |param| buf.push(param.type.?);
|
||||||
|
return buf.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move somewhere else?
|
||||||
|
fn shortName(name: []const u8, delim: u8) []const u8 {
|
||||||
|
return if (std.mem.lastIndexOfScalar(u8, name, delim)) |i| name[i + 1 ..] else name;
|
||||||
|
}
|
||||||
149
src/util.zig
Normal file
149
src/util.zig
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
// https://github.com/cztomsik/tokamak
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const whitespace = std.ascii.whitespace;
|
||||||
|
|
||||||
|
pub fn plural(n: i64, singular: []const u8, plural_str: []const u8) []const u8 {
|
||||||
|
return if (n == 1) singular else plural_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(slice: []const u8) []const u8 {
|
||||||
|
return std.mem.trim(u8, slice, &whitespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn truncateEnd(text: []const u8, width: usize) []const u8 {
|
||||||
|
return if (text.len <= width) text else text[text.len - width ..];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn truncateStart(text: []const u8, width: usize) []const u8 {
|
||||||
|
return if (text.len <= width) text else text[0..width];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn countScalar(comptime T: type, slice: []const T, value: T) usize {
|
||||||
|
var n: usize = 0;
|
||||||
|
for (slice) |c| {
|
||||||
|
if (c == value) n += 1;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Cmp(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
// TODO: can we somehow flatten the anytype?
|
||||||
|
// pub const cmp = if (std.meta.hasMethod(T, "cmp")) T.cmp else std.math.order;
|
||||||
|
|
||||||
|
pub fn cmp(a: T, b: T) std.math.Order {
|
||||||
|
if (std.meta.hasMethod(T, "cmp")) {
|
||||||
|
return a.cmp(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std.math.order(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lt(a: T, b: T) bool {
|
||||||
|
return @This().cmp(a, b) == .lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eq(a: T, b: T) bool {
|
||||||
|
return @This().cmp(a, b) == .eq;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gt(a: T, b: T) bool {
|
||||||
|
return @This().cmp(a, b) == .gt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmp(a: anytype, b: @TypeOf(a)) std.math.Order {
|
||||||
|
return Cmp(@TypeOf(a)).cmp(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lt(a: anytype, b: @TypeOf(a)) bool {
|
||||||
|
return Cmp(@TypeOf(a)).lt(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eq(a: anytype, b: @TypeOf(a)) bool {
|
||||||
|
return Cmp(@TypeOf(a)).eq(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gt(a: anytype, b: @TypeOf(a)) bool {
|
||||||
|
return Cmp(@TypeOf(a)).gt(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// ---------------------------------------------------------------
|
||||||
|
// /// 1️⃣ Detecta se um tipo é std.ArrayList(T)
|
||||||
|
// /// ---------------------------------------------------------------
|
||||||
|
// fn isArrayList(comptime T: type) bool {
|
||||||
|
// // @typeName devolve a string completa, por exemplo:
|
||||||
|
// // "std.ArrayList(i32)" ou "std.ArrayList([]const u8)"
|
||||||
|
// const name = @typeName(T);
|
||||||
|
// // Queremos garantir que o prefixo seja exatamente "std.ArrayList("
|
||||||
|
// // (inclui o parêntese de abertura para evitar colisões com nomes
|
||||||
|
// // semelhantes, como "my_std.ArrayListHelper").
|
||||||
|
// // return std.mem.startsWith(u8, name, "std.ArrayList(");
|
||||||
|
// return std.mem.startsWith(u8, name, "array_list");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// ---------------------------------------------------------------
|
||||||
|
// /// 2️⃣ Obtém o tipo dos elementos armazenados em std.ArrayList(T)
|
||||||
|
// /// ---------------------------------------------------------------
|
||||||
|
// fn arrayListElemType(comptime ListT: type) type {
|
||||||
|
// // Sabemos que ListT tem a forma std.ArrayList(T). O primeiro campo
|
||||||
|
// // interno do struct é `items: []T`. Vamos ler esse campo.
|
||||||
|
// const ti = @typeInfo(ListT);
|
||||||
|
// if (ti != .@"struct") @compileError("Esperado um struct");
|
||||||
|
//
|
||||||
|
// // O campo `items` está na posição 0 da lista de fields:
|
||||||
|
// const items_field = ti.@"struct".fields[0];
|
||||||
|
// // Seu tipo é []T (slice). Em Zig, slices são representados como
|
||||||
|
// // ponteiros (`*T`) com comprimento separado, mas o tipo declarado
|
||||||
|
// // aqui é exatamente `[]T`, que corresponde a .pointer com
|
||||||
|
// // .is_slice = true.
|
||||||
|
// const slice_type = items_field.type;
|
||||||
|
// const slice_info = @typeInfo(slice_type);
|
||||||
|
// if (slice_info != .pointer) @compileError("Campo `items` não é slice");
|
||||||
|
//
|
||||||
|
// // O tipo filho da slice é o T que procuramos.
|
||||||
|
// return slice_info.pointer.child;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// ---------------------------------------------------------------
|
||||||
|
// /// 3️⃣ Função genérica que aceita *qualquer* tipo e age
|
||||||
|
// /// de acordo se o argumento for um ArrayList ou não.
|
||||||
|
// /// ---------------------------------------------------------------
|
||||||
|
// pub fn handle(comptime ArgT: type, arg: ArgT) void {
|
||||||
|
// if (isArrayList(ArgT)) {
|
||||||
|
// // É um ArrayList – descobrimos o tipo dos elementos.
|
||||||
|
// const Elem = arrayListElemType(ArgT);
|
||||||
|
//
|
||||||
|
// // Exemplo de uso genérico: imprimir tamanho e tipo dos itens.
|
||||||
|
// std.debug.print(
|
||||||
|
// "Recebi um std.ArrayList<{s}> contendo {d} itens.\n",
|
||||||
|
// .{ @typeName(Elem), arg.items.len },
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// // // Iterar de forma genérica (não sabemos o tipo exato de Elem,
|
||||||
|
// // // então só fazemos operações que são válidas para *qualquer* tipo).
|
||||||
|
// // var it = arg.iterator();
|
||||||
|
// // while (it.next()) |item| {
|
||||||
|
// // // `item` tem tipo `Elem`. Se precisar de lógica específica,
|
||||||
|
// // // pode fazer outra inspeção de tipo aqui.
|
||||||
|
// // _ = item; // evita warning de variável não usada
|
||||||
|
// // }
|
||||||
|
// for(arg.items) |item| {
|
||||||
|
// _=item;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // Não é um ArrayList – apenas informamos o tipo real.
|
||||||
|
// std.debug.print(
|
||||||
|
// "O argumento NÃO é um std.ArrayList (é {s}).\n",
|
||||||
|
// .{@typeName(ArgT)},
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
test {
|
||||||
|
try std.testing.expect(lt(1, 2));
|
||||||
|
try std.testing.expect(eq(2, 2));
|
||||||
|
try std.testing.expect(gt(2, 1));
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue