196 lines
4.4 KiB
Zig
196 lines
4.4 KiB
Zig
const coral = @import("./coral.zig");
|
|
|
|
const io = @import("./io.zig");
|
|
|
|
const scalars = @import("./scalars.zig");
|
|
|
|
const std = @import("std");
|
|
|
|
pub const DecimalFormat = struct {
|
|
delimiter: []const coral.Byte,
|
|
positive_prefix: enum {none, plus, space},
|
|
|
|
pub const default = DecimalFormat{
|
|
.delimiter = "",
|
|
.positive_prefix = .none,
|
|
};
|
|
|
|
pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
|
|
if (utf8.len == 0) {
|
|
return null;
|
|
}
|
|
|
|
switch (@typeInfo(Decimal)) {
|
|
.Int => |int| {
|
|
const has_sign = switch (utf8[0]) {
|
|
'-', '+', ' ' => true,
|
|
else => false,
|
|
};
|
|
|
|
var result = @as(Decimal, 0);
|
|
|
|
for (@intFromBool(has_sign) .. utf8.len) |index| {
|
|
const radix = 10;
|
|
const code = utf8[index];
|
|
|
|
switch (code) {
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
|
const offset_code = scalars.sub(code, '0') orelse return null;
|
|
|
|
result = scalars.mul(result, radix) orelse return null;
|
|
result = scalars.add(result, offset_code) orelse return null;
|
|
},
|
|
|
|
else => {
|
|
if (self.delimiter.len == 0 or !coral.are_equal(self.delimiter, utf8[index ..])) {
|
|
return null;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
switch (int.signedness) {
|
|
.signed => {
|
|
return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1);
|
|
},
|
|
|
|
.unsigned => {
|
|
if (has_sign and utf8[0] == '-') {
|
|
return null;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
}
|
|
},
|
|
|
|
.Float => {
|
|
const has_sign = switch (utf8[0]) {
|
|
'-', '+', ' ' => true,
|
|
else => false,
|
|
};
|
|
|
|
// "-"
|
|
if (has_sign and utf8.len == 1) {
|
|
return null;
|
|
}
|
|
|
|
const sign_offset = @intFromBool(has_sign);
|
|
var has_decimal = utf8[sign_offset] == '.';
|
|
|
|
// "-."
|
|
if (has_decimal and (utf8.len == 2)) {
|
|
return null;
|
|
}
|
|
|
|
var result = @as(Decimal, 0);
|
|
var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1);
|
|
|
|
for (utf8[sign_offset + @intFromBool(has_decimal) .. utf8.len]) |code| {
|
|
switch (code) {
|
|
'.' => {
|
|
if (has_decimal) {
|
|
return null;
|
|
}
|
|
|
|
has_decimal = true;
|
|
},
|
|
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
|
if (has_decimal) {
|
|
factor /= 10.0;
|
|
}
|
|
|
|
result = ((result * 10.0) + @as(Decimal, @floatFromInt(code - '0')));
|
|
},
|
|
|
|
else => return null,
|
|
}
|
|
}
|
|
|
|
return result * factor;
|
|
},
|
|
|
|
else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be parsed from a decimal string"),
|
|
}
|
|
}
|
|
|
|
pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void {
|
|
if (value == 0) {
|
|
return io.print(writer, switch (self.positive_prefix) {
|
|
.none => "0",
|
|
.plus => "+0",
|
|
.space => " 0",
|
|
});
|
|
}
|
|
|
|
const Value = @TypeOf(value);
|
|
|
|
switch (@typeInfo(Value)) {
|
|
.Int => |int| {
|
|
const radix = 10;
|
|
var buffer = [_]u8{0} ** (1 + @max(int.bits, 1));
|
|
var buffer_start = buffer.len - 1;
|
|
|
|
{
|
|
var decomposable_value = value;
|
|
|
|
while (decomposable_value != 0) : (buffer_start -= 1) {
|
|
buffer[buffer_start] = @intCast(@mod(decomposable_value, radix) + '0');
|
|
decomposable_value = @divTrunc(decomposable_value, radix);
|
|
}
|
|
}
|
|
|
|
if (int.signedness == .unsigned and value < 0) {
|
|
buffer[buffer_start] = '-';
|
|
} else {
|
|
switch (self.positive_prefix) {
|
|
.none => buffer_start += 1,
|
|
.plus => buffer[buffer_start] = '+',
|
|
.space => buffer[buffer_start] = ' ',
|
|
}
|
|
}
|
|
|
|
return io.print(writer, buffer[buffer_start ..]);
|
|
},
|
|
|
|
.Float => |float| {
|
|
if (value < 0) {
|
|
try io.print(writer, "-");
|
|
}
|
|
|
|
const Float = @TypeOf(value);
|
|
const Int = std.meta.Int(.unsigned, float.bits);
|
|
const integer = @as(Int, @intFromFloat(value));
|
|
|
|
try self.print(writer, integer);
|
|
try io.print(writer, ".");
|
|
try self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
|
},
|
|
|
|
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"),
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const HexadecimalFormat = struct {
|
|
delimiter: []const u8 = "",
|
|
positive_prefix: enum {none, plus, space} = .none,
|
|
casing: enum {lower, upper} = .lower,
|
|
|
|
pub const default = HexadecimalFormat{
|
|
.delimiter = "",
|
|
.positive_prefix = .none,
|
|
.casing = .lower,
|
|
};
|
|
|
|
pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
|
|
// TODO: Implement.
|
|
_ = self;
|
|
_ = writer;
|
|
_ = value;
|
|
|
|
unreachable;
|
|
}
|
|
};
|