String Formatting module
Cnx's formatting API creates a composable, ergonomic, human-readable way to format data into strings for storage or I/O.
The interface and functionality is similar to C++'s std::format
, and the popular fmtlib
it's derived from, and Rust's std::format
. Example:
let x = 0.0F; let y = 1.0F; // formats x and y as normal decimal format with one significant figure after the decimal point let formatted = cnx_format("x: {d1}, y: {d1}", x, y);
Formatting supports custom specifiers within the brackets, "{}". Any character except curly brackets ('{' and '}') are valid to use for format specifies for custom types.
Builtin types provide some limited formatting options. For integral types, such as char
, u32
, or pointers, these include options for notation: 'd' (Decimal, ie Base 10), 'x' (Lower-case Hex), or 'X' (Upper-case Hex). For floating point numbers, such as f32
, this include options for notation: 'd' (Decimal) or 'e' (Scientific/Exponential notation), and for the number of significant figures after the decimal point: a number directly following the notation specifier, or on its own if no specifier was given. The default for integral types other than pointer is decimal, for pointers is lower-case hexadecimal, and for pointers is scientific notation with 3 significant figures after the decimal point. In addition to these specifiers, builtin types also accept a "Debug" specifier D
, and D
should be used as the standard specifier to indicate a debugging format. All Cnx library types providing a CnxFormat
implementation (such as CnxDuration
or CnxString
) support the D
specifier and provide a debugging format.
Bools are a special case among builtin types, in that they only support the D
specifier, even though they are technically integral types. Bools with always format to directly to "true" or "false".
String formatting is extensible and composable in Cnx because it uses a Trait, CnxFormat
, to enable user-defined types to provide their own formatting implementations. This trait provides the functionality for validating format specifiers and performing string formatting for an implementing type.
To provide an implementation of CnxFormat
for your type, only three functions and the Trait implementation are required. The functions take the following signatures:
CnxFormatContext (*const is_specifier_valid)(const CnxFormat* restrict self, CnxStringView specifier); CnxString (*const format)(const CnxFormat* restrict self, CnxFormatContext context); CnxString (*const format_with_allocator)(const CnxFormat* restrict self, CnxFormatContext context, CnxAllocator allocator);
is_specifier_valid
takes the specifier in the format string, should validate it, and return the result in a CnxFormatContext
. A CnxFormatContext
stores the validation result as well as up to 32 bytes of aligned storage to store any interim state parsed from the format specifier necessary for formatting your type. For example, implementations for Cnx types store whether the debug specifier D
occurred and other state, such as the number of significant figures to use, in the case of floating point types. This context will later be passed to your formatting function in order to format the instance of your type.
format
and format_with_allocator
use your type (pointed to in the trait object, self
) and the CnxFormatContext
(that was obtained when Cnx called your is_specifier_valid
function) to perform the string formatting. This will likely be implemented in terms of another call to cnx_format
, formatting the constituent parts of your type. In addition, format
is generally implemented in terms of calling format_with_allocator
with the default Cnx allocator.
Arguments passed to cnx_format
must be l-values cast to their CnxFormat
trait object representation with as_
or as_
. Cnx will automatically cast builtin types, cstrings, string-literals, and CnxString
and CnxStringView
to their CnxFormat
implementation. Generally, other types will need to be cast explicitly, however, automatic conversions for specific user defined types can be enabled by defining a pair of macros prior to including "CnxFormat.h". First, define CNX_TRUE
. Then, define CNX_
#define CNX_AS_FORMAT_USES_USER_SUPPLIED_TYPES TRUE #define CNX_AS_FORMAT_USER_SUPPLIED_TYPES T* : as_format_t(T, x), \ const T* : as_format_t(T2, x), \ T2* : as_format_t(T2, x), \ const T2* : as_format_t(T2, x),
where T
and T2
are your supplied types. x
should always be the second argument to the conversion function (literally always give x
as the second argument). cnx_
automatically applies the as_
_Generic
macro to all format arguments in the parameter pack to perform the automatic conversion to CnxFormat
. That is why this syntax is necessary for the CNX_AS_FORMAT_USER_SUPPLIED types macro, to provide valid match arms in the as_
macro for that conversion.
A complete example of implementing and using CnxFormat
:
typedef struct Point2D { f32 x; f32 y; } Point2D; #define CNX_AS_FORMAT_USES_USER_SUPPLIED_TYPES TRUE #define CNX_AS_FORMAT_USER_SUPPLIED_TYPES Point2D* : as_format_t(Point2D, x), \ const Point2D* : as_format_t(Point2D, x), #include <Cnx/Format.h> // `specifier` will be a string view over the characters making up the format specifier. // For example, in the specifier `{e}`, `specifier` would view `e`. // For this example, we'll make `d` (for decimal/normal notation) or `e` // (for scientific/exponential notation) followed by a number (ie `4` or `10`, for the number of // significant figures) a valid specifier, and anything else invalid. // This is the same set of options available to floating point numbers, so we can just forward // our implementation to that one. inline static CnxFormatContext point2d_is_format_specifier_valid(const CnxFormat* restrict self, CnxStringView specifier) { let an_f32 = static_cast(f32)(0.0); let format_obj = as_format_t(f32, an_f32); return trait_call(is_specifier_valid, format_obj, specifier); } typedef struct Point2DFormatContext { bool is_exponential; bool is_debug; u32 num_sig_figs; } Point2DFormatContext; inline static CnxString point2d_format_with_allocator(const CnxFormat* restrict self, CnxFormatContext context, CnxAllocator allocator) { // first we create our interim format string to use as the specifier for formatting // x and y let context = static_cast(const Point2DFormatContext*)(context.data); let exponential = context->is_exponential ? "e" : ""; let debug = context->is_debug ? "D" : ""; let num_sig_figs = context->num_sig_figs; CnxScopedString specifier = cnx_format_with_allocator("{}{}{}", allocator, exponential, num_sig_figs, debug); // Then we can forward two copies of the specifier to format to create our format string, // which we'll then pass to our actual format call, letting the implementation for f32 // handle the actual details CnxScopedString format_str = cnx_format_with_allocator("Point2D: [x: \{{}\}, y: \{{}\}]", allocator, specifier, specifier); let _self = static_cast(const Point2D*)(self.m_self); return cnx_format_with_allocator(cnx_string_into_cstring(format_str), allocator, _self->x, _self->y); } inline static CnxString point2d_format(const CnxFormat* restrict self, CnxStringView specifier) { return point2d_format_with_allocator(self, specifier, cnx_allocator_new()); } __attr(maybe_unused) static ImplTraitFor(CnxFormat, Point2D, point2d_format, point2d_format_with_allocator); void point2d_print_legacy(const Point2D* restrict self) { let formatted = cnx_format("{}", *self); printf("%s", cnx_string_into_cstring(formatted)); }
Classes
- struct CnxFormat
Enums
- enum CnxFormatErrorTypes { CNX_FORMAT_SUCCESS = 0, CNX_FORMAT_BAD_SPECIFIER_INVALID_CHAR_IN_SPECIFIER, CNX_FORMAT_INVALID_CLOSING_BRACE_LOCATION, CNX_FORMAT_UNCLOSED_SPECIFIER, CNX_FORMAT_MORE_SPECIFIERS_THAN_ARGS, CNX_FORMAT_FEWER_SPECIFIERS_THAN_ARGS }
- Specifies possible errors that could occur when parsing a format specifier.
- enum CnxFormatDefaults { CNX_FORMAT_DEFAULT_NUM_SIG_FIGS = 3 }
- Default values for various formatting parameters for Cnx string formatting.
Typedefs
- using CnxFormatErrorTypes = enum CnxFormatErrorTypes
- Specifies possible errors that could occur when parsing a format specifier.
- using CnxFormatDefaults = enum CnxFormatDefaults
- Default values for various formatting parameters for Cnx string formatting.
Defines
- #define as_format_t(T, x)
- Converts the given variable into its associated
CnxFormat
Trait implementation. - #define CNX_AS_FORMAT_USES_USER_SUPPLIED_TYPES
- Feature enable macro to allow specific user-defined types to be automatically converted to their
CnxFormat
implementation when passed tocnx_
or a similar Cnx string formatting function.format(format_ string, ...) - #define CNX_AS_FORMAT_USER_SUPPLIED_TYPES
- Define this macro to a comma separated list of conversions to enable specific user-defined types to be automatically converted to their
CnxFormat
implementation when passed tocnx_
or a similar Cnx string formatting function.format(format_ string, ...) - #define as_format(x)
- Converts the given variable into its associated
CnxFormat
Trait implementation. - #define cnx_format_with_allocator(format_string, allocator, ...)
- Formats the various parameter pack arguments into their associated place in the given format string, using the provided allocator.
- #define cnx_format(format_string, ...)
- Formats the various parameter pack arguments into their associated place in the given format string, using the default allocator.
- #define cnx_vformat_with_allocator(format_string, allocator, num_args, list)
- Formats the various
va_list
parameter pack arguments into their associated place in the given format string, using the provided allocator. - #define cnx_vformat(format_string, num_args, list)
- Formats the various
va_list
parameter pack arguments into their associated place in the given format string, using the default allocator.
Enum documentation
enum CnxFormatErrorTypes
#include <include/Cnx/Format.h>
Specifies possible errors that could occur when parsing a format specifier.
Enumerators | |
---|---|
CNX_FORMAT_SUCCESS |
No error, the specifier is valid. |
CNX_FORMAT_BAD_SPECIFIER_INVALID_CHAR_IN_SPECIFIER |
An invalid character occurred in the specifier sequence. |
CNX_FORMAT_INVALID_CLOSING_BRACE_LOCATION |
A closing specifier brace occurred in an invalid location. |
CNX_FORMAT_UNCLOSED_SPECIFIER |
An unclosed format specifier (ie |
CNX_FORMAT_MORE_SPECIFIERS_THAN_ARGS |
More format specifiers were given in the format string than there were arguments to be formatted. |
CNX_FORMAT_FEWER_SPECIFIERS_THAN_ARGS |
Fewer format specifiers were given in the format string than there were arguments to be formatted. |
enum CnxFormatDefaults
#include <include/Cnx/Format.h>
Default values for various formatting parameters for Cnx string formatting.
Enumerators | |
---|---|
CNX_FORMAT_DEFAULT_NUM_SIG_FIGS |
The default number of significant figures for floating point formatting. By default, Cnx floating point formatting provides 3 significant figures after the decimal point in formatted output @ingroup format |
Typedef documentation
typedef enum CnxFormatErrorTypes CnxFormatErrorTypes
#include <include/Cnx/Format.h>
Specifies possible errors that could occur when parsing a format specifier.
typedef enum CnxFormatDefaults CnxFormatDefaults
#include <include/Cnx/Format.h>
Default values for various formatting parameters for Cnx string formatting.
Define documentation
#define as_format_t(T,
x)
#include <include/Cnx/Format.h>
Converts the given variable into its associated CnxFormat
Trait implementation.
Parameters | |
---|---|
T | - The concrete type of x |
x | - The variable to convert to its CnxFormat Trait implementation |
Returns | x as CnxFormat |
There must be an implementation of CnxFormat
for the type T
and x
must be an lvalue of type T
.
#define CNX_AS_FORMAT_USES_USER_SUPPLIED_TYPES
#include <include/Cnx/Format.h>
Feature enable macro to allow specific user-defined types to be automatically converted to their CnxFormat
implementation when passed to cnx_
or a similar Cnx string formatting function.
Requires that CNX_
#define CNX_AS_FORMAT_USES_USER_SUPPLIED_TYPES true #define CNX_AS_FORMAT_USER_SUPPLIED_TYPES T* : as_format_t(T, x), \ const T* : as_format_t(T2, x), \ T2* : as_format_t(T2, x), \ const T2* : as_format_t(T2, x),
#define CNX_AS_FORMAT_USER_SUPPLIED_TYPES
#include <include/Cnx/Format.h>
Define this macro to a comma separated list of conversions to enable specific user-defined types to be automatically converted to their CnxFormat
implementation when passed to cnx_
or a similar Cnx string formatting function.
Requires that CNX_true
Example:
#define CNX_AS_FORMAT_USES_USER_SUPPLIED_TYPES true #define CNX_AS_FORMAT_USER_SUPPLIED_TYPES T* : as_format_t(T, x), \ const T* : as_format_t(T2, x), \ T2* : as_format_t(T2, x), \ const T2* : as_format_t(T2, x),
#define as_format(x)
#include <include/Cnx/Format.h>
Converts the given variable into its associated CnxFormat
Trait implementation.
Parameters | |
---|---|
x | - The variable to convert to its CnxFormat Trait implementation |
Returns | x as CnxFormat |
There must be an implementation of CnxFormat
for the type of x
and x
must be an lvalue
#define cnx_format_with_allocator(format_string,
allocator,
...)
#include <include/Cnx/Format.h>
Formats the various parameter pack arguments into their associated place in the given format string, using the provided allocator.
Parameters | |
---|---|
format_string | - The string specifying the format positions, specifiers, and other text that should be present in the output string |
allocator | - The CnxAllocator to allocate the output string with |
... | - The parameter pack of arguments to be formatted |
Returns | The formatted output string |
#define cnx_format(format_string,
...)
#include <include/Cnx/Format.h>
Formats the various parameter pack arguments into their associated place in the given format string, using the default allocator.
Parameters | |
---|---|
format_string | - The string specifying the format positions, specifiers, and other text that should be present in the output string |
... | - The parameter pack of arguments to be formatted |
Returns | The formatted output string |
#define cnx_vformat_with_allocator(format_string,
allocator,
num_args,
list)
#include <include/Cnx/Format.h>
Formats the various va_list
parameter pack arguments into their associated place in the given format string, using the provided allocator.
Parameters | |
---|---|
format_string | - The string specifying the format positions, specifiers, and other text that should be present in the output string |
allocator | - The CnxAllocator to allocate the output string with |
num_args | - The number of arguments in the parameter pack |
list | - The va_list parameter pack of arguments to be formatted |
Returns | The formatted output string |
#define cnx_vformat(format_string,
num_args,
list)
#include <include/Cnx/Format.h>
Formats the various va_list
parameter pack arguments into their associated place in the given format string, using the default allocator.
Parameters | |
---|---|
format_string | - The string specifying the format positions, specifiers, and other text that should be present in the output string |
num_args | - The number of arguments in the parameter pack |
list | - The va_list parameter pack of arguments to be formatted |
Returns | The formatted output string |