146 lines
5.1 KiB
Zig
146 lines
5.1 KiB
Zig
const std = @import("std");
|
|
|
|
pub const Fmt = union(enum) {
|
|
int: IntFmt,
|
|
float: FloatFmt,
|
|
generic: void,
|
|
|
|
pub fn parse(T: type, comptime fmt_: []const u8) Fmt {
|
|
return switch (@typeInfo(T)) {
|
|
.float, .comptime_float => .{ .float = FloatFmt.parseComptime(fmt_) },
|
|
.int, .comptime_int => .{ .int = IntFmt.parseComptime(fmt_) },
|
|
else => .{ .generic = {} },
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const FullFormatOptions = struct {
|
|
fmt: Fmt,
|
|
options: std.fmt.FormatOptions,
|
|
};
|
|
|
|
pub const IntFmt = struct {
|
|
base: u8,
|
|
case: std.fmt.Case = .lower,
|
|
|
|
pub fn parseComptime(comptime fmt_: []const u8) IntFmt {
|
|
return parse(fmt_) catch @panic("invalid fmt for int: " ++ fmt_);
|
|
}
|
|
|
|
pub fn parse(fmt_: []const u8) error{InvalidArgument}!IntFmt {
|
|
return if (fmt_.len == 0 or std.mem.eql(u8, fmt_, "d"))
|
|
.{ .base = 10, .case = .lower }
|
|
else if (std.mem.eql(u8, fmt_, "x"))
|
|
.{ .base = 16, .case = .lower }
|
|
else if (std.mem.eql(u8, fmt_, "X"))
|
|
.{ .base = 16, .case = .upper }
|
|
else if (std.mem.eql(u8, fmt_, "o"))
|
|
.{ .base = 8, .case = .upper }
|
|
else
|
|
// TODO: unicode/ascii
|
|
error.InvalidArgument;
|
|
}
|
|
};
|
|
|
|
pub const FloatFmt = enum(u8) {
|
|
scientific = @intFromEnum(std.fmt.Number.Mode.scientific),
|
|
decimal = @intFromEnum(std.fmt.Number.Mode.decimal),
|
|
hex,
|
|
|
|
pub fn parseComptime(comptime fmt_: []const u8) FloatFmt {
|
|
return parse(fmt_) catch @panic("invalid fmt for float: " ++ fmt_);
|
|
}
|
|
|
|
pub fn parse(fmt_: []const u8) error{InvalidArgument}!FloatFmt {
|
|
return if (fmt_.len == 0 or std.mem.eql(u8, fmt_, "e"))
|
|
.scientific
|
|
else if (std.mem.eql(u8, fmt_, "d"))
|
|
.decimal
|
|
else if (std.mem.eql(u8, fmt_, "x"))
|
|
.hex
|
|
else
|
|
error.InvalidArgument;
|
|
}
|
|
};
|
|
|
|
pub fn formatValue(value: anytype, full: FullFormatOptions, writer: anytype) !void {
|
|
return switch (@typeInfo(@TypeOf(value))) {
|
|
.comptime_float, .float => try formatFloatValue(value, full, writer),
|
|
.comptime_int, .int => try formatIntValue(value, full, writer),
|
|
else => try formatAnyValue(value, full, writer),
|
|
};
|
|
}
|
|
|
|
pub fn formatFloatValue(value: anytype, full: FullFormatOptions, writer: *std.Io.Writer) !void {
|
|
const x = switch (@typeInfo(@TypeOf(value))) {
|
|
.@"struct" => value.toF32(),
|
|
.float => value,
|
|
else => @compileError("formatFloatValue expects a float, got: " ++ @typeName(@TypeOf(value))),
|
|
};
|
|
try switch (full.fmt.float) {
|
|
.scientific => writer.printFloat(x, .{ .mode = .scientific, .precision = full.options.precision }),
|
|
.decimal => writer.printFloat(x, .{ .mode = .decimal, .precision = full.options.precision }),
|
|
.hex => writer.printFloatHexOptions(x, .{ .mode = .hex }),
|
|
};
|
|
}
|
|
|
|
pub fn formatIntValue(value: anytype, full: FullFormatOptions, writer: *std.Io.Writer) !void {
|
|
switch (@typeInfo(@TypeOf(value))) {
|
|
.int => {},
|
|
else => @compileError("formatIntValue expects an int, got: " ++ @typeName(@TypeOf(value))),
|
|
}
|
|
return writer.printInt(value, full.fmt.int.base, full.fmt.int.case, full.options);
|
|
}
|
|
|
|
pub fn formatAnyValue(value: anytype, full: FullFormatOptions, writer: *std.Io.Writer) !void {
|
|
var buf: [48]u8 = undefined;
|
|
const s = std.fmt.bufPrint(&buf, "{any}", .{value}) catch blk: {
|
|
buf[45..].* = "...".*;
|
|
break :blk buf[0..];
|
|
};
|
|
return try writer.alignBufferOptions(s, full.options);
|
|
}
|
|
|
|
pub fn formatSliceCustom(fmt_func: anytype, values: anytype, full: FullFormatOptions, writer: anytype) !void {
|
|
|
|
// Write first rows
|
|
const num_cols: usize = full.options.width orelse 12;
|
|
const n: usize = values.len;
|
|
_ = try writer.write("{");
|
|
if (n <= num_cols) {
|
|
for (values, 0..) |v, i| {
|
|
// Force inlining so that the switch and the buffer can be done once.
|
|
try @call(.always_inline, fmt_func, .{ v, full, writer });
|
|
if (i < n - 1) _ = try writer.write(",");
|
|
}
|
|
} else {
|
|
const half = @divFloor(num_cols, 2);
|
|
for (values[0..half]) |v| {
|
|
try @call(.always_inline, fmt_func, .{ v, full, writer });
|
|
_ = try writer.write(",");
|
|
}
|
|
_ = try writer.write(" ..., ");
|
|
for (values[n - half ..], 0..) |v, i| {
|
|
try @call(.always_inline, fmt_func, .{ v, full, writer });
|
|
if (i < half - 1) _ = try writer.write(",");
|
|
}
|
|
}
|
|
_ = try writer.write("}");
|
|
}
|
|
|
|
pub fn formatAny(values: anytype, full: FullFormatOptions, writer: anytype) !void {
|
|
return try formatSliceCustom(formatAnyValue, values, full, writer);
|
|
}
|
|
|
|
pub fn formatFloatSlice(values: anytype, full: FullFormatOptions, writer: anytype) !void {
|
|
return try formatSliceCustom(formatFloatValue, values, full, writer);
|
|
}
|
|
|
|
pub fn formatIntSlice(values: anytype, full: FullFormatOptions, writer: anytype) !void {
|
|
return try formatSliceCustom(formatIntValue, values, full, writer);
|
|
}
|
|
|
|
pub fn formatAnySlice(values: anytype, full: FullFormatOptions, writer: anytype) !void {
|
|
return try formatSliceCustom(formatAnyValue, values, full, writer);
|
|
}
|