From 8add17e91c55e3fd41ffae127c91709acc78f8bc Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Tue, 29 Nov 2022 21:06:01 +0400 Subject: [PATCH] Change printf format API (#122) --- ChangeLog | 1 + README.md | 2 +- bindings/mruby/src/printf.c | 4 +- bindings/ruby/ext/default/printf.c | 4 +- examples/printf_fmt.c | 41 +++--- include/kernaux/printf_fmt.h | 33 +++-- src/printf.c | 4 +- src/printf_fmt.c | 192 ++++++++++++++++++++--------- tests/printf_fmt_gen.jinja | 9 +- 9 files changed, 188 insertions(+), 102 deletions(-) diff --git a/ChangeLog b/ChangeLog index d6a0adea..8ea852b4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ "KERNAUX_PACKED" * include/kernaux/macro.h: Add macros "KERNAUX_UNUSED", "KERNAUX_NORETURN", "KERNAUX_RETURNS_TWICE", "KERNAUX_ASM" + * include/kernaux/printf_fmt.h: Make stable 2022-11-26 Alex Kotov diff --git a/README.md b/README.md index 91a18d6f..c0be2cff 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ zero). Work-in-progress APIs can change at any time. * [Example: To human](/examples/units_human.c) * [Memory map](/include/kernaux/memmap.h) (*non-breaking since* **0.4.0**) * [Example](/examples/memmap.c) - * [printf format parser](/include/kernaux/printf_fmt.h) (*work in progress*) + * [printf format parser](/include/kernaux/printf_fmt.h) (*non-breaking since* **?.?.?**) * [Example](/examples/printf_fmt.c) * Usual functions * [itoa/ftoa replacement](/include/kernaux/ntoa.h) (*non-breaking since* **0.4.0**) diff --git a/bindings/mruby/src/printf.c b/bindings/mruby/src/printf.c index 27aa7a9b..85fdbd0e 100644 --- a/bindings/mruby/src/printf.c +++ b/bindings/mruby/src/printf.c @@ -56,9 +56,7 @@ mrb_value rb_KernAux_snprintf1(mrb_state *const mrb, mrb_value self) while (*fmt && *fmt != '%') ++fmt; if (*(fmt++) != '%') mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format"); - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - fmt = KernAux_PrintfFmt_Spec_parse(&spec, fmt); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out(&fmt); while (*fmt) { if (*(fmt++) == '%') mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format"); diff --git a/bindings/ruby/ext/default/printf.c b/bindings/ruby/ext/default/printf.c index 873b6b06..f638188f 100644 --- a/bindings/ruby/ext/default/printf.c +++ b/bindings/ruby/ext/default/printf.c @@ -85,9 +85,7 @@ VALUE rb_KernAux_snprintf1( while (*fmt && *fmt != '%') ++fmt; if (*(fmt++) != '%') rb_raise(rb_eArgError, "invalid format"); - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - fmt = KernAux_PrintfFmt_Spec_parse(&spec, fmt); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out(&fmt); while (*fmt) { if (*(fmt++) == '%') rb_raise(rb_eArgError, "invalid format"); diff --git a/examples/printf_fmt.c b/examples/printf_fmt.c index 07cfee4e..6de4dc46 100644 --- a/examples/printf_fmt.c +++ b/examples/printf_fmt.c @@ -1,15 +1,14 @@ #include #include +#include void example_main() { { - const char *format = "s"; + const char *const format = "s"; - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - format = KernAux_PrintfFmt_Spec_parse(&spec, format); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(format); if (spec.set_width) { // Actually this line won't be executed. @@ -20,24 +19,23 @@ void example_main() KernAux_PrintfFmt_Spec_set_precision(&spec, 0); } + assert(spec.format_start == format); + assert(spec.format_limit == &format[1]); + assert(spec.flags == 0); assert(spec.width == 0); assert(spec.precision == 0); assert(spec.type == KERNAUX_PRINTF_FMT_TYPE_STR); assert(spec.base == 0); + + assert(!spec.set_width); + assert(!spec.set_precision); } { const char *format = "012.34f"; - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - // Parsing of each part may be done separately. - KernAux_PrintfFmt_Spec_parse_flags(&spec, &format); - KernAux_PrintfFmt_Spec_parse_width(&spec, &format); - KernAux_PrintfFmt_Spec_parse_precision(&spec, &format); - KernAux_PrintfFmt_Spec_parse_length(&spec, &format); - KernAux_PrintfFmt_Spec_parse_type(&spec, &format); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out(&format); if (spec.set_width) { // Actually this line won't be executed. @@ -48,6 +46,9 @@ void example_main() KernAux_PrintfFmt_Spec_set_precision(&spec, 0); } + assert(spec.format_start == &format[-7]); + assert(spec.format_limit == format); + assert( spec.flags == ( @@ -59,15 +60,16 @@ void example_main() assert(spec.precision == 34); assert(spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT); assert(spec.base == 0); + + assert(!spec.set_width); + assert(!spec.set_precision); } { const char *const format = " *.*ld"; + const char *new_format = NULL; - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - // Returning value may be ignored. - KernAux_PrintfFmt_Spec_parse(&spec, format); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out_new(format, &new_format); if (spec.set_width) { KernAux_PrintfFmt_Spec_set_width(&spec, 12); @@ -76,6 +78,10 @@ void example_main() KernAux_PrintfFmt_Spec_set_precision(&spec, 34); } + assert(spec.format_start == format); + assert(spec.format_limit == &format[6]); + assert(new_format == &format[6]); + assert( spec.flags == ( @@ -88,5 +94,8 @@ void example_main() assert(spec.precision == 34); assert(spec.type == KERNAUX_PRINTF_FMT_TYPE_INT); assert(spec.base == 10); + + assert(spec.set_width); + assert(spec.set_precision); } } diff --git a/include/kernaux/printf_fmt.h b/include/kernaux/printf_fmt.h index 2b0b3584..e5669220 100644 --- a/include/kernaux/printf_fmt.h +++ b/include/kernaux/printf_fmt.h @@ -35,6 +35,9 @@ enum KernAux_PrintfFmt_Type { }; struct KernAux_PrintfFmt_Spec { + const char *format_start; + const char *format_limit; + unsigned int flags; unsigned int width; unsigned int precision; @@ -45,19 +48,25 @@ struct KernAux_PrintfFmt_Spec { bool set_precision; }; -struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create(); -void KernAux_PrintfFmt_Spec_init(struct KernAux_PrintfFmt_Spec *spec); +struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create( + const char *format +); +struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create_out( + const char **format +); +struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create_out_new( + const char *format, + const char **new_format +); -const char *KernAux_PrintfFmt_Spec_parse(struct KernAux_PrintfFmt_Spec *spec, const char *format); - -void KernAux_PrintfFmt_Spec_parse_flags(struct KernAux_PrintfFmt_Spec *spec, const char **format); -void KernAux_PrintfFmt_Spec_parse_width(struct KernAux_PrintfFmt_Spec *spec, const char **format); -void KernAux_PrintfFmt_Spec_parse_precision(struct KernAux_PrintfFmt_Spec *spec, const char **format); -void KernAux_PrintfFmt_Spec_parse_length(struct KernAux_PrintfFmt_Spec *spec, const char **format); -void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *spec, const char **format); - -void KernAux_PrintfFmt_Spec_set_width(struct KernAux_PrintfFmt_Spec *spec, int width); -void KernAux_PrintfFmt_Spec_set_precision(struct KernAux_PrintfFmt_Spec *spec, int precision); +void KernAux_PrintfFmt_Spec_set_width( + struct KernAux_PrintfFmt_Spec *spec, + int width +); +void KernAux_PrintfFmt_Spec_set_precision( + struct KernAux_PrintfFmt_Spec *spec, + int precision +); #ifdef __cplusplus } diff --git a/src/printf.c b/src/printf.c index 8ffb6bb1..48255e4a 100644 --- a/src/printf.c +++ b/src/printf.c @@ -151,9 +151,7 @@ int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format++; } - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - format = KernAux_PrintfFmt_Spec_parse(&spec, format); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out(&format); if (spec.set_width) { KernAux_PrintfFmt_Spec_set_width(&spec, va_arg(va, int)); diff --git a/src/printf_fmt.c b/src/printf_fmt.c index 43dbf666..490292dd 100644 --- a/src/printf_fmt.c +++ b/src/printf_fmt.c @@ -18,44 +18,97 @@ #include #include +typedef struct KernAux_PrintfFmt_Spec *Spec; + +static void parse_flags (Spec spec, const char **format); +static void parse_width (Spec spec, const char **format); +static void parse_precision(Spec spec, const char **format); +static void parse_length (Spec spec, const char **format); +static void parse_type (Spec spec, const char **format); + static unsigned int _atoi(const char** str); -struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create() -{ - struct KernAux_PrintfFmt_Spec spec; - KernAux_PrintfFmt_Spec_init(&spec); +/*********************************** + * Public function implementations * + ***********************************/ + +struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create_out( + const char **const format +) { + KERNAUX_ASSERT(format); + + const struct KernAux_PrintfFmt_Spec spec = + KernAux_PrintfFmt_Spec_create(*format); + *format = spec.format_limit; return spec; } -void KernAux_PrintfFmt_Spec_init(struct KernAux_PrintfFmt_Spec *const spec) -{ - KERNAUX_ASSERT(spec); +struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create_out_new( + const char *const format, + const char **const new_format +) { + KERNAUX_ASSERT(format); + KERNAUX_ASSERT(new_format); - spec->flags = 0u; - spec->width = 0u; - spec->precision = 0u; - spec->type = KERNAUX_PRINTF_FMT_TYPE_NONE; - spec->base = 0; - - spec->set_width = false; - spec->set_precision = false; + *new_format = NULL; + const struct KernAux_PrintfFmt_Spec spec = + KernAux_PrintfFmt_Spec_create(format); + *new_format = spec.format_limit; + return spec; } -const char *KernAux_PrintfFmt_Spec_parse(struct KernAux_PrintfFmt_Spec *spec, const char *format) +struct KernAux_PrintfFmt_Spec KernAux_PrintfFmt_Spec_create(const char *format) { - KERNAUX_ASSERT(spec); KERNAUX_ASSERT(format); - KernAux_PrintfFmt_Spec_parse_flags(spec, &format); - KernAux_PrintfFmt_Spec_parse_width(spec, &format); - KernAux_PrintfFmt_Spec_parse_precision(spec, &format); - KernAux_PrintfFmt_Spec_parse_length(spec, &format); - KernAux_PrintfFmt_Spec_parse_type(spec, &format); + struct KernAux_PrintfFmt_Spec spec; - return format; + spec.format_start = format; + + spec.flags = 0u; + spec.width = 0u; + spec.precision = 0u; + spec.type = KERNAUX_PRINTF_FMT_TYPE_NONE; + spec.base = 0; + + spec.set_width = false; + spec.set_precision = false; + + parse_flags(&spec, &format); + parse_width(&spec, &format); + parse_precision(&spec, &format); + parse_length(&spec, &format); + parse_type(&spec, &format); + + spec.format_limit = format; + + return spec; } -void KernAux_PrintfFmt_Spec_parse_flags(struct KernAux_PrintfFmt_Spec *const spec, const char **const format) +void KernAux_PrintfFmt_Spec_set_width(const Spec spec, const int width) +{ + KERNAUX_ASSERT(spec); + + if (width < 0) { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LEFT; // reverse padding + spec->width = (unsigned int)-width; + } else { + spec->width = (unsigned int)width; + } +} + +void KernAux_PrintfFmt_Spec_set_precision(const Spec spec, const int precision) +{ + KERNAUX_ASSERT(spec); + + spec->precision = precision > 0 ? (unsigned int)precision : 0u; +} + +/************************************ + * Private function implementations * + ************************************/ + +void parse_flags(const Spec spec, const char **const format) { KERNAUX_ASSERT(spec); KERNAUX_ASSERT(format); @@ -64,17 +117,32 @@ void KernAux_PrintfFmt_Spec_parse_flags(struct KernAux_PrintfFmt_Spec *const spe bool running = true; do { switch (**format) { - case '0': spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_ZEROPAD; ++(*format); break; - case '-': spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LEFT; ++(*format); break; - case '+': spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_PLUS; ++(*format); break; - case ' ': spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_SPACE; ++(*format); break; - case '#': spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_HASH; ++(*format); break; + case '0': + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_ZEROPAD; + ++(*format); + break; + case '-': + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LEFT; + ++(*format); + break; + case '+': + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_PLUS; + ++(*format); + break; + case ' ': + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_SPACE; + ++(*format); + break; + case '#': + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_HASH; + ++(*format); + break; default: running = false; break; } } while (running); } -void KernAux_PrintfFmt_Spec_parse_width(struct KernAux_PrintfFmt_Spec *const spec, const char **const format) +void parse_width(const Spec spec, const char **const format) { KERNAUX_ASSERT(spec); KERNAUX_ASSERT(format); @@ -91,7 +159,7 @@ void KernAux_PrintfFmt_Spec_parse_width(struct KernAux_PrintfFmt_Spec *const spe } } -void KernAux_PrintfFmt_Spec_parse_precision(struct KernAux_PrintfFmt_Spec *const spec, const char **const format) +void parse_precision(const Spec spec, const char **const format) { KERNAUX_ASSERT(spec); KERNAUX_ASSERT(format); @@ -114,7 +182,7 @@ void KernAux_PrintfFmt_Spec_parse_precision(struct KernAux_PrintfFmt_Spec *const } } -void KernAux_PrintfFmt_Spec_parse_length(struct KernAux_PrintfFmt_Spec *const spec, const char **const format) +void parse_length(const Spec spec, const char **const format) { KERNAUX_ASSERT(spec); KERNAUX_ASSERT(format); @@ -138,15 +206,27 @@ void KernAux_PrintfFmt_Spec_parse_length(struct KernAux_PrintfFmt_Spec *const sp } break; case 't': - spec->flags |= (sizeof(ptrdiff_t) == sizeof(long) ? KERNAUX_PRINTF_FMT_FLAGS_LONG : KERNAUX_PRINTF_FMT_FLAGS_LONG_LONG); + if (sizeof(ptrdiff_t) == sizeof(long)) { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LONG; + } else { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LONG_LONG; + } ++(*format); break; case 'j': - spec->flags |= (sizeof(intmax_t) == sizeof(long) ? KERNAUX_PRINTF_FMT_FLAGS_LONG : KERNAUX_PRINTF_FMT_FLAGS_LONG_LONG); + if (sizeof(ptrdiff_t) == sizeof(long)) { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LONG; + } else { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LONG_LONG; + } ++(*format); break; case 'z': - spec->flags |= (sizeof(size_t) == sizeof(long) ? KERNAUX_PRINTF_FMT_FLAGS_LONG : KERNAUX_PRINTF_FMT_FLAGS_LONG_LONG); + if (sizeof(ptrdiff_t) == sizeof(long)) { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LONG; + } else { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LONG_LONG; + } ++(*format); break; default: @@ -154,7 +234,7 @@ void KernAux_PrintfFmt_Spec_parse_length(struct KernAux_PrintfFmt_Spec *const sp } } -void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec, const char **const format) +void parse_type(const Spec spec, const char **const format) { KERNAUX_ASSERT(spec); KERNAUX_ASSERT(format); @@ -177,7 +257,8 @@ void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec spec->base = 2u; } else { spec->base = 10u; - spec->flags &= ~KERNAUX_PRINTF_FMT_FLAGS_HASH; // no hash for dec format + // no hash for dec format + spec->flags &= ~KERNAUX_PRINTF_FMT_FLAGS_HASH; } // uppercase if (**format == 'X') { @@ -186,7 +267,8 @@ void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec // no plus or space flag for u, x, X, o, b if ((**format != 'i') && (**format != 'd')) { - spec->flags &= ~(KERNAUX_PRINTF_FMT_FLAGS_PLUS | KERNAUX_PRINTF_FMT_FLAGS_SPACE); + spec->flags &= ~(KERNAUX_PRINTF_FMT_FLAGS_PLUS | + KERNAUX_PRINTF_FMT_FLAGS_SPACE); } // ignore '0' flag when precision is given @@ -206,7 +288,9 @@ void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec #ifdef ENABLE_FLOAT case 'f': case 'F': - if (**format == 'F') spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_UPPERCASE; + if (**format == 'F') { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_UPPERCASE; + } spec->type = KERNAUX_PRINTF_FMT_TYPE_FLOAT; ++(*format); break; @@ -215,8 +299,12 @@ void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec case 'E': case 'g': case 'G': - if ((**format == 'g')||(**format == 'G')) spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_ADAPT_EXP; - if ((**format == 'E')||(**format == 'G')) spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_UPPERCASE; + if ((**format == 'g') || (**format == 'G')) { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_ADAPT_EXP; + } + if ((**format == 'E') || (**format == 'G')) { + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_UPPERCASE; + } spec->type = KERNAUX_PRINTF_FMT_TYPE_EXP; ++(*format); break; @@ -234,7 +322,8 @@ void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec case 'p': spec->width = sizeof(void*) * 2u; - spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_ZEROPAD | KERNAUX_PRINTF_FMT_FLAGS_UPPERCASE; + spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_ZEROPAD | + KERNAUX_PRINTF_FMT_FLAGS_UPPERCASE; spec->type = KERNAUX_PRINTF_FMT_TYPE_PTR; ++(*format); break; @@ -250,25 +339,6 @@ void KernAux_PrintfFmt_Spec_parse_type(struct KernAux_PrintfFmt_Spec *const spec } } -void KernAux_PrintfFmt_Spec_set_width(struct KernAux_PrintfFmt_Spec *const spec, const int width) -{ - KERNAUX_ASSERT(spec); - - if (width < 0) { - spec->flags |= KERNAUX_PRINTF_FMT_FLAGS_LEFT; // reverse padding - spec->width = (unsigned int)-width; - } else { - spec->width = (unsigned int)width; - } -} - -void KernAux_PrintfFmt_Spec_set_precision(struct KernAux_PrintfFmt_Spec *const spec, const int precision) -{ - KERNAUX_ASSERT(spec); - - spec->precision = precision > 0 ? (unsigned int)precision : 0u; -} - // internal ASCII string to unsigned int conversion unsigned int _atoi(const char** str) { diff --git a/tests/printf_fmt_gen.jinja b/tests/printf_fmt_gen.jinja index ecda4a07..adc774a0 100644 --- a/tests/printf_fmt_gen.jinja +++ b/tests/printf_fmt_gen.jinja @@ -5,6 +5,9 @@ #include #include +#include + +#include void test_main() { @@ -12,9 +15,7 @@ void test_main() { const char *const format = {{ escape_str(case.in.format) }}; - struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(); - - KernAux_PrintfFmt_Spec_parse(&spec, format); + struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create(format); if (spec.set_width) { KernAux_PrintfFmt_Spec_set_width(&spec, {{ none_to_zero(case.in.width) }}); @@ -23,6 +24,8 @@ void test_main() KernAux_PrintfFmt_Spec_set_precision(&spec, {{ none_to_zero(case.in.precision) }}); } + assert(spec.format_start == format); + assert(spec.flags == {{ escape_flags(case.out.flags) }}); assert(spec.width == {{ case.out.width }}); assert(spec.precision == {{ case.out.precision }});