Zig's @import("std").fmt
may be good, but in some cases you just need a little bit more functionality.
Now why not just link in libc
and use sprintf()
and such?
Either because you are on some device where there isn't (yet) a libc
to link against,
because you just don't want to link against libc
,
or lastly because you're using some types c doesn't understand (u19
, f128
, etc.) and want a native zig solution.
That being said, this is not a drop in replacement, the format specifiers are a little different from c's. I did design them to be as close as possible though, requiring little rethinking if you're already used to c.
A lot of what's written here isn't implemented yet, I am working on it though. Also any ideas are appreciated, making this more feature rich would be nice :3
Also also, I will add a build.zig.zon
once this can be reasonably used as a library.
Might even make it so you can compile it to a static/dynamic library to use with other languages.
Format specifiers in cfmt
look something like this:
"%s" // a string
"%.4f" // a floating-point value rounded to 4 digits after the period
"%-4.8i" // a left alligned integer of at least 4 and at most 8 digits
They can be split into five parts in this exact order:
A %
character,
flags,
minimum width,
precision and lastly
the specifier.
All besides the specifier are optional.
The only exception to this is the specifier for writing a %
character, which is %%
.
Specifiers tell cfmt
what kind of type you're formatting and how you want it formatted.
The following specifiers exist:
Integers:
i
: Decimal integerb
: Binary integero
: Octal integerx
: Hexadecimal integer (lowercase)X
: Hexadecimal integer (uppercase)
Floating-point:
f
: Decimal floating-point
Other:
s
: Stringc
: Characterp
: Pointer (hexadecimal lowercase)P
: Pointer (hexadecimal uppercase)
Special:
*cricket noises*
For better compatibility with c's format specifiers, d
and u
work the same as i
.
If a minimum width is specified and the formatted string isn't that long, the string will be padded with space characters.
The minimum width can either be a decimal number, such as 4
, 9
or 142
,
or a *
, in which case an usize
is read from the arguments and its value is used as the minimum width.
If a *
is used, the dynamic minimum width must be given before the value to be formatted (and the dynamic precision if used).
Please note that the decimal number may not have a leading zero as that could interfere with the 0
flag.
By default, the formatted contents will be right alligned (space characters on the left),
though this behavior can be changed with the -
flag.
What exactly precision means depends on the type to be formatted:
If it's a floating point value, precision is the amount of digits after the decimal separator.
If it's a string, precision defines the maximum amount of characters written. When truncation happens, the last characters are cut off.
If it's a character, it will be repeated precision times. Does it make sense to call that precision? No. Could it still be a useful functionality? Yes.
For any other type, precision doesn't do anything.
To set the precision, a .
followed by a number or a *
is used, similar to minimum width except with a .
character.
Here however, the precision is put between the dynamic minimum width and value to be formatted.
If neither a *
or a number follows the .
, precision is set to 0
.
Flags change some parts of formatting.
The following flags exist:
-
: Allign to the left instead of right (space characters on the right)+
: Always write+
and-
sign for numeric types<
: Write sign before any padding0
: Pad with0
characters instead of space characters