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]); }