1266 lines
40 KiB
C
1266 lines
40 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 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)
|
|
{
|
|
/* 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 *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;
|
|
}
|
|
|