diff options
-rw-r--r-- | build-aux/measurestack/app_main.py | 5 | ||||
-rw-r--r-- | libmisc/CMakeLists.txt | 2 | ||||
-rw-r--r-- | libmisc/fmt.c | 225 | ||||
-rw-r--r-- | libmisc/include/libmisc/fmt.h | 156 | ||||
-rw-r--r-- | libmisc/tests/test_fmt.c | 170 |
5 files changed, 558 insertions, 0 deletions
diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py index f705876..f933967 100644 --- a/build-aux/measurestack/app_main.py +++ b/build-aux/measurestack/app_main.py @@ -94,6 +94,11 @@ def main( QName("fmt_vsnprintf"), ]: return 1, False + for prefix in ["fmt_print_", "_fmt_print_"]: + if str(name.base()).startswith(prefix): + return 1, False + if str(name.base()).endswith("_putb"): + return 1, False return 0, False extra_includes: list[BaseName] = [] diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 8c9fa57..48407bd 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(libmisc INTERFACE) target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libmisc INTERFACE assert.c + fmt.c intercept.c linkedlist.c log.c @@ -16,6 +17,7 @@ target_sources(libmisc INTERFACE add_lib_test(libmisc test_assert) add_lib_test(libmisc test_assert_min) add_lib_test(libmisc test_endian) +add_lib_test(libmisc test_fmt) add_lib_test(libmisc test_hash) add_lib_test(libmisc test_log) add_lib_test(libmisc test_macro) diff --git a/libmisc/fmt.c b/libmisc/fmt.c new file mode 100644 index 0000000..33788b6 --- /dev/null +++ b/libmisc/fmt.c @@ -0,0 +1,225 @@ +/* libmisc/fmt.c - Write formatted text + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for strnlen() */ + +#include <libmisc/fmt.h> + +static const char *const hexdig = "0123456789ABCDEF"; + +/* small/trivial formatters ***************************************************/ + +void fmt_print_byte(lo_interface fmt_dest w, uint8_t b) { + LO_CALL(w, putb, b); +} + +void fmt_print_bool(lo_interface fmt_dest w, bool b) { + fmt_print_str(w, b ? "true" : "false"); +} + +void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x) { + fmt_print(w, "0x", (rjust, 2, '0', (base16, x))); +} +void fmt_print_base16_u16_(lo_interface fmt_dest w, uint16_t x) { + fmt_print(w, "0x", (rjust, 4, '0', (base16, x))); +} +void fmt_print_base16_u32_(lo_interface fmt_dest w, uint32_t x) { + fmt_print(w, "0x", (rjust, 8, '0', (base16, x))); +} +void fmt_print_base16_u64_(lo_interface fmt_dest w, uint64_t x) { + fmt_print(w, "0x", (rjust, 16, '0', (base16, x))); +} + +void fmt_print_ptr(lo_interface fmt_dest w, void *ptr) { + LM_CAT3_(fmt_print_base16_u, __INTPTR_WIDTH__, _)(w, (uintptr_t)ptr); +} + +/* quote **********************************************************************/ + +/** + * Quote a byte to ASCII-only C syntax. + */ +void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b) { + fmt_print_byte(w, '\''); + if (' ' <= b && b <= '~') { + if (b == '\'' || b == '\\') + fmt_print_byte(w, '\\'); + fmt_print_byte(w, b); + } else { + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'x'); + fmt_print_byte(w, hexdig[(b >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(b >> 0) & 0xF]); + } + fmt_print_byte(w, '\''); +} + +/** + * Quote a region of memory to ASCII-only C string syntax. Valid + * UTF-8 is quoted as short C-escape characters, \uABCD, or + * \UABCDABCD; invalid UTF-8 is quoted as \xAB. + */ +void fmt_print_qmem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + fmt_print_byte(w, '"'); + for (size_t pos = 0; pos < size;) { + uint32_t ch; + uint8_t chlen; + if ((str[pos] & 0b10000000) == 0b00000000) { ch = str[pos] & 0b01111111; chlen = 1; } + else if ((str[pos] & 0b11100000) == 0b11000000) { ch = str[pos] & 0b00011111; chlen = 2; } + else if ((str[pos] & 0b11110000) == 0b11100000) { ch = str[pos] & 0b00001111; chlen = 3; } + else if ((str[pos] & 0b11111000) == 0b11110000) { ch = str[pos] & 0b00000111; chlen = 4; } + else goto invalid_utf8; + if ((ch == 0 && chlen != 1) || pos + chlen > size) goto invalid_utf8; + for (uint8_t i = 1; i < chlen; i++) { + if ((str[pos+i] & 0b11000000) != 0b10000000) goto invalid_utf8; + ch = (ch << 6) | (str[pos+i] & 0b00111111); + } + if (ch > 0x10FFFF) goto invalid_utf8; + + if (ch == '\0' || + ch == '\b' || + ch == '\f' || + ch == '\n' || + ch == '\r' || + ch == '\t' || + ch == '\v' || + ch == '\\' || + ch == '\'' || + ch == '"' || + ch == '?') { + /* short C-escape */ + fmt_print_byte(w, '\\'); + switch (ch) { + case '\0': fmt_print_byte(w, '0'); break; + case '\a': fmt_print_byte(w, 'a'); break; + case '\b': fmt_print_byte(w, 'b'); break; + case '\f': fmt_print_byte(w, 'f'); break; + case '\n': fmt_print_byte(w, 'n'); break; + case '\r': fmt_print_byte(w, 'r'); break; + case '\t': fmt_print_byte(w, 't'); break; + case '\v': fmt_print_byte(w, 'v'); break; + case '\\': fmt_print_byte(w, '\\'); break; + case '\'': fmt_print_byte(w, '\''); break; + case '"': fmt_print_byte(w, '"'); break; + case '?': fmt_print_byte(w, '?'); break; + } + } else if (' ' <= ch && ch <= '~') { + /* no escaping */ + fmt_print_byte(w, ch); + } else if (ch < 0x10000) { + /* \uABCD */ + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'u'); + fmt_print_byte(w, hexdig[(ch >> 12) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 8) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 0) & 0xF]); + } else { + /* \UABCDABCD */ + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'U'); + fmt_print_byte(w, hexdig[(ch >> 28) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 24) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 20) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 16) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 12) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 8) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(ch >> 0) & 0xF]); + } + pos += chlen; + continue; + + invalid_utf8: + /* \xAB */ + fmt_print_byte(w, '\\'); + fmt_print_byte(w, 'x'); + fmt_print_byte(w, hexdig[(str[pos] >> 4) & 0xF]); + fmt_print_byte(w, hexdig[(str[pos] >> 0) & 0xF]); + pos++; + } + fmt_print_byte(w, '"'); +} + +void fmt_print_qstr(lo_interface fmt_dest w, const char *str) { + fmt_print_qmem(w, str, strlen(str)); +} + +void fmt_print_qstrn(lo_interface fmt_dest w, const char *str, size_t n) { + fmt_print_qmem(w, str, strnlen(str, n)); +} + +/* int ************************************************************************/ + +#define declare(BASE, BITS) \ + void _fmt_print_base##BASE##_s##BITS(lo_interface fmt_dest w, \ + int##BITS##_t val) { \ + if (val < 0) { \ + fmt_print_byte(w, '-'); \ + val = -val; \ + } \ + _fmt_print_base##BASE##_u##BITS(w, (uint##BITS##_t)val); \ + } \ + \ + void _fmt_print_base##BASE##_u##BITS(lo_interface fmt_dest w, \ + uint##BITS##_t absval) { \ + /* This digit-counting is O(log(absval)); there are \ + * `__builtin_clz`-based O(1) ways to do this, but when I \ + * tried them they bloated the code-size too much. And this \ + * function as a whole is already O(log(absval)) anyway \ + * because of actually printing the digits. */ \ + unsigned ndigits = 1; \ + uint##BITS##_t div = 1; \ + while (absval / div >= BASE) { \ + div *= BASE; \ + ndigits++; \ + } \ + \ + for (unsigned i = 0; i < ndigits; i++) { \ + unsigned digit = (unsigned) (absval / div); \ + absval %= div; \ + div /= BASE; \ + fmt_print_byte(w, hexdig[digit]); \ + } \ + } \ + LM_FORCE_SEMICOLON + +declare(2, 8); +declare(2, 16); +declare(2, 32); +declare(2, 64); + +declare(8, 8); +declare(8, 16); +declare(8, 32); +declare(8, 64); + +declare(10, 8); +declare(10, 16); +declare(10, 32); +declare(10, 64); + +declare(16, 8); +declare(16, 16); +declare(16, 32); +declare(16, 64); + +#undef declare + +/* fmt_buf ********************************************************************/ + +LO_IMPLEMENTATION_C(fmt_dest, struct fmt_buf, fmt_buf, static); + +static void fmt_buf_putb(struct fmt_buf *buf, uint8_t b) { + if (buf->len < buf->cap) + ((uint8_t *)(buf->dat))[buf->len] = b; + buf->len++; +} + +static size_t fmt_buf_tell(struct fmt_buf *buf) { + return buf->len; +} diff --git a/libmisc/include/libmisc/fmt.h b/libmisc/include/libmisc/fmt.h new file mode 100644 index 0000000..c29c085 --- /dev/null +++ b/libmisc/include/libmisc/fmt.h @@ -0,0 +1,156 @@ +/* libmisc/fmt.h - Write formatted text + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_FMT_H_ +#define _LIBMISC_FMT_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for (u)int{n}_t */ + +#include <libmisc/macro.h> +#include <libmisc/obj.h> + +/* destination interface ******************************************************/ + +#define fmt_dest_LO_IFACE \ + LO_FUNC(void , putb, uint8_t b) \ + LO_FUNC(size_t, tell) +LO_INTERFACE(fmt_dest); + +/* type-specific fmt_print_() functions ***************************************/ + +/* Simple bytes. */ +void fmt_print_byte(lo_interface fmt_dest w, uint8_t b); + +/* These are `static inline` so that the compiler can unroll the loops. */ +static inline void fmt_print_mem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + while (size--) + fmt_print_byte(w, *(str++)); +} +static inline void fmt_print_str(lo_interface fmt_dest w, const char *str) { + while (*str) + fmt_print_byte(w, *(str++)); +} +static inline void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size) { + while (size-- && *str) + fmt_print_byte(w, *(str++)); +} + +/* Quoted bytes. */ +void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b); +void fmt_print_qmem(lo_interface fmt_dest w, const void *str, size_t size); +void fmt_print_qstr(lo_interface fmt_dest w, const char *str); +void fmt_print_qstrn(lo_interface fmt_dest w, const char *str, size_t size); + +/* Integers. */ +#define _fmt_declare_base(base) \ + void _fmt_print_base##base##_u8(lo_interface fmt_dest w, uint8_t val); \ + void _fmt_print_base##base##_u16(lo_interface fmt_dest w, uint16_t val); \ + void _fmt_print_base##base##_u32(lo_interface fmt_dest w, uint32_t val); \ + void _fmt_print_base##base##_u64(lo_interface fmt_dest w, uint64_t val); \ + void _fmt_print_base##base##_s8(lo_interface fmt_dest w, int8_t val); \ + void _fmt_print_base##base##_s16(lo_interface fmt_dest w, int16_t val); \ + void _fmt_print_base##base##_s32(lo_interface fmt_dest w, int32_t val); \ + void _fmt_print_base##base##_s64(lo_interface fmt_dest w, int64_t val); \ + LM_FORCE_SEMICOLON +_fmt_declare_base(2); +_fmt_declare_base(8); +_fmt_declare_base(10); +_fmt_declare_base(16); +#undef _fmt_declare_base + +#define _fmt_intswitch(val, prefix) _Generic((val) , \ + unsigned char : LM_CAT3_(prefix, u, __SCHAR_WIDTH__) , \ + unsigned short : LM_CAT3_(prefix, u, __SHRT_WIDTH__) , \ + unsigned int : LM_CAT3_(prefix, u, __INT_WIDTH__) , \ + unsigned long : LM_CAT3_(prefix, u, __LONG_WIDTH__) , \ + unsigned long long : LM_CAT3_(prefix, u, __LONG_LONG_WIDTH__) , \ + signed char : LM_CAT3_(prefix, s, __SCHAR_WIDTH__) , \ + signed short : LM_CAT3_(prefix, s, __SHRT_WIDTH__) , \ + signed int : LM_CAT3_(prefix, s, __INT_WIDTH__) , \ + signed long : LM_CAT3_(prefix, s, __LONG_WIDTH__) , \ + signed long long : LM_CAT3_(prefix, s, __LONG_LONG_WIDTH__) ) +#define fmt_print_base2(w, val) (_fmt_intswitch((val), _fmt_print_base2_)((w), (val))) +#define fmt_print_base8(w, val) (_fmt_intswitch((val), _fmt_print_base8_)((w), (val))) +#define fmt_print_base10(w, val) (_fmt_intswitch((val), _fmt_print_base10_)((w), (val))) +#define fmt_print_base16(w, val) (_fmt_intswitch((val), _fmt_print_base16_)((w), (val))) + +/* Booleans. */ +void fmt_print_bool(lo_interface fmt_dest w, bool b); + +/* The high-level fmt_print() interface ***************************************/ + +#define fmt_print(w, ...) do { LM_FOREACH_PARAM_(_fmt_param, (w), __VA_ARGS__) } while (0) +#define _fmt_param(w, arg) \ + LM_IF(LM_IS_TUPLE(arg))( \ + _fmt_param_tuple LM_EAT() (w, LM_EXPAND arg) \ + )( \ + _fmt_param_nontuple(w, arg) \ + ); +#define _fmt_param_tuple(w, fn, ...) fmt_print_##fn(w __VA_OPT__(,) __VA_ARGS__) +#define _fmt_param_nontuple(w, val) _Generic((val), \ + unsigned char : LM_CAT2_(_fmt_print_base10_u, __SCHAR_WIDTH__) , \ + unsigned short : LM_CAT2_(_fmt_print_base10_u, __SHRT_WIDTH__) , \ + unsigned int : LM_CAT2_(_fmt_print_base10_u, __INT_WIDTH__) , \ + unsigned long : LM_CAT2_(_fmt_print_base10_u, __LONG_WIDTH__) , \ + unsigned long long : LM_CAT2_(_fmt_print_base10_u, __LONG_LONG_WIDTH__) , \ + signed char : LM_CAT2_(_fmt_print_base10_s, __SCHAR_WIDTH__) , \ + signed short : LM_CAT2_(_fmt_print_base10_s, __SHRT_WIDTH__) , \ + signed int : LM_CAT2_(_fmt_print_base10_s, __INT_WIDTH__) , \ + signed long : LM_CAT2_(_fmt_print_base10_s, __LONG_WIDTH__) , \ + signed long long : LM_CAT2_(_fmt_print_base10_s, __LONG_LONG_WIDTH__) , \ + char * : fmt_print_str , \ + const char * : fmt_print_str , \ + bool : fmt_print_bool )(w, val) + +/* print-to-memory ************************************************************/ + +struct fmt_buf { + void *dat; + size_t len, cap; +}; +LO_IMPLEMENTATION_H(fmt_dest, struct fmt_buf, fmt_buf); + +#define fmt_snprint(buf, n, ...) ({ \ + struct fmt_buf _w = { .dat = buf, .cap = n }; \ + lo_interface fmt_dest w = lo_box_fmt_buf_as_fmt_dest(&_w); \ + fmt_print(w, __VA_ARGS__); \ + if (_w.len < _w.cap) \ + ((char *)_w.dat)[_w.len] = '\0'; \ + _w.len; \ +}) + +/* justify ********************************************************************/ + +/* *grubles about not being allowed to nest things* */ +#define _fmt_param_indirect() _fmt_param +#define _fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0) +#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__) + +#define fmt_print_ljust(w, width, fillchar, ...) do { \ + size_t beg = LO_CALL(w, tell); \ + _fmt_print2(w, __VA_ARGS__); \ + while ((LO_CALL(w, tell) - beg) < width) \ + fmt_print_byte(w, fillchar); \ +} while (0) + +#define fmt_print_rjust(w, width, fillchar, ...) do { \ + struct fmt_buf _discard = {}; \ + lo_interface fmt_dest discard = lo_box_fmt_buf_as_fmt_dest(&_discard); \ + _fmt_print2(discard, __VA_ARGS__); \ + while (_discard.len++ < width) \ + fmt_print_byte(w, fillchar); \ + _fmt_print2(w, __VA_ARGS__); \ +} while (0) + +void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x); +void fmt_print_base16_u16_(lo_interface fmt_dest w, uint16_t x); +void fmt_print_base16_u32_(lo_interface fmt_dest w, uint32_t x); +void fmt_print_base16_u64_(lo_interface fmt_dest w, uint64_t x); +void fmt_print_ptr(lo_interface fmt_dest w, void *ptr); + +#endif /* _LIBMISC_FMT_H_ */ diff --git a/libmisc/tests/test_fmt.c b/libmisc/tests/test_fmt.c new file mode 100644 index 0000000..6a6eb7c --- /dev/null +++ b/libmisc/tests/test_fmt.c @@ -0,0 +1,170 @@ +/* libmisc/tests/test_fmt.c - Tests for <libmisc/fmt.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for strcmp(), memcmp(), memset() */ + +#include <libmisc/fmt.h> + +#include "test.h" + +int main() { + char str[128] = {}; +#define do_print(...) fmt_snprint(str, sizeof(str), __VA_ARGS__) + + do_print("hello ", 9, " world!\n"); + test_assert(strcmp(str, "hello 9 world!\n") == 0); + memset(str, 0, sizeof(str)); + + do_print("hello ", (base8, 9), " world!\n"); + test_assert(strcmp(str, "hello 11 world!\n") == 0); + memset(str, 0, sizeof(str)); + + do_print("hello ", (base2, 9), (qstr, " world!\n")); + test_assert(strcmp(str, "hello 1001\" world!\\n\"") == 0); + memset(str, 0, sizeof(str)); + + do_print("hello ", (base16, 17), " world!\n"); + test_assert(strcmp(str, "hello 11 world!\n") == 0); + memset(str, 0, sizeof(str)); + + do_print((strn, "hello ", 4)); + test_assert(strcmp(str, "hell") == 0); + memset(str, 0, sizeof(str)); + + do_print((strn, "h\0ello ", 4)); + test_assert(memcmp(str, "h\0\0", 3) == 0); + memset(str, 0, sizeof(str)); + + do_print((mem, "hello ", 4)); + test_assert(strcmp(str, "hell") == 0); + memset(str, 0, sizeof(str)); + + do_print((mem, "hello\0world", strlen("hello world")+1)); + test_assert(memcmp(str, "hello\0world", strlen("hello world")+1) == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "hello\0world", strlen("hello world")+1)); + test_assert(strcmp(str, "\"hello\\0world\\0\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstr, "hello\0world")); + test_assert(strcmp(str, "\"hello\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstrn, "hello\0world", strlen("hello world")+1)); + test_assert(strcmp(str, "\"hello\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstrn, "hello\0world", 4)); + test_assert(strcmp(str, "\"hell\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((byte, 'h'), (byte, 'w')); + test_assert(strcmp(str, "hw") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, 'h'), (qbyte, 'w')); + test_assert(strcmp(str, "'h''w'") == 0); + memset(str, 0, sizeof(str)); + + do_print("zero ", 0); + test_assert(strcmp(str, "zero 0") == 0); + memset(str, 0, sizeof(str)); + + const char *const_str = "hello"; + do_print(const_str); + test_assert(strcmp(str, "hello") == 0); + memset(str, 0, sizeof(str)); + + bool t = true; + do_print(t); + test_assert(strcmp(str, "true") == 0); + memset(str, 0, sizeof(str)); + + bool f = false; + do_print(f); + test_assert(strcmp(str, "false") == 0); + memset(str, 0, sizeof(str)); + + /* Check that it accepts all primitive types of integer, not + * just all sizes of integer (e.g., on x86-64, + * sizeof(long)==sizeof(int), but they're different primitive + * types). */ + { + signed char x = 42; + do_print("schar ", x); + test_assert(strcmp(str, "schar 42") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned char x = 43; + do_print("uchar ", x); + test_assert(strcmp(str, "uchar 43") == 0); + memset(str, 0, sizeof(str)); + } + + { + short x = 44; + do_print("short ", x); + test_assert(strcmp(str, "short 44") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned short x = 45; + do_print("ushort ", x); + test_assert(strcmp(str, "ushort 45") == 0); + memset(str, 0, sizeof(str)); + } + + { + int x = 46; + do_print("int ", x); + test_assert(strcmp(str, "int 46") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned int x = 47; + do_print("uint ", x); + test_assert(strcmp(str, "uint 47") == 0); + memset(str, 0, sizeof(str)); + } + + { + long x = 48; + do_print("long ", x); + test_assert(strcmp(str, "long 48") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned long x = 49; + do_print("ulong ", x); + test_assert(strcmp(str, "ulong 49") == 0); + memset(str, 0, sizeof(str)); + } + + { + long long x = 50; + do_print("long long ", x); + test_assert(strcmp(str, "long long 50") == 0); + memset(str, 0, sizeof(str)); + } + { + unsigned long long x = 51; + do_print("ulong long ", x); + test_assert(strcmp(str, "ulong long 51") == 0); + memset(str, 0, sizeof(str)); + } + + do_print((ljust, 10, ' ', (base10, 1), "x")); + test_assert(strcmp(str, "1x ") == 0); + memset(str, 0, sizeof(str)); + + do_print((rjust, 10, ' ', (base10, 1), "x")); + test_assert(strcmp(str, " 1x") == 0); + memset(str, 0, sizeof(str)); + + return 0; +} |