/* ----> DO NOT REMOVE THE FOLLOWING NOTICE <---- * * Copyright (c) 2014-2015 Datalight, Inc. * All Rights Reserved Worldwide. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; use version 2 of the License. * * This program is distributed in the hope that it will be useful, * but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Businesses and individuals that for commercial or other reasons cannot * comply with the terms of the GPLv2 license may obtain a commercial license * before incorporating Reliance Edge into proprietary software for * distribution in any form. Visit http://www.datalight.com/reliance-edge for * more information. */ /** @file * @brief Implements functions for printing. * * These functions are intended to be used in portable test code, which cannot * assume the standard I/O functions will be available. Similar to their ANSI * C counterparts, these functions allow formatting text strings and (if the * configuration allows it) outputing formatted text. The latter ability * relies on the RedOsOutputString() OS service function. * * Do *not* use these functions in code which can safely assume the standard * I/O functions are available (e.g., in host tools code). * * Do *not* use these functions from within the file system driver. These * functions use variable arguments and thus are not MISRA-C:2012 compliant. */ #include #include #include #include /** @brief Maximum number of bytes of output supported by RedPrintf(). * * Typically only Datalight code uses these functions, and that could should be * written to respect this limit, so it should not normally be necessary to * adjust this value. */ #define OUTPUT_BUFFER_SIZE 256U typedef enum { PRFMT_UNKNOWN = 0, PRFMT_CHAR, PRFMT_ANSISTRING, PRFMT_SIGNED8BIT, PRFMT_UNSIGNED8BIT, PRFMT_SIGNED16BIT, PRFMT_UNSIGNED16BIT, PRFMT_SIGNED32BIT, PRFMT_UNSIGNED32BIT, PRFMT_SIGNED64BIT, PRFMT_UNSIGNED64BIT, PRFMT_HEX8BIT, PRFMT_HEX16BIT, PRFMT_HEX32BIT, PRFMT_HEX64BIT, PRFMT_POINTER, PRFMT_DOUBLEPERCENT } PRINTTYPE; typedef struct { PRINTTYPE type; /* The PRFMT_* type found */ uint32_t ulSpecifierIdx; /* Returns a pointer to the % sign */ uint32_t ulFillLen; char cFillChar; bool fLeftJustified; bool fHasIllegalType; /* TRUE if an illegal sequence was skipped over */ bool fHasVarWidth; } PRINTFORMAT; /* Our output handlers are written for standard fixed width data types. Map * the standard ANSI C data types onto our handlers. Currently this code has * the following requirements: * * 1) shorts must be either 16 or 32 bits * 2) ints must be either 16 or 32 bits * 3) longs must be between 32 or 64 bits * 4) long longs must be 64 bits */ #if ( USHRT_MAX == 0xFFFFU ) #define MAPSHORT PRFMT_SIGNED16BIT #define MAPUSHORT PRFMT_UNSIGNED16BIT #define MAPHEXUSHORT PRFMT_HEX16BIT #elif ( USHRT_MAX == 0xFFFFFFFFU ) #define MAPSHORT PRFMT_SIGNED32BIT #define MAPUSHORT PRFMT_UNSIGNED32BIT #define MAPHEXUSHORT PRFMT_HEX32BIT #else #error "The 'short' data type does not have a 16 or 32-bit width" #endif #if ( UINT_MAX == 0xFFFFU ) #define MAPINT PRFMT_SIGNED16BIT #define MAPUINT PRFMT_UNSIGNED16BIT #define MAPHEXUINT PRFMT_HEX16BIT #elif ( UINT_MAX == 0xFFFFFFFFU ) #define MAPINT PRFMT_SIGNED32BIT #define MAPUINT PRFMT_UNSIGNED32BIT #define MAPHEXUINT PRFMT_HEX32BIT #else #error "The 'int' data type does not have a 16 or 32-bit width" #endif #if ( ULONG_MAX == 0xFFFFFFFFU ) #define MAPLONG PRFMT_SIGNED32BIT #define MAPULONG PRFMT_UNSIGNED32BIT #define MAPHEXULONG PRFMT_HEX32BIT #elif ( ULONG_MAX <= UINT64_SUFFIX( 0xFFFFFFFFFFFFFFFF ) ) /* We've run into unusual environments where "longs" are 40-bits wide. * In this event, map them to 64-bit types so no data is lost. */ #define MAPLONG PRFMT_SIGNED64BIT #define MAPULONG PRFMT_UNSIGNED64BIT #define MAPHEXULONG PRFMT_HEX64BIT #else #error "The 'long' data type is not between 32 and 64 bits wide" #endif /* if ( ULONG_MAX == 0xFFFFFFFFU ) */ #if defined( ULLONG_MAX ) && ( ULLONG_MAX != UINT64_SUFFIX( 0xFFFFFFFFFFFFFFFF ) ) #error "The 'long long' data type is not 64 bits wide" #else #define MAPLONGLONG PRFMT_SIGNED64BIT #define MAPULONGLONG PRFMT_UNSIGNED64BIT #define MAPHEXULONGLONG PRFMT_HEX64BIT #endif static uint32_t ProcessFormatSegment( char * pcBuffer, uint32_t ulBufferLen, const char * pszFormat, PRINTFORMAT * pFormat, uint32_t * pulSpecifierLen ); static uint32_t ParseFormatSpecifier( char const * pszFormat, PRINTFORMAT * pFormatType ); static PRINTTYPE ParseFormatType( const char * pszFormat, uint32_t * pulTypeLen ); static uint32_t LtoA( char * pcBuffer, uint32_t ulBufferLen, int32_t lNum, uint32_t ulFillLen, char cFill ); static uint32_t LLtoA( char * pcBuffer, uint32_t ulBufferLen, int64_t llNum, uint32_t ulFillLen, char cFill ); static uint32_t ULtoA( char * pcBuffer, uint32_t ulBufferLen, uint32_t ulNum, bool fHex, uint32_t ulFillLen, char cFill ); static uint32_t ULLtoA( char * pcBuffer, uint32_t ulBufferLen, uint64_t ullNum, bool fHex, uint32_t ulFillLen, char cFill ); static uint32_t FinishToA( const char * pcDigits, uint32_t ulDigits, char * pcOutBuffer, uint32_t ulBufferLen, uint32_t ulFillLen, char cFill ); /* Digits for the *LtoA() routines. */ static const char gacDigits[] = "0123456789ABCDEF"; #if REDCONF_OUTPUT == 1 /** @brief Print formatted data with a variable length argument list. * * This function provides a subset of the ANSI C printf() functionality with * several extensions to support fixed size data types. * * See RedVSNPrintf() for the list of supported types. * * @param pszFormat A pointer to the null-terminated format string. * @param ... The variable length argument list. */ void RedPrintf( const char * pszFormat, ... ) { va_list arglist; va_start( arglist, pszFormat ); RedVPrintf( pszFormat, arglist ); va_end( arglist ); } /** @brief Print formatted data using a pointer to a variable length argument * list. * * This function provides a subset of the ANSI C vprintf() functionality. * * See RedVSNPrintf() for the list of supported types. * * This function accommodates a maximum output length of #OUTPUT_BUFFER_SIZE. * If this function must truncate the output, and the original string was * \n terminated, the truncated output will be \n terminated as well. * * @param pszFormat A pointer to the null-terminated format string. * @param arglist The variable length argument list. */ void RedVPrintf( const char * pszFormat, va_list arglist ) { char achBuffer[ OUTPUT_BUFFER_SIZE ]; if( RedVSNPrintf( achBuffer, sizeof( achBuffer ), pszFormat, arglist ) == -1 ) { /* Ensure the buffer is null terminated. */ achBuffer[ sizeof( achBuffer ) - 1U ] = '\0'; /* If the original string was \n terminated and the new one is not, due to * truncation, stuff a \n into the new one. */ if( pszFormat[ RedStrLen( pszFormat ) - 1U ] == '\n' ) { achBuffer[ sizeof( achBuffer ) - 2U ] = '\n'; } } RedOsOutputString( achBuffer ); } #endif /* #if REDCONF_OUTPUT == 1 */ /** @brief Format arguments into a string using a subset of the ANSI C * vsprintf() functionality. * * This function is modeled after the Microsoft _snprint() extension to the * ANSI C sprintf() function, and allows a buffer length to be specified so * that overflow is avoided. * * See RedVSNPrintf() for the list of supported types. * * @param pcBuffer A pointer to the output buffer * @param ulBufferLen The output buffer length * @param pszFormat A pointer to the null terminated format string * @param ... Variable argument list * * @return The length output, or -1 if the buffer filled up. If -1 is * returned, the output buffer may not be null-terminated. */ int32_t RedSNPrintf( char * pcBuffer, uint32_t ulBufferLen, const char * pszFormat, ... ) { int32_t iLen; va_list arglist; va_start( arglist, pszFormat ); iLen = RedVSNPrintf( pcBuffer, ulBufferLen, pszFormat, arglist ); va_end( arglist ); return iLen; } /** @brief Format arguments into a string using a subset of the ANSI C * vsprintf() functionality. * * This function is modeled after the Microsoft _vsnprint() extension to the * ANSI C vsprintf() function, and requires a buffer length to be specified so * that overflow is avoided. * * The following ANSI C standard formatting codes are supported: * | Code | Meaning | | ---- | ---------------------------------- | | %c | Format a character | | %s | Format a null-terminated C string | | %hd | Format a signed short | | %hu | Format an unsigned short | | %d | Format a signed integer | | %u | Format an unsigned integer | | %ld | Format a signed long | | %lu | Format an unsigned long | | %lld | Format a signed long long | | %llu | Format an unsigned long long | | %hx | Format a short in hex | | %x | Format an integer in hex | | %lx | Format a long in hex | | %llx | Format a long long in hex | | %p | Format a pointer (hex value) | | | @note All formatting codes are case-sensitive. | | Fill characters and field widths are supported per the ANSI standard, as is | left justification with the '-' character. | | The only supported fill characters are '0', ' ', and '_'. | | '*' is supported to specify variable length field widths. | | Hexidecimal numbers are always displayed in upper case. Formatting codes | which specifically request upper case (e.g., "%lX") are not supported. | | Unsupported behaviors: | - Precision is not supported. | - Floating point is not supported. | | Errata: | - There is a subtle difference in the return value for this function versus | the Microsoft implementation. In the Microsoft version, if the buffer | exactly fills up, but there is no room for a null-terminator, the return | value will be the length of the buffer. In this code, -1 will be returned | when this happens. | - When using left justified strings, the only supported fill character is a | space, regardless of what may be specified. It is not clear if this is | ANSI standard or just the way the Microsoft function works, but we emulate | the Microsoft behavior. | | @param pcBuffer A pointer to the output buffer. | @param ulBufferLen The output buffer length. | @param pszFormat A pointer to the null terminated ANSI format string. | @param arglist Variable argument list. | | @return The length output, or -1 if the buffer filled up. If -1 is | returned, the output buffer may not be null-terminated. */ int32_t RedVSNPrintf( char * pcBuffer, uint32_t ulBufferLen, const char * pszFormat, va_list arglist ) { uint32_t ulBufIdx = 0U; uint32_t ulFmtIdx = 0U; int32_t iLen; while( ( pszFormat[ ulFmtIdx ] != '\0' ) && ( ulBufIdx < ulBufferLen ) ) { PRINTFORMAT fmt; uint32_t ulSpecifierLen; uint32_t ulWidth; /* Process the next segment of the format string, outputting * any non-format specifiers, as output buffer space allows, * and return information about the next format specifier. */ ulWidth = ProcessFormatSegment( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, &pszFormat[ ulFmtIdx ], &fmt, &ulSpecifierLen ); if( ulWidth ) { REDASSERT( ulWidth <= ( ulBufferLen - ulBufIdx ) ); ulBufIdx += ulWidth; } /* If no specifier was found, or if the output buffer is * full, we're done -- get out. */ if( ( ulSpecifierLen == 0U ) || ( ulBufIdx == ulBufferLen ) ) { break; } /* Otherwise, the math should add up for these things... */ REDASSERT( &pszFormat[ fmt.ulSpecifierIdx ] == &pszFormat[ ulWidth ] ); /* Point past the specifier, to the next piece of the format string. */ ulFmtIdx = ulFmtIdx + fmt.ulSpecifierIdx + ulSpecifierLen; if( fmt.fHasVarWidth ) { int iFillLen = va_arg( arglist, int ); if( iFillLen >= 0 ) { fmt.ulFillLen = ( uint32_t ) iFillLen; } else { /* Bogus fill length. Ignore. */ fmt.ulFillLen = 0U; } } switch( fmt.type ) { case PRFMT_DOUBLEPERCENT: /* Nothing to do. A single percent has already been output, * and we just finished skipping past the second percent. */ break; /*-----------------> Small int handling <------------------ * * Values smaller than "int" will be promoted to "int" by * the compiler, so we must retrieve them using "int" when * calling va_arg(). Once we've done that, we immediately * put the value into the desired data type. *---------------------------------------------------------*/ case PRFMT_CHAR: pcBuffer[ ulBufIdx ] = ( char ) va_arg( arglist, int ); ulBufIdx++; break; case PRFMT_SIGNED8BIT: { int8_t num = ( int8_t ) va_arg( arglist, int ); ulBufIdx += LtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, num, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_UNSIGNED8BIT: { uint8_t bNum = ( uint8_t ) va_arg( arglist, unsigned ); ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, bNum, false, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_HEX8BIT: { uint8_t bNum = ( uint8_t ) va_arg( arglist, unsigned ); ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, bNum, true, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_SIGNED16BIT: { int16_t num = ( int16_t ) va_arg( arglist, int ); ulBufIdx += LtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, num, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_UNSIGNED16BIT: { uint16_t uNum = ( uint16_t ) va_arg( arglist, unsigned ); ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, uNum, false, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_HEX16BIT: { uint16_t uNum = ( uint16_t ) va_arg( arglist, unsigned ); ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, uNum, true, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_SIGNED32BIT: { int32_t lNum = va_arg( arglist, int32_t ); ulBufIdx += LtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, lNum, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_UNSIGNED32BIT: { uint32_t ulNum = va_arg( arglist, uint32_t ); ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, ulNum, false, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_HEX32BIT: { uint32_t ulNum = va_arg( arglist, uint32_t ); ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, ulNum, true, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_SIGNED64BIT: { int64_t llNum = va_arg( arglist, int64_t ); ulBufIdx += LLtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, llNum, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_UNSIGNED64BIT: { uint64_t ullNum = va_arg( arglist, uint64_t ); ulBufIdx += ULLtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, ullNum, false, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_HEX64BIT: { uint64_t ullNum = va_arg( arglist, uint64_t ); ulBufIdx += ULLtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, ullNum, true, fmt.ulFillLen, fmt.cFillChar ); break; } case PRFMT_POINTER: { const void * ptr = va_arg( arglist, const void * ); /* Assert our assumption. */ REDASSERT( sizeof( void * ) <= 8U ); /* Format as either a 64-bit or a 32-bit value. */ if( sizeof( void * ) > 4U ) { /* Attempt to quiet warnings. */ uintptr_t ptrval = ( uintptr_t ) ptr; uint64_t ullPtrVal = ( uint64_t ) ptrval; ulBufIdx += ULLtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, ullPtrVal, true, fmt.ulFillLen, fmt.cFillChar ); } else { /* Attempt to quiet warnings. */ uintptr_t ptrval = ( uintptr_t ) ptr; uint32_t ulPtrVal = ( uint32_t ) ptrval; ulBufIdx += ULtoA( &pcBuffer[ ulBufIdx ], ulBufferLen - ulBufIdx, ulPtrVal, true, fmt.ulFillLen, fmt.cFillChar ); } break; } case PRFMT_ANSISTRING: { const char * pszArg = va_arg( arglist, const char * ); uint32_t ulArgIdx = 0U; if( pszArg == NULL ) { pszArg = "null"; } if( fmt.ulFillLen > 0U ) { if( !fmt.fLeftJustified ) { uint32_t ulLen = RedStrLen( pszArg ); /* So long as we are not left justifying, fill as many * characters as is necessary to make the string right * justified. */ while( ( ( ulBufferLen - ulBufIdx ) > 0U ) && ( fmt.ulFillLen > ulLen ) ) { pcBuffer[ ulBufIdx ] = fmt.cFillChar; ulBufIdx++; fmt.ulFillLen--; } } /* Move as many characters as we have space for into the * output buffer. */ while( ( ( ulBufferLen - ulBufIdx ) > 0U ) && ( pszArg[ ulArgIdx ] != '\0' ) ) { pcBuffer[ ulBufIdx ] = pszArg[ ulArgIdx ]; ulBufIdx++; ulArgIdx++; if( fmt.ulFillLen > 0U ) { fmt.ulFillLen--; } } /* If there is any space left to fill, do it (the string * must have been left justified). */ while( ( ( ulBufferLen - ulBufIdx ) > 0U ) && ( fmt.ulFillLen > 0U ) ) { /* This is NOT a typo -- when using left justified * strings, spaces are the only allowed fill character. * See the errata. */ pcBuffer[ ulBufIdx ] = ' '; ulBufIdx++; fmt.ulFillLen--; } } else { /* No fill characters, just move up to as many * characters as we have space for in the output * buffer. */ while( ( ( ulBufferLen - ulBufIdx ) > 0U ) && ( pszArg[ ulArgIdx ] != '\0' ) ) { pcBuffer[ ulBufIdx ] = pszArg[ ulArgIdx ]; ulBufIdx++; ulArgIdx++; } } break; } default: REDERROR(); break; } } /* If there is space, tack on a null and return the output length * processed, not including the null. */ if( ulBufIdx < ulBufferLen ) { pcBuffer[ ulBufIdx ] = '\0'; iLen = ( int32_t ) ulBufIdx; } else { /* Not enough space, just return -1, with no null termination */ iLen = -1; } return iLen; } /** @brief Process the next segment of the format string, outputting any * non-format specifiers, as output buffer space allows, and return * information about the next format specifier. * * @note If the returned value is the same as the supplied @p ulBufferLen, * the output buffer will not be null-terminated. In all other cases, * the result will be null-terminated. The returned length will never * include the null in the count. * * @param pcBuffer The output buffer. * @param ulBufferLen The output buffer length. * @param pszFormat The format string to process. * @param pFormat The PRINTFORMAT structure to fill. * @param pulSpecifierLen Returns the length of any format specifier string, * or zero if no specifier was found. * * @return The count of characters from pszFormatt which were processed and * copied to pcBuffer. * - If zero is returned and *pulSpecifierLen is non-zero, then * a format specifier string was found at the start of pszFmt. * - If non-zero is returned and *pulSpecifierLen is zero, then * no format specifier string was found, and the entire pszFmt * string was copied to pBuffer (or as much as will fit). */ static uint32_t ProcessFormatSegment( char * pcBuffer, uint32_t ulBufferLen, const char * pszFormat, PRINTFORMAT * pFormat, uint32_t * pulSpecifierLen ) { uint32_t ulWidth = 0U; /* Find the next format specifier string, and information about it. */ *pulSpecifierLen = ParseFormatSpecifier( pszFormat, pFormat ); if( *pulSpecifierLen == 0U ) { /* If no specifier was found at all, then simply output the full length * of the string, or as much as will fit. */ ulWidth = REDMIN( ulBufferLen, RedStrLen( pszFormat ) ); RedMemCpy( pcBuffer, pszFormat, ulWidth ); } else { /* If we encountered a double percent, skip past one of them so it is * copied into the output buffer. */ if( pFormat->type == PRFMT_DOUBLEPERCENT ) { pFormat->ulSpecifierIdx++; /* A double percent specifier always has a length of two. Since * we're processing one of those percent signs, reduce the length * to one. Assert it so. */ REDASSERT( *pulSpecifierLen == 2U ); ( *pulSpecifierLen )--; } /* So long as the specifier is not the very first thing in the format * string... */ if( pFormat->ulSpecifierIdx != 0U ) { /* A specifier was found, but there is other data preceding it. * Copy as much as allowed to the output buffer. */ ulWidth = REDMIN( ulBufferLen, pFormat->ulSpecifierIdx ); RedMemCpy( pcBuffer, pszFormat, ulWidth ); } } /* If there is room in the output buffer, null-terminate whatever is there. * But note that the returned length never includes the null. */ if( ulWidth < ulBufferLen ) { pcBuffer[ ulWidth ] = 0U; } return ulWidth; } /** @brief Parse the specified format string for a valid RedVSNPrintf() format * sequence, and return information about it. * * @param pszFormat The format string to process. * @param pFormatType The PRINTFORMAT structure to fill. The data is only * valid if a non-zero length is returned. * * @return The length of the full format specifier string, starting at * pFormat->ulSpecifierIdx. Returns zero if a valid specifier was * not found. */ static uint32_t ParseFormatSpecifier( char const * pszFormat, PRINTFORMAT * pFormatType ) { bool fContainsIllegalSequence = false; uint32_t ulLen = 0U; uint32_t ulIdx = 0U; while( pszFormat[ ulIdx ] != '\0' ) { uint32_t ulTypeLen; /* general output */ if( pszFormat[ ulIdx ] != '%' ) { ulIdx++; } else { RedMemSet( pFormatType, 0U, sizeof( *pFormatType ) ); /* Record the location of the start of the format sequence */ pFormatType->ulSpecifierIdx = ulIdx; ulIdx++; if( pszFormat[ ulIdx ] == '-' ) { pFormatType->fLeftJustified = true; ulIdx++; } if( ( pszFormat[ ulIdx ] == '0' ) || ( pszFormat[ ulIdx ] == '_' ) ) { pFormatType->cFillChar = pszFormat[ ulIdx ]; ulIdx++; } else { pFormatType->cFillChar = ' '; } if( pszFormat[ ulIdx ] == '*' ) { pFormatType->fHasVarWidth = true; ulIdx++; } else if( ISDIGIT( pszFormat[ ulIdx ] ) ) { pFormatType->ulFillLen = ( uint32_t ) RedAtoI( &pszFormat[ ulIdx ] ); while( ISDIGIT( pszFormat[ ulIdx ] ) ) { ulIdx++; } } else { /* No fill length. */ } pFormatType->type = ParseFormatType( &pszFormat[ ulIdx ], &ulTypeLen ); if( pFormatType->type != PRFMT_UNKNOWN ) { /* Even though we are returning successfully, keep track of * whether an illegal sequence was encountered and skipped. */ pFormatType->fHasIllegalType = fContainsIllegalSequence; ulLen = ( ulIdx - pFormatType->ulSpecifierIdx ) + ulTypeLen; break; } /* In the case of an unrecognized type string, simply ignore * it entirely. Reset the pointer to the position following * the percent sign, so it is not found again. */ fContainsIllegalSequence = false; ulIdx = pFormatType->ulSpecifierIdx + 1U; } } return ulLen; } /** @brief Parse a RedPrintf() format type string to determine the proper data * type. * * @param pszFormat The format string to process. This must be a pointer to * the character following any width or justification * characters. * @param pulTypeLen The location in which to store the type length. The * value will be 0 if PRFMT_UNKNOWN is returned. * * @return Rhe PRFMT_* type value, or PRFMT_UNKNOWN if the type is not * recognized. */ static PRINTTYPE ParseFormatType( const char * pszFormat, uint32_t * pulTypeLen ) { PRINTTYPE fmtType = PRFMT_UNKNOWN; uint32_t ulIdx = 0U; switch( pszFormat[ ulIdx ] ) { case '%': fmtType = PRFMT_DOUBLEPERCENT; break; case 'c': fmtType = PRFMT_CHAR; break; case 's': fmtType = PRFMT_ANSISTRING; break; case 'p': fmtType = PRFMT_POINTER; break; case 'd': fmtType = MAPINT; break; case 'u': fmtType = MAPUINT; break; case 'x': fmtType = MAPHEXUINT; break; case 'h': ulIdx++; switch( pszFormat[ ulIdx ] ) { case 'd': fmtType = MAPSHORT; break; case 'u': fmtType = MAPUSHORT; break; case 'x': fmtType = MAPHEXUSHORT; break; default: break; } break; case 'l': ulIdx++; switch( pszFormat[ ulIdx ] ) { case 'd': fmtType = MAPLONG; break; case 'u': fmtType = MAPULONG; break; case 'x': fmtType = MAPHEXULONG; break; case 'l': ulIdx++; switch( pszFormat[ ulIdx ] ) { case 'd': fmtType = MAPLONGLONG; break; case 'u': fmtType = MAPULONGLONG; break; case 'x': case 'X': fmtType = MAPHEXULONGLONG; break; default: break; } break; default: break; } break; default: break; } if( fmtType != PRFMT_UNKNOWN ) { *pulTypeLen = ulIdx + 1U; } else { *pulTypeLen = 0U; } return fmtType; } /** @brief Format a signed 32-bit integer as a base 10 ASCII string. * * @note If the output buffer length is exhausted, the result will *not* be * null-terminated. * * @note If the @p ulFillLen value is greater than or equal to the buffer * length, the result will not be null-terminated, even if the * formatted portion of the data is shorter than the buffer length. * * @param pcBuffer The output buffer * @param ulBufferLen A pointer to the output buffer length * @param lNum The 32-bit signed number to convert * @param ulFillLen The fill length, if any * @param cFill The fill character to use * * @return The length of the string. */ static uint32_t LtoA( char * pcBuffer, uint32_t ulBufferLen, int32_t lNum, uint32_t ulFillLen, char cFill ) { uint32_t ulLen; if( pcBuffer == NULL ) { REDERROR(); ulLen = 0U; } else { char ach[ 12U ]; /* big enough for a int32_t in base 10 */ uint32_t ulDigits = 0U; uint32_t ulNum; bool fSign; if( lNum < 0 ) { fSign = true; ulNum = ( uint32_t ) -lNum; } else { fSign = false; ulNum = ( uint32_t ) lNum; } do { ach[ ulDigits ] = gacDigits[ ulNum % 10U ]; ulNum = ulNum / 10U; ulDigits++; } while( ulNum ); if( fSign ) { ach[ ulDigits ] = '-'; ulDigits++; } ulLen = FinishToA( ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill ); } return ulLen; } /** @brief Format a signed 64-bit integer as a base 10 ASCII string. * * @note If the output buffer length is exhausted, the result will *not* be * null-terminated. * * @note If the @p ulFillLen value is greater than or equal to the buffer * length, the result will not be null-terminated, even if the * formatted portion of the data is shorter than the buffer length. * * @param pcBuffer The output buffer * @param ulBufferLen A pointer to the output buffer length * @param llNum The 64-bit signed number to convert * @param ulFillLen The fill length, if any * @param cFill The fill character to use * * @return The length of the string. */ static uint32_t LLtoA( char * pcBuffer, uint32_t ulBufferLen, int64_t llNum, uint32_t ulFillLen, char cFill ) { uint32_t ulLen; if( pcBuffer == NULL ) { REDERROR(); ulLen = 0U; } else { char ach[ 12U ]; /* big enough for a int32_t in base 10 */ uint32_t ulDigits = 0U; uint64_t ullNum; bool fSign; if( llNum < 0 ) { fSign = true; ullNum = ( uint64_t ) -llNum; } else { fSign = false; ullNum = ( uint64_t ) llNum; } /* Not allowed to assume that 64-bit division is OK, so use a * software division routine. */ do { uint64_t ullQuotient; uint32_t ulRemainder; /* Note: RedUint64DivMod32() is smart enough to use normal division * once ullNumericVal <= UINT32_MAX. */ ullQuotient = RedUint64DivMod32( ullNum, 10U, &ulRemainder ); ach[ ulDigits ] = gacDigits[ ulRemainder ]; ullNum = ullQuotient; ulDigits++; } while( ullNum > 0U ); if( fSign ) { ach[ ulDigits ] = '-'; ulDigits++; } ulLen = FinishToA( ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill ); } return ulLen; } /** @brief Format an unsigned 32-bit integer as an ASCII string as decimal or * hex. * * @note If the output buffer length is exhausted, the result will *not* be * null-terminated. * * @param pcBuffer The output buffer * @param ulBufferLen The output buffer length * @param ulNum The 32-bit unsigned number to convert * @param fHex If true, format as hex; if false, decimal. * @param ulFillLen The fill length, if any * @param cFill The fill character to use * * @return The length of the string. */ static uint32_t ULtoA( char * pcBuffer, uint32_t ulBufferLen, uint32_t ulNum, bool fHex, uint32_t ulFillLen, char cFill ) { uint32_t ulLen; if( pcBuffer == NULL ) { REDERROR(); ulLen = 0U; } else { char ach[ 11U ]; /* Big enough for a uint32_t in radix 10 */ uint32_t ulDigits = 0U; uint32_t ulNumericVal = ulNum; uint32_t ulRadix = fHex ? 16U : 10U; do { ach[ ulDigits ] = gacDigits[ ulNumericVal % ulRadix ]; ulNumericVal = ulNumericVal / ulRadix; ulDigits++; } while( ulNumericVal > 0U ); ulLen = FinishToA( ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill ); } return ulLen; } /** @brief Format an unsigned 64-bit integer as an ASCII string as decimal or * hex. * * @note If the output buffer length is exhausted, the result will *not* be * null-terminated. * * @param pcBuffer The output buffer. * @param ulBufferLen The output buffer length. * @param ullNum The unsigned 64-bit number to convert. * @param fHex If true, format as hex; if false, decimal. * @param ulFillLen The fill length, if any. * @param cFill The fill character to use. * * @return The length of the string. */ static uint32_t ULLtoA( char * pcBuffer, uint32_t ulBufferLen, uint64_t ullNum, bool fHex, uint32_t ulFillLen, char cFill ) { uint32_t ulLen; if( pcBuffer == NULL ) { REDERROR(); ulLen = 0U; } else { char ach[ 21U ]; /* Big enough for a uint64_t in radix 10 */ uint32_t ulDigits = 0U; uint64_t ullNumericVal = ullNum; if( fHex ) { /* We can figure out the digits using bit operations. */ do { ach[ ulDigits ] = gacDigits[ ullNumericVal & 15U ]; ullNumericVal >>= 4U; ulDigits++; } while( ullNumericVal > 0U ); } else { /* Not allowed to assume that 64-bit division is OK, so use a * software division routine. */ do { uint64_t ullQuotient; uint32_t ulRemainder; /* Note: RedUint64DivMod32() is smart enough to use normal division * once ullNumericVal <= UINT32_MAX. */ ullQuotient = RedUint64DivMod32( ullNumericVal, 10U, &ulRemainder ); ach[ ulDigits ] = gacDigits[ ulRemainder ]; ullNumericVal = ullQuotient; ulDigits++; } while( ullNumericVal > 0U ); } ulLen = FinishToA( ach, ulDigits, pcBuffer, ulBufferLen, ulFillLen, cFill ); } return ulLen; } /** @brief Finish converting a number into an ASCII string representing that * number. * * This helper function contains common logic that needs to run at the end of * all the "toA" functions. It adds the fill character and reverses the digits * string. * * @param pcDigits The digits (and sign) for the ASCII string, in reverse * order as they were computed. * @param ulDigits The number of digit characters. * @param pcOutBuffer The output buffer. * @param ulBufferLen The length of the output buffer. * @param ulFillLen The fill length. If the number string is shorter than * this, the remaining bytes are filled with @p cFill. * @param cFill The fill character. * * @return The length of @p pcOutBuffer. */ static uint32_t FinishToA( const char * pcDigits, uint32_t ulDigits, char * pcOutBuffer, uint32_t ulBufferLen, uint32_t ulFillLen, char cFill ) { uint32_t ulIdx = 0U; uint32_t ulDigitIdx = ulDigits; /* user may have asked for a fill char */ if( ulFillLen > ulDigits ) { uint32_t ulFillRem = ulFillLen - ulDigits; while( ( ulFillRem > 0U ) && ( ulIdx < ulBufferLen ) ) { pcOutBuffer[ ulIdx ] = cFill; ulIdx++; ulFillRem--; } } /* reverse the string */ while( ( ulDigitIdx > 0 ) && ( ulIdx < ulBufferLen ) ) { ulDigitIdx--; pcOutBuffer[ ulIdx ] = pcDigits[ ulDigitIdx ]; ulIdx++; } if( ulIdx < ulBufferLen ) { pcOutBuffer[ ulIdx ] = '\0'; } return ulIdx; }