/* ----> 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 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 *pszFomat, 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) | |
{ | |
/* Ensture 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 *pszFomat, | |
PRINTFORMAT *pFormatType) | |
{ | |
bool fContainsIllegalSequence = false; | |
uint32_t ulLen = 0U; | |
uint32_t ulIdx = 0U; | |
while(pszFomat[ulIdx] != '\0') | |
{ | |
uint32_t ulTypeLen; | |
/* general output | |
*/ | |
if(pszFomat[ulIdx] != '%') | |
{ | |
ulIdx++; | |
} | |
else | |
{ | |
RedMemSet(pFormatType, 0U, sizeof(*pFormatType)); | |
/* Record the location of the start of the format sequence | |
*/ | |
pFormatType->ulSpecifierIdx = ulIdx; | |
ulIdx++; | |
if(pszFomat[ulIdx] == '-') | |
{ | |
pFormatType->fLeftJustified = true; | |
ulIdx++; | |
} | |
if((pszFomat[ulIdx] == '0') || (pszFomat[ulIdx] == '_')) | |
{ | |
pFormatType->cFillChar = pszFomat[ulIdx]; | |
ulIdx++; | |
} | |
else | |
{ | |
pFormatType->cFillChar = ' '; | |
} | |
if(pszFomat[ulIdx] == '*') | |
{ | |
pFormatType->fHasVarWidth = true; | |
ulIdx++; | |
} | |
else if(ISDIGIT(pszFomat[ulIdx])) | |
{ | |
pFormatType->ulFillLen = (uint32_t)RedAtoI(&pszFomat[ulIdx]); | |
while(ISDIGIT(pszFomat[ulIdx])) | |
{ | |
ulIdx++; | |
} | |
} | |
else | |
{ | |
/* No fill length. | |
*/ | |
} | |
pFormatType->type = ParseFormatType(&pszFomat[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': | |
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; | |
} | |