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 digitsThey 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: (space) Write a space character at start of positive numeric values<: Write sign before any padding0: Pad with0characters instead of space characters