# EASprintf

## Introduction

EAStdC provides a portable version of the C printf family of functions which improves on the C family in the following ways:

- Behaves identically on all platforms
- Provides the full gamut of printf functions, including vsnprintf
- Is more efficient; executes 20-80% faster.
- Never allocates memory
- Provides both 8 and 16 bit versions for all platforms
- Provides useful extended functionality
- Complies with the C99 standard (except for the %a format, as of this writing)
- Is readable enough to be traced/debugged by non-experts
- Is savvy to UTF8 Unicode

The primary disadvantages of EAStdC's printf are:

- It doesn't use locale-specific formatting
- It isn't part of the C/C++ standard library

EAStdC doesn't attempt to solve the locale formatting problem because users really are better off using serious locale libraries for such things rather than using the meager support provided by the C standard library. The EALocale library attempts to provide such functionality.

EAStdC provides the following functions in 8, 16, and 32 bit versions:

    int Cprintf(WriteFunction8 pWriteFunction, void* pContext, const char8_t* pFormat, ...);
    int Fprintf(FILE* pFile, const char8_t* pFormat, ...);
    int Printf(const char8_t* pFormat, ...);
    int Sprintf(char8_t* pDestination, const char8_t* pFormat, ...);
    int Snprintf(char8_t* pDestination, size_t n, const char8_t* pFormat, ...);

    int Vcprintf(WriteFunction8 pWriteFunction8, void* pContext, const char8_t* pFormat, va_list arguments);
    int Vfprintf(FILE* pFile, const char8_t* pFormat, va_list arguments);
    int Vprintf(const char8_t* pFormat, va_list arguments);
    int Vsprintf(char8_t* pDestination, const char8_t* pFormat, va_list arguments);
    int Vsnprintf(char8_t* pDestination, size_t n, const char8_t* pFormat, va_list arguments);
    int Vscprintf(const char8_t* EA_RESTRICT pFormat, va_list arguments);

Also there are:

    template <typename String>
    int StringPrintf(String& s, const typename String::value_type* EA_RESTRICT pFormat, ...);

    template <typename String>
    int StringVcprintf(String& s, const typename String::value_type* EA_RESTRICT pFormat, va_list arguments);

## Snprintf uses C99 behavior

The EASprintf 'n' functions (Snprintf and Vsnprintf) follow the C99 standard for return value, which is different from some C standard library implementations such as VC++. These functions return the number of characters that would have been written had n been sufficiently large, not counting the terminating null character, or a negative value if an encoding error occurred. Thus, the null-terminated output has been completely written if and only if the returned value is nonnegative and less than n. Another way of saying it is that the return value equal to the strlen of the intended output. See the examples below.

## Watch out for common mistakes and security problems

The sprintf family of functions are very convenient but offer numerous opportunities for incorrect usage and security problems. Here is a list of some of the most common issues.

- Don't use sprintf unless you are absolutely certain the result will fit into the output buffer. Instead use snprintf.
- When printing a string, always use printf("%s", pStr) and never printf(pStr), as the latter may have formatting characters in it.
- Watch out for platform differences between format types. For example, %u means unsigned int, and you can't use a size_t argument with it on 64 bit platforms. Instead, explicitly cast your arguments to the expected type or use the EASprintf extended format types (see below).
- Similarly, watch out when passing int8_t and int16_t arguments to EAPrintf (and regular printf), as the compiler will promote them to int and they might not print as you expected, depending on your output format.
- Watch out for EASprintf's C99 behaviour. C99 behaviour is standard C and is superior to previous C library behaviour, but the previous behaviour exists in the C library today with compilers such as VC++. This behaviour affects the return value of Snprintf/Vsnprintf, and affects the way some format specifiers are rendered.

## Extended Functionality

Printf provides extended format functionality not found in the C99 standard but which is useful nevertheless:

Format/modifier | Description | Example | Example output
b | Binary output field type (joins d, i, x, o, etc.). | printf("0b%b", 255); printf("%#b", 255); printf("%#B", 255); | 0b11111111 0b11111111 0B11111111
I8 | 8 bit integer field modifier. | printf("%I8d", 0xff); | -1
I16 | 16 bit integer field modifier. | printf("%I16u", 0xffff); | 65535
I32 | 32 bit integer field modifier. | printf("%I32d", 0xffffffff); | -1
I64 | 64 bit integer field modifier. | printf("%I64u", 0xffffffffffffffff); | 18446744073709551615
I128 | 128 bit integer field modifier. | printf("%I128d", 0xffffffffffffffffffffffffffffffff); | -1
' | Display a thousands separator. | printf("%'I16u", 0xffff); | 65,535

## SprintfOrdered

EAStdC also provides an ordered sprintf

Ordered printf is like printf except it works on "ordered" printf specifiers. This means that instead of using "%4d %f" we give an order to the arguments via an index and colon, as with "%1:4d %2:f". The point, however, is that you can reorder the indexes, as with "%2:f %1:4d". This is particularly useful for formatted string localization, as different locales use subjects and verbs in different orders.
User indexes can start at either 0 or 1. Oprintf detects which is in use as it goes. So the following have identical effect:

    OPrintf("%1:s" %0:f", 3.0, "hello");
    OPrintf("%2:s" %1:f", 3.0, "hello");

// User indexes must be contiguous and the behaviour of Oprintf is undefined
// if the ordering is non-contiguous. There are debug checks to validate
// contiguity, so debug tests should catch mistakes. The following is an
// example of non-contiguous ordering, as it is missing a "3:" format:
// OPrintf("%1:d" %0:d %3:d", 17, 18, 19, 20);

Example usage:

    char16_t buffer[80];

// The module provides ordered versions of the printf family of functions:
// int OCprintf(WriteFunction pFunction, void* pContext, const char_t* pFormat, ...);
// int OFprintf(FILE* pFile, const char_t* pFormat, ...);
// int OPrintf(cconst char_t* pFormat, ...);
// int OSprintf(char_t* pDestination, const char_t* pFormat, ...);
// int OSnprintf(char_t* pDestination, size_t n, const char_t* pFormat, ...);
//
// int OVcprintf(WriteFunction pFunction, void* pContext, const char_t* pFormat, va_list arguments);
// int OVfprintf(FILE* pFile, const char_t* pFormat, va_list arguments);
// int OVprintf(const char_t* pFormat, va_list arguments);
// int OVsprintf(char_t* pDestination, const char_t* pFormat, va_list arguments);
// int OVsnprintf(char_t* pDestination, size_t n, const char_t* pFormat, va_list arguments);
// int OVscprintf(const char* pFormat, va_list arguments);
//
// Ordered printf is like printf except it works on "ordered" printf specifiers.
// This means that instead of using "%4d %f" we give an order to the arguments via
// an index and colon, as with "%1:4d %2:f". The point, however, is that you can
// reorder the indexes, as with "%2:f %1:4d". This is particularly useful for
// formatted string localization, as different locales use subjects and verbs
// in different orders.
//
// User indexes can start at either 0 or 1. Oprintf detects which is in use as
// it goes. So the following have identical effect:
// OPrintf("%1:s" %0:f", 3.0, "hello");
// OPrintf("%2:s" %1:f", 3.0, "hello");
//
// User indexes must be contiguous and the behaviour of Oprintf is undefined
// if the ordering is non-contiguous. There are debug checks to validate
// contiguity, so debug tests should catch mistakes. The following is an
// example of non-contiguous ordering, as it is missing a "3:" format:
// OPrintf("%1:d" %0:d %3:d", 17, 18, 19, 20);
/////////////////////////////////////////////////////////////////////////////

## Example usage

All examples presume the #include of EASprintf.h.

Snprintf usage:

    char16_t buffer[80];
    int nStrlen = Snprintf16(buffer, 80, "Columbus arrived in %d.", 1492);

    if((unsigned)nStrlen < 80) // Cast to unsigned in order to make any negative values be positive and > 80.
        puts("success");

How to write a function that takes variable arguments (e.g. ...) and passes them to EASprintf:

    #include <stdarg.h>

    void CustomFormattedOutput(const char* pFormat, ...)
    {
        va_list arguments;
        va_start(arguments, pFormat);
        Vprintf8(pFormat, arguments);
        va_end(arguments);
    }

How to write a function that does custom formatted output to a buffer that is resized as needed for it. This is useful for the implementation of fail-safe utility functions and for debug tracing systems, among other things.

    #include <stdarg.h>
    #include <string>

    // This is a generic function for doing a sprintf into a C++ std::string object.
    // If the string object lacks enough space for the output, then the string will
    // be resized to exactly fit the output.
    size_t StringSprintf(std::string& s, const char* pFormat, ...)
    {
        va_list arguments;
        va_start(arguments, pFormat);

        if(s.size() < s.capacity())
            s.resize(s.capacity());

        const int nStrlen = Vsnprintf8(const_cast<char*>(s.data()), s.capacity() + 1, pFormat, arguments);

        va_end(arguments);

        if(nStrlen > (int)s.size())
        {
            s.resize(nStrlen);
            Vsnprintf8(const_cast<char*>(s.data()), s.capacity() + 1, pFormat, arguments);

        return (size_t)nStrlen;
    }

Write to the C stderr stream:

    #include <stdio.h>

    Fprintf8(stderr, "Columbus arrived in %d.", 1492);

Vsscanf usage. A complete discussion of the ins and outs of vsscanf are currently beyond the scope of this document. The scanf found in EAPrintf acts the same as with the C++ standard, so you can read about that elsewhere for the time being.

    char16_t buffer[64] = EAText16("Columbus arrived in 1492");
    int      year;

    Vsscanf16(buffer, "Columbus arrived in %d.", &year);

## Interface

Printf family

    int     Fprintf8(FILE* pFile, const char8_t* pFormat, ...);
    int     Printf8(const char8_t* pFormat, ...);
    int     Sprintf8(char8_t* pDestination, const char8_t* pFormat, ...);
    int     Snprintf8(char8_t* pDestination, size_t n, const char8_t* pFormat, ...);

    int     Fprintf16(FILE* pFile, const char16_t* pFormat, ...);
    int     Printf16(const char16_t* pFormat, ...);
    int     Sprintf16(char16_t* pDestination, const char16_t* pFormat, ...);
    int     Snprintf16(char16_t* pDestination, size_t n, const char16_t* pFormat, ...);

Vprintf family

    /// Note that vsnprintf was not added to the C standard until C99.
    /// Here we follow the C99 standard and have the return value of vsnprintf
    /// return the number of required characters to complete the formatted string.
    /// This is more useful than the way some libraries implement vsnprintf by
    /// returning -1 if there isn't enough space. The return value is -1 if there
    /// was an "encoding error" or the implementation was unable to return a
    /// value > n upon lack of sufficient space as per the C99 standard.
    ///
    /// Specification:
    /// The vsnprintf function is equivalent to snprintf, with the variable
    /// argument list replaced by arguments, which shall have been initialized
    /// by the va_start macro (and possibly subsequent va_arg calls).
    /// The vsnprintf function does not invoke the va_end macro. If copying
    /// takes place between objects that overlap, the behavior is undefined.
    ///
    /// The vsnprintf function returns the number of characters that would
    /// have been written had n been sufficiently large, not counting the
    /// terminating null character, or a neg ative value if an encoding error
    /// occurred. Thus, the null-terminated output has been completely written
    /// if and only if the returned value is nonnegative and less than n.
    ///
    int Vfprintf8(FILE* pFile, const char8_t* pFormat, va_list arguments);
    int Vprintf8(const char8_t* pFormat, va_list arguments);
    int Vsprintf8(char8_t* pDestination, const char8_t* pFormat, va_list arguments);
    int Vsnprintf8(char8_t* pDestination, size_t n, const char8_t* pFormat, va_list arguments);

    int Vfprintf16(FILE* pFile, const char16_t* pFormat, va_list arguments);
    int Vprintf16(const char16_t* pFormat, va_list arguments);
    int Vsprintf16(char16_t* pDestination, const char16_t* pFormat, va_list arguments);
    int Vsnprintf16(char16_t* pDestination, size_t n, const char16_t* pFormat, va_list arguments);