ona/source/coral/utf8.zig

299 lines
6.6 KiB
Zig
Raw Normal View History

2023-06-02 23:43:53 +02:00
const debug = @import("./debug.zig");
2023-05-28 03:19:46 +02:00
2023-05-23 02:08:34 +02:00
const io = @import("./io.zig");
2023-04-19 01:25:35 +02:00
const math = @import("./math.zig");
2023-06-02 23:43:53 +02:00
const std = @import("std");
///
///
///
2023-06-03 16:04:42 +02:00
pub const DecimalFormat = struct {
delimiter: []const u8 = "",
positive_prefix: enum {none, plus, space} = .none,
2023-06-02 23:43:53 +02:00
};
///
///
///
2023-06-03 16:04:42 +02:00
pub const HexadecimalFormat = struct {
2023-05-28 03:19:46 +02:00
delimiter: []const u8 = "",
2023-06-03 16:04:42 +02:00
positive_prefix: enum {none, plus, space} = .none,
casing: enum {lower, upper} = .lower,
2023-05-28 03:19:46 +02:00
};
///
/// Errors that may occur during any kind of utf8-encoded parsing.
///
2023-04-19 01:25:35 +02:00
pub const ParseError = error {
BadSyntax,
};
2023-05-28 03:19:46 +02:00
///
/// Errors that may occur during any kind of utf8-encoded printing.
///
pub const PrintError = error {
PrintFailed,
PrintIncomplete,
};
///
///
///
2023-06-03 16:04:42 +02:00
pub fn parse_decimal(comptime Decimal: type, utf8: []const u8, format: DecimalFormat) !Decimal {
2023-05-28 03:19:46 +02:00
if (utf8.len == 0) {
return error.BadSyntax;
}
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
switch (@typeInfo(Decimal)) {
.Int => |int| {
var has_sign = switch (utf8[0]) {
'-', '+', ' ' => true,
else => false,
};
var result = @as(Decimal, 0);
for (@boolToInt(has_sign) .. utf8.len) |index| {
const radix = 10;
const code = utf8[index];
switch (code) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
result = try math.checked_add(
try math.checked_mul(result, radix),
try math.checked_sub(code, '0'));
},
else => {
if (format.delimiter.len == 0 or !io.equals(format.delimiter, utf8[index ..])) {
return error.BadSyntax;
}
},
}
}
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
switch (int.signedness) {
.signed => {
return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1);
},
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
.unsigned => {
if (has_sign and utf8[0] == '-') {
return error.OutOfMemory;
}
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
return result;
},
}
2023-04-19 01:25:35 +02:00
},
2023-06-03 16:04:42 +02:00
.Float => {
// ""
if (utf8.len == 0) {
return error.BadSyntax;
}
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
var has_sign = switch (utf8[0]) {
'-', '+', ' ' => true,
else => false,
};
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
// "-"
if (has_sign and utf8.len == 1) {
return error.BadSyntax;
}
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
const sign_offset = @boolToInt(has_sign);
var has_decimal = utf8[sign_offset] == '.';
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
// "-."
if (has_decimal and (utf8.len == 2)) {
return error.BadSyntax;
}
2023-05-28 03:19:46 +02:00
2023-06-03 16:04:42 +02:00
var result = @as(Decimal, 0);
var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1);
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
for (utf8[0 .. (sign_offset + @boolToInt(has_decimal))]) |code| switch (code) {
'.' => {
if (has_decimal) return error.BadSyntax;
2023-05-28 03:19:46 +02:00
2023-06-03 16:04:42 +02:00
has_decimal = true;
},
2023-05-28 03:19:46 +02:00
2023-06-03 16:04:42 +02:00
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
if (has_decimal) factor /= 10.0;
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
result = ((result * 10.0) + @intToFloat(Decimal, code - '0'));
},
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
else => return error.BadSyntax,
};
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
return result * factor;
},
2023-04-19 01:25:35 +02:00
2023-06-03 16:04:42 +02:00
else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"),
2023-04-19 01:25:35 +02:00
}
}
2023-05-23 02:08:34 +02:00
2023-05-28 03:19:46 +02:00
///
/// Attempts to print `utf8` to `writer`.
///
/// The function returns [PrintError] if the write failed to complete partially or entirely.
///
pub fn print(writer: io.Writer, utf8: []const u8) PrintError!void {
if ((writer.invoke(utf8) orelse return error.PrintFailed) != utf8.len) {
return error.PrintIncomplete;
}
}
2023-06-02 23:43:53 +02:00
///
///
///
2023-06-03 16:04:42 +02:00
pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void {
switch (@typeInfo(@TypeOf(arguments))) {
.Struct => |arguments_struct| {
comptime var arg_index = 0;
comptime var head = 0;
comptime var tail = 0;
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
inline while (tail < format.len) : (tail += 1) {
if (format[tail] == '{') {
if (tail > format.len) {
@compileError("expected an idenifier after opening `{`");
}
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
tail += 1;
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
switch (format[tail]) {
'{' => {
try print(writer, format[head .. (tail - 1)]);
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
tail += 1;
head = tail;
},
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
'}' => {
if (!arguments_struct.is_tuple) {
@compileError("all format specifiers must be named when using a named struct");
}
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
try print(writer, arguments[arg_index]);
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
arg_index += 1;
tail += 1;
head = tail;
},
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
else => {
if (arguments_struct.is_tuple) {
@compileError("format specifiers cannot be named when using a tuple struct");
}
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
try print(writer, format[head .. (tail - 1)]);
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
head = tail;
tail += 1;
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
if (tail >= format.len) {
@compileError("expected closing `}` or another `{` after opening `{`");
}
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
debug.assert(tail < format.len);
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
inline while (format[tail] != '}') {
tail += 1;
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
debug.assert(tail < format.len);
}
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
try print_value(writer, @field(arguments, format[head .. tail]));
2023-06-02 23:43:53 +02:00
2023-06-03 16:04:42 +02:00
tail += 1;
head = tail;
}
}
2023-06-02 23:43:53 +02:00
}
}
2023-06-03 16:04:42 +02:00
},
else => @compileError("`arguments` must be a struct type"),
2023-06-02 23:43:53 +02:00
}
}
2023-05-28 03:19:46 +02:00
///
///
///
2023-06-03 16:04:42 +02:00
pub fn print_decimal(writer: io.Writer, value: anytype, format: DecimalFormat) PrintError!void {
2023-05-28 03:19:46 +02:00
if (value == 0) {
2023-06-03 16:04:42 +02:00
return print(writer, switch (format.positive_prefix) {
.none => "0",
.plus => "+0",
.space => " 0",
});
2023-05-28 03:19:46 +02:00
}
2023-05-23 02:08:34 +02:00
2023-06-03 16:04:42 +02:00
switch (@typeInfo(@TypeOf(value))) {
.Int => |int| {
const radix = 10;
var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1));
var buffer_start = buffer.len - 1;
2023-05-23 02:08:34 +02:00
2023-06-03 16:04:42 +02:00
{
var decomposable_value = value;
2023-05-28 03:19:46 +02:00
2023-06-03 16:04:42 +02:00
while (decomposable_value != 0) : (buffer_start -= 1) {
buffer[buffer_start] = @intCast(u8, (decomposable_value % radix) + '0');
decomposable_value = (decomposable_value / radix);
}
}
2023-05-23 02:08:34 +02:00
2023-06-03 16:04:42 +02:00
if (int.signedness == .unsigned and value < 0) {
buffer[buffer_start] = '-';
} else {
switch (format.positive_prefix) {
.none => buffer_start += 1,
.plus => buffer[buffer_start] = '+',
.space => buffer[buffer_start] = ' ',
}
}
2023-05-23 02:08:34 +02:00
2023-06-03 16:04:42 +02:00
try print(writer, buffer[buffer_start ..]);
},
2023-05-23 02:08:34 +02:00
2023-06-03 16:04:42 +02:00
else => @compileError("`arguments` must be a struct type"),
2023-05-23 02:08:34 +02:00
}
2023-06-03 16:04:42 +02:00
}
pub fn print_hexadecimal(writer: io.Writer, value: anytype, format: HexadecimalFormat) PrintError!void {
// TODO: Implement.
_ = writer;
_ = value;
_ = format;
unreachable;
}
noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void {
const Value = @TypeOf(value);
return switch (@typeInfo(Value)) {
.Int => print_decimal(writer, value, .{}),
.Float => print_decimal(writer, value, .{}),
.Pointer => |pointer| switch (pointer.size) {
.One, .Many, .C => print_hexadecimal(writer, @ptrToInt(value), .{}),
.Slice => if (pointer.child == u8) print(writer, value) else @compileError(unformattableMessage(Value)),
},
else => @compileError(unformattableMessage(Value)),
};
}
2023-05-23 02:08:34 +02:00
2023-06-03 16:04:42 +02:00
fn unformattableMessage(comptime Value: type) []const u8 {
return "`" ++ @typeName(Value) ++ "` are not formattable";
2023-05-23 02:08:34 +02:00
}