FreeRTOS-Kernel/FreeRTOS-Plus/Source/Reliance-Edge/tests/util/printf.c
Soren Ptak 3a2f6646f0
Use CI-CD-Github-Actions for spelling and formatting, add in the bot formatting action, update the CI-CD workflow files. Fix incorrect spelling and formatting on files. (#1083)
* Use new version of CI-CD Actions,  checkout@v3 instead of checkout@v2 on all jobs
* Use cSpell spell check, and use ubuntu-20.04 for formatting check
* Add in bot formatting action
* Update freertos_demo.yml and freertos_plus_demo.yml files to increase github log readability
* Add in a Qemu demo onto the workflows.
2023-09-06 12:35:37 -07:00

1318 lines
42 KiB
C

/* ----> 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 <redfs.h>
#include <redtestutils.h>
#include <limits.h>
#include <stdarg.h>
/** @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;
}