/format

Lightweight text formatting in the style of vprintf, for embedded systems.

Primary LanguageCThe UnlicenseUnlicense

Lightweight text formatting in the style of vprintf. Does not require heap or
large memory buffers. It is intended for use with gcc in embedded systems.

The function prototype is:

    int format(int(*outchar)(int), const char *fmt, va_list ap);

Characters are sent, one-by-one, to the designated outchar() function. If the
outchar() function return value is less than 0 then format() returns
immediately with -1. Otherwise it returns 0 after all characters have been
processed.

The following format sequences are supported:

     %[flags][width]c
        -- argument is char (promoted to int)

     %[flags][width][.precision]s
        -- argument is pointer to char (NULL will be treated as "(null)")

     %[flags][width][.precision][size]{i|d}
        -- argument is signed int, printed as decimal with a sign

     %[flags][width][.precision][size]{u|b|o|x|X}
        -- argument is unsigned int, printed as decimal, binary, octal or hex

Note %f and other floating point formats are not supported.

Flags are zero or more of:

     "-" : output is left-justified within specified width instead of
           right-justified

     "0" : right-justified numbers with less digits than precision have leading
           "0"s instead of spaces

     "+" : positive signed integers have a leading "+"

     " " : positive signed integers have a leading space

     "#" : ignored

Width is minimum total number of characters to be output, padded with space or
"0" as required. Specify "*" to use the value of the next int argument.

Precision is maximum number of string characters to output (including 0), or
mininum number of numeric digits to output (possibly with leading "0"s).
Specify ".*" to use the the value of the next int argument as the precision.

Size is one of:
     none : argument is an int
     "l"  : argument is a long int
     "ll" : argument is a long long int
All other values must be cast to one of these.

Examples:

    // Write formatted text via putchar()..
    int example_printf(const char *fmt, ...)
    {
        va_list ap;
        va_start(ap, fmt);
        int res = format(putchar, fmt, ap);
        va_end(ap);
        return res;
    }

    // Write formatted text via the (hypothetical) send_via_uart() function,
    // expanding '\n' to '\r\n'. Fail if if DTR is not asserted.
    int example_uart_printf(const char *fmt, ...)
    {
        int txchar(int c)
        {
            if (c == '\n') send_via_uart('\r');
            int dtr = send_via_uart(c); // returns 0 if no DTR
            return dtr ? 0 : -1;
        }

        va_list ap;
        va_start(ap, fmt);
        int res = format(txchar, fmt, ap); // res == -1 if no DTR
        va_end(ap);
        return res;
    }

    // Write formatted text to *str, but not more than size bytes including the
    // zero-termination.  Return the total number of characters that would have
    // been written, even if str was too small to contain it.
    int example_snprintf(char *str, int size, const char *fmt, ...)
    {
        int total = 0;

        int addchar(int c)
        {
            if (total++ < size-1)   // add character only if it fits
                *str++ = c;
            return 0;               // never fails
        }

        va_list ap;
        va_start(ap, fmt);
        format(addchar, fmt, ap);   // write via addchar()
        va_end(ap);

        *str = 0;                   // ensure zero termination
        return total;               // return total formatted chars
    }

Note GCC allows the prototypes for these functions to include:

    __attribute__((format (printf, x, y)))

Where x and y are the argument offsets of *fmt and ... respectively. This
enables parameter sanity checking at compile time.

The program test.c simply outputs a large number of formatted strings, it can
be built to use format() or printf(). The Makefile builds both flavors and
verifies they have the same output.

This software is released as-is into the public domain, as described at
https://unlicense.org. Do whatever you like with it.