298 lines
7.1 KiB
Zig
Executable File
298 lines
7.1 KiB
Zig
Executable File
const debug = @import("./debug.zig");
|
|
|
|
const io = @import("./io.zig");
|
|
|
|
const math = @import("./math.zig");
|
|
|
|
const std = @import("std");
|
|
|
|
///
|
|
///
|
|
///
|
|
pub const ArgsContext = struct {
|
|
writer: io.Writer,
|
|
index: usize,
|
|
};
|
|
|
|
///
|
|
///
|
|
///
|
|
pub const ArgsFormatter = io.Functor(PrintError!void, ArgsContext);
|
|
|
|
///
|
|
/// Errors that may occur during utf8-encoded int parsing.
|
|
///
|
|
pub const IntParseError = math.CheckedArithmeticError || ParseError;
|
|
|
|
///
|
|
/// Optional rules for int parsing logic to consider during parsing.
|
|
///
|
|
pub const IntParseOptions = struct {
|
|
delimiter: []const u8 = "",
|
|
};
|
|
|
|
///
|
|
/// Errors that may occur during any kind of utf8-encoded parsing.
|
|
///
|
|
pub const ParseError = error {
|
|
BadSyntax,
|
|
};
|
|
|
|
///
|
|
/// Errors that may occur during any kind of utf8-encoded printing.
|
|
///
|
|
pub const PrintError = error {
|
|
PrintFailed,
|
|
PrintIncomplete,
|
|
};
|
|
|
|
pub fn format_args(args: anytype) ArgsFormatter {
|
|
const Args = @TypeOf(args);
|
|
|
|
return ArgsFormatter.bind(Args, &args, struct {
|
|
fn get(typed_args: *const Args, context: ArgsContext) PrintError!void {
|
|
_ = typed_args;
|
|
_ = context;
|
|
}
|
|
}.get);
|
|
}
|
|
|
|
///
|
|
/// Attempts to parse a float value of type described by `float` from `utf8`.
|
|
///
|
|
/// The function returns a [ParseError] if `utf8` does not conform to the syntax of a float.
|
|
///
|
|
pub fn parse_float(comptime float: std.builtin.Type.Float, utf8: []const u8) ParseError!math.Float(float) {
|
|
// ""
|
|
if (utf8.len == 0) {
|
|
return error.BadSyntax;
|
|
}
|
|
|
|
const is_negative = utf8[0] == '-';
|
|
|
|
// "-"
|
|
if (is_negative and (utf8.len == 1)) {
|
|
return error.BadSyntax;
|
|
}
|
|
|
|
const negative_offset = @boolToInt(is_negative);
|
|
var has_decimal = utf8[negative_offset] == '.';
|
|
|
|
// "-."
|
|
if (has_decimal and (utf8.len == 2)) {
|
|
return error.BadSyntax;
|
|
}
|
|
|
|
const Float = math.Float(float);
|
|
var result: Float = 0;
|
|
var factor: Float = 1;
|
|
|
|
for (utf8[0 .. negative_offset + @boolToInt(has_decimal)]) |code| switch (code) {
|
|
'.' => {
|
|
if (has_decimal) return error.BadSyntax;
|
|
|
|
has_decimal = true;
|
|
},
|
|
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
|
if (has_decimal) factor /= 10.0;
|
|
|
|
result = ((result * 10.0) + @intToFloat(Float, code - '0'));
|
|
},
|
|
|
|
else => return error.BadSyntax,
|
|
};
|
|
|
|
return result * factor;
|
|
}
|
|
|
|
///
|
|
/// Attempts to parse an int value of type described by `int` from `utf8`, with `options` as additional rules for the
|
|
/// parsing logic to consider.
|
|
///
|
|
/// The function returns a [IntParseError] if `utf8` does not conform to the syntax of a float, does not match the rules
|
|
/// specified in `option`, or exceeds the maximum size of the int described by `int`.
|
|
///
|
|
pub fn parse_int(
|
|
comptime int: std.builtin.Type.Int,
|
|
utf8: []const u8,
|
|
options: IntParseOptions) IntParseError!math.Int(int) {
|
|
|
|
if (utf8.len == 0) {
|
|
return error.BadSyntax;
|
|
}
|
|
|
|
{
|
|
const is_negative = utf8[0] == '-';
|
|
|
|
switch (int.signedness) {
|
|
.signed => {
|
|
if (is_negative and utf8.len == 1) {
|
|
return error.BadSyntax;
|
|
}
|
|
},
|
|
|
|
.unsigned => {
|
|
if (is_negative) {
|
|
return error.BadSyntax;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
var result = @as(math.Int(int), 0);
|
|
|
|
for (0 .. utf8.len) |index| {
|
|
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, 10), try math.checked_sub(code, '0'));
|
|
},
|
|
|
|
else => {
|
|
if (options.delimiter.len == 0 or !io.equals(options.delimiter, utf8[index ..])) {
|
|
return error.BadSyntax;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
///
|
|
/// 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;
|
|
}
|
|
}
|
|
|
|
///
|
|
///
|
|
///
|
|
pub fn print_float(comptime float: std.builtin.Type.Float, writer: io.Writer, value: @Type(float)) PrintError!void {
|
|
_ = writer;
|
|
_ = value;
|
|
}
|
|
|
|
///
|
|
/// Prints the format string `format` with all `{...}` placeholders substituted with the a value in `args` corresponding
|
|
/// to the ordinality of each substitution.
|
|
///
|
|
/// Specifiers may be placed inside `{}` to augment the behavior of the corresponding [FormatArg]. For example, to print
|
|
/// a float formatted with an integer component padding of `3`, a decimal padding of `2`, and a prefix for positives as
|
|
/// well as negatives, you may specify it as `{+3.2}`.
|
|
///
|
|
/// To prevent braces from being interpreted as format placeholders, simply double-brace them (`"{{"`) and the format
|
|
/// string will substitute them as a single brace.
|
|
///
|
|
/// *Note* the function assumes that, for every specifier in `format`, there is a corresponding [FormatArg] in `args`.
|
|
/// Further, any instance of `{` is assumed to be a placeholder and must be terminated with a corresponding `}` before
|
|
/// the end of string. Failure to meet any of these requirements will result in safety-checked runtime behavior.
|
|
///
|
|
pub fn print_formatted(writer: io.Writer, format: []const u8, args_formatter: ArgsFormatter) PrintError!void {
|
|
const usize_int = @typeInfo(usize).Int;
|
|
var head = @as(usize, 0);
|
|
var tail = @as(usize, 0);
|
|
var arg_index = @as(usize, 0);
|
|
|
|
while (tail < format.len) : (tail += 1) {
|
|
if (format[tail] == '{') {
|
|
debug.assert(tail < format.len);
|
|
|
|
tail += 1;
|
|
|
|
switch (format[tail]) {
|
|
'{' => {
|
|
try print(writer, format[head .. tail]);
|
|
|
|
tail += 1;
|
|
head = tail;
|
|
},
|
|
|
|
'}' => {
|
|
try print(writer, format[head .. tail]);
|
|
|
|
try args_formatter.invoke(.{
|
|
.index = arg_index,
|
|
.writer = writer,
|
|
});
|
|
|
|
arg_index += 1;
|
|
tail += 1;
|
|
head = tail;
|
|
},
|
|
|
|
else => {
|
|
try print(writer, format[head .. tail]);
|
|
|
|
tail += 1;
|
|
head = tail;
|
|
|
|
debug.assert(tail < format.len);
|
|
|
|
while (format[tail] != '}') {
|
|
tail += 1;
|
|
|
|
debug.assert(tail < format.len);
|
|
}
|
|
|
|
const arg_index_name = format[head .. tail];
|
|
|
|
try args_formatter.invoke(.{
|
|
.index = parse_int(usize_int, arg_index_name, .{}) catch @panic("invalid arg index value"),
|
|
.writer = writer,
|
|
});
|
|
|
|
tail += 1;
|
|
head = tail;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Attempts to print the int `value` described by `int` to `writer`.
|
|
///
|
|
/// The function returns [PrintError] if the write failed to complete partially or entirely.
|
|
///
|
|
pub fn print_int(comptime int: std.builtin.Type.Int, writer: io.Writer, value: math.Int(int)) PrintError!void {
|
|
if (value == 0) {
|
|
return try print(writer, "0");
|
|
}
|
|
|
|
// TODO: Don't make this buffer arbitrarily size cause big int types WILL overflow.
|
|
var buffer = [_]u8{0} ** 40;
|
|
var buffer_count: usize = 0;
|
|
var split_value = value;
|
|
|
|
if ((int.signedness == .unsigned) and (value < 0)) {
|
|
buffer[0] = '-';
|
|
buffer_count += 1;
|
|
}
|
|
|
|
while (split_value != 0) : (buffer_count += 1) {
|
|
const radix = 10;
|
|
|
|
buffer[buffer_count] = @intCast(u8, (split_value % radix) + '0');
|
|
split_value = (split_value / radix);
|
|
}
|
|
|
|
{
|
|
const half_buffer_count = buffer_count / 2;
|
|
var index: usize = 0;
|
|
|
|
while (index < half_buffer_count) : (index += 1) {
|
|
io.swap(u8, &buffer[index], &buffer[buffer_count - index - 1]);
|
|
}
|
|
}
|
|
|
|
try print(writer, buffer[0 .. buffer_count]);
|
|
}
|