2019-03-02 21:46:04 +00:00
const std = @import ( " std.zig " ) ;
2022-07-17 06:38:31 +00:00
const io = std . io ;
2017-12-24 03:08:53 +00:00
const math = std . math ;
2019-11-20 01:29:08 +00:00
const assert = std . debug . assert ;
2017-12-24 03:08:53 +00:00
const mem = std . mem ;
2020-09-21 13:59:10 +00:00
const unicode = std . unicode ;
2020-09-24 14:40:33 +00:00
const meta = std . meta ;
2021-05-17 23:08:09 +00:00
const builtin = @import ( " builtin " ) ;
2019-03-02 21:46:04 +00:00
const errol = @import ( " fmt/errol.zig " ) ;
2018-06-17 06:57:07 +00:00
const lossyCast = std . math . lossyCast ;
2021-01-12 01:30:43 +00:00
const expectFmt = std . testing . expectFmt ;
2017-03-10 00:12:15 +00:00
2019-04-27 09:36:48 +00:00
pub const default_max_depth = 3 ;
2019-06-25 05:11:04 +00:00
pub const Alignment = enum {
Left ,
Center ,
Right ,
} ;
2019-06-20 08:07:43 +00:00
pub const FormatOptions = struct {
precision : ? usize = null ,
width : ? usize = null ,
2020-09-16 22:41:26 +00:00
alignment : Alignment = . Right ,
2019-06-25 05:11:04 +00:00
fill : u8 = ' ' ,
2019-06-20 08:07:43 +00:00
} ;
2022-05-23 08:58:13 +00:00
/// Renders fmt string with args, calling `writer` with slices of bytes.
/// If `writer` returns an error, the error is returned from `format` and
/// `writer` is not called again.
2019-10-22 18:58:27 +00:00
///
2022-10-05 18:58:23 +00:00
/// The format string must be comptime-known and may contain placeholders following
2019-10-22 18:58:27 +00:00
/// this format:
2021-03-06 08:57:21 +00:00
/// `{[argument][specifier]:[fill][alignment][width].[precision]}`
2019-10-30 02:59:30 +00:00
///
2022-07-11 08:16:28 +00:00
/// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something:
2019-10-22 18:58:27 +00:00
///
2022-07-11 08:16:28 +00:00
/// - *argument* is either the numeric index or the field name of the argument that should be inserted
/// - when using a field name, you are required to enclose the field name (an identifier) in square
/// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...}
2019-10-22 18:58:27 +00:00
/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below)
/// - *fill* is a single character which is used to pad the formatted text
/// - *alignment* is one of the three characters `<`, `^` or `>`. they define if the text is *left*, *center*, or *right* aligned
/// - *width* is the total width of the field in characters
/// - *precision* specifies how many decimals a formatted number should have
///
/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when
/// all parameters after the separator are omitted.
/// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise
/// the digits after `:` is interpreted as *width*, not *fill*.
///
/// The *specifier* has several options for types:
2021-03-06 08:57:21 +00:00
/// - `x` and `X`: output numeric value in hexadecimal notation
2020-10-31 03:31:03 +00:00
/// - `s`:
/// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination
/// - for slices of u8, print the entire slice as a string without zero-termination
2019-10-22 18:58:27 +00:00
/// - `e`: output floating point value in scientific notation
/// - `d`: output numeric value in decimal notation
/// - `b`: output integer value in binary notation
2020-07-14 06:27:58 +00:00
/// - `o`: output integer value in octal notation
2019-10-22 18:58:27 +00:00
/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
2020-09-21 14:15:37 +00:00
/// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max.
2022-07-25 12:29:07 +00:00
/// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value.
/// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value.
2019-10-30 02:59:30 +00:00
/// - `*`: output the address of the value instead of the value itself.
2022-07-25 12:29:07 +00:00
/// - `any`: output a value of any type using its default format.
2019-10-22 18:58:27 +00:00
///
/// If a formatted user type contains a function of the type
/// ```
2020-07-11 19:04:38 +00:00
/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void
2019-10-22 18:58:27 +00:00
/// ```
/// with `?` being the type formatted, this function will be called instead of the default implementation.
/// This allows user types to be formatted in a logical manner instead of dumping all fields of the type.
///
2019-12-14 09:23:42 +00:00
/// A user type may be a `struct`, `vector`, `union` or `enum` type.
2020-09-04 19:11:09 +00:00
///
/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
2019-06-17 10:03:56 +00:00
pub fn format (
2020-07-11 11:09:04 +00:00
writer : anytype ,
2019-06-17 10:03:56 +00:00
comptime fmt : [ ] const u8 ,
2020-07-11 11:09:04 +00:00
args : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2020-09-24 14:40:33 +00:00
const ArgsType = @TypeOf ( args ) ;
2022-01-01 16:36:53 +00:00
const args_type_info = @typeInfo ( ArgsType ) ;
if ( args_type_info ! = . Struct ) {
2022-07-23 15:02:55 +00:00
@compileError ( " expected tuple or struct argument, found " + + @typeName ( ArgsType ) ) ;
2019-12-31 18:13:13 +00:00
}
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
const fields_info = args_type_info . Struct . fields ;
if ( fields_info . len > max_format_args ) {
2019-06-20 08:07:43 +00:00
@compileError ( " 32 arguments max are supported per format call " ) ;
}
2020-09-21 19:34:51 +00:00
@setEvalBranchQuota ( 2000000 ) ;
2022-01-01 16:36:53 +00:00
comptime var arg_state : ArgState = . { . args_len = fields_info . len } ;
2020-09-21 19:34:51 +00:00
comptime var i = 0 ;
inline while ( i < fmt . len ) {
2021-04-23 01:07:46 +00:00
const start_index = i ;
2020-09-21 19:34:51 +00:00
inline while ( i < fmt . len ) : ( i + = 1 ) {
switch ( fmt [ i ] ) {
'{' , '}' = > break ,
2018-05-30 15:18:11 +00:00
else = > { } ,
2020-09-21 19:34:51 +00:00
}
}
comptime var end_index = i ;
comptime var unescape_brace = false ;
// Handle {{ and }}, those are un-escaped as single braces
if ( i + 1 < fmt . len and fmt [ i + 1 ] = = fmt [ i ] ) {
unescape_brace = true ;
// Make the first brace part of the literal...
end_index + = 1 ;
// ...and skip both
i + = 2 ;
}
// Write out the literal
if ( start_index ! = end_index ) {
try writer . writeAll ( fmt [ start_index . . end_index ] ) ;
}
// We've already skipped the other brace, restart the loop
if ( unescape_brace ) continue ;
if ( i > = fmt . len ) break ;
if ( fmt [ i ] = = '}' ) {
2022-07-23 15:02:55 +00:00
@compileError ( " missing opening { " ) ;
2020-09-21 19:34:51 +00:00
}
// Get past the {
comptime assert ( fmt [ i ] = = '{' ) ;
i + = 1 ;
2021-04-23 01:07:46 +00:00
const fmt_begin = i ;
2020-09-21 19:34:51 +00:00
// Find the closing brace
inline while ( i < fmt . len and fmt [ i ] ! = '}' ) : ( i + = 1 ) { }
2021-04-23 01:07:46 +00:00
const fmt_end = i ;
2020-09-21 19:34:51 +00:00
if ( i > = fmt . len ) {
2022-07-23 15:02:55 +00:00
@compileError ( " missing closing } " ) ;
2020-09-21 19:34:51 +00:00
}
// Get past the }
comptime assert ( fmt [ i ] = = '}' ) ;
i + = 1 ;
2022-01-01 16:36:53 +00:00
const placeholder = comptime parsePlaceholder ( fmt [ fmt_begin . . fmt_end ] . * ) ;
const arg_pos = comptime switch ( placeholder . arg ) {
. none = > null ,
. number = > | pos | pos ,
. named = > | arg_name | meta . fieldIndex ( ArgsType , arg_name ) orelse
2022-07-23 15:02:55 +00:00
@compileError ( " no argument with name ' " + + arg_name + + " ' " ) ,
2022-01-01 16:36:53 +00:00
} ;
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
const width = switch ( placeholder . width ) {
. none = > null ,
. number = > | v | v ,
. named = > | arg_name | blk : {
const arg_i = comptime meta . fieldIndex ( ArgsType , arg_name ) orelse
2022-07-23 15:02:55 +00:00
@compileError ( " no argument with name ' " + + arg_name + + " ' " ) ;
_ = comptime arg_state . nextArg ( arg_i ) orelse @compileError ( " too few arguments " ) ;
2022-01-01 16:36:53 +00:00
break : blk @field ( args , arg_name ) ;
} ,
} ;
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
const precision = switch ( placeholder . precision ) {
. none = > null ,
. number = > | v | v ,
. named = > | arg_name | blk : {
const arg_i = comptime meta . fieldIndex ( ArgsType , arg_name ) orelse
2022-07-23 15:02:55 +00:00
@compileError ( " no argument with name ' " + + arg_name + + " ' " ) ;
_ = comptime arg_state . nextArg ( arg_i ) orelse @compileError ( " too few arguments " ) ;
2022-01-01 16:36:53 +00:00
break : blk @field ( args , arg_name ) ;
} ,
2020-09-24 14:40:33 +00:00
} ;
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
const arg_to_print = comptime arg_state . nextArg ( arg_pos ) orelse
2022-07-23 15:02:55 +00:00
@compileError ( " too few arguments " ) ;
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
try formatType (
@field ( args , fields_info [ arg_to_print ] . name ) ,
placeholder . specifier_arg ,
FormatOptions {
. fill = placeholder . fill ,
. alignment = placeholder . alignment ,
. width = width ,
. precision = precision ,
} ,
writer ,
default_max_depth ,
) ;
}
if ( comptime arg_state . hasUnusedArgs ( ) ) {
2022-08-21 14:24:04 +00:00
const missing_count = arg_state . args_len - @popCount ( arg_state . used_args ) ;
2022-01-01 16:36:53 +00:00
switch ( missing_count ) {
0 = > unreachable ,
2022-07-23 15:02:55 +00:00
1 = > @compileError ( " unused argument in ' " + + fmt + + " ' " ) ,
2022-12-04 13:03:32 +00:00
else = > @compileError ( comptimePrint ( " {d} " , . { missing_count } ) + + " unused arguments in ' " + + fmt + + " ' " ) ,
2020-09-21 19:34:51 +00:00
}
2022-01-01 16:36:53 +00:00
}
}
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
fn parsePlaceholder ( comptime str : anytype ) Placeholder {
comptime var parser = Parser { . buf = & str } ;
// Parse the positional argument number
const arg = comptime parser . specifier ( ) catch | err |
@compileError ( @errorName ( err ) ) ;
// Parse the format specifier
const specifier_arg = comptime parser . until ( ':' ) ;
// Skip the colon, if present
if ( comptime parser . char ( ) ) | ch | {
if ( ch ! = ':' ) {
2022-07-23 15:02:55 +00:00
@compileError ( " expected : or }, found ' " + + [ 1 ] u8 { ch } + + " ' " ) ;
2020-09-21 19:34:51 +00:00
}
2022-01-01 16:36:53 +00:00
}
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
// Parse the fill character
// The fill parameter requires the alignment parameter to be specified
// too
const fill = comptime if ( parser . peek ( 1 ) ) | ch |
switch ( ch ) {
'<' , '^' , '>' = > parser . char ( ) . ? ,
else = > ' ' ,
2020-09-21 19:34:51 +00:00
}
2022-01-01 16:36:53 +00:00
else
' ' ;
// Parse the alignment parameter
const alignment : Alignment = comptime if ( parser . peek ( 0 ) ) | ch | init : {
switch ( ch ) {
'<' , '^' , '>' = > _ = parser . char ( ) ,
else = > { } ,
}
break : init switch ( ch ) {
'<' = > . Left ,
'^' = > . Center ,
else = > . Right ,
} ;
} else . Right ;
2019-06-20 08:07:43 +00:00
2022-01-01 16:36:53 +00:00
// Parse the width parameter
const width = comptime parser . specifier ( ) catch | err |
@compileError ( @errorName ( err ) ) ;
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
// Skip the dot, if present
if ( comptime parser . char ( ) ) | ch | {
if ( ch ! = '.' ) {
2022-07-23 15:02:55 +00:00
@compileError ( " expected . or }, found ' " + + [ 1 ] u8 { ch } + + " ' " ) ;
2022-01-01 16:36:53 +00:00
}
}
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
// Parse the precision parameter
const precision = comptime parser . specifier ( ) catch | err |
@compileError ( @errorName ( err ) ) ;
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
if ( comptime parser . char ( ) ) | ch | {
2022-07-23 15:02:55 +00:00
@compileError ( " extraneous trailing character ' " + + [ 1 ] u8 { ch } + + " ' " ) ;
2022-01-01 16:36:53 +00:00
}
2019-06-20 08:07:43 +00:00
2022-01-01 16:36:53 +00:00
return Placeholder {
. specifier_arg = cacheString ( specifier_arg [ 0 . . specifier_arg . len ] . * ) ,
. fill = fill ,
. alignment = alignment ,
. arg = arg ,
. width = width ,
. precision = precision ,
} ;
}
fn cacheString ( str : anytype ) [ ] const u8 {
return & str ;
}
const Placeholder = struct {
specifier_arg : [ ] const u8 ,
fill : u8 ,
alignment : Alignment ,
arg : Specifier ,
width : Specifier ,
precision : Specifier ,
} ;
const Specifier = union ( enum ) {
none ,
number : usize ,
named : [ ] const u8 ,
} ;
const Parser = struct {
buf : [ ] const u8 ,
pos : usize = 0 ,
// Returns a decimal number or null if the current character is not a
// digit
fn number ( self : * @This ( ) ) ? usize {
var r : ? usize = null ;
while ( self . pos < self . buf . len ) : ( self . pos + = 1 ) {
switch ( self . buf [ self . pos ] ) {
'0' . . . '9' = > {
if ( r = = null ) r = 0 ;
r . ? * = 10 ;
r . ? + = self . buf [ self . pos ] - '0' ;
} ,
else = > break ,
2020-09-24 14:40:33 +00:00
}
2017-03-10 00:12:15 +00:00
}
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
return r ;
}
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
// Returns a substring of the input starting from the current position
// and ending where `ch` is found or until the end if not found
fn until ( self : * @This ( ) , ch : u8 ) [ ] const u8 {
const start = self . pos ;
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
if ( start > = self . buf . len )
return & [ _ ] u8 { } ;
2020-09-24 14:40:33 +00:00
2022-01-01 16:36:53 +00:00
while ( self . pos < self . buf . len ) : ( self . pos + = 1 ) {
if ( self . buf [ self . pos ] = = ch ) break ;
}
return self . buf [ start . . self . pos ] ;
}
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
// Returns one character, if available
fn char ( self : * @This ( ) ) ? u8 {
if ( self . pos < self . buf . len ) {
const ch = self . buf [ self . pos ] ;
self . pos + = 1 ;
return ch ;
2017-03-10 00:12:15 +00:00
}
2022-01-01 16:36:53 +00:00
return null ;
}
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
fn maybe ( self : * @This ( ) , val : u8 ) bool {
if ( self . pos < self . buf . len and self . buf [ self . pos ] = = val ) {
self . pos + = 1 ;
return true ;
}
return false ;
2017-03-10 00:12:15 +00:00
}
2020-09-21 19:34:51 +00:00
2022-01-01 16:36:53 +00:00
// Returns a decimal number or null if the current character is not a
// digit
fn specifier ( self : * @This ( ) ) ! Specifier {
if ( self . maybe ( '[' ) ) {
const arg_name = self . until ( ']' ) ;
if ( ! self . maybe ( ']' ) )
return @field ( anyerror , " Expected closing ] " ) ;
return Specifier { . named = arg_name } ;
2021-06-09 03:42:29 +00:00
}
2022-01-01 16:36:53 +00:00
if ( self . number ( ) ) | i |
return Specifier { . number = i } ;
return Specifier { . none = { } } ;
}
// Returns the n-th next character or null if that's past the end
fn peek ( self : * @This ( ) , n : usize ) ? u8 {
return if ( self . pos + n < self . buf . len ) self . buf [ self . pos + n ] else null ;
}
} ;
const ArgSetType = u32 ;
const max_format_args = @typeInfo ( ArgSetType ) . Int . bits ;
const ArgState = struct {
next_arg : usize = 0 ,
used_args : ArgSetType = 0 ,
args_len : usize ,
fn hasUnusedArgs ( self : * @This ( ) ) bool {
2022-08-21 14:24:04 +00:00
return @popCount ( self . used_args ) ! = self . args_len ;
2017-03-10 00:12:15 +00:00
}
2022-01-01 16:36:53 +00:00
fn nextArg ( self : * @This ( ) , arg_index : ? usize ) ? usize {
const next_index = arg_index orelse init : {
const arg = self . next_arg ;
self . next_arg + = 1 ;
break : init arg ;
} ;
if ( next_index > = self . args_len ) {
return null ;
}
// Mark this argument as used
self . used_args | = @as ( ArgSetType , 1 ) < < @intCast ( u5 , next_index ) ;
return next_index ;
}
} ;
2017-03-10 00:12:15 +00:00
2020-10-29 21:22:25 +00:00
pub fn formatAddress ( value : anytype , options : FormatOptions , writer : anytype ) @TypeOf ( writer ) . Error ! void {
2021-06-20 01:10:22 +00:00
_ = options ;
2020-10-29 21:22:25 +00:00
const T = @TypeOf ( value ) ;
switch ( @typeInfo ( T ) ) {
. Pointer = > | info | {
try writer . writeAll ( @typeName ( info . child ) + + " @ " ) ;
if ( info . size = = . Slice )
2021-04-10 11:09:38 +00:00
try formatInt ( @ptrToInt ( value . ptr ) , 16 , . lower , FormatOptions { } , writer )
2020-10-29 21:22:25 +00:00
else
2021-04-10 11:09:38 +00:00
try formatInt ( @ptrToInt ( value ) , 16 , . lower , FormatOptions { } , writer ) ;
2020-10-29 21:22:25 +00:00
return ;
} ,
. Optional = > | info | {
if ( @typeInfo ( info . child ) = = . Pointer ) {
try writer . writeAll ( @typeName ( info . child ) + + " @ " ) ;
2021-04-10 11:09:38 +00:00
try formatInt ( @ptrToInt ( value ) , 16 , . lower , FormatOptions { } , writer ) ;
2020-10-29 21:22:25 +00:00
return ;
}
} ,
else = > { } ,
}
2022-07-23 15:02:55 +00:00
@compileError ( " cannot format non-pointer type " + + @typeName ( T ) + + " with * specifier " ) ;
2020-10-29 21:22:25 +00:00
}
2021-01-03 09:20:37 +00:00
// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
const ANY = " any " ;
fn defaultSpec ( comptime T : type ) [ : 0 ] const u8 {
switch ( @typeInfo ( T ) ) {
. Array = > | _ | return ANY ,
. Pointer = > | ptr_info | switch ( ptr_info . size ) {
. One = > switch ( @typeInfo ( ptr_info . child ) ) {
. Array = > | _ | return " * " ,
else = > { } ,
} ,
. Many , . C = > return " * " ,
. Slice = > return ANY ,
} ,
2022-07-25 12:29:07 +00:00
. Optional = > | info | return " ? " + + defaultSpec ( info . child ) ,
. ErrorUnion = > | info | return " ! " + + defaultSpec ( info . payload ) ,
2021-01-03 09:20:37 +00:00
else = > { } ,
}
return " " ;
}
2022-07-25 12:29:07 +00:00
fn stripOptionalOrErrorUnionSpec ( comptime fmt : [ ] const u8 ) [ ] const u8 {
return if ( std . mem . eql ( u8 , fmt [ 1 . . ] , ANY ) )
ANY
else
fmt [ 1 . . ] ;
}
2022-11-12 19:03:24 +00:00
pub fn invalidFmtError ( comptime fmt : [ ] const u8 , value : anytype ) void {
2022-07-23 14:53:59 +00:00
@compileError ( " invalid format string ' " + + fmt + + " ' for type ' " + + @typeName ( @TypeOf ( value ) ) + + " ' " ) ;
}
2018-06-04 15:06:55 +00:00
pub fn formatType (
2020-07-11 11:09:04 +00:00
value : anytype ,
2018-06-04 15:06:55 +00:00
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2019-04-27 09:36:48 +00:00
max_depth : usize ,
2020-06-20 10:02:48 +00:00
) @TypeOf ( writer ) . Error ! void {
2022-07-25 12:29:07 +00:00
const T = @TypeOf ( value ) ;
const actual_fmt = comptime if ( std . mem . eql ( u8 , fmt , ANY ) )
defaultSpec ( @TypeOf ( value ) )
else if ( fmt . len ! = 0 and ( fmt [ 0 ] = = '?' or fmt [ 0 ] = = '!' ) ) switch ( @typeInfo ( T ) ) {
. Optional , . ErrorUnion = > fmt ,
else = > stripOptionalOrErrorUnionSpec ( fmt ) ,
} else fmt ;
2021-01-03 09:20:37 +00:00
if ( comptime std . mem . eql ( u8 , actual_fmt , " * " ) ) {
2020-10-29 21:22:25 +00:00
return formatAddress ( value , options , writer ) ;
2019-08-05 07:07:31 +00:00
}
2020-03-06 22:59:21 +00:00
if ( comptime std . meta . trait . hasFn ( " format " ) ( T ) ) {
2021-01-03 09:20:37 +00:00
return try value . format ( actual_fmt , options , writer ) ;
2020-03-06 22:59:21 +00:00
}
2018-06-06 04:39:39 +00:00
switch ( @typeInfo ( T ) ) {
2020-05-25 06:05:08 +00:00
. ComptimeInt , . Int , . ComptimeFloat , . Float = > {
2021-01-03 09:20:37 +00:00
return formatValue ( value , actual_fmt , options , writer ) ;
2017-05-17 16:26:35 +00:00
} ,
2019-06-17 10:03:56 +00:00
. Void = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-06-20 10:02:48 +00:00
return formatBuf ( " void " , options , writer ) ;
2017-05-17 16:26:35 +00:00
} ,
2019-06-17 10:03:56 +00:00
. Bool = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-06-20 10:02:48 +00:00
return formatBuf ( if ( value ) " true " else " false " , options , writer ) ;
2017-05-17 16:26:35 +00:00
} ,
2019-06-17 10:03:56 +00:00
. Optional = > {
2022-07-25 12:29:07 +00:00
if ( actual_fmt . len = = 0 or actual_fmt [ 0 ] ! = '?' )
@compileError ( " cannot format optional without a specifier (i.e. {?} or {any}) " ) ;
const remaining_fmt = comptime stripOptionalOrErrorUnionSpec ( actual_fmt ) ;
2017-05-23 22:38:41 +00:00
if ( value ) | payload | {
2022-07-25 12:29:07 +00:00
return formatType ( payload , remaining_fmt , options , writer , max_depth ) ;
2017-05-23 22:38:41 +00:00
} else {
2020-06-20 10:02:48 +00:00
return formatBuf ( " null " , options , writer ) ;
2017-05-23 22:38:41 +00:00
}
} ,
2019-06-17 10:03:56 +00:00
. ErrorUnion = > {
2022-07-25 12:29:07 +00:00
if ( actual_fmt . len = = 0 or actual_fmt [ 0 ] ! = '!' )
@compileError ( " cannot format error union without a specifier (i.e. {!} or {any}) " ) ;
const remaining_fmt = comptime stripOptionalOrErrorUnionSpec ( actual_fmt ) ;
2017-05-23 22:38:41 +00:00
if ( value ) | payload | {
2022-07-25 12:29:07 +00:00
return formatType ( payload , remaining_fmt , options , writer , max_depth ) ;
2017-05-23 22:38:41 +00:00
} else | err | {
2022-07-23 14:53:59 +00:00
return formatType ( err , " " , options , writer , max_depth ) ;
2017-05-23 22:38:41 +00:00
}
} ,
2019-06-17 10:03:56 +00:00
. ErrorSet = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " error. " ) ;
return writer . writeAll ( @errorName ( value ) ) ;
2017-05-23 22:38:41 +00:00
} ,
2020-01-31 10:18:20 +00:00
. Enum = > | enumInfo | {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( @typeName ( T ) ) ;
2020-01-31 10:18:20 +00:00
if ( enumInfo . is_exhaustive ) {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
try writer . writeAll ( @tagName ( value ) ) ;
2020-05-26 16:15:08 +00:00
return ;
2020-03-10 12:36:27 +00:00
}
2020-05-26 16:15:08 +00:00
// Use @tagName only if value is one of known fields
2020-07-02 05:04:05 +00:00
@setEvalBranchQuota ( 3 * enumInfo . fields . len ) ;
2020-05-26 16:15:08 +00:00
inline for ( enumInfo . fields ) | enumField | {
if ( @enumToInt ( value ) = = enumField . value ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
try writer . writeAll ( @tagName ( value ) ) ;
2020-05-26 16:15:08 +00:00
return ;
}
2020-03-10 12:36:27 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " ( " ) ;
2021-01-03 09:20:37 +00:00
try formatType ( @enumToInt ( value ) , actual_fmt , options , writer , max_depth ) ;
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " ) " ) ;
2019-06-17 10:03:56 +00:00
} ,
2020-10-31 14:16:59 +00:00
. Union = > | info | {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-06-20 10:02:48 +00:00
try writer . writeAll ( @typeName ( T ) ) ;
2019-06-17 10:03:56 +00:00
if ( max_depth = = 0 ) {
2020-06-20 10:02:48 +00:00
return writer . writeAll ( " { ... } " ) ;
2019-06-17 10:03:56 +00:00
}
if ( info . tag_type ) | UnionTagType | {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " { . " ) ;
try writer . writeAll ( @tagName ( @as ( UnionTagType , value ) ) ) ;
try writer . writeAll ( " = " ) ;
2019-06-17 10:03:56 +00:00
inline for ( info . fields ) | u_field | {
2020-08-26 19:38:58 +00:00
if ( value = = @field ( UnionTagType , u_field . name ) ) {
2021-01-03 09:20:37 +00:00
try formatType ( @field ( value , u_field . name ) , ANY , options , writer , max_depth - 1 ) ;
2018-08-27 21:25:33 +00:00
}
2019-06-17 10:03:56 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " } " ) ;
2019-06-17 10:03:56 +00:00
} else {
2020-06-20 10:02:48 +00:00
try format ( writer , " @{x} " , . { @ptrToInt ( & value ) } ) ;
2018-08-20 20:04:03 +00:00
}
} ,
2020-10-31 14:12:05 +00:00
. Struct = > | info | {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2021-06-06 10:39:07 +00:00
if ( info . is_tuple ) {
// Skip the type and field names when formatting tuples.
if ( max_depth = = 0 ) {
return writer . writeAll ( " { ... } " ) ;
}
try writer . writeAll ( " { " ) ;
inline for ( info . fields ) | f , i | {
if ( i = = 0 ) {
try writer . writeAll ( " " ) ;
} else {
try writer . writeAll ( " , " ) ;
}
try formatType ( @field ( value , f . name ) , ANY , options , writer , max_depth - 1 ) ;
}
return writer . writeAll ( " } " ) ;
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( @typeName ( T ) ) ;
2019-06-17 10:03:56 +00:00
if ( max_depth = = 0 ) {
2020-06-20 10:02:48 +00:00
return writer . writeAll ( " { ... } " ) ;
2019-06-17 10:03:56 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " { " ) ;
2020-10-31 14:12:05 +00:00
inline for ( info . fields ) | f , i | {
2020-02-26 09:07:47 +00:00
if ( i = = 0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
2019-06-17 10:03:56 +00:00
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " , . " ) ;
2019-06-17 10:03:56 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( f . name ) ;
try writer . writeAll ( " = " ) ;
2021-01-03 09:20:37 +00:00
try formatType ( @field ( value , f . name ) , ANY , options , writer , max_depth - 1 ) ;
2019-06-17 10:03:56 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " } " ) ;
2019-06-17 10:03:56 +00:00
} ,
. Pointer = > | ptr_info | switch ( ptr_info . size ) {
. One = > switch ( @typeInfo ( ptr_info . child ) ) {
2020-02-24 21:03:30 +00:00
. Array = > | info | {
2021-01-03 09:20:37 +00:00
if ( actual_fmt . len = = 0 )
@compileError ( " cannot format array ref without a specifier (i.e. {s} or {*}) " ) ;
2018-06-06 04:39:39 +00:00
if ( info . child = = u8 ) {
2022-01-01 16:36:53 +00:00
switch ( actual_fmt [ 0 ] ) {
's' , 'x' , 'X' , 'e' , 'E' = > {
comptime checkTextFmt ( actual_fmt ) ;
return formatBuf ( value , options , writer ) ;
} ,
else = > { } ,
2020-11-26 14:43:28 +00:00
}
2018-05-30 15:18:11 +00:00
}
2021-08-19 11:12:11 +00:00
if ( comptime std . meta . trait . isZigString ( info . child ) ) {
for ( value ) | item , i | {
2022-01-01 16:36:53 +00:00
comptime checkTextFmt ( actual_fmt ) ;
if ( i ! = 0 ) try formatBuf ( " , " , options , writer ) ;
try formatBuf ( item , options , writer ) ;
2021-08-19 11:12:11 +00:00
}
return ;
}
2022-11-12 19:03:24 +00:00
invalidFmtError ( fmt , value ) ;
2018-05-30 15:18:11 +00:00
} ,
2020-02-24 21:03:30 +00:00
. Enum , . Union , . Struct = > {
2021-01-03 09:20:37 +00:00
return formatType ( value . * , actual_fmt , options , writer , max_depth ) ;
2018-05-30 15:18:11 +00:00
} ,
2020-11-26 08:48:12 +00:00
else = > return format ( writer , " {s}@{x} " , . { @typeName ( ptr_info . child ) , @ptrToInt ( value ) } ) ,
2018-06-06 04:39:39 +00:00
} ,
2020-01-20 17:23:43 +00:00
. Many , . C = > {
2021-01-03 09:20:37 +00:00
if ( actual_fmt . len = = 0 )
@compileError ( " cannot format pointer without a specifier (i.e. {s} or {*}) " ) ;
2021-06-20 01:10:22 +00:00
if ( ptr_info . sentinel ) | _ | {
2021-01-03 09:20:37 +00:00
return formatType ( mem . span ( value ) , actual_fmt , options , writer , max_depth ) ;
2019-12-27 03:31:20 +00:00
}
2018-06-10 16:57:21 +00:00
if ( ptr_info . child = = u8 ) {
2022-01-01 16:36:53 +00:00
switch ( actual_fmt [ 0 ] ) {
's' , 'x' , 'X' , 'e' , 'E' = > {
comptime checkTextFmt ( actual_fmt ) ;
return formatBuf ( mem . span ( value ) , options , writer ) ;
} ,
else = > { } ,
2018-06-10 16:57:21 +00:00
}
}
2022-11-12 19:03:24 +00:00
invalidFmtError ( fmt , value ) ;
2018-06-06 04:39:39 +00:00
} ,
2019-06-17 10:03:56 +00:00
. Slice = > {
2021-01-03 09:20:37 +00:00
if ( actual_fmt . len = = 0 )
@compileError ( " cannot format slice without a specifier (i.e. {s} or {any}) " ) ;
2020-10-31 14:12:05 +00:00
if ( max_depth = = 0 ) {
2020-11-19 18:02:30 +00:00
return writer . writeAll ( " { ... } " ) ;
2020-10-31 14:12:05 +00:00
}
2019-05-29 15:23:21 +00:00
if ( ptr_info . child = = u8 ) {
2022-01-01 16:36:53 +00:00
switch ( actual_fmt [ 0 ] ) {
's' , 'x' , 'X' , 'e' , 'E' = > {
comptime checkTextFmt ( actual_fmt ) ;
return formatBuf ( value , options , writer ) ;
} ,
else = > { } ,
2020-10-31 14:12:05 +00:00
}
2020-10-29 21:22:25 +00:00
}
2020-11-19 18:02:30 +00:00
try writer . writeAll ( " { " ) ;
2020-10-29 21:22:25 +00:00
for ( value ) | elem , i | {
2021-01-03 09:20:37 +00:00
try formatType ( elem , actual_fmt , options , writer , max_depth - 1 ) ;
2020-10-29 21:22:25 +00:00
if ( i ! = value . len - 1 ) {
try writer . writeAll ( " , " ) ;
2020-10-14 16:38:59 +00:00
}
2019-05-29 15:23:21 +00:00
}
2020-11-19 18:02:30 +00:00
try writer . writeAll ( " } " ) ;
2018-06-06 04:39:39 +00:00
} ,
2017-05-17 16:26:35 +00:00
} ,
2019-06-17 10:03:56 +00:00
. Array = > | info | {
2021-01-03 09:20:37 +00:00
if ( actual_fmt . len = = 0 )
@compileError ( " cannot format array without a specifier (i.e. {s} or {any}) " ) ;
2020-10-31 14:12:05 +00:00
if ( max_depth = = 0 ) {
return writer . writeAll ( " { ... } " ) ;
}
if ( info . child = = u8 ) {
2022-01-01 16:36:53 +00:00
switch ( actual_fmt [ 0 ] ) {
's' , 'x' , 'X' , 'e' , 'E' = > {
comptime checkTextFmt ( actual_fmt ) ;
return formatBuf ( & value , options , writer ) ;
} ,
else = > { } ,
2020-10-31 14:12:05 +00:00
}
}
try writer . writeAll ( " { " ) ;
for ( value ) | elem , i | {
2021-01-03 09:20:37 +00:00
try formatType ( elem , actual_fmt , options , writer , max_depth - 1 ) ;
2020-10-31 14:12:05 +00:00
if ( i < value . len - 1 ) {
try writer . writeAll ( " , " ) ;
}
}
try writer . writeAll ( " } " ) ;
2018-06-15 17:49:39 +00:00
} ,
2020-10-31 14:12:05 +00:00
. Vector = > | info | {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " { " ) ;
2019-12-14 09:23:42 +00:00
var i : usize = 0 ;
2020-10-31 14:12:05 +00:00
while ( i < info . len ) : ( i + = 1 ) {
2021-01-03 09:20:37 +00:00
try formatValue ( value [ i ] , actual_fmt , options , writer ) ;
2020-10-31 14:12:05 +00:00
if ( i < info . len - 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " , " ) ;
2019-12-14 09:23:42 +00:00
}
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " } " ) ;
2019-12-14 09:23:42 +00:00
} ,
2019-06-17 10:03:56 +00:00
. Fn = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-11-26 08:48:12 +00:00
return format ( writer , " {s}@{x} " , . { @typeName ( T ) , @ptrToInt ( value ) } ) ;
2018-12-19 15:07:35 +00:00
} ,
2022-07-23 14:53:59 +00:00
. Type = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2022-07-23 14:53:59 +00:00
return formatBuf ( @typeName ( value ) , options , writer ) ;
} ,
2020-03-05 17:19:08 +00:00
. EnumLiteral = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2020-03-10 19:22:29 +00:00
const buffer = [ _ ] u8 { '.' } + + @tagName ( value ) ;
2020-12-02 19:02:51 +00:00
return formatBuf ( buffer , options , writer ) ;
2020-03-05 17:19:08 +00:00
} ,
2022-07-23 14:53:59 +00:00
. Null = > {
2022-11-12 19:03:24 +00:00
if ( actual_fmt . len ! = 0 ) invalidFmtError ( fmt , value ) ;
2022-07-23 14:53:59 +00:00
return formatBuf ( " null " , options , writer ) ;
} ,
2022-07-23 15:02:55 +00:00
else = > @compileError ( " unable to format type ' " + + @typeName ( T ) + + " ' " ) ,
2017-03-10 00:12:15 +00:00
}
}
2018-06-04 15:06:55 +00:00
fn formatValue (
2020-07-11 11:09:04 +00:00
value : anytype ,
2018-06-04 15:06:55 +00:00
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2019-06-20 08:07:43 +00:00
if ( comptime std . mem . eql ( u8 , fmt , " B " ) ) {
2021-03-06 08:57:21 +00:00
@compileError ( " specifier 'B' has been deprecated, wrap your argument in std.fmt.fmtIntSizeDec instead " ) ;
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " Bi " ) ) {
2021-03-06 08:57:21 +00:00
@compileError ( " specifier 'Bi' has been deprecated, wrap your argument in std.fmt.fmtIntSizeBin instead " ) ;
2018-05-30 15:18:11 +00:00
}
2018-06-04 15:06:55 +00:00
2019-12-09 20:56:19 +00:00
const T = @TypeOf ( value ) ;
2020-02-24 21:03:30 +00:00
switch ( @typeInfo ( T ) ) {
2020-06-20 10:02:48 +00:00
. Float , . ComptimeFloat = > return formatFloatValue ( value , fmt , options , writer ) ,
. Int , . ComptimeInt = > return formatIntValue ( value , fmt , options , writer ) ,
. Bool = > return formatBuf ( if ( value ) " true " else " false " , options , writer ) ,
2018-12-19 10:50:29 +00:00
else = > comptime unreachable ,
2018-05-30 15:18:11 +00:00
}
}
2018-06-04 15:06:55 +00:00
pub fn formatIntValue (
2020-07-11 11:09:04 +00:00
value : anytype ,
2018-06-04 15:06:55 +00:00
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2018-05-30 15:18:11 +00:00
comptime var radix = 10 ;
2021-04-10 11:09:38 +00:00
comptime var case : Case = . lower ;
2019-04-05 01:36:31 +00:00
2019-12-09 20:56:19 +00:00
const int_value = if ( @TypeOf ( value ) = = comptime_int ) blk : {
2019-04-05 01:36:31 +00:00
const Int = math . IntFittingRange ( value , value ) ;
2019-11-07 23:52:09 +00:00
break : blk @as ( Int , value ) ;
2021-02-25 04:29:01 +00:00
} else value ;
2019-04-05 01:36:31 +00:00
2019-06-20 08:07:43 +00:00
if ( fmt . len = = 0 or comptime std . mem . eql ( u8 , fmt , " d " ) ) {
radix = 10 ;
2021-04-10 11:09:38 +00:00
case = . lower ;
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " c " ) ) {
2020-09-03 15:09:55 +00:00
if ( @typeInfo ( @TypeOf ( int_value ) ) . Int . bits < = 8 ) {
2020-06-20 10:02:48 +00:00
return formatAsciiChar ( @as ( u8 , int_value ) , options , writer ) ;
2019-06-20 08:07:43 +00:00
} else {
2022-07-23 15:02:55 +00:00
@compileError ( " cannot print integer that is larger than 8 bits as an ASCII character " ) ;
2018-05-30 15:18:11 +00:00
}
2019-12-22 10:38:27 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " u " ) ) {
2020-09-21 10:39:35 +00:00
if ( @typeInfo ( @TypeOf ( int_value ) ) . Int . bits < = 21 ) {
return formatUnicodeCodepoint ( @as ( u21 , int_value ) , options , writer ) ;
2019-12-22 10:38:27 +00:00
} else {
2022-07-23 15:02:55 +00:00
@compileError ( " cannot print integer that is larger than 21 bits as an UTF-8 sequence " ) ;
2019-12-22 10:38:27 +00:00
}
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " b " ) ) {
radix = 2 ;
2021-04-10 11:09:38 +00:00
case = . lower ;
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " x " ) ) {
radix = 16 ;
2021-04-10 11:09:38 +00:00
case = . lower ;
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " X " ) ) {
radix = 16 ;
2021-04-10 11:09:38 +00:00
case = . upper ;
2020-07-14 06:27:58 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " o " ) ) {
radix = 8 ;
2021-04-10 11:09:38 +00:00
case = . lower ;
2019-06-20 08:07:43 +00:00
} else {
2022-11-12 19:03:24 +00:00
invalidFmtError ( fmt , value ) ;
2018-05-30 15:18:11 +00:00
}
2019-06-20 08:07:43 +00:00
2021-04-10 11:09:38 +00:00
return formatInt ( int_value , radix , case , options , writer ) ;
2018-05-30 15:18:11 +00:00
}
2018-06-04 15:06:55 +00:00
fn formatFloatValue (
2020-07-11 11:09:04 +00:00
value : anytype ,
2018-06-04 15:06:55 +00:00
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2020-08-09 10:48:26 +00:00
// this buffer should be enough to display all decimal places of a decimal f64 number.
var buf : [ 512 ] u8 = undefined ;
var buf_stream = std . io . fixedBufferStream ( & buf ) ;
2019-06-20 08:07:43 +00:00
if ( fmt . len = = 0 or comptime std . mem . eql ( u8 , fmt , " e " ) ) {
2020-08-09 12:09:02 +00:00
formatFloatScientific ( value , options , buf_stream . writer ( ) ) catch | err | switch ( err ) {
error . NoSpaceLeft = > unreachable ,
} ;
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " d " ) ) {
2020-08-09 12:09:02 +00:00
formatFloatDecimal ( value , options , buf_stream . writer ( ) ) catch | err | switch ( err ) {
error . NoSpaceLeft = > unreachable ,
} ;
2021-03-23 09:08:34 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " x " ) ) {
formatFloatHexadecimal ( value , options , buf_stream . writer ( ) ) catch | err | switch ( err ) {
error . NoSpaceLeft = > unreachable ,
} ;
2019-06-20 08:07:43 +00:00
} else {
2022-11-12 19:03:24 +00:00
invalidFmtError ( fmt , value ) ;
2018-05-30 15:18:11 +00:00
}
2020-08-09 10:48:26 +00:00
2020-08-09 15:40:58 +00:00
return formatBuf ( buf_stream . getWritten ( ) , options , writer ) ;
2018-05-30 15:18:11 +00:00
}
2021-04-10 11:09:38 +00:00
pub const Case = enum { lower , upper } ;
fn formatSliceHexImpl ( comptime case : Case ) type {
const charset = " 0123456789 " + + if ( case = = . upper ) " ABCDEF " else " abcdef " ;
2021-02-12 19:44:19 +00:00
return struct {
2022-11-27 21:26:41 +00:00
pub fn formatSliceHexImpl (
2021-02-12 19:44:19 +00:00
bytes : [ ] const u8 ,
comptime fmt : [ ] const u8 ,
options : std . fmt . FormatOptions ,
writer : anytype ,
) ! void {
2021-06-20 01:10:22 +00:00
_ = fmt ;
_ = options ;
2021-02-12 19:44:19 +00:00
var buf : [ 2 ] u8 = undefined ;
for ( bytes ) | c | {
buf [ 0 ] = charset [ c > > 4 ] ;
buf [ 1 ] = charset [ c & 15 ] ;
try writer . writeAll ( & buf ) ;
}
}
} ;
}
2022-11-27 21:26:41 +00:00
const formatSliceHexLower = formatSliceHexImpl ( . lower ) . formatSliceHexImpl ;
const formatSliceHexUpper = formatSliceHexImpl ( . upper ) . formatSliceHexImpl ;
2021-02-12 19:44:19 +00:00
/// Return a Formatter for a []const u8 where every byte is formatted as a pair
/// of lowercase hexadecimal digits.
pub fn fmtSliceHexLower ( bytes : [ ] const u8 ) std . fmt . Formatter ( formatSliceHexLower ) {
return . { . data = bytes } ;
}
2021-04-10 11:09:38 +00:00
/// Return a Formatter for a []const u8 where every byte is formatted as pair
2021-02-12 19:44:19 +00:00
/// of uppercase hexadecimal digits.
pub fn fmtSliceHexUpper ( bytes : [ ] const u8 ) std . fmt . Formatter ( formatSliceHexUpper ) {
return . { . data = bytes } ;
}
2021-04-10 11:09:38 +00:00
fn formatSliceEscapeImpl ( comptime case : Case ) type {
const charset = " 0123456789 " + + if ( case = = . upper ) " ABCDEF " else " abcdef " ;
2021-02-12 19:44:19 +00:00
return struct {
2022-11-27 21:26:41 +00:00
pub fn formatSliceEscapeImpl (
2021-02-12 19:44:19 +00:00
bytes : [ ] const u8 ,
comptime fmt : [ ] const u8 ,
options : std . fmt . FormatOptions ,
writer : anytype ,
) ! void {
2021-06-20 01:10:22 +00:00
_ = fmt ;
_ = options ;
2021-02-12 19:44:19 +00:00
var buf : [ 4 ] u8 = undefined ;
buf [ 0 ] = '\\' ;
buf [ 1 ] = 'x' ;
for ( bytes ) | c | {
if ( std . ascii . isPrint ( c ) ) {
try writer . writeByte ( c ) ;
} else {
buf [ 2 ] = charset [ c > > 4 ] ;
buf [ 3 ] = charset [ c & 15 ] ;
try writer . writeAll ( & buf ) ;
}
}
}
} ;
}
2022-11-27 21:26:41 +00:00
const formatSliceEscapeLower = formatSliceEscapeImpl ( . lower ) . formatSliceEscapeImpl ;
const formatSliceEscapeUpper = formatSliceEscapeImpl ( . upper ) . formatSliceEscapeImpl ;
2021-02-12 19:44:19 +00:00
/// Return a Formatter for a []const u8 where every non-printable ASCII
/// character is escaped as \xNN, where NN is the character in lowercase
/// hexadecimal notation.
pub fn fmtSliceEscapeLower ( bytes : [ ] const u8 ) std . fmt . Formatter ( formatSliceEscapeLower ) {
return . { . data = bytes } ;
}
/// Return a Formatter for a []const u8 where every non-printable ASCII
/// character is escaped as \xNN, where NN is the character in uppercase
/// hexadecimal notation.
pub fn fmtSliceEscapeUpper ( bytes : [ ] const u8 ) std . fmt . Formatter ( formatSliceEscapeUpper ) {
return . { . data = bytes } ;
}
2021-03-06 08:57:21 +00:00
fn formatSizeImpl ( comptime radix : comptime_int ) type {
return struct {
2022-11-27 21:26:41 +00:00
fn formatSizeImpl (
2021-03-06 08:57:21 +00:00
value : u64 ,
comptime fmt : [ ] const u8 ,
options : FormatOptions ,
writer : anytype ,
) ! void {
2021-06-20 01:10:22 +00:00
_ = fmt ;
2021-03-06 08:57:21 +00:00
if ( value = = 0 ) {
2022-07-17 06:38:31 +00:00
return formatBuf ( " 0B " , options , writer ) ;
2021-03-06 08:57:21 +00:00
}
2022-07-17 06:38:31 +00:00
// The worst case in terms of space needed is 32 bytes + 3 for the suffix.
var buf : [ 35 ] u8 = undefined ;
var bufstream = io . fixedBufferStream ( buf [ 0 . . ] ) ;
2021-03-06 08:57:21 +00:00
const mags_si = " kMGTPEZY " ;
const mags_iec = " KMGTPEZY " ;
const log2 = math . log2 ( value ) ;
const magnitude = switch ( radix ) {
1000 = > math . min ( log2 / comptime math . log2 ( 1000 ) , mags_si . len - 1 ) ,
1024 = > math . min ( log2 / 10 , mags_iec . len - 1 ) ,
else = > unreachable ,
} ;
const new_value = lossyCast ( f64 , value ) / math . pow ( f64 , lossyCast ( f64 , radix ) , lossyCast ( f64 , magnitude ) ) ;
const suffix = switch ( radix ) {
1000 = > mags_si [ magnitude ] ,
1024 = > mags_iec [ magnitude ] ,
else = > unreachable ,
} ;
2022-07-17 06:38:31 +00:00
formatFloatDecimal ( new_value , options , bufstream . writer ( ) ) catch | err | switch ( err ) {
error . NoSpaceLeft = > unreachable , // 35 bytes should be enough
} ;
2021-03-06 08:57:21 +00:00
2022-07-17 06:38:31 +00:00
bufstream . writer ( ) . writeAll ( if ( suffix = = ' ' )
" B "
else switch ( radix ) {
2021-03-06 08:57:21 +00:00
1000 = > & [ _ ] u8 { suffix , 'B' } ,
1024 = > & [ _ ] u8 { suffix , 'i' , 'B' } ,
else = > unreachable ,
2022-07-17 06:38:31 +00:00
} ) catch | err | switch ( err ) {
error . NoSpaceLeft = > unreachable ,
2021-03-06 08:57:21 +00:00
} ;
2022-07-17 06:38:31 +00:00
return formatBuf ( bufstream . getWritten ( ) , options , writer ) ;
2021-03-06 08:57:21 +00:00
}
} ;
}
2022-11-27 21:26:41 +00:00
const formatSizeDec = formatSizeImpl ( 1000 ) . formatSizeImpl ;
const formatSizeBin = formatSizeImpl ( 1024 ) . formatSizeImpl ;
2021-03-06 08:57:21 +00:00
/// Return a Formatter for a u64 value representing a file size.
/// This formatter represents the number as multiple of 1000 and uses the SI
/// measurement units (kB, MB, GB, ...).
pub fn fmtIntSizeDec ( value : u64 ) std . fmt . Formatter ( formatSizeDec ) {
return . { . data = value } ;
}
/// Return a Formatter for a u64 value representing a file size.
/// This formatter represents the number as multiple of 1024 and uses the IEC
/// measurement units (KiB, MiB, GiB, ...).
pub fn fmtIntSizeBin ( value : u64 ) std . fmt . Formatter ( formatSizeBin ) {
return . { . data = value } ;
}
2022-01-01 16:36:53 +00:00
fn checkTextFmt ( comptime fmt : [ ] const u8 ) void {
if ( fmt . len ! = 1 )
2022-07-23 15:02:55 +00:00
@compileError ( " unsupported format string ' " + + fmt + + " ' when formatting text " ) ;
2022-01-01 16:36:53 +00:00
switch ( fmt [ 0 ] ) {
'x' = > @compileError ( " specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead " ) ,
'X' = > @compileError ( " specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead " ) ,
'e' = > @compileError ( " specifier 'e' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeLower instead " ) ,
'E' = > @compileError ( " specifier 'E' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeUpper instead " ) ,
'z' = > @compileError ( " specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead " ) ,
'Z' = > @compileError ( " specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead " ) ,
else = > { } ,
}
}
2018-06-04 15:06:55 +00:00
pub fn formatText (
bytes : [ ] const u8 ,
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2022-01-01 16:36:53 +00:00
comptime checkTextFmt ( fmt ) ;
return formatBuf ( bytes , options , writer ) ;
2018-05-30 15:18:11 +00:00
}
2018-06-04 15:06:55 +00:00
pub fn formatAsciiChar (
c : u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2022-09-23 09:20:38 +00:00
return formatBuf ( @as ( * const [ 1 ] u8 , & c ) , options , writer ) ;
2017-03-10 00:12:15 +00:00
}
2020-09-21 10:39:35 +00:00
pub fn formatUnicodeCodepoint (
c : u21 ,
2019-12-22 10:38:27 +00:00
options : FormatOptions ,
2020-09-21 10:39:35 +00:00
writer : anytype ,
) ! void {
2020-11-19 17:16:23 +00:00
var buf : [ 4 ] u8 = undefined ;
2022-04-13 16:28:49 +00:00
const len = unicode . utf8Encode ( c , & buf ) catch | err | switch ( err ) {
2020-11-19 17:16:23 +00:00
error . Utf8CannotEncodeSurrogateHalf , error . CodepointTooLarge = > {
2022-04-13 16:28:49 +00:00
const len = unicode . utf8Encode ( unicode . replacement_character , & buf ) catch unreachable ;
return formatBuf ( buf [ 0 . . len ] , options , writer ) ;
2020-11-19 17:16:23 +00:00
} ,
} ;
return formatBuf ( buf [ 0 . . len ] , options , writer ) ;
2019-12-22 10:38:27 +00:00
}
2018-06-04 15:06:55 +00:00
pub fn formatBuf (
buf : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2020-09-21 14:14:47 +00:00
if ( options . width ) | min_width | {
// In case of error assume the buffer content is ASCII-encoded
2021-04-23 02:32:57 +00:00
const width = unicode . utf8CountCodepoints ( buf ) catch buf . len ;
2020-09-21 14:14:47 +00:00
const padding = if ( width < min_width ) min_width - width else 0 ;
if ( padding = = 0 )
return writer . writeAll ( buf ) ;
switch ( options . alignment ) {
. Left = > {
try writer . writeAll ( buf ) ;
try writer . writeByteNTimes ( options . fill , padding ) ;
} ,
. Center = > {
const left_padding = padding / 2 ;
const right_padding = ( padding + 1 ) / 2 ;
try writer . writeByteNTimes ( options . fill , left_padding ) ;
try writer . writeAll ( buf ) ;
try writer . writeByteNTimes ( options . fill , right_padding ) ;
} ,
. Right = > {
try writer . writeByteNTimes ( options . fill , padding ) ;
try writer . writeAll ( buf ) ;
} ,
}
} else {
// Fast path, avoid counting the number of codepoints
try writer . writeAll ( buf ) ;
2017-04-06 09:34:04 +00:00
}
}
2020-09-22 12:15:41 +00:00
/// Print a float in scientific notation to the specified precision. Null uses full precision.
/// It should be the case that every full precision, printed value can be re-parsed back to the
/// same type unambiguously.
2018-06-04 15:06:55 +00:00
pub fn formatFloatScientific (
2020-07-11 11:09:04 +00:00
value : anytype ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2018-06-30 08:15:02 +00:00
var x = @floatCast ( f64 , value ) ;
2017-10-23 20:40:49 +00:00
// Errol doesn't handle these special cases.
2017-10-24 19:18:50 +00:00
if ( math . signbit ( x ) ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " - " ) ;
2017-10-24 19:18:50 +00:00
x = - x ;
}
2018-04-17 10:58:10 +00:00
if ( math . isNan ( x ) ) {
2020-06-20 10:02:48 +00:00
return writer . writeAll ( " nan " ) ;
2018-04-17 10:58:10 +00:00
}
2017-10-23 20:40:49 +00:00
if ( math . isPositiveInf ( x ) ) {
2020-06-20 10:02:48 +00:00
return writer . writeAll ( " inf " ) ;
2017-10-23 20:40:49 +00:00
}
if ( x = = 0.0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
2019-08-17 23:17:16 +00:00
if ( options . precision ) | precision | {
2018-04-17 10:58:10 +00:00
if ( precision ! = 0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
2018-04-17 10:58:10 +00:00
var i : usize = 0 ;
while ( i < precision ) : ( i + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
}
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " .0 " ) ;
2018-04-17 10:58:10 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " e+00 " ) ;
2018-04-17 10:58:10 +00:00
return ;
2017-10-23 20:40:49 +00:00
}
var buffer : [ 32 ] u8 = undefined ;
2018-04-17 10:58:10 +00:00
var float_decimal = errol . errol3 ( x , buffer [ 0 . . ] ) ;
2019-08-17 23:17:16 +00:00
if ( options . precision ) | precision | {
2018-04-17 10:58:10 +00:00
errol . roundToPrecision ( & float_decimal , precision , errol . RoundMode . Scientific ) ;
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ 0 . . 1 ] ) ;
2018-04-17 10:58:10 +00:00
// {e0} case prints no `.`
if ( precision ! = 0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
2018-04-17 10:58:10 +00:00
var printed : usize = 0 ;
if ( float_decimal . digits . len > 1 ) {
const num_digits = math . min ( float_decimal . digits . len , precision + 1 ) ;
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ 1 . . num_digits ] ) ;
2018-04-17 10:58:10 +00:00
printed + = num_digits - 1 ;
}
while ( printed < precision ) : ( printed + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
}
2017-08-18 21:54:42 +00:00
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ 0 . . 1 ] ) ;
try writer . writeAll ( " . " ) ;
2018-04-17 10:58:10 +00:00
if ( float_decimal . digits . len > 1 ) {
2019-12-09 20:56:19 +00:00
const num_digits = if ( @TypeOf ( value ) = = f32 ) math . min ( @as ( usize , 9 ) , float_decimal . digits . len ) else float_decimal . digits . len ;
2018-04-17 10:58:10 +00:00
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ 1 . . num_digits ] ) ;
2018-04-17 10:58:10 +00:00
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
2017-08-18 21:54:42 +00:00
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " e " ) ;
2018-04-17 10:58:10 +00:00
const exp = float_decimal . exp - 1 ;
if ( exp > = 0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " + " ) ;
2018-04-17 10:58:10 +00:00
if ( exp > - 10 and exp < 10 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
2021-04-10 11:09:38 +00:00
try formatInt ( exp , 10 , . lower , FormatOptions { . width = 0 } , writer ) ;
2018-04-17 10:58:10 +00:00
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " - " ) ;
2018-04-17 10:58:10 +00:00
if ( exp > - 10 and exp < 10 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
2021-04-10 11:09:38 +00:00
try formatInt ( - exp , 10 , . lower , FormatOptions { . width = 0 } , writer ) ;
2017-08-18 21:54:42 +00:00
}
2017-06-08 02:56:57 +00:00
}
2017-04-06 09:34:04 +00:00
2021-03-23 09:08:34 +00:00
pub fn formatFloatHexadecimal (
value : anytype ,
options : FormatOptions ,
writer : anytype ,
) ! void {
if ( math . signbit ( value ) ) {
try writer . writeByte ( '-' ) ;
}
if ( math . isNan ( value ) ) {
return writer . writeAll ( " nan " ) ;
}
if ( math . isInf ( value ) ) {
return writer . writeAll ( " inf " ) ;
}
const T = @TypeOf ( value ) ;
2022-04-26 15:08:54 +00:00
const TU = std . meta . Int ( . unsigned , @bitSizeOf ( T ) ) ;
2021-03-23 09:08:34 +00:00
const mantissa_bits = math . floatMantissaBits ( T ) ;
2022-04-25 23:32:42 +00:00
const fractional_bits = math . floatFractionalBits ( T ) ;
2021-03-23 09:08:34 +00:00
const exponent_bits = math . floatExponentBits ( T ) ;
const mantissa_mask = ( 1 < < mantissa_bits ) - 1 ;
const exponent_mask = ( 1 < < exponent_bits ) - 1 ;
const exponent_bias = ( 1 < < ( exponent_bits - 1 ) ) - 1 ;
const as_bits = @bitCast ( TU , value ) ;
var mantissa = as_bits & mantissa_mask ;
var exponent : i32 = @truncate ( u16 , ( as_bits > > mantissa_bits ) & exponent_mask ) ;
const is_denormal = exponent = = 0 and mantissa ! = 0 ;
const is_zero = exponent = = 0 and mantissa = = 0 ;
if ( is_zero ) {
// Handle this case here to simplify the logic below.
try writer . writeAll ( " 0x0 " ) ;
if ( options . precision ) | precision | {
if ( precision > 0 ) {
try writer . writeAll ( " . " ) ;
try writer . writeByteNTimes ( '0' , precision ) ;
}
} else {
try writer . writeAll ( " .0 " ) ;
}
try writer . writeAll ( " p0 " ) ;
return ;
}
if ( is_denormal ) {
// Adjust the exponent for printing.
exponent + = 1 ;
} else {
2022-04-25 23:32:42 +00:00
if ( fractional_bits = = mantissa_bits )
mantissa | = 1 < < fractional_bits ; // Add the implicit integer bit.
2021-03-23 09:08:34 +00:00
}
2022-04-25 23:32:42 +00:00
const mantissa_digits = ( fractional_bits + 3 ) / 4 ;
2022-10-25 09:06:54 +00:00
// Fill in zeroes to round the fraction width to a multiple of 4.
mantissa < < = mantissa_digits * 4 - fractional_bits ;
2021-03-23 09:08:34 +00:00
if ( options . precision ) | precision | {
// Round if needed.
if ( precision < mantissa_digits ) {
// We always have at least 4 extra bits.
var extra_bits = ( mantissa_digits - precision ) * 4 ;
// The result LSB is the Guard bit, we need two more (Round and
// Sticky) to round the value.
while ( extra_bits > 2 ) {
mantissa = ( mantissa > > 1 ) | ( mantissa & 1 ) ;
extra_bits - = 1 ;
}
// Round to nearest, tie to even.
mantissa | = @boolToInt ( mantissa & 0b100 ! = 0 ) ;
mantissa + = 1 ;
// Drop the excess bits.
mantissa > > = 2 ;
// Restore the alignment.
mantissa < < = @intCast ( math . Log2Int ( TU ) , ( mantissa_digits - precision ) * 4 ) ;
const overflow = mantissa & ( 1 < < 1 + mantissa_digits * 4 ) ! = 0 ;
// Prefer a normalized result in case of overflow.
if ( overflow ) {
mantissa > > = 1 ;
exponent + = 1 ;
}
}
}
// +1 for the decimal part.
var buf : [ 1 + mantissa_digits ] u8 = undefined ;
2021-06-10 01:35:42 +00:00
_ = formatIntBuf ( & buf , mantissa , 16 , . lower , . { . fill = '0' , . width = 1 + mantissa_digits } ) ;
2021-03-23 09:08:34 +00:00
try writer . writeAll ( " 0x " ) ;
try writer . writeByte ( buf [ 0 ] ) ;
const trimmed = mem . trimRight ( u8 , buf [ 1 . . ] , " 0 " ) ;
2021-05-27 21:08:54 +00:00
if ( options . precision ) | precision | {
if ( precision > 0 ) try writer . writeAll ( " . " ) ;
} else if ( trimmed . len > 0 ) {
try writer . writeAll ( " . " ) ;
}
2021-03-23 09:08:34 +00:00
try writer . writeAll ( trimmed ) ;
// Add trailing zeros if explicitly requested.
if ( options . precision ) | precision | if ( precision > 0 ) {
if ( precision > trimmed . len )
try writer . writeByteNTimes ( '0' , precision - trimmed . len ) ;
} ;
try writer . writeAll ( " p " ) ;
2021-04-10 11:09:38 +00:00
try formatInt ( exponent - exponent_bias , 10 , . lower , . { } , writer ) ;
2021-03-23 09:08:34 +00:00
}
2020-09-22 12:15:41 +00:00
/// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
/// By default floats are printed at full precision (no rounding).
2018-06-04 15:06:55 +00:00
pub fn formatFloatDecimal (
2020-07-11 11:09:04 +00:00
value : anytype ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2019-11-07 04:25:57 +00:00
var x = @as ( f64 , value ) ;
2017-12-30 23:27:58 +00:00
// Errol doesn't handle these special cases.
if ( math . signbit ( x ) ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " - " ) ;
2017-12-30 23:27:58 +00:00
x = - x ;
}
2018-04-17 10:58:10 +00:00
if ( math . isNan ( x ) ) {
2020-06-20 10:02:48 +00:00
return writer . writeAll ( " nan " ) ;
2018-04-17 10:58:10 +00:00
}
2017-12-30 23:27:58 +00:00
if ( math . isPositiveInf ( x ) ) {
2020-06-20 10:02:48 +00:00
return writer . writeAll ( " inf " ) ;
2017-12-30 23:27:58 +00:00
}
if ( x = = 0.0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
2019-08-17 23:17:16 +00:00
if ( options . precision ) | precision | {
2018-04-17 10:58:10 +00:00
if ( precision ! = 0 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
2018-04-17 10:58:10 +00:00
var i : usize = 0 ;
while ( i < precision ) : ( i + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
}
}
return ;
2017-12-30 23:27:58 +00:00
}
2018-04-17 10:58:10 +00:00
// non-special case, use errol3
2017-12-30 23:27:58 +00:00
var buffer : [ 32 ] u8 = undefined ;
2018-04-17 10:58:10 +00:00
var float_decimal = errol . errol3 ( x , buffer [ 0 . . ] ) ;
2019-08-17 23:17:16 +00:00
if ( options . precision ) | precision | {
2018-04-17 10:58:10 +00:00
errol . roundToPrecision ( & float_decimal , precision , errol . RoundMode . Decimal ) ;
// exp < 0 means the leading is always 0 as errol result is normalized.
2018-06-17 06:57:07 +00:00
var num_digits_whole = if ( float_decimal . exp > 0 ) @intCast ( usize , float_decimal . exp ) else 0 ;
2018-04-17 10:58:10 +00:00
// the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
var num_digits_whole_no_pad = math . min ( num_digits_whole , float_decimal . digits . len ) ;
if ( num_digits_whole > 0 ) {
// We may have to zero pad, for instance 1e4 requires zero padding.
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ 0 . . num_digits_whole_no_pad ] ) ;
2018-04-17 10:58:10 +00:00
var i = num_digits_whole_no_pad ;
while ( i < num_digits_whole ) : ( i + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
// {.0} special case doesn't want a trailing '.'
if ( precision = = 0 ) {
return ;
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
2018-04-17 10:58:10 +00:00
// Keep track of fractional count printed for case where we pre-pad then post-pad with 0's.
var printed : usize = 0 ;
// Zero-fill until we reach significant digits or run out of precision.
if ( float_decimal . exp < = 0 ) {
2018-06-17 06:57:07 +00:00
const zero_digit_count = @intCast ( usize , - float_decimal . exp ) ;
2018-04-17 10:58:10 +00:00
const zeros_to_print = math . min ( zero_digit_count , precision ) ;
var i : usize = 0 ;
while ( i < zeros_to_print ) : ( i + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
printed + = 1 ;
}
if ( printed > = precision ) {
return ;
}
}
// Remaining fractional portion, zero-padding if insufficient.
2019-02-08 23:18:47 +00:00
assert ( precision > = printed ) ;
2018-04-17 10:58:10 +00:00
if ( num_digits_whole_no_pad + precision - printed < float_decimal . digits . len ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ num_digits_whole_no_pad . . num_digits_whole_no_pad + precision - printed ] ) ;
2018-04-17 10:58:10 +00:00
return ;
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ num_digits_whole_no_pad . . ] ) ;
2018-04-17 10:58:10 +00:00
printed + = float_decimal . digits . len - num_digits_whole_no_pad ;
while ( printed < precision ) : ( printed + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
}
2017-12-30 23:27:58 +00:00
} else {
2018-04-17 10:58:10 +00:00
// exp < 0 means the leading is always 0 as errol result is normalized.
2018-06-17 06:57:07 +00:00
var num_digits_whole = if ( float_decimal . exp > 0 ) @intCast ( usize , float_decimal . exp ) else 0 ;
2018-04-17 10:58:10 +00:00
// the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
var num_digits_whole_no_pad = math . min ( num_digits_whole , float_decimal . digits . len ) ;
if ( num_digits_whole > 0 ) {
// We may have to zero pad, for instance 1e4 requires zero padding.
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ 0 . . num_digits_whole_no_pad ] ) ;
2018-04-17 10:58:10 +00:00
var i = num_digits_whole_no_pad ;
while ( i < num_digits_whole ) : ( i + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
} else {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
// Omit `.` if no fractional portion
if ( float_decimal . exp > = 0 and num_digits_whole_no_pad = = float_decimal . digits . len ) {
return ;
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " . " ) ;
2018-04-17 10:58:10 +00:00
// Zero-fill until we reach significant digits or run out of precision.
if ( float_decimal . exp < 0 ) {
2018-06-17 06:57:07 +00:00
const zero_digit_count = @intCast ( usize , - float_decimal . exp ) ;
2018-04-17 10:58:10 +00:00
var i : usize = 0 ;
while ( i < zero_digit_count ) : ( i + = 1 ) {
2020-06-20 10:02:48 +00:00
try writer . writeAll ( " 0 " ) ;
2018-04-17 10:58:10 +00:00
}
}
2020-06-20 10:02:48 +00:00
try writer . writeAll ( float_decimal . digits [ num_digits_whole_no_pad . . ] ) ;
2017-12-30 23:27:58 +00:00
}
}
2018-05-29 00:23:55 +00:00
pub fn formatInt (
2020-07-11 11:09:04 +00:00
value : anytype ,
2018-05-29 00:23:55 +00:00
base : u8 ,
2021-04-10 11:09:38 +00:00
case : Case ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2020-09-16 22:41:26 +00:00
assert ( base > = 2 ) ;
2019-12-09 20:56:19 +00:00
const int_value = if ( @TypeOf ( value ) = = comptime_int ) blk : {
2019-04-05 01:36:31 +00:00
const Int = math . IntFittingRange ( value , value ) ;
2019-11-10 23:08:24 +00:00
break : blk @as ( Int , value ) ;
2021-02-25 04:29:01 +00:00
} else value ;
2019-04-05 01:36:31 +00:00
2020-09-16 22:41:26 +00:00
const value_info = @typeInfo ( @TypeOf ( int_value ) ) . Int ;
2017-03-10 00:12:15 +00:00
2020-09-16 22:41:26 +00:00
// The type must have the same size as `base` or be wider in order for the
// division to work
const min_int_bits = comptime math . max ( value_info . bits , 8 ) ;
2020-10-17 12:09:59 +00:00
const MinInt = std . meta . Int ( . unsigned , min_int_bits ) ;
2017-03-10 00:12:15 +00:00
2020-09-16 22:41:26 +00:00
const abs_value = math . absCast ( int_value ) ;
// The worst case in terms of space needed is base 2, plus 1 for the sign
var buf : [ 1 + math . max ( value_info . bits , 1 ) ] u8 = undefined ;
2017-03-10 00:12:15 +00:00
2020-09-16 22:41:26 +00:00
var a : MinInt = abs_value ;
var index : usize = buf . len ;
2022-12-05 20:58:45 +00:00
// TODO isComptime here because of https://github.com/ziglang/zig/issues/13335.
if ( base = = 10 and ! isComptime ( ) ) {
while ( a > = 100 ) : ( a = @divTrunc ( a , 100 ) ) {
index - = 2 ;
buf [ index . . ] [ 0 . . 2 ] . * = digits2 ( @intCast ( usize , a % 100 ) ) ;
}
if ( a < 10 ) {
index - = 1 ;
buf [ index ] = '0' + @intCast ( u8 , a ) ;
} else {
index - = 2 ;
buf [ index . . ] [ 0 . . 2 ] . * = digits2 ( @intCast ( usize , a ) ) ;
}
} else {
while ( true ) {
const digit = a % base ;
index - = 1 ;
buf [ index ] = digitToChar ( @intCast ( u8 , digit ) , case ) ;
a / = base ;
if ( a = = 0 ) break ;
}
2017-03-10 00:12:15 +00:00
}
2020-10-18 00:04:53 +00:00
if ( value_info . signedness = = . signed ) {
2020-09-16 22:41:26 +00:00
if ( value < 0 ) {
// Negative integer
index - = 1 ;
buf [ index ] = '-' ;
} else if ( options . width = = null or options . width . ? = = 0 ) {
// Positive integer, omit the plus sign
} else {
// Positive integer
index - = 1 ;
buf [ index ] = '+' ;
2017-03-10 00:12:15 +00:00
}
}
2020-09-16 22:41:26 +00:00
return formatBuf ( buf [ index . . ] , options , writer ) ;
2017-03-10 00:12:15 +00:00
}
2022-12-05 20:58:45 +00:00
// TODO: Remove once https://github.com/ziglang/zig/issues/868 is resolved.
fn isComptime ( ) bool {
var a : u8 = 0 ;
return @typeInfo ( @TypeOf ( . { a } ) ) . Struct . fields [ 0 ] . is_comptime ;
}
2021-04-10 11:09:38 +00:00
pub fn formatIntBuf ( out_buf : [ ] u8 , value : anytype , base : u8 , case : Case , options : FormatOptions ) usize {
2020-03-06 22:59:21 +00:00
var fbs = std . io . fixedBufferStream ( out_buf ) ;
2021-04-10 11:09:38 +00:00
formatInt ( value , base , case , options , fbs . writer ( ) ) catch unreachable ;
2020-03-06 22:59:21 +00:00
return fbs . pos ;
2017-03-10 00:12:15 +00:00
}
2022-12-05 20:58:45 +00:00
// Converts values in the range [0, 100) to a string.
fn digits2 ( value : usize ) [ 2 ] u8 {
return ( " 0001020304050607080910111213141516171819 " + +
" 2021222324252627282930313233343536373839 " + +
" 4041424344454647484950515253545556575859 " + +
" 6061626364656667686970717273747576777879 " + +
" 8081828384858687888990919293949596979899 " ) [ value * 2 . . ] [ 0 . . 2 ] . * ;
}
2021-12-18 22:10:02 +00:00
const FormatDurationData = struct {
ns : u64 ,
negative : bool = false ,
} ;
fn formatDuration ( data : FormatDurationData , comptime fmt : [ ] const u8 , options : std . fmt . FormatOptions , writer : anytype ) ! void {
2021-06-20 01:10:22 +00:00
_ = fmt ;
2021-12-18 22:10:02 +00:00
// worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24
var buf : [ 24 ] u8 = undefined ;
var fbs = std . io . fixedBufferStream ( & buf ) ;
var buf_writer = fbs . writer ( ) ;
if ( data . negative ) {
try buf_writer . writeByte ( '-' ) ;
}
var ns_remaining = data . ns ;
2021-01-12 00:15:56 +00:00
inline for ( . {
. { . ns = 365 * std . time . ns_per_day , . sep = 'y' } ,
. { . ns = std . time . ns_per_week , . sep = 'w' } ,
. { . ns = std . time . ns_per_day , . sep = 'd' } ,
. { . ns = std . time . ns_per_hour , . sep = 'h' } ,
. { . ns = std . time . ns_per_min , . sep = 'm' } ,
} ) | unit | {
if ( ns_remaining > = unit . ns ) {
const units = ns_remaining / unit . ns ;
2021-12-18 22:10:02 +00:00
try formatInt ( units , 10 , . lower , . { } , buf_writer ) ;
try buf_writer . writeByte ( unit . sep ) ;
2021-01-12 00:15:56 +00:00
ns_remaining - = units * unit . ns ;
2021-12-18 22:10:02 +00:00
if ( ns_remaining = = 0 )
return formatBuf ( fbs . getWritten ( ) , options , writer ) ;
2021-01-12 00:15:56 +00:00
}
}
inline for ( . {
. { . ns = std . time . ns_per_s , . sep = " s " } ,
. { . ns = std . time . ns_per_ms , . sep = " ms " } ,
. { . ns = std . time . ns_per_us , . sep = " us " } ,
} ) | unit | {
const kunits = ns_remaining * 1000 / unit . ns ;
if ( kunits > = 1000 ) {
2021-12-18 22:10:02 +00:00
try formatInt ( kunits / 1000 , 10 , . lower , . { } , buf_writer ) ;
2021-03-28 03:16:03 +00:00
const frac = kunits % 1000 ;
if ( frac > 0 ) {
2021-01-12 00:15:56 +00:00
// Write up to 3 decimal places
2021-12-18 22:10:02 +00:00
var decimal_buf = [ _ ] u8 { '.' , 0 , 0 , 0 } ;
_ = formatIntBuf ( decimal_buf [ 1 . . ] , frac , 10 , . lower , . { . fill = '0' , . width = 3 } ) ;
2021-01-12 00:15:56 +00:00
var end : usize = 4 ;
while ( end > 1 ) : ( end - = 1 ) {
2021-12-18 22:10:02 +00:00
if ( decimal_buf [ end - 1 ] ! = '0' ) break ;
2021-01-12 00:15:56 +00:00
}
2021-12-18 22:10:02 +00:00
try buf_writer . writeAll ( decimal_buf [ 0 . . end ] ) ;
2021-01-12 00:15:56 +00:00
}
2021-12-18 22:10:02 +00:00
try buf_writer . writeAll ( unit . sep ) ;
return formatBuf ( fbs . getWritten ( ) , options , writer ) ;
2021-01-12 00:15:56 +00:00
}
}
2021-12-18 22:10:02 +00:00
try formatInt ( ns_remaining , 10 , . lower , . { } , buf_writer ) ;
try buf_writer . writeAll ( " ns " ) ;
return formatBuf ( fbs . getWritten ( ) , options , writer ) ;
2021-01-12 00:15:56 +00:00
}
2021-03-07 13:00:15 +00:00
/// Return a Formatter for number of nanoseconds according to its magnitude:
/// [#y][#w][#d][#h][#m]#[.###][n|u|m]s
pub fn fmtDuration ( ns : u64 ) Formatter ( formatDuration ) {
2021-12-18 22:10:02 +00:00
const data = FormatDurationData { . ns = ns } ;
return . { . data = data } ;
2021-03-07 13:00:15 +00:00
}
test " fmtDuration " {
2021-01-12 00:15:56 +00:00
var buf : [ 24 ] u8 = undefined ;
inline for ( . {
. { . s = " 0ns " , . d = 0 } ,
. { . s = " 1ns " , . d = 1 } ,
. { . s = " 999ns " , . d = std . time . ns_per_us - 1 } ,
. { . s = " 1us " , . d = std . time . ns_per_us } ,
. { . s = " 1.45us " , . d = 1450 } ,
. { . s = " 1.5us " , . d = 3 * std . time . ns_per_us / 2 } ,
2021-03-28 03:16:03 +00:00
. { . s = " 14.5us " , . d = 14500 } ,
. { . s = " 145us " , . d = 145000 } ,
2021-01-12 00:15:56 +00:00
. { . s = " 999.999us " , . d = std . time . ns_per_ms - 1 } ,
. { . s = " 1ms " , . d = std . time . ns_per_ms + 1 } ,
. { . s = " 1.5ms " , . d = 3 * std . time . ns_per_ms / 2 } ,
2021-03-28 03:16:03 +00:00
. { . s = " 1.11ms " , . d = 1110000 } ,
. { . s = " 1.111ms " , . d = 1111000 } ,
. { . s = " 1.111ms " , . d = 1111100 } ,
2021-01-12 00:15:56 +00:00
. { . s = " 999.999ms " , . d = std . time . ns_per_s - 1 } ,
. { . s = " 1s " , . d = std . time . ns_per_s } ,
. { . s = " 59.999s " , . d = std . time . ns_per_min - 1 } ,
. { . s = " 1m " , . d = std . time . ns_per_min } ,
. { . s = " 1h " , . d = std . time . ns_per_hour } ,
. { . s = " 1d " , . d = std . time . ns_per_day } ,
. { . s = " 1w " , . d = std . time . ns_per_week } ,
. { . s = " 1y " , . d = 365 * std . time . ns_per_day } ,
. { . s = " 1y52w23h59m59.999s " , . d = 730 * std . time . ns_per_day - 1 } , // 365d = 52w1d
. { . s = " 1y1h1.001s " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_s + std . time . ns_per_ms } ,
. { . s = " 1y1h1s " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_s + 999 * std . time . ns_per_us } ,
. { . s = " 1y1h999.999us " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms - 1 } ,
. { . s = " 1y1h1ms " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms } ,
. { . s = " 1y1h1ms " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms + 1 } ,
2021-03-07 13:00:15 +00:00
. { . s = " 1y1m999ns " , . d = 365 * std . time . ns_per_day + std . time . ns_per_min + 999 } ,
2021-12-18 22:10:02 +00:00
. { . s = " 584y49w23h34m33.709s " , . d = math . maxInt ( u64 ) } ,
2021-01-12 00:15:56 +00:00
} ) | tc | {
2021-03-07 13:00:15 +00:00
const slice = try bufPrint ( & buf , " {} " , . { fmtDuration ( tc . d ) } ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expectEqualStrings ( tc . s , slice ) ;
2021-01-12 00:15:56 +00:00
}
2021-12-18 22:10:02 +00:00
inline for ( . {
. { . s = " =======0ns " , . f = " {s:=>10} " , . d = 0 } ,
. { . s = " 1ns======= " , . f = " {s:=<10} " , . d = 1 } ,
. { . s = " 999ns " , . f = " {s:^10} " , . d = std . time . ns_per_us - 1 } ,
} ) | tc | {
const slice = try bufPrint ( & buf , tc . f , . { fmtDuration ( tc . d ) } ) ;
try std . testing . expectEqualStrings ( tc . s , slice ) ;
}
2021-01-12 00:15:56 +00:00
}
2021-06-26 13:21:27 +00:00
fn formatDurationSigned ( ns : i64 , comptime fmt : [ ] const u8 , options : std . fmt . FormatOptions , writer : anytype ) ! void {
if ( ns < 0 ) {
2021-12-18 22:10:02 +00:00
const data = FormatDurationData { . ns = @intCast ( u64 , - ns ) , . negative = true } ;
try formatDuration ( data , fmt , options , writer ) ;
2021-06-26 13:21:27 +00:00
} else {
2021-12-18 22:10:02 +00:00
const data = FormatDurationData { . ns = @intCast ( u64 , ns ) } ;
try formatDuration ( data , fmt , options , writer ) ;
2021-06-26 13:21:27 +00:00
}
}
/// Return a Formatter for number of nanoseconds according to its signed magnitude:
/// [#y][#w][#d][#h][#m]#[.###][n|u|m]s
pub fn fmtDurationSigned ( ns : i64 ) Formatter ( formatDurationSigned ) {
return . { . data = ns } ;
}
test " fmtDurationSigned " {
var buf : [ 24 ] u8 = undefined ;
inline for ( . {
. { . s = " 0ns " , . d = 0 } ,
. { . s = " 1ns " , . d = 1 } ,
. { . s = " -1ns " , . d = - ( 1 ) } ,
. { . s = " 999ns " , . d = std . time . ns_per_us - 1 } ,
. { . s = " -999ns " , . d = - ( std . time . ns_per_us - 1 ) } ,
. { . s = " 1us " , . d = std . time . ns_per_us } ,
. { . s = " -1us " , . d = - ( std . time . ns_per_us ) } ,
. { . s = " 1.45us " , . d = 1450 } ,
. { . s = " -1.45us " , . d = - ( 1450 ) } ,
. { . s = " 1.5us " , . d = 3 * std . time . ns_per_us / 2 } ,
. { . s = " -1.5us " , . d = - ( 3 * std . time . ns_per_us / 2 ) } ,
. { . s = " 14.5us " , . d = 14500 } ,
. { . s = " -14.5us " , . d = - ( 14500 ) } ,
. { . s = " 145us " , . d = 145000 } ,
. { . s = " -145us " , . d = - ( 145000 ) } ,
. { . s = " 999.999us " , . d = std . time . ns_per_ms - 1 } ,
. { . s = " -999.999us " , . d = - ( std . time . ns_per_ms - 1 ) } ,
. { . s = " 1ms " , . d = std . time . ns_per_ms + 1 } ,
. { . s = " -1ms " , . d = - ( std . time . ns_per_ms + 1 ) } ,
. { . s = " 1.5ms " , . d = 3 * std . time . ns_per_ms / 2 } ,
. { . s = " -1.5ms " , . d = - ( 3 * std . time . ns_per_ms / 2 ) } ,
. { . s = " 1.11ms " , . d = 1110000 } ,
. { . s = " -1.11ms " , . d = - ( 1110000 ) } ,
. { . s = " 1.111ms " , . d = 1111000 } ,
. { . s = " -1.111ms " , . d = - ( 1111000 ) } ,
. { . s = " 1.111ms " , . d = 1111100 } ,
. { . s = " -1.111ms " , . d = - ( 1111100 ) } ,
. { . s = " 999.999ms " , . d = std . time . ns_per_s - 1 } ,
. { . s = " -999.999ms " , . d = - ( std . time . ns_per_s - 1 ) } ,
. { . s = " 1s " , . d = std . time . ns_per_s } ,
. { . s = " -1s " , . d = - ( std . time . ns_per_s ) } ,
. { . s = " 59.999s " , . d = std . time . ns_per_min - 1 } ,
. { . s = " -59.999s " , . d = - ( std . time . ns_per_min - 1 ) } ,
. { . s = " 1m " , . d = std . time . ns_per_min } ,
. { . s = " -1m " , . d = - ( std . time . ns_per_min ) } ,
. { . s = " 1h " , . d = std . time . ns_per_hour } ,
. { . s = " -1h " , . d = - ( std . time . ns_per_hour ) } ,
. { . s = " 1d " , . d = std . time . ns_per_day } ,
. { . s = " -1d " , . d = - ( std . time . ns_per_day ) } ,
. { . s = " 1w " , . d = std . time . ns_per_week } ,
. { . s = " -1w " , . d = - ( std . time . ns_per_week ) } ,
. { . s = " 1y " , . d = 365 * std . time . ns_per_day } ,
. { . s = " -1y " , . d = - ( 365 * std . time . ns_per_day ) } ,
. { . s = " 1y52w23h59m59.999s " , . d = 730 * std . time . ns_per_day - 1 } , // 365d = 52w1d
. { . s = " -1y52w23h59m59.999s " , . d = - ( 730 * std . time . ns_per_day - 1 ) } , // 365d = 52w1d
. { . s = " 1y1h1.001s " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_s + std . time . ns_per_ms } ,
. { . s = " -1y1h1.001s " , . d = - ( 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_s + std . time . ns_per_ms ) } ,
. { . s = " 1y1h1s " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_s + 999 * std . time . ns_per_us } ,
. { . s = " -1y1h1s " , . d = - ( 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_s + 999 * std . time . ns_per_us ) } ,
. { . s = " 1y1h999.999us " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms - 1 } ,
. { . s = " -1y1h999.999us " , . d = - ( 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms - 1 ) } ,
. { . s = " 1y1h1ms " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms } ,
. { . s = " -1y1h1ms " , . d = - ( 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms ) } ,
. { . s = " 1y1h1ms " , . d = 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms + 1 } ,
. { . s = " -1y1h1ms " , . d = - ( 365 * std . time . ns_per_day + std . time . ns_per_hour + std . time . ns_per_ms + 1 ) } ,
. { . s = " 1y1m999ns " , . d = 365 * std . time . ns_per_day + std . time . ns_per_min + 999 } ,
. { . s = " -1y1m999ns " , . d = - ( 365 * std . time . ns_per_day + std . time . ns_per_min + 999 ) } ,
2021-12-18 22:10:02 +00:00
. { . s = " 292y24w3d23h47m16.854s " , . d = math . maxInt ( i64 ) } ,
. { . s = " -292y24w3d23h47m16.854s " , . d = math . minInt ( i64 ) + 1 } ,
2021-06-26 13:21:27 +00:00
} ) | tc | {
const slice = try bufPrint ( & buf , " {} " , . { fmtDurationSigned ( tc . d ) } ) ;
try std . testing . expectEqualStrings ( tc . s , slice ) ;
}
2021-12-18 22:10:02 +00:00
inline for ( . {
. { . s = " =======0ns " , . f = " {s:=>10} " , . d = 0 } ,
. { . s = " 1ns======= " , . f = " {s:=<10} " , . d = 1 } ,
. { . s = " -1ns====== " , . f = " {s:=<10} " , . d = - ( 1 ) } ,
. { . s = " -999ns " , . f = " {s:^10} " , . d = - ( std . time . ns_per_us - 1 ) } ,
} ) | tc | {
const slice = try bufPrint ( & buf , tc . f , . { fmtDurationSigned ( tc . d ) } ) ;
try std . testing . expectEqualStrings ( tc . s , slice ) ;
}
2021-06-26 13:21:27 +00:00
}
2020-07-03 19:35:18 +00:00
pub const ParseIntError = error {
2018-02-02 19:26:14 +00:00
/// The result cannot fit in the type specified
Overflow ,
2018-05-01 05:53:04 +00:00
2020-07-03 19:35:18 +00:00
/// The input was empty or had a byte that was not a digit
2018-02-02 19:26:14 +00:00
InvalidCharacter ,
} ;
2021-01-03 20:49:51 +00:00
/// Creates a Formatter type from a format function. Wrapping data in Formatter(func) causes
/// the data to be formatted using the given function `func`. `func` must be of the following
/// form:
///
/// fn formatExample(
/// data: T,
/// comptime fmt: []const u8,
/// options: std.fmt.FormatOptions,
/// writer: anytype,
/// ) !void;
///
pub fn Formatter ( comptime format_fn : anytype ) type {
2022-12-13 20:54:04 +00:00
const Data = @typeInfo ( @TypeOf ( format_fn ) ) . Fn . params [ 0 ] . type . ? ;
2021-01-03 20:49:51 +00:00
return struct {
data : Data ,
pub fn format (
self : @This ( ) ,
comptime fmt : [ ] const u8 ,
options : std . fmt . FormatOptions ,
writer : anytype ,
) @TypeOf ( writer ) . Error ! void {
try format_fn ( self . data , fmt , options , writer ) ;
}
} ;
}
2020-10-31 11:06:42 +00:00
/// Parses the string `buf` as signed or unsigned representation in the
/// specified radix of an integral value of type `T`.
///
/// When `radix` is zero the string prefix is examined to detect the true radix:
/// * A prefix of "0b" implies radix=2,
/// * A prefix of "0o" implies radix=8,
/// * A prefix of "0x" implies radix=16,
/// * Otherwise radix=10 is assumed.
///
2021-04-20 20:53:59 +00:00
/// Ignores '_' character in `buf`.
2020-10-31 11:06:42 +00:00
/// See also `parseUnsigned`.
2020-07-03 19:35:18 +00:00
pub fn parseInt ( comptime T : type , buf : [ ] const u8 , radix : u8 ) ParseIntError ! T {
if ( buf . len = = 0 ) return error . InvalidCharacter ;
if ( buf [ 0 ] = = '+' ) return parseWithSign ( T , buf [ 1 . . ] , radix , . Pos ) ;
if ( buf [ 0 ] = = '-' ) return parseWithSign ( T , buf [ 1 . . ] , radix , . Neg ) ;
return parseWithSign ( T , buf , radix , . Pos ) ;
}
test " parseInt " {
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " -10 " , 10 ) ) = = - 10 ) ;
try std . testing . expect ( ( try parseInt ( i32 , " +10 " , 10 ) ) = = 10 ) ;
try std . testing . expect ( ( try parseInt ( u32 , " +10 " , 10 ) ) = = 10 ) ;
try std . testing . expectError ( error . Overflow , parseInt ( u32 , " -10 " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 10 " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 10 " , 10 ) ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " _10_ " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 0x_10_ " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 0x10_ " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 0x_10 " , 10 ) ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( u8 , " 255 " , 10 ) ) = = 255 ) ;
try std . testing . expectError ( error . Overflow , parseInt ( u8 , " 256 " , 10 ) ) ;
2020-07-03 19:35:18 +00:00
// +0 and -0 should work for unsigned
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( u8 , " -0 " , 10 ) ) = = 0 ) ;
try std . testing . expect ( ( try parseInt ( u8 , " +0 " , 10 ) ) = = 0 ) ;
2020-07-03 19:35:18 +00:00
// ensure minInt is parsed correctly
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i8 , " -128 " , 10 ) ) = = math . minInt ( i8 ) ) ;
try std . testing . expect ( ( try parseInt ( i43 , " -4398046511104 " , 10 ) ) = = math . minInt ( i43 ) ) ;
2020-07-03 19:35:18 +00:00
// empty string or bare +- is invalid
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( i32 , " " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " + " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( i32 , " + " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " - " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( i32 , " - " , 10 ) ) ;
2020-10-31 11:06:42 +00:00
// autodectect the radix
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " 111 " , 0 ) ) = = 111 ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " 1_1_1 " , 0 ) ) = = 111 ) ;
try std . testing . expect ( ( try parseInt ( i32 , " 1_1_1 " , 0 ) ) = = 111 ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0b111 " , 0 ) ) = = 7 ) ;
2022-02-15 17:41:52 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0B111 " , 0 ) ) = = 7 ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0b1_11 " , 0 ) ) = = 7 ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0o111 " , 0 ) ) = = 73 ) ;
2022-02-15 17:41:52 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0O111 " , 0 ) ) = = 73 ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0o11_1 " , 0 ) ) = = 73 ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " +0x111 " , 0 ) ) = = 273 ) ;
try std . testing . expect ( ( try parseInt ( i32 , " -0b111 " , 0 ) ) = = - 7 ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " -0b11_1 " , 0 ) ) = = - 7 ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " -0o111 " , 0 ) ) = = - 73 ) ;
try std . testing . expect ( ( try parseInt ( i32 , " -0x111 " , 0 ) ) = = - 273 ) ;
2022-02-15 17:41:52 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " -0X111 " , 0 ) ) = = - 273 ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseInt ( i32 , " -0x1_11 " , 0 ) ) = = - 273 ) ;
2020-10-31 11:06:42 +00:00
// bare binary/octal/decimal prefix is invalid
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 0b " , 0 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 0o " , 0 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseInt ( u32 , " 0x " , 0 ) ) ;
2020-07-03 19:35:18 +00:00
}
fn parseWithSign (
comptime T : type ,
buf : [ ] const u8 ,
radix : u8 ,
comptime sign : enum { Pos , Neg } ,
) ParseIntError ! T {
if ( buf . len = = 0 ) return error . InvalidCharacter ;
2020-10-31 11:06:42 +00:00
var buf_radix = radix ;
var buf_start = buf ;
if ( radix = = 0 ) {
// Treat is as a decimal number by default.
buf_radix = 10 ;
// Detect the radix by looking at buf prefix.
if ( buf . len > 2 and buf [ 0 ] = = '0' ) {
2022-02-15 17:41:52 +00:00
switch ( std . ascii . toLower ( buf [ 1 ] ) ) {
2020-10-31 11:06:42 +00:00
'b' = > {
buf_radix = 2 ;
buf_start = buf [ 2 . . ] ;
} ,
'o' = > {
buf_radix = 8 ;
buf_start = buf [ 2 . . ] ;
} ,
'x' = > {
buf_radix = 16 ;
buf_start = buf [ 2 . . ] ;
} ,
else = > { } ,
}
}
}
2020-07-03 19:35:18 +00:00
const add = switch ( sign ) {
. Pos = > math . add ,
. Neg = > math . sub ,
} ;
2020-07-11 11:09:04 +00:00
2017-03-10 00:12:15 +00:00
var x : T = 0 ;
2021-04-21 19:30:06 +00:00
if ( buf_start [ 0 ] = = '_' or buf_start [ buf_start . len - 1 ] = = '_' ) return error . InvalidCharacter ;
2020-10-31 11:06:42 +00:00
for ( buf_start ) | c | {
2021-04-20 20:53:59 +00:00
if ( c = = '_' ) continue ;
2020-10-31 11:06:42 +00:00
const digit = try charToDigit ( c , buf_radix ) ;
2018-11-28 05:17:45 +00:00
2022-05-22 15:06:59 +00:00
if ( x ! = 0 ) x = try math . mul ( T , x , math . cast ( T , buf_radix ) orelse return error . Overflow ) ;
x = try add ( T , x , math . cast ( T , digit ) orelse return error . Overflow ) ;
2017-03-10 00:12:15 +00:00
}
return x ;
}
2020-10-31 11:06:42 +00:00
/// Parses the string `buf` as unsigned representation in the specified radix
/// of an integral value of type `T`.
///
/// When `radix` is zero the string prefix is examined to detect the true radix:
/// * A prefix of "0b" implies radix=2,
/// * A prefix of "0o" implies radix=8,
/// * A prefix of "0x" implies radix=16,
/// * Otherwise radix=10 is assumed.
///
2021-04-20 20:53:59 +00:00
/// Ignores '_' character in `buf`.
2020-10-31 11:06:42 +00:00
/// See also `parseInt`.
2020-07-03 19:35:18 +00:00
pub fn parseUnsigned ( comptime T : type , buf : [ ] const u8 , radix : u8 ) ParseIntError ! T {
return parseWithSign ( T , buf , radix , . Pos ) ;
}
2019-06-17 10:03:56 +00:00
test " parseUnsigned " {
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( u16 , " 050124 " , 10 ) ) = = 50124 ) ;
try std . testing . expect ( ( try parseUnsigned ( u16 , " 65535 " , 10 ) ) = = 65535 ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseUnsigned ( u16 , " 65_535 " , 10 ) ) = = 65535 ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . Overflow , parseUnsigned ( u16 , " 65536 " , 10 ) ) ;
2018-11-28 05:17:45 +00:00
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( u64 , " 0ffffffffffffffff " , 16 ) ) = = 0xffffffffffffffff ) ;
2021-05-08 21:45:21 +00:00
try std . testing . expect ( ( try parseUnsigned ( u64 , " 0f_fff_fff_fff_fff_fff " , 16 ) ) = = 0xffffffffffffffff ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . Overflow , parseUnsigned ( u64 , " 10000000000000000 " , 16 ) ) ;
2018-11-28 05:17:45 +00:00
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( u32 , " DeadBeef " , 16 ) ) = = 0xDEADBEEF ) ;
2018-11-28 05:17:45 +00:00
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( u7 , " 1 " , 10 ) ) = = 1 ) ;
try std . testing . expect ( ( try parseUnsigned ( u7 , " 1000 " , 2 ) ) = = 8 ) ;
2018-11-28 05:17:45 +00:00
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . InvalidCharacter , parseUnsigned ( u32 , " f " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseUnsigned ( u8 , " 109 " , 8 ) ) ;
2018-11-28 05:17:45 +00:00
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( u32 , " NUMBER " , 36 ) ) = = 1442151747 ) ;
2018-11-28 05:17:45 +00:00
// these numbers should fit even though the radix itself doesn't fit in the destination type
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( u1 , " 0 " , 10 ) ) = = 0 ) ;
try std . testing . expect ( ( try parseUnsigned ( u1 , " 1 " , 10 ) ) = = 1 ) ;
try std . testing . expectError ( error . Overflow , parseUnsigned ( u1 , " 2 " , 10 ) ) ;
try std . testing . expect ( ( try parseUnsigned ( u1 , " 001 " , 16 ) ) = = 1 ) ;
try std . testing . expect ( ( try parseUnsigned ( u2 , " 3 " , 16 ) ) = = 3 ) ;
try std . testing . expectError ( error . Overflow , parseUnsigned ( u2 , " 4 " , 16 ) ) ;
2020-07-03 19:35:18 +00:00
// parseUnsigned does not expect a sign
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . InvalidCharacter , parseUnsigned ( u8 , " +0 " , 10 ) ) ;
try std . testing . expectError ( error . InvalidCharacter , parseUnsigned ( u8 , " -0 " , 10 ) ) ;
2020-07-03 19:35:18 +00:00
// test empty string error
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . InvalidCharacter , parseUnsigned ( u8 , " " , 10 ) ) ;
2018-11-28 05:17:45 +00:00
}
2019-03-02 21:46:04 +00:00
pub const parseFloat = @import ( " fmt/parse_float.zig " ) . parseFloat ;
2021-08-20 11:52:48 +00:00
pub const ParseFloatError = @import ( " fmt/parse_float.zig " ) . ParseFloatError ;
2019-02-13 11:06:32 +00:00
2021-04-28 19:01:15 +00:00
test {
2021-04-28 22:13:43 +00:00
_ = parseFloat ;
2019-02-13 11:06:32 +00:00
}
2018-11-13 13:08:37 +00:00
pub fn charToDigit ( c : u8 , radix : u8 ) ( error { InvalidCharacter } ! u8 ) {
2017-03-10 00:12:15 +00:00
const value = switch ( c ) {
2018-05-29 00:23:55 +00:00
'0' . . . '9' = > c - '0' ,
'A' . . . 'Z' = > c - 'A' + 10 ,
'a' . . . 'z' = > c - 'a' + 10 ,
2018-02-02 19:26:14 +00:00
else = > return error . InvalidCharacter ,
2017-03-10 00:12:15 +00:00
} ;
2018-05-01 05:53:04 +00:00
if ( value > = radix ) return error . InvalidCharacter ;
2017-03-10 00:12:15 +00:00
return value ;
}
2021-04-10 11:09:38 +00:00
pub fn digitToChar ( digit : u8 , case : Case ) u8 {
2017-03-10 00:12:15 +00:00
return switch ( digit ) {
2018-05-29 00:23:55 +00:00
0 . . . 9 = > digit + '0' ,
2021-04-10 11:09:38 +00:00
10 . . . 35 = > digit + ( ( if ( case = = . upper ) @as ( u8 , 'A' ) else @as ( u8 , 'a' ) ) - 10 ) ,
2017-03-26 08:58:48 +00:00
else = > unreachable ,
2017-03-10 00:12:15 +00:00
} ;
}
2019-10-18 00:20:22 +00:00
pub const BufPrintError = error {
/// As much as possible was written to the buffer, but it was too small to fit all the printed bytes.
2020-03-13 15:55:50 +00:00
NoSpaceLeft ,
2019-10-18 00:20:22 +00:00
} ;
2022-05-23 08:58:13 +00:00
/// print a Formatter string into `buf`. Actually just a thin wrapper around `format` and `fixedBufferStream`.
/// returns a slice of the bytes printed to.
2020-07-11 11:09:04 +00:00
pub fn bufPrint ( buf : [ ] u8 , comptime fmt : [ ] const u8 , args : anytype ) BufPrintError ! [ ] u8 {
2020-03-06 22:59:21 +00:00
var fbs = std . io . fixedBufferStream ( buf ) ;
2020-06-20 10:02:48 +00:00
try format ( fbs . writer ( ) , fmt , args ) ;
2020-03-13 15:55:50 +00:00
return fbs . getWritten ( ) ;
2020-03-06 22:59:21 +00:00
}
2020-10-15 10:11:03 +00:00
pub fn bufPrintZ ( buf : [ ] u8 , comptime fmt : [ ] const u8 , args : anytype ) BufPrintError ! [ : 0 ] u8 {
const result = try bufPrint ( buf , fmt + + " \x00 " , args ) ;
return result [ 0 . . result . len - 1 : 0 ] ;
}
2020-09-22 12:15:41 +00:00
/// Count the characters needed for format. Useful for preallocating memory
2020-07-11 11:09:04 +00:00
pub fn count ( comptime fmt : [ ] const u8 , args : anytype ) u64 {
2020-06-20 10:02:48 +00:00
var counting_writer = std . io . countingWriter ( std . io . null_writer ) ;
format ( counting_writer . writer ( ) , fmt , args ) catch | err | switch ( err ) { } ;
return counting_writer . bytes_written ;
2017-04-06 09:34:04 +00:00
}
2018-11-13 13:08:37 +00:00
pub const AllocPrintError = error { OutOfMemory } ;
2018-07-17 22:36:47 +00:00
2021-10-28 23:37:25 +00:00
pub fn allocPrint ( allocator : mem . Allocator , comptime fmt : [ ] const u8 , args : anytype ) AllocPrintError ! [ ] u8 {
2022-05-22 15:06:59 +00:00
const size = math . cast ( usize , count ( fmt , args ) ) orelse return error . OutOfMemory ;
2018-01-07 21:51:46 +00:00
const buf = try allocator . alloc ( u8 , size ) ;
2018-07-17 22:36:47 +00:00
return bufPrint ( buf , fmt , args ) catch | err | switch ( err ) {
2020-03-13 15:55:50 +00:00
error . NoSpaceLeft = > unreachable , // we just counted the size above
2018-07-17 22:36:47 +00:00
} ;
2017-04-06 09:34:04 +00:00
}
2021-10-28 23:37:25 +00:00
pub fn allocPrintZ ( allocator : mem . Allocator , comptime fmt : [ ] const u8 , args : anytype ) AllocPrintError ! [ : 0 ] u8 {
2020-01-19 07:40:35 +00:00
const result = try allocPrint ( allocator , fmt + + " \x00 " , args ) ;
return result [ 0 . . result . len - 1 : 0 ] ;
}
2019-06-17 10:03:56 +00:00
test " bufPrintInt " {
2019-03-19 14:11:28 +00:00
var buffer : [ 100 ] u8 = undefined ;
2017-05-19 14:39:59 +00:00
const buf = buffer [ 0 . . ] ;
2017-03-10 00:12:15 +00:00
2021-04-10 11:09:38 +00:00
try std . testing . expectEqualSlices ( u8 , " -1 " , bufPrintIntToSlice ( buf , @as ( i1 , - 1 ) , 10 , . lower , FormatOptions { } ) ) ;
2017-03-10 00:12:15 +00:00
2021-04-10 11:09:38 +00:00
try std . testing . expectEqualSlices ( u8 , " -101111000110000101001110 " , bufPrintIntToSlice ( buf , @as ( i32 , - 12345678 ) , 2 , . lower , FormatOptions { } ) ) ;
try std . testing . expectEqualSlices ( u8 , " -12345678 " , bufPrintIntToSlice ( buf , @as ( i32 , - 12345678 ) , 10 , . lower , FormatOptions { } ) ) ;
try std . testing . expectEqualSlices ( u8 , " -bc614e " , bufPrintIntToSlice ( buf , @as ( i32 , - 12345678 ) , 16 , . lower , FormatOptions { } ) ) ;
try std . testing . expectEqualSlices ( u8 , " -BC614E " , bufPrintIntToSlice ( buf , @as ( i32 , - 12345678 ) , 16 , . upper , FormatOptions { } ) ) ;
2017-03-10 00:12:15 +00:00
2021-04-10 11:09:38 +00:00
try std . testing . expectEqualSlices ( u8 , " 12345678 " , bufPrintIntToSlice ( buf , @as ( u32 , 12345678 ) , 10 , . upper , FormatOptions { } ) ) ;
2017-03-10 00:12:15 +00:00
2021-04-10 11:09:38 +00:00
try std . testing . expectEqualSlices ( u8 , " 666 " , bufPrintIntToSlice ( buf , @as ( u32 , 666 ) , 10 , . lower , FormatOptions { . width = 6 } ) ) ;
try std . testing . expectEqualSlices ( u8 , " 1234 " , bufPrintIntToSlice ( buf , @as ( u32 , 0x1234 ) , 16 , . lower , FormatOptions { . width = 6 } ) ) ;
try std . testing . expectEqualSlices ( u8 , " 1234 " , bufPrintIntToSlice ( buf , @as ( u32 , 0x1234 ) , 16 , . lower , FormatOptions { . width = 1 } ) ) ;
2020-03-05 04:51:21 +00:00
2021-04-10 11:09:38 +00:00
try std . testing . expectEqualSlices ( u8 , " +42 " , bufPrintIntToSlice ( buf , @as ( i32 , 42 ) , 10 , . lower , FormatOptions { . width = 3 } ) ) ;
try std . testing . expectEqualSlices ( u8 , " -42 " , bufPrintIntToSlice ( buf , @as ( i32 , - 42 ) , 10 , . lower , FormatOptions { . width = 3 } ) ) ;
2017-03-10 00:12:15 +00:00
}
2021-04-10 11:09:38 +00:00
pub fn bufPrintIntToSlice ( buf : [ ] u8 , value : anytype , base : u8 , case : Case , options : FormatOptions ) [ ] u8 {
return buf [ 0 . . formatIntBuf ( buf , value , base , case , options ) ] ;
2017-03-10 00:12:15 +00:00
}
2020-10-07 04:21:06 +00:00
pub fn comptimePrint ( comptime fmt : [ ] const u8 , args : anytype ) * const [ count ( fmt , args ) : 0 ] u8 {
2020-10-11 14:06:46 +00:00
comptime {
var buf : [ count ( fmt , args ) : 0 ] u8 = undefined ;
_ = bufPrint ( & buf , fmt , args ) catch unreachable ;
buf [ buf . len ] = 0 ;
return & buf ;
}
2020-09-25 16:51:57 +00:00
}
test " comptimePrint " {
2020-10-07 17:43:23 +00:00
@setEvalBranchQuota ( 2000 ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expectEqual ( * const [ 3 : 0 ] u8 , @TypeOf ( comptime comptimePrint ( " {} " , . { 100 } ) ) ) ;
try std . testing . expectEqualSlices ( u8 , " 100 " , comptime comptimePrint ( " {} " , . { 100 } ) ) ;
2020-09-25 16:51:57 +00:00
}
2017-05-08 03:25:02 +00:00
test " parse u64 digit too big " {
2018-01-07 22:28:20 +00:00
_ = parseUnsigned ( u64 , " 123a " , 10 ) catch | err | {
2018-02-05 23:09:13 +00:00
if ( err = = error . InvalidCharacter ) return ;
2017-03-26 08:58:48 +00:00
unreachable ;
2017-03-10 00:12:15 +00:00
} ;
2017-03-26 08:58:48 +00:00
unreachable ;
2017-03-10 00:12:15 +00:00
}
2017-05-08 03:25:02 +00:00
test " parse unsigned comptime " {
2017-03-10 00:12:15 +00:00
comptime {
2021-05-04 17:47:26 +00:00
try std . testing . expect ( ( try parseUnsigned ( usize , " 2 " , 10 ) ) = = 2 ) ;
2017-03-10 00:12:15 +00:00
}
}
2017-05-23 22:38:41 +00:00
2020-09-21 19:34:51 +00:00
test " escaped braces " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " escaped: {{foo}} \n " , " escaped: {{{{foo}}}} \n " , . { } ) ;
try expectFmt ( " escaped: {foo} \n " , " escaped: {{foo}} \n " , . { } ) ;
2020-09-21 19:34:51 +00:00
}
2019-06-20 08:07:43 +00:00
test " optional " {
2017-05-23 22:38:41 +00:00
{
const value : ? i32 = 1234 ;
2022-07-25 12:29:07 +00:00
try expectFmt ( " optional: 1234 \n " , " optional: {?} \n " , . { value } ) ;
2022-07-23 14:53:59 +00:00
try expectFmt ( " optional: 1234 \n " , " optional: {?d} \n " , . { value } ) ;
try expectFmt ( " optional: 4d2 \n " , " optional: {?x} \n " , . { value } ) ;
}
{
const value : ? [ ] const u8 = " string " ;
try expectFmt ( " optional: string \n " , " optional: {?s} \n " , . { value } ) ;
2017-05-23 22:38:41 +00:00
}
{
const value : ? i32 = null ;
2022-07-25 12:29:07 +00:00
try expectFmt ( " optional: null \n " , " optional: {?} \n " , . { value } ) ;
2017-05-23 22:38:41 +00:00
}
2020-09-16 11:45:54 +00:00
{
const value = @intToPtr ( ? * i32 , 0xf000d000 ) ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " optional: *i32@f000d000 \n " , " optional: {*} \n " , . { value } ) ;
2020-09-16 11:45:54 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " error " {
2017-05-23 22:38:41 +00:00
{
2018-11-13 13:08:37 +00:00
const value : anyerror ! i32 = 1234 ;
2022-07-25 12:29:07 +00:00
try expectFmt ( " error union: 1234 \n " , " error union: {!} \n " , . { value } ) ;
2022-07-23 14:53:59 +00:00
try expectFmt ( " error union: 1234 \n " , " error union: {!d} \n " , . { value } ) ;
try expectFmt ( " error union: 4d2 \n " , " error union: {!x} \n " , . { value } ) ;
}
{
const value : anyerror ! [ ] const u8 = " string " ;
try expectFmt ( " error union: string \n " , " error union: {!s} \n " , . { value } ) ;
2017-05-23 22:38:41 +00:00
}
{
2018-11-13 13:08:37 +00:00
const value : anyerror ! i32 = error . InvalidChar ;
2022-07-25 12:29:07 +00:00
try expectFmt ( " error union: error.InvalidChar \n " , " error union: {!} \n " , . { value } ) ;
2017-05-23 22:38:41 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " int.small " {
2017-10-21 17:03:08 +00:00
{
const value : u3 = 0b101 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " u3: 5 \n " , " u3: {} \n " , . { value } ) ;
2018-05-16 02:11:03 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " int.specifier " {
2018-05-30 17:18:24 +00:00
{
const value : u8 = 'a' ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " u8: a \n " , " u8: {c} \n " , . { value } ) ;
2018-05-30 17:18:24 +00:00
}
2018-08-01 15:38:04 +00:00
{
const value : u8 = 0b1100 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " u8: 0b1100 \n " , " u8: 0b{b} \n " , . { value } ) ;
2018-08-01 15:38:04 +00:00
}
2020-07-14 06:27:58 +00:00
{
const value : u16 = 0o1234 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " u16: 0o1234 \n " , " u16: 0o{o} \n " , . { value } ) ;
2020-07-14 06:27:58 +00:00
}
2019-12-22 10:38:27 +00:00
{
const value : u8 = 'a' ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " UTF-8: a \n " , " UTF-8: {u} \n " , . { value } ) ;
2019-12-22 10:38:27 +00:00
}
{
2020-09-21 10:39:35 +00:00
const value : u21 = 0x1F310 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " UTF-8: 🌐 \n " , " UTF-8: {u} \n " , . { value } ) ;
2019-12-22 10:38:27 +00:00
}
2020-09-21 10:39:35 +00:00
{
const value : u21 = 0xD800 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " UTF-8: <20> \n " , " UTF-8: {u} \n " , . { value } ) ;
2020-09-21 10:39:35 +00:00
}
{
const value : u21 = 0x110001 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " UTF-8: <20> \n " , " UTF-8: {u} \n " , . { value } ) ;
2020-09-21 10:39:35 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " int.padded " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " u8: ' 1' " , " u8: '{:4}' " , . { @as ( u8 , 1 ) } ) ;
try expectFmt ( " u8: '1000' " , " u8: '{:0<4}' " , . { @as ( u8 , 1 ) } ) ;
try expectFmt ( " u8: '0001' " , " u8: '{:0>4}' " , . { @as ( u8 , 1 ) } ) ;
try expectFmt ( " u8: '0100' " , " u8: '{:0^4}' " , . { @as ( u8 , 1 ) } ) ;
try expectFmt ( " i8: '-1 ' " , " i8: '{:<4}' " , . { @as ( i8 , - 1 ) } ) ;
try expectFmt ( " i8: ' -1' " , " i8: '{:>4}' " , . { @as ( i8 , - 1 ) } ) ;
try expectFmt ( " i8: ' -1 ' " , " i8: '{:^4}' " , . { @as ( i8 , - 1 ) } ) ;
try expectFmt ( " i16: '-1234' " , " i16: '{:4}' " , . { @as ( i16 , - 1234 ) } ) ;
try expectFmt ( " i16: '+1234' " , " i16: '{:4}' " , . { @as ( i16 , 1234 ) } ) ;
try expectFmt ( " i16: '-12345' " , " i16: '{:4}' " , . { @as ( i16 , - 12345 ) } ) ;
try expectFmt ( " i16: '+12345' " , " i16: '{:4}' " , . { @as ( i16 , 12345 ) } ) ;
try expectFmt ( " u16: '12345' " , " u16: '{:4}' " , . { @as ( u16 , 12345 ) } ) ;
try expectFmt ( " UTF-8: 'ü ' " , " UTF-8: '{u:<4}' " , . { 'ü' } ) ;
try expectFmt ( " UTF-8: ' ü' " , " UTF-8: '{u:>4}' " , . { 'ü' } ) ;
try expectFmt ( " UTF-8: ' ü ' " , " UTF-8: '{u:^4}' " , . { 'ü' } ) ;
2019-06-20 08:07:43 +00:00
}
test " buffer " {
2018-12-19 10:50:29 +00:00
{
var buf1 : [ 32 ] u8 = undefined ;
2020-03-06 22:59:21 +00:00
var fbs = std . io . fixedBufferStream ( & buf1 ) ;
2020-06-20 10:02:48 +00:00
try formatType ( 1234 , " " , FormatOptions { } , fbs . writer ( ) , default_max_depth ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " 1234 " ) ) ;
2020-03-06 22:59:21 +00:00
fbs . reset ( ) ;
2020-06-20 10:02:48 +00:00
try formatType ( 'a' , " c " , FormatOptions { } , fbs . writer ( ) , default_max_depth ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " a " ) ) ;
2020-03-06 22:59:21 +00:00
fbs . reset ( ) ;
2020-06-20 10:02:48 +00:00
try formatType ( 0b1100 , " b " , FormatOptions { } , fbs . writer ( ) , default_max_depth ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " 1100 " ) ) ;
2018-12-19 10:50:29 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " array " {
2018-07-31 15:34:42 +00:00
{
2019-11-20 01:29:08 +00:00
const value : [ 3 ] u8 = " abc " . * ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " array: abc \n " , " array: {s} \n " , . { value } ) ;
try expectFmt ( " array: abc \n " , " array: {s} \n " , . { & value } ) ;
try expectFmt ( " array: { 97, 98, 99 } \n " , " array: {d} \n " , . { value } ) ;
2018-07-31 15:34:42 +00:00
var buf : [ 100 ] u8 = undefined ;
2021-01-12 01:30:43 +00:00
try expectFmt (
2019-12-09 03:53:51 +00:00
try bufPrint ( buf [ 0 . . ] , " array: [3]u8@{x} \n " , . { @ptrToInt ( & value ) } ) ,
2018-07-31 15:34:42 +00:00
" array: {*} \n " ,
2019-12-09 03:53:51 +00:00
. { & value } ,
2018-07-31 15:34:42 +00:00
) ;
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " slice " {
2018-07-31 15:34:42 +00:00
{
const value : [ ] const u8 = " abc " ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " slice: abc \n " , " slice: {s} \n " , . { value } ) ;
2018-07-31 15:34:42 +00:00
}
2019-05-29 15:23:21 +00:00
{
2020-03-19 00:35:19 +00:00
var runtime_zero : usize = 0 ;
const value = @intToPtr ( [ * ] align ( 1 ) const [ ] const u8 , 0xdeadbeef ) [ runtime_zero . . runtime_zero ] ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " slice: []const u8@deadbeef \n " , " slice: {*} \n " , . { value } ) ;
2019-05-29 15:23:21 +00:00
}
2020-10-31 03:48:41 +00:00
{
const null_term_slice : [ : 0 ] const u8 = " \x00 hello \x00 " ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " buf: \x00 hello \x00 \n " , " buf: {s} \n " , . { null_term_slice } ) ;
2020-10-31 03:48:41 +00:00
}
2019-06-17 10:03:56 +00:00
2021-01-12 01:30:43 +00:00
try expectFmt ( " buf: Test \n " , " buf: {s:5} \n " , . { " Test " } ) ;
try expectFmt ( " buf: Test \n Other text " , " buf: {s} \n Other text " , . { " Test " } ) ;
2020-10-29 21:22:25 +00:00
{
var int_slice = [ _ ] u32 { 1 , 4096 , 391891 , 1111111111 } ;
var runtime_zero : usize = 0 ;
2021-01-03 09:20:37 +00:00
try expectFmt ( " int: { 1, 4096, 391891, 1111111111 } " , " int: {any} " , . { int_slice [ runtime_zero . . ] } ) ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " int: { 1, 4096, 391891, 1111111111 } " , " int: {d} " , . { int_slice [ runtime_zero . . ] } ) ;
try expectFmt ( " int: { 1, 1000, 5fad3, 423a35c7 } " , " int: {x} " , . { int_slice [ runtime_zero . . ] } ) ;
try expectFmt ( " int: { 00001, 01000, 5fad3, 423a35c7 } " , " int: {x:0>5} " , . { int_slice [ runtime_zero . . ] } ) ;
2020-10-29 21:22:25 +00:00
}
2019-06-17 10:03:56 +00:00
}
2020-08-31 12:31:29 +00:00
test " escape non-printable " {
2022-11-18 22:12:27 +00:00
try expectFmt ( " abc 123 " , " {s} " , . { fmtSliceEscapeLower ( " abc 123 " ) } ) ;
2021-02-12 19:44:19 +00:00
try expectFmt ( " ab \\ xffc " , " {s} " , . { fmtSliceEscapeLower ( " ab \xff c " ) } ) ;
2022-11-18 22:12:27 +00:00
try expectFmt ( " abc 123 " , " {s} " , . { fmtSliceEscapeUpper ( " abc 123 " ) } ) ;
2021-02-12 19:44:19 +00:00
try expectFmt ( " ab \\ xFFc " , " {s} " , . { fmtSliceEscapeUpper ( " ab \xff c " ) } ) ;
2020-08-31 12:31:29 +00:00
}
2019-06-20 08:07:43 +00:00
test " pointer " {
2018-07-31 15:34:42 +00:00
{
2019-12-15 11:48:35 +00:00
const value = @intToPtr ( * align ( 1 ) i32 , 0xdeadbeef ) ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " pointer: i32@deadbeef \n " , " pointer: {} \n " , . { value } ) ;
try expectFmt ( " pointer: i32@deadbeef \n " , " pointer: {*} \n " , . { value } ) ;
2018-07-31 15:34:42 +00:00
}
2022-12-07 03:35:50 +00:00
const FnPtr = * align ( 1 ) const fn ( ) void ;
2018-12-19 10:50:29 +00:00
{
2022-09-29 18:45:30 +00:00
const value = @intToPtr ( FnPtr , 0xdeadbeef ) ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " pointer: fn() void@deadbeef \n " , " pointer: {} \n " , . { value } ) ;
2018-12-19 10:50:29 +00:00
}
2018-12-19 15:07:35 +00:00
{
2022-09-29 18:45:30 +00:00
const value = @intToPtr ( FnPtr , 0xdeadbeef ) ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " pointer: fn() void@deadbeef \n " , " pointer: {} \n " , . { value } ) ;
2018-12-19 15:07:35 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " cstr " {
2021-01-12 01:30:43 +00:00
try expectFmt (
2020-01-20 17:23:43 +00:00
" cstr: Test C \n " ,
" cstr: {s} \n " ,
. { @ptrCast ( [ * c ] const u8 , " Test C " ) } ,
) ;
2021-01-12 01:30:43 +00:00
try expectFmt (
2020-09-16 22:41:26 +00:00
" cstr: Test C \n " ,
2020-01-20 17:23:43 +00:00
" cstr: {s:10} \n " ,
. { @ptrCast ( [ * c ] const u8 , " Test C " ) } ,
) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " filesize " {
2021-03-06 08:57:21 +00:00
try expectFmt ( " file size: 42B \n " , " file size: {} \n " , . { fmtIntSizeDec ( 42 ) } ) ;
try expectFmt ( " file size: 42B \n " , " file size: {} \n " , . { fmtIntSizeBin ( 42 ) } ) ;
try expectFmt ( " file size: 63MB \n " , " file size: {} \n " , . { fmtIntSizeDec ( 63 * 1000 * 1000 ) } ) ;
try expectFmt ( " file size: 63MiB \n " , " file size: {} \n " , . { fmtIntSizeBin ( 63 * 1024 * 1024 ) } ) ;
try expectFmt ( " file size: 66.06MB \n " , " file size: {:.2} \n " , . { fmtIntSizeDec ( 63 * 1024 * 1024 ) } ) ;
try expectFmt ( " file size: 60.08MiB \n " , " file size: {:.2} \n " , . { fmtIntSizeBin ( 63 * 1000 * 1000 ) } ) ;
2022-07-17 06:38:31 +00:00
try expectFmt ( " file size: =66.06MB= \n " , " file size: {:=^9.2} \n " , . { fmtIntSizeDec ( 63 * 1024 * 1024 ) } ) ;
try expectFmt ( " file size: 66.06MB \n " , " file size: {: >9.2} \n " , . { fmtIntSizeDec ( 63 * 1024 * 1024 ) } ) ;
try expectFmt ( " file size: 66.06MB \n " , " file size: {: <9.2} \n " , . { fmtIntSizeDec ( 63 * 1024 * 1024 ) } ) ;
try expectFmt ( " file size: 0.01844674407370955ZB \n " , " file size: {} \n " , . { fmtIntSizeDec ( math . maxInt ( u64 ) ) } ) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " struct " {
2018-02-07 21:18:48 +00:00
{
2018-11-13 13:08:37 +00:00
const Struct = struct {
2018-08-20 20:04:03 +00:00
field : u8 ,
2018-02-22 16:53:58 +00:00
} ;
2018-11-13 13:08:37 +00:00
const value = Struct { . field = 42 } ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " struct: Struct{ .field = 42 } \n " , " struct: {} \n " , . { value } ) ;
try expectFmt ( " struct: Struct{ .field = 42 } \n " , " struct: {} \n " , . { & value } ) ;
2018-02-07 21:18:48 +00:00
}
2018-09-20 17:46:20 +00:00
{
2018-11-13 13:08:37 +00:00
const Struct = struct {
2018-09-20 17:46:20 +00:00
a : u0 ,
b : u1 ,
} ;
2018-11-13 13:08:37 +00:00
const value = Struct { . a = 0 , . b = 1 } ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " struct: Struct{ .a = 0, .b = 1 } \n " , " struct: {} \n " , . { value } ) ;
2018-09-20 17:46:20 +00:00
}
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " enum " {
2019-06-17 10:03:56 +00:00
const Enum = enum {
One ,
Two ,
} ;
const value = Enum . Two ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " enum: Enum.Two \n " , " enum: {} \n " , . { value } ) ;
try expectFmt ( " enum: Enum.Two \n " , " enum: {} \n " , . { & value } ) ;
2022-07-23 14:53:59 +00:00
try expectFmt ( " enum: Enum.One \n " , " enum: {} \n " , . { Enum . One } ) ;
try expectFmt ( " enum: Enum.Two \n " , " enum: {} \n " , . { Enum . Two } ) ;
2020-07-02 05:04:05 +00:00
// test very large enum to verify ct branch quota is large enough
2022-03-19 11:26:21 +00:00
try expectFmt ( " enum: os.windows.win32error.Win32Error.INVALID_FUNCTION \n " , " enum: {} \n " , . { std . os . windows . Win32Error . INVALID_FUNCTION } ) ;
2019-06-17 10:03:56 +00:00
}
2020-02-10 13:56:39 +00:00
test " non-exhaustive enum " {
const Enum = enum ( u16 ) {
One = 0x000f ,
Two = 0xbeef ,
_ ,
} ;
2022-06-11 08:33:09 +00:00
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum.One \n " , " enum: {} \n " , . { Enum . One } ) ;
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum.Two \n " , " enum: {} \n " , . { Enum . Two } ) ;
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum(4660) \n " , " enum: {} \n " , . { @intToEnum ( Enum , 0x1234 ) } ) ;
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum.One \n " , " enum: {x} \n " , . { Enum . One } ) ;
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum.Two \n " , " enum: {x} \n " , . { Enum . Two } ) ;
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum.Two \n " , " enum: {X} \n " , . { Enum . Two } ) ;
try expectFmt ( " enum: fmt.test.non-exhaustive enum.Enum(1234) \n " , " enum: {x} \n " , . { @intToEnum ( Enum , 0x1234 ) } ) ;
2020-02-10 13:56:39 +00:00
}
2019-06-20 08:07:43 +00:00
test " float.scientific " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " f32: 1.34000003e+00 " , " f32: {e} " , . { @as ( f32 , 1.34 ) } ) ;
try expectFmt ( " f32: 1.23400001e+01 " , " f32: {e} " , . { @as ( f32 , 12.34 ) } ) ;
try expectFmt ( " f64: -1.234e+11 " , " f64: {e} " , . { @as ( f64 , - 12.34e10 ) } ) ;
try expectFmt ( " f64: 9.99996e-40 " , " f64: {e} " , . { @as ( f64 , 9.999960e-40 ) } ) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " float.scientific.precision " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: 1.40971e-42 " , " f64: {e:.5} " , . { @as ( f64 , 1.409706e-42 ) } ) ;
try expectFmt ( " f64: 1.00000e-09 " , " f64: {e:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 814313563 ) ) ) } ) ;
try expectFmt ( " f64: 7.81250e-03 " , " f64: {e:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1006632960 ) ) ) } ) ;
2019-06-20 08:07:43 +00:00
// libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
// In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: 1.00001e+05 " , " f64: {e:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1203982400 ) ) ) } ) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " float.special " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: nan " , " f64: {} " , . { math . nan_f64 } ) ;
2019-06-20 08:07:43 +00:00
// negative nan is not defined by IEE 754,
// and ARM thus normalizes it to positive nan
2021-05-17 23:08:09 +00:00
if ( builtin . target . cpu . arch ! = . arm ) {
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: -nan " , " f64: {} " , . { - math . nan_f64 } ) ;
2018-04-23 05:18:05 +00:00
}
2022-04-07 02:49:05 +00:00
try expectFmt ( " f64: inf " , " f64: {} " , . { math . inf ( f64 ) } ) ;
try expectFmt ( " f64: -inf " , " f64: {} " , . { - math . inf ( f64 ) } ) ;
2019-06-17 10:03:56 +00:00
}
2021-03-23 09:08:34 +00:00
test " float.hexadecimal.special " {
try expectFmt ( " f64: nan " , " f64: {x} " , . { math . nan_f64 } ) ;
// negative nan is not defined by IEE 754,
// and ARM thus normalizes it to positive nan
2021-05-17 23:08:09 +00:00
if ( builtin . target . cpu . arch ! = . arm ) {
2021-03-23 09:08:34 +00:00
try expectFmt ( " f64: -nan " , " f64: {x} " , . { - math . nan_f64 } ) ;
}
2022-04-07 02:49:05 +00:00
try expectFmt ( " f64: inf " , " f64: {x} " , . { math . inf ( f64 ) } ) ;
try expectFmt ( " f64: -inf " , " f64: {x} " , . { - math . inf ( f64 ) } ) ;
2021-03-23 09:08:34 +00:00
try expectFmt ( " f64: 0x0.0p0 " , " f64: {x} " , . { @as ( f64 , 0 ) } ) ;
try expectFmt ( " f64: -0x0.0p0 " , " f64: {x} " , . { - @as ( f64 , 0 ) } ) ;
}
test " float.hexadecimal " {
try expectFmt ( " f16: 0x1.554p-2 " , " f16: {x} " , . { @as ( f16 , 1.0 / 3.0 ) } ) ;
try expectFmt ( " f32: 0x1.555556p-2 " , " f32: {x} " , . { @as ( f32 , 1.0 / 3.0 ) } ) ;
try expectFmt ( " f64: 0x1.5555555555555p-2 " , " f64: {x} " , . { @as ( f64 , 1.0 / 3.0 ) } ) ;
2022-10-25 09:06:54 +00:00
try expectFmt ( " f80: 0x1.5555555555555556p-2 " , " f80: {x} " , . { @as ( f80 , 1.0 / 3.0 ) } ) ;
2021-03-23 09:08:34 +00:00
try expectFmt ( " f128: 0x1.5555555555555555555555555555p-2 " , " f128: {x} " , . { @as ( f128 , 1.0 / 3.0 ) } ) ;
2022-04-07 02:49:05 +00:00
try expectFmt ( " f16: 0x1p-14 " , " f16: {x} " , . { math . floatMin ( f16 ) } ) ;
try expectFmt ( " f32: 0x1p-126 " , " f32: {x} " , . { math . floatMin ( f32 ) } ) ;
try expectFmt ( " f64: 0x1p-1022 " , " f64: {x} " , . { math . floatMin ( f64 ) } ) ;
2022-10-25 09:06:54 +00:00
try expectFmt ( " f80: 0x1p-16382 " , " f80: {x} " , . { math . floatMin ( f80 ) } ) ;
2022-04-07 02:49:05 +00:00
try expectFmt ( " f128: 0x1p-16382 " , " f128: {x} " , . { math . floatMin ( f128 ) } ) ;
2021-03-23 09:08:34 +00:00
2022-04-07 02:49:05 +00:00
try expectFmt ( " f16: 0x0.004p-14 " , " f16: {x} " , . { math . floatTrueMin ( f16 ) } ) ;
try expectFmt ( " f32: 0x0.000002p-126 " , " f32: {x} " , . { math . floatTrueMin ( f32 ) } ) ;
try expectFmt ( " f64: 0x0.0000000000001p-1022 " , " f64: {x} " , . { math . floatTrueMin ( f64 ) } ) ;
2022-10-25 09:06:54 +00:00
try expectFmt ( " f80: 0x0.0000000000000002p-16382 " , " f80: {x} " , . { math . floatTrueMin ( f80 ) } ) ;
2022-04-07 02:49:05 +00:00
try expectFmt ( " f128: 0x0.0000000000000000000000000001p-16382 " , " f128: {x} " , . { math . floatTrueMin ( f128 ) } ) ;
2021-03-23 09:08:34 +00:00
2022-04-07 02:49:05 +00:00
try expectFmt ( " f16: 0x1.ffcp15 " , " f16: {x} " , . { math . floatMax ( f16 ) } ) ;
try expectFmt ( " f32: 0x1.fffffep127 " , " f32: {x} " , . { math . floatMax ( f32 ) } ) ;
try expectFmt ( " f64: 0x1.fffffffffffffp1023 " , " f64: {x} " , . { math . floatMax ( f64 ) } ) ;
2022-10-25 09:06:54 +00:00
try expectFmt ( " f80: 0x1.fffffffffffffffep16383 " , " f80: {x} " , . { math . floatMax ( f80 ) } ) ;
2022-04-07 02:49:05 +00:00
try expectFmt ( " f128: 0x1.ffffffffffffffffffffffffffffp16383 " , " f128: {x} " , . { math . floatMax ( f128 ) } ) ;
2021-03-23 09:08:34 +00:00
}
test " float.hexadecimal.precision " {
try expectFmt ( " f16: 0x1.5p-2 " , " f16: {x:.1} " , . { @as ( f16 , 1.0 / 3.0 ) } ) ;
try expectFmt ( " f32: 0x1.555p-2 " , " f32: {x:.3} " , . { @as ( f32 , 1.0 / 3.0 ) } ) ;
try expectFmt ( " f64: 0x1.55555p-2 " , " f64: {x:.5} " , . { @as ( f64 , 1.0 / 3.0 ) } ) ;
2022-10-25 09:06:54 +00:00
try expectFmt ( " f80: 0x1.5555555p-2 " , " f80: {x:.7} " , . { @as ( f80 , 1.0 / 3.0 ) } ) ;
try expectFmt ( " f128: 0x1.555555555p-2 " , " f128: {x:.9} " , . { @as ( f128 , 1.0 / 3.0 ) } ) ;
2021-03-23 09:08:34 +00:00
try expectFmt ( " f16: 0x1.00000p0 " , " f16: {x:.5} " , . { @as ( f16 , 1.0 ) } ) ;
try expectFmt ( " f32: 0x1.00000p0 " , " f32: {x:.5} " , . { @as ( f32 , 1.0 ) } ) ;
try expectFmt ( " f64: 0x1.00000p0 " , " f64: {x:.5} " , . { @as ( f64 , 1.0 ) } ) ;
2022-10-25 09:06:54 +00:00
try expectFmt ( " f80: 0x1.00000p0 " , " f80: {x:.5} " , . { @as ( f80 , 1.0 ) } ) ;
2021-03-23 09:08:34 +00:00
try expectFmt ( " f128: 0x1.00000p0 " , " f128: {x:.5} " , . { @as ( f128 , 1.0 ) } ) ;
}
2019-06-20 08:07:43 +00:00
test " float.decimal " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: 152314000000000000000000000000 " , " f64: {d} " , . { @as ( f64 , 1.52314e+29 ) } ) ;
try expectFmt ( " f32: 0 " , " f32: {d} " , . { @as ( f32 , 0.0 ) } ) ;
2021-08-30 17:39:05 +00:00
try expectFmt ( " f32: 0 " , " f32: {d:.0} " , . { @as ( f32 , 0.0 ) } ) ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " f32: 1.1 " , " f32: {d:.1} " , . { @as ( f32 , 1.1234 ) } ) ;
try expectFmt ( " f32: 1234.57 " , " f32: {d:.2} " , . { @as ( f32 , 1234.567 ) } ) ;
2019-06-20 08:07:43 +00:00
// -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
// -11.12339... is rounded back up to -11.1234
2021-01-12 01:30:43 +00:00
try expectFmt ( " f32: -11.1234 " , " f32: {d:.4} " , . { @as ( f32 , - 11.1234 ) } ) ;
try expectFmt ( " f32: 91.12345 " , " f32: {d:.5} " , . { @as ( f32 , 91.12345 ) } ) ;
try expectFmt ( " f64: 91.1234567890 " , " f64: {d:.10} " , . { @as ( f64 , 91.12345678901235 ) } ) ;
try expectFmt ( " f64: 0.00000 " , " f64: {d:.5} " , . { @as ( f64 , 0.0 ) } ) ;
try expectFmt ( " f64: 6 " , " f64: {d:.0} " , . { @as ( f64 , 5.700 ) } ) ;
try expectFmt ( " f64: 10.0 " , " f64: {d:.1} " , . { @as ( f64 , 9.999 ) } ) ;
try expectFmt ( " f64: 1.000 " , " f64: {d:.3} " , . { @as ( f64 , 1.0 ) } ) ;
try expectFmt ( " f64: 0.00030000 " , " f64: {d:.8} " , . { @as ( f64 , 0.0003 ) } ) ;
try expectFmt ( " f64: 0.00000 " , " f64: {d:.5} " , . { @as ( f64 , 1.40130e-45 ) } ) ;
try expectFmt ( " f64: 0.00000 " , " f64: {d:.5} " , . { @as ( f64 , 9.999960e-40 ) } ) ;
std.fmt: fix out-of-bounds array write in float printing
This commit fixes an out of bounds write that can occur when
formatting certain float values. The write messes up the stack and
causes incorrect results, segfaults, or nothing at all, depending on the
optimization mode used.
The `errol` function writes the digits of the float into `buffer`
starting from index 1, leaving index 0 untouched, and returns `buffer[1..]`
and the exponent. This is because `roundToPrecision` relies on index 0 being
unused in case the rounding adds a digit (e.g rounding 999.99
to 1000.00). When this happens, pointer arithmetic is used
[here](https://github.com/ziglang/zig/blob/0e6d2184cacf2dd1fad7508b2f9ae99d78763148/lib/std/fmt/errol.zig#L61-L65)
to access index 0 and put the ones digit in the right place.
However, `errol3u` contains two special cases: `errolInt` and `errolFixed`,
which return from the function early. For these two special cases
index 0 was never reserved, and the return value contains `buffer`
instead of `buffer[1..]`. This causes the pointer arithmetic in
`roundToPrecision` to write out of bounds, which in the case of
`std.fmt.formatFloatDecimal` messes up the stack and causes undefined behavior.
The fix is to move the slicing of `buffer` to `buffer[1..]` from `errol3u`
to `errol` so that both the default and the special cases operate on the sliced
buffer.
2022-01-29 11:25:25 +00:00
try expectFmt ( " f64: 10000000000000.00 " , " f64: {d:.2} " , . { @as ( f64 , 9999999999999.999 ) } ) ;
2022-04-03 15:47:17 +00:00
try expectFmt ( " f64: 10000000000000000000000000000000000000 " , " f64: {d} " , . { @as ( f64 , 1e37 ) } ) ;
try expectFmt ( " f64: 100000000000000000000000000000000000000 " , " f64: {d} " , . { @as ( f64 , 1e38 ) } ) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " float.libc.sanity " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: 0.00001 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 916964781 ) ) ) } ) ;
try expectFmt ( " f64: 0.00001 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 925353389 ) ) ) } ) ;
try expectFmt ( " f64: 0.10000 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1036831278 ) ) ) } ) ;
try expectFmt ( " f64: 1.00000 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1065353133 ) ) ) } ) ;
try expectFmt ( " f64: 10.00000 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1092616192 ) ) ) } ) ;
2019-06-20 08:07:43 +00:00
2018-04-23 05:18:05 +00:00
// libc differences
2019-06-20 08:07:43 +00:00
//
// This is 0.015625 exactly according to gdb. We thus round down,
// however glibc rounds up for some reason. This occurs for all
// floats of the form x.yyyy25 on a precision point.
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: 0.01563 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1015021568 ) ) ) } ) ;
2019-06-20 08:07:43 +00:00
// errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
// also rounds to 630 so I'm inclined to believe libc is not
// optimal here.
2021-01-12 01:30:43 +00:00
try expectFmt ( " f64: 18014400656965630.00000 " , " f64: {d:.5} " , . { @as ( f64 , @bitCast ( f32 , @as ( u32 , 1518338049 ) ) ) } ) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " custom " {
2019-06-17 10:03:56 +00:00
const Vec2 = struct {
const SelfType = @This ( ) ;
x : f32 ,
y : f32 ,
pub fn format (
self : SelfType ,
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2021-06-20 01:10:22 +00:00
_ = options ;
2019-06-20 08:07:43 +00:00
if ( fmt . len = = 0 or comptime std . mem . eql ( u8 , fmt , " p " ) ) {
2020-06-20 10:02:48 +00:00
return std . fmt . format ( writer , " ({d:.3},{d:.3}) " , . { self . x , self . y } ) ;
2019-06-20 08:07:43 +00:00
} else if ( comptime std . mem . eql ( u8 , fmt , " d " ) ) {
2020-06-20 10:02:48 +00:00
return std . fmt . format ( writer , " {d:.3}x{d:.3} " , . { self . x , self . y } ) ;
2019-06-20 08:07:43 +00:00
} else {
2022-07-23 15:02:55 +00:00
@compileError ( " unknown format character: ' " + + fmt + + " ' " ) ;
2018-05-30 15:18:11 +00:00
}
2019-06-17 10:03:56 +00:00
}
} ;
2018-06-04 15:06:55 +00:00
2019-06-17 10:03:56 +00:00
var value = Vec2 {
. x = 10.2 ,
. y = 2.22 ,
} ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " point: (10.200,2.220) \n " , " point: {} \n " , . { & value } ) ;
try expectFmt ( " dim: 10.200x2.220 \n " , " dim: {d} \n " , . { & value } ) ;
2018-08-20 20:04:03 +00:00
2019-06-17 10:03:56 +00:00
// same thing but not passing a pointer
2021-01-12 01:30:43 +00:00
try expectFmt ( " point: (10.200,2.220) \n " , " point: {} \n " , . { value } ) ;
try expectFmt ( " dim: 10.200x2.220 \n " , " dim: {d} \n " , . { value } ) ;
2019-06-17 10:03:56 +00:00
}
2018-08-27 23:25:18 +00:00
2019-06-20 08:07:43 +00:00
test " struct " {
2019-06-17 10:03:56 +00:00
const S = struct {
a : u32 ,
b : anyerror ,
} ;
2018-08-27 23:25:18 +00:00
2019-06-17 10:03:56 +00:00
const inst = S {
. a = 456 ,
. b = error . Unused ,
} ;
2018-08-27 23:25:18 +00:00
2022-06-11 08:33:09 +00:00
try expectFmt ( " fmt.test.struct.S{ .a = 456, .b = error.Unused } " , " {} " , . { inst } ) ;
2021-06-06 10:39:07 +00:00
// Tuples
try expectFmt ( " { } " , " {} " , . { . { } } ) ;
try expectFmt ( " { -1 } " , " {} " , . { . { - 1 } } ) ;
try expectFmt ( " { -1, 42, 2.5e+04 } " , " {} " , . { . { - 1 , 42 , 0.25e5 } } ) ;
2019-06-17 10:03:56 +00:00
}
2018-08-27 23:25:18 +00:00
2019-06-20 08:07:43 +00:00
test " union " {
2019-06-17 10:03:56 +00:00
const TU = union ( enum ) {
float : f32 ,
int : u32 ,
} ;
2018-08-27 23:25:18 +00:00
2019-06-17 10:03:56 +00:00
const UU = union {
float : f32 ,
int : u32 ,
} ;
2018-08-27 23:25:18 +00:00
2019-06-17 10:03:56 +00:00
const EU = extern union {
float : f32 ,
int : u32 ,
} ;
2018-08-27 23:25:18 +00:00
2019-06-17 10:03:56 +00:00
const tu_inst = TU { . int = 123 } ;
const uu_inst = UU { . int = 456 } ;
const eu_inst = EU { . float = 321.123 } ;
2018-08-27 23:25:18 +00:00
2022-06-11 08:33:09 +00:00
try expectFmt ( " fmt.test.union.TU{ .int = 123 } " , " {} " , . { tu_inst } ) ;
2018-08-27 23:25:18 +00:00
2019-06-17 10:03:56 +00:00
var buf : [ 100 ] u8 = undefined ;
2019-12-09 03:53:51 +00:00
const uu_result = try bufPrint ( buf [ 0 . . ] , " {} " , . { uu_inst } ) ;
2022-06-11 08:33:09 +00:00
try std . testing . expect ( mem . eql ( u8 , uu_result [ 0 . . 18 ] , " fmt.test.union.UU@ " ) ) ;
2018-08-27 23:25:18 +00:00
2019-12-09 03:53:51 +00:00
const eu_result = try bufPrint ( buf [ 0 . . ] , " {} " , . { eu_inst } ) ;
2022-06-11 08:33:09 +00:00
try std . testing . expect ( mem . eql ( u8 , eu_result [ 0 . . 18 ] , " fmt.test.union.EU@ " ) ) ;
2019-06-17 10:03:56 +00:00
}
2019-04-27 09:36:48 +00:00
2019-06-20 08:07:43 +00:00
test " enum " {
2019-06-17 10:03:56 +00:00
const E = enum {
One ,
Two ,
Three ,
} ;
2019-04-27 09:36:48 +00:00
2019-06-17 10:03:56 +00:00
const inst = E . Two ;
2022-06-11 08:33:09 +00:00
try expectFmt ( " fmt.test.enum.E.Two " , " {} " , . { inst } ) ;
2019-06-17 10:03:56 +00:00
}
2019-06-20 08:07:43 +00:00
test " struct.self-referential " {
2019-06-17 10:03:56 +00:00
const S = struct {
const SelfType = @This ( ) ;
a : ? * SelfType ,
} ;
var inst = S {
. a = null ,
} ;
inst . a = & inst ;
2022-06-11 08:33:09 +00:00
try expectFmt ( " fmt.test.struct.self-referential.S{ .a = fmt.test.struct.self-referential.S{ .a = fmt.test.struct.self-referential.S{ .a = fmt.test.struct.self-referential.S{ ... } } } } " , " {} " , . { inst } ) ;
2019-06-17 10:03:56 +00:00
}
2019-07-29 01:37:35 +00:00
test " struct.zero-size " {
const A = struct {
fn foo ( ) void { }
} ;
const B = struct {
a : A ,
c : i32 ,
} ;
const a = A { } ;
const b = B { . a = a , . c = 0 } ;
2022-06-11 08:33:09 +00:00
try expectFmt ( " fmt.test.struct.zero-size.B{ .a = fmt.test.struct.zero-size.A{ }, .c = 0 } " , " {} " , . { b } ) ;
2019-07-29 01:37:35 +00:00
}
2019-06-20 08:07:43 +00:00
test " bytes.hex " {
2019-06-17 10:03:56 +00:00
const some_bytes = " \xCA \xFE \xBA \xBE " ;
2021-02-12 19:44:19 +00:00
try expectFmt ( " lowercase: cafebabe \n " , " lowercase: {x} \n " , . { fmtSliceHexLower ( some_bytes ) } ) ;
try expectFmt ( " uppercase: CAFEBABE \n " , " uppercase: {X} \n " , . { fmtSliceHexUpper ( some_bytes ) } ) ;
2019-06-17 10:03:56 +00:00
//Test Slices
2021-02-12 19:44:19 +00:00
try expectFmt ( " uppercase: CAFE \n " , " uppercase: {X} \n " , . { fmtSliceHexUpper ( some_bytes [ 0 . . 2 ] ) } ) ;
try expectFmt ( " lowercase: babe \n " , " lowercase: {x} \n " , . { fmtSliceHexLower ( some_bytes [ 2 . . ] ) } ) ;
2019-06-17 10:03:56 +00:00
const bytes_with_zeros = " \x00 \x0E \xBA \xBE " ;
2021-02-12 19:44:19 +00:00
try expectFmt ( " lowercase: 000ebabe \n " , " lowercase: {x} \n " , . { fmtSliceHexLower ( bytes_with_zeros ) } ) ;
2018-05-16 22:22:39 +00:00
}
2021-02-18 19:28:59 +00:00
/// Decodes the sequence of bytes represented by the specified string of
/// hexadecimal characters.
/// Returns a slice of the output buffer containing the decoded bytes.
pub fn hexToBytes ( out : [ ] u8 , input : [ ] const u8 ) ! [ ] u8 {
// Expect 0 or n pairs of hexadecimal digits.
if ( input . len & 1 ! = 0 )
2018-09-02 23:23:30 +00:00
return error . InvalidLength ;
2021-02-18 19:28:59 +00:00
if ( out . len * 2 < input . len )
return error . NoSpaceLeft ;
2018-09-02 09:02:24 +00:00
2018-09-02 23:23:30 +00:00
var in_i : usize = 0 ;
2021-02-18 19:28:59 +00:00
while ( in_i < input . len ) : ( in_i + = 2 ) {
2018-09-02 23:23:30 +00:00
const hi = try charToDigit ( input [ in_i ] , 16 ) ;
const lo = try charToDigit ( input [ in_i + 1 ] , 16 ) ;
out [ in_i / 2 ] = ( hi < < 4 ) | lo ;
}
2021-02-18 19:28:59 +00:00
return out [ 0 . . in_i / 2 ] ;
2018-09-02 09:02:24 +00:00
}
2019-06-20 08:07:43 +00:00
test " hexToBytes " {
2021-02-18 19:28:59 +00:00
var buf : [ 32 ] u8 = undefined ;
2021-02-12 19:44:19 +00:00
try expectFmt ( " 90 " * * 32 , " {s} " , . { fmtSliceHexUpper ( try hexToBytes ( & buf , " 90 " * * 32 ) ) } ) ;
try expectFmt ( " ABCD " , " {s} " , . { fmtSliceHexUpper ( try hexToBytes ( & buf , " ABCD " ) ) } ) ;
try expectFmt ( " " , " {s} " , . { fmtSliceHexUpper ( try hexToBytes ( & buf , " " ) ) } ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expectError ( error . InvalidCharacter , hexToBytes ( & buf , " 012Z " ) ) ;
try std . testing . expectError ( error . InvalidLength , hexToBytes ( & buf , " AAA " ) ) ;
try std . testing . expectError ( error . NoSpaceLeft , hexToBytes ( buf [ 0 . . 1 ] , " ABAB " ) ) ;
2018-09-02 09:02:24 +00:00
}
2019-04-05 01:36:31 +00:00
2019-06-20 08:07:43 +00:00
test " formatIntValue with comptime_int " {
2019-04-05 01:36:31 +00:00
const value : comptime_int = 123456789123456789 ;
2020-03-06 22:59:21 +00:00
var buf : [ 20 ] u8 = undefined ;
var fbs = std . io . fixedBufferStream ( & buf ) ;
2020-06-20 10:02:48 +00:00
try formatIntValue ( value , " " , FormatOptions { } , fbs . writer ( ) ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " 123456789123456789 " ) ) ;
2019-04-05 01:36:31 +00:00
}
2019-04-27 09:36:48 +00:00
2020-05-25 06:05:08 +00:00
test " formatFloatValue with comptime_float " {
const value : comptime_float = 1.0 ;
var buf : [ 20 ] u8 = undefined ;
var fbs = std . io . fixedBufferStream ( & buf ) ;
2020-06-20 10:02:48 +00:00
try formatFloatValue ( value , " " , FormatOptions { } , fbs . writer ( ) ) ;
2021-05-04 17:47:26 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " 1.0e+00 " ) ) ;
2020-05-25 06:05:08 +00:00
2021-01-12 01:30:43 +00:00
try expectFmt ( " 1.0e+00 " , " {} " , . { value } ) ;
try expectFmt ( " 1.0e+00 " , " {} " , . { 1.0 } ) ;
2020-05-25 06:05:08 +00:00
}
2019-06-20 08:07:43 +00:00
test " formatType max_depth " {
2019-04-27 09:36:48 +00:00
const Vec2 = struct {
const SelfType = @This ( ) ;
x : f32 ,
y : f32 ,
pub fn format (
self : SelfType ,
comptime fmt : [ ] const u8 ,
2019-08-19 10:28:13 +00:00
options : FormatOptions ,
2020-07-11 11:09:04 +00:00
writer : anytype ,
2020-03-06 22:59:21 +00:00
) ! void {
2021-06-20 01:10:22 +00:00
_ = options ;
2019-06-20 08:07:43 +00:00
if ( fmt . len = = 0 ) {
2020-06-20 10:02:48 +00:00
return std . fmt . format ( writer , " ({d:.3},{d:.3}) " , . { self . x , self . y } ) ;
2019-06-20 08:07:43 +00:00
} else {
2022-07-23 15:02:55 +00:00
@compileError ( " unknown format string: ' " + + fmt + + " ' " ) ;
2019-06-20 08:07:43 +00:00
}
2019-04-27 09:36:48 +00:00
}
} ;
const E = enum {
One ,
Two ,
Three ,
} ;
const TU = union ( enum ) {
const SelfType = @This ( ) ;
float : f32 ,
int : u32 ,
ptr : ? * SelfType ,
} ;
const S = struct {
const SelfType = @This ( ) ;
a : ? * SelfType ,
tu : TU ,
e : E ,
vec : Vec2 ,
} ;
var inst = S {
. a = null ,
. tu = TU { . ptr = null } ,
. e = E . Two ,
. vec = Vec2 { . x = 10.2 , . y = 2.22 } ,
} ;
inst . a = & inst ;
inst . tu . ptr = & inst . tu ;
2020-03-06 22:59:21 +00:00
var buf : [ 1000 ] u8 = undefined ;
var fbs = std . io . fixedBufferStream ( & buf ) ;
2020-06-20 10:02:48 +00:00
try formatType ( inst , " " , FormatOptions { } , fbs . writer ( ) , 0 ) ;
2022-06-11 08:33:09 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " fmt.test.formatType max_depth.S{ ... } " ) ) ;
2020-03-06 22:59:21 +00:00
fbs . reset ( ) ;
2020-06-20 10:02:48 +00:00
try formatType ( inst , " " , FormatOptions { } , fbs . writer ( ) , 1 ) ;
2022-06-11 08:33:09 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) } " ) ) ;
2020-03-06 22:59:21 +00:00
fbs . reset ( ) ;
2020-06-20 10:02:48 +00:00
try formatType ( inst , " " , FormatOptions { } , fbs . writer ( ) , 2 ) ;
2022-06-11 08:33:09 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) } " ) ) ;
2020-03-06 22:59:21 +00:00
fbs . reset ( ) ;
2020-06-20 10:02:48 +00:00
try formatType ( inst , " " , FormatOptions { } , fbs . writer ( ) , 3 ) ;
2022-06-11 08:33:09 +00:00
try std . testing . expect ( mem . eql ( u8 , fbs . getWritten ( ) , " fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ .a = fmt.test.formatType max_depth.S{ ... }, .tu = fmt.test.formatType max_depth.TU{ ... }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) }, .tu = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ .ptr = fmt.test.formatType max_depth.TU{ ... } } }, .e = fmt.test.formatType max_depth.E.Two, .vec = (10.200,2.220) } " ) ) ;
2019-04-27 09:36:48 +00:00
}
2019-06-20 08:07:43 +00:00
test " positional " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " 2 1 0 " , " {2} {1} {0} " , . { @as ( usize , 0 ) , @as ( usize , 1 ) , @as ( usize , 2 ) } ) ;
try expectFmt ( " 2 1 0 " , " {2} {1} {} " , . { @as ( usize , 0 ) , @as ( usize , 1 ) , @as ( usize , 2 ) } ) ;
try expectFmt ( " 0 0 " , " {0} {0} " , . { @as ( usize , 0 ) } ) ;
try expectFmt ( " 0 1 " , " {} {1} " , . { @as ( usize , 0 ) , @as ( usize , 1 ) } ) ;
try expectFmt ( " 1 0 0 1 " , " {1} {} {0} {} " , . { @as ( usize , 0 ) , @as ( usize , 1 ) } ) ;
2019-06-20 08:07:43 +00:00
}
test " positional with specifier " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " 10.0 " , " {0d:.1} " , . { @as ( f64 , 9.999 ) } ) ;
2019-06-20 08:07:43 +00:00
}
2019-06-25 05:11:04 +00:00
test " positional/alignment/width/precision " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " 10.0 " , " {0d: >3.1} " , . { @as ( f64 , 9.999 ) } ) ;
2019-06-25 05:11:04 +00:00
}
2019-12-14 09:23:42 +00:00
test " vector " {
2021-05-17 23:08:09 +00:00
if ( builtin . target . cpu . arch = = . riscv64 ) {
2020-02-17 05:06:19 +00:00
// https://github.com/ziglang/zig/issues/4486
return error . SkipZigTest ;
}
2019-12-14 09:23:42 +00:00
2022-03-30 18:12:14 +00:00
const vbool : @Vector ( 4 , bool ) = [ _ ] bool { true , false , true , false } ;
const vi64 : @Vector ( 4 , i64 ) = [ _ ] i64 { - 2 , - 1 , 0 , 1 } ;
const vu64 : @Vector ( 4 , u64 ) = [ _ ] u64 { 1000 , 2000 , 3000 , 4000 } ;
2019-12-14 09:23:42 +00:00
2021-01-12 01:30:43 +00:00
try expectFmt ( " { true, false, true, false } " , " {} " , . { vbool } ) ;
try expectFmt ( " { -2, -1, 0, 1 } " , " {} " , . { vi64 } ) ;
try expectFmt ( " { -2, -1, +0, +1 } " , " {d:5} " , . { vi64 } ) ;
try expectFmt ( " { 1000, 2000, 3000, 4000 } " , " {} " , . { vu64 } ) ;
try expectFmt ( " { 3e8, 7d0, bb8, fa0 } " , " {x} " , . { vu64 } ) ;
2019-12-14 09:23:42 +00:00
}
2020-03-05 17:19:08 +00:00
test " enum-literal " {
2022-07-23 14:53:59 +00:00
try expectFmt ( " .hello_world " , " {} " , . { . hello_world } ) ;
2020-03-05 17:19:08 +00:00
}
2020-05-13 13:07:52 +00:00
test " padding " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " Simple " , " {s} " , . { " Simple " } ) ;
try expectFmt ( " true " , " {:10} " , . { true } ) ;
try expectFmt ( " true " , " {:>10} " , . { true } ) ;
try expectFmt ( " ======true " , " {:=>10} " , . { true } ) ;
try expectFmt ( " true====== " , " {:=<10} " , . { true } ) ;
try expectFmt ( " true " , " {:^10} " , . { true } ) ;
try expectFmt ( " ===true=== " , " {:=^10} " , . { true } ) ;
try expectFmt ( " Minimum width " , " {s:18} width " , . { " Minimum " } ) ;
try expectFmt ( " ==================Filled " , " {s:=>24} " , . { " Filled " } ) ;
try expectFmt ( " Centered " , " {s:^24} " , . { " Centered " } ) ;
try expectFmt ( " - " , " {s:-^1} " , . { " " } ) ;
try expectFmt ( " ==crêpe=== " , " {s:=^10} " , . { " crêpe " } ) ;
try expectFmt ( " =====crêpe " , " {s:=>10} " , . { " crêpe " } ) ;
try expectFmt ( " crêpe===== " , " {s:=<10} " , . { " crêpe " } ) ;
2022-09-23 09:20:38 +00:00
try expectFmt ( " ====a " , " {c:=>5} " , . { 'a' } ) ;
try expectFmt ( " ==a== " , " {c:=^5} " , . { 'a' } ) ;
try expectFmt ( " a==== " , " {c:=<5} " , . { 'a' } ) ;
2020-05-13 13:07:52 +00:00
}
2020-08-09 10:48:26 +00:00
test " decimal float padding " {
var number : f32 = 3.1415 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " left-pad: **3.141 \n " , " left-pad: {d:*>7.3} \n " , . { number } ) ;
try expectFmt ( " center-pad: *3.141* \n " , " center-pad: {d:*^7.3} \n " , . { number } ) ;
try expectFmt ( " right-pad: 3.141** \n " , " right-pad: {d:*<7.3} \n " , . { number } ) ;
2020-08-09 10:48:26 +00:00
}
test " sci float padding " {
var number : f32 = 3.1415 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " left-pad: **3.141e+00 \n " , " left-pad: {e:*>11.3} \n " , . { number } ) ;
try expectFmt ( " center-pad: *3.141e+00* \n " , " center-pad: {e:*^11.3} \n " , . { number } ) ;
try expectFmt ( " right-pad: 3.141e+00** \n " , " right-pad: {e:*<11.3} \n " , . { number } ) ;
2020-08-09 10:48:26 +00:00
}
2020-10-21 17:02:38 +00:00
test " null " {
const inst = null ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " null " , " {} " , . { inst } ) ;
2020-10-21 17:02:38 +00:00
}
2020-09-24 14:40:33 +00:00
2020-12-13 19:02:15 +00:00
test " type " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " u8 " , " {} " , . { u8 } ) ;
try expectFmt ( " ?f32 " , " {} " , . { ? f32 } ) ;
try expectFmt ( " []const u8 " , " {} " , . { [ ] const u8 } ) ;
2020-12-13 19:02:15 +00:00
}
2020-09-24 14:40:33 +00:00
test " named arguments " {
2021-01-12 01:30:43 +00:00
try expectFmt ( " hello world! " , " {s} world{c} " , . { " hello " , '!' } ) ;
try expectFmt ( " hello world! " , " {[greeting]s} world{[punctuation]c} " , . { . punctuation = '!' , . greeting = " hello " } ) ;
try expectFmt ( " hello world! " , " {[1]s} world{[0]c} " , . { '!' , " hello " } ) ;
2020-09-24 14:40:33 +00:00
}
test " runtime width specifier " {
var width : usize = 9 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " ~~hello~~ " , " {s:~^[1]} " , . { " hello " , width } ) ;
try expectFmt ( " ~~hello~~ " , " {s:~^[width]} " , . { . string = " hello " , . width = width } ) ;
2021-10-17 13:52:19 +00:00
try expectFmt ( " hello " , " {s:[1]} " , . { " hello " , width } ) ;
try expectFmt ( " 42 hello " , " {d} {s:[2]} " , . { 42 , " hello " , width } ) ;
2020-09-24 14:40:33 +00:00
}
test " runtime precision specifier " {
var number : f32 = 3.1415 ;
var precision : usize = 2 ;
2021-01-12 01:30:43 +00:00
try expectFmt ( " 3.14e+00 " , " {:1.[1]} " , . { number , precision } ) ;
try expectFmt ( " 3.14e+00 " , " {:1.[precision]} " , . { . number = number , . precision = precision } ) ;
2020-09-24 14:40:33 +00:00
}
2022-12-31 12:28:21 +00:00
test " recursive format function " {
const R = union ( enum ) {
const R = @This ( ) ;
Leaf : i32 ,
Branch : struct { left : * const R , right : * const R } ,
pub fn format ( self : R , comptime _ : [ ] const u8 , _ : std . fmt . FormatOptions , writer : anytype ) ! void {
return switch ( self ) {
. Leaf = > | n | std . fmt . format ( writer , " Leaf({}) " , . { n } ) ,
. Branch = > | b | std . fmt . format ( writer , " Branch({}, {}) " , . { b . left , b . right } ) ,
} ;
}
} ;
var r = R { . Leaf = 1 } ;
try expectFmt ( " Leaf(1) \n " , " {} \n " , . { & r } ) ;
}