Files
jeanlemotan 48ab06b1d9 First
2024-07-02 18:10:39 +02:00

1948 lines
59 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
#include <EAStdC/internal/Config.h>
#include <EAStdC/EAScanf.h>
#include <EAStdC/internal/ScanfCore.h>
#include <EAStdC/EAString.h>
#include <EAStdC/EACType.h>
#include <EAStdC/EAMathHelp.h>
#include <EAAssert/eaassert.h>
EA_DISABLE_ALL_VC_WARNINGS()
#include <math.h>
#include <float.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
EA_RESTORE_ALL_VC_WARNINGS()
EA_DISABLE_VC_WARNING(4127 4146) // C4127: conditional expression is constant.
// C4146: unary minus operator applied to unsigned type, result still unsigned
#if defined(EA_PLATFORM_WINDOWS)
// Users sometimes get link problems when mixing DLL and non-DLL builds under Windows
// due to HUGE_VAL. This is due to how they are building the app, though we can help
// avoid the problem by using a different source for HUGE_VAL.
#define EASTDC_HUGE_VAL EA::StdC::kFloat64Infinity
#else
#define EASTDC_HUGE_VAL HUGE_VAL
#endif
// EA_ENABLE_PRECISE_FP / EA_RESTORE_PRECISE_FP
//
// Allows you to force the usage of precise floating point support by the compiler, whereas the
// compiler may have been set to default to another form which is faster but doesn't strictly
// follow conventions. For example, see:
// http://connect.microsoft.com/VisualStudio/feedback/details/754839/signed-double-0-0-vs-0-0-msvs2012-visual-c-bug-in-x64-mode-when-compiled-with-fp-fast-o2
// Reference:
// http://msdn.microsoft.com/en-us/library/45ec64h6.aspx
//
// Example usage:
// EA_ENABLE_PRECISE_FP()
// void SomeFunction(){ ... }
// EA_RESTORE_PRECISE_FP()
//
#if !defined(EA_ENABLE_PRECISE_FP)
#if defined(EA_COMPILER_MSVC)
#define EA_ENABLE_PRECISE_FP() __pragma(float_control(precise, on, push))
#else
#define EA_ENABLE_PRECISE_FP()
#endif
#endif
#if !defined(EA_RESTORE_PRECISE_FP)
#if defined(EA_COMPILER_MSVC)
#define EA_RESTORE_PRECISE_FP() __pragma(float_control(pop))
#else
#define EA_RESTORE_PRECISE_FP()
#endif
#endif
namespace EA
{
namespace StdC
{
extern uint8_t utf8lengthTable[256];
namespace ScanfLocal
{
int FILEReader8(ReadAction readAction, int value, void* pContext)
{
// We verify that the FILE functions work as desired. If this fails to
// be so for some compiler/platform then we can modify this code.
EA_COMPILETIME_ASSERT(EOF == kReadError);
FILE* const pFile = (FILE*)pContext;
switch(readAction)
{
case kReadActionBegin:
{
#if (!defined(__GNUC__) || (__GNUC__ >= 4)) // Older versions of GCC don't support fwide().
// Question: Is this doing the right thing? Or do we need to be doing something else?
if(value == 1) // "The value param will be 1 for UTF8 and 2 for UCS2."
return (fwide(pFile, -1) < 0) ? 1 : 0; // Set the file to be interpreted as UTF8.
else
{
// Problem: wide is 2 bytes for some platforms and 4 for others. The only way for us
// to fix this problem properly is to handle 32->16 or 16->32 conversions on our
// side. But we don't have state information in this FileReader8 function. We would
// need to revise pContext to provide some space for us to write state.
return (fwide(pFile, 1) > 0) ? 1 : 0; // Set the file to be interpreted as wide.
}
#endif
}
case kReadActionEnd:
// Currently we do nothing, but possibly we should restore the file byte/wide state.
break;
case kReadActionRead:
return fgetc(pFile);
case kReadActionUnread:
return ungetc(value, pFile);
case kReadActionGetAtEnd:
return feof(pFile);
case kReadActionGetLastError:
return ferror(pFile);
}
return 0;
}
int FILEReader16(ReadAction readAction, int value, void* pContext)
{
return FILEReader8(readAction, value, pContext);
}
int FILEReader32(ReadAction readAction, int value, void* pContext)
{
return FILEReader8(readAction, value, pContext);
}
int StringReader8(ReadAction readAction, int /*value*/, void* pContext)
{
SscanfContext8* const pSscanfContext8 = (SscanfContext8*)pContext;
switch(readAction)
{
case kReadActionBegin:
case kReadActionEnd:
case kReadActionGetLastError:
break;
case kReadActionRead:
if(*pSscanfContext8->mpSource)
return (uint8_t)*pSscanfContext8->mpSource++;
else
{
pSscanfContext8->mbEndFound = 1;
return kReadError;
}
case kReadActionUnread:
if(!pSscanfContext8->mbEndFound)
pSscanfContext8->mpSource--; // We don't error-check this; we currently assume the caller is bug-free.
else
pSscanfContext8->mbEndFound = 0;
break;
case kReadActionGetAtEnd:
return pSscanfContext8->mbEndFound;
}
return 0;
}
int StringReader16(ReadAction readAction, int /*value*/, void* pContext)
{
SscanfContext16* const pSscanfContext16 = (SscanfContext16*)pContext;
switch(readAction)
{
case kReadActionBegin:
case kReadActionEnd:
case kReadActionGetLastError:
break;
case kReadActionRead:
if(*pSscanfContext16->mpSource)
{
EA_COMPILETIME_ASSERT(sizeof(int) >= sizeof(char16_t));
return (int)(char16_t)*pSscanfContext16->mpSource++;
}
else
{
pSscanfContext16->mbEndFound = 1;
return kReadError;
}
case kReadActionUnread:
if(!pSscanfContext16->mbEndFound)
pSscanfContext16->mpSource--; // We don't error-check this; we currently assume the caller is bug-free.
else
pSscanfContext16->mbEndFound = 0;
break;
case kReadActionGetAtEnd:
return pSscanfContext16->mbEndFound;
}
return 0;
}
int StringReader32(ReadAction readAction, int /*value*/, void* pContext)
{
SscanfContext32* const pSscanfContext32 = (SscanfContext32*)pContext;
switch(readAction)
{
case kReadActionBegin:
case kReadActionEnd:
case kReadActionGetLastError:
break;
case kReadActionRead:
if(*pSscanfContext32->mpSource)
{
EA_COMPILETIME_ASSERT(sizeof(int) >= sizeof(char32_t));
return (int)(char32_t)*pSscanfContext32->mpSource++;
}
else
{
pSscanfContext32->mbEndFound = 1;
return kReadError;
}
case kReadActionUnread:
if(!pSscanfContext32->mbEndFound)
pSscanfContext32->mpSource--; // We don't error-check this; we currently assume the caller is bug-free.
else
pSscanfContext32->mbEndFound = 0;
break;
case kReadActionGetAtEnd:
return pSscanfContext32->mbEndFound;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// ToDouble
//
// We have a string of digits and an exponent. We need to convert them to
// a double.
//
// The proper conversion from string to double is not entirely trivial,
// as documented in the papers:
// What Every Computer Scientist Should Know About Floating Point Arithmetic
// How to Read Floating Point Numbers Accurately
// Correctly Rounded Binary-Decimal and Decimal-Binary Conversions
//
const double powerTable[18] = { 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11 };
double DoubleValue::ToDouble() const
{
if(mExponent >= -6 && mExponent <= 11) // If we can handle the result quickly and accurately with a simple implementation...
{
// We could handle exponents bigger than this if we used a bigger table or (better) if we
// wrote code that allowed us to apply a multiplier to the existing table. On the other hand,
// for our purposes it is uncommon to work with such huge numbers.
double result = 0.0;
for(int i = 0; i < mSigLen; ++i)
result = (result * 10.0) + (float)(mSigStr[i] - '0');
result *= powerTable[mExponent + 6]; // +6 because our smallest exponent above is 1e-6
return result;
}
else
{
// Negative exponents mean the number has a fractional component.
// However, floating point hardware can't perfectly represent decimal
// fractions. A result of this, as noted in the above papers, is that
// a simple loop which multiplies by 10 won't yield an ideal floating
// point representation for all decimal values. In the absence of
// FPU hardware support for decimal to binary conversions, the only
// way to achieve the ideal solution is to use an iterative approximating
// algorithm.
//
// Until we implement that algorithm we do a fallback to the system
// provided strtod function, which in many cases will implement such
// an algorithm itself internally.
char buffer[kMaxSignificandDigits + 12];
int i;
for(i = 0; i < mSigLen; ++i)
buffer[i] = mSigStr[i];
if(mExponent)
{
int multiplier;
int e = mExponent;
buffer[i++] = 'e';
if(e < 0)
{
buffer[i++] = '-';
e = -e;
}
if(e >= 100)
multiplier = 100;
else if(e >= 10)
multiplier = 10;
else
multiplier = 1;
while(multiplier)
{
buffer[i++] = (char)('0' + (e / multiplier));
e %= multiplier;
multiplier /= 10;
}
}
buffer[i] = 0;
return strtod(buffer, NULL); // This is not a fast function. That's why we want to replace it.
}
}
// Force precise floating support by the compiler. This is necessary under VC++ because otherwise
// it's possible that the /fp:fast compiler argument was used, which causes the -0.0 code below to
// be generated by the compiler in a non-conformant way. This generation is by design, but prevents
// us from having a conforming scanf implementation.
EA_ENABLE_PRECISE_FP()
template<typename ReadFunctionT, typename ReadFormatSpanFunctionT, typename CharT>
class VscanfUtil
{
public:
int VscanfCore(ReadFunctionT pReadFunction, ReadFormatSpanFunctionT pReadFormatSpanFunction, void* pContext, const CharT* pFormat, va_list arguments)
{
using namespace ScanfLocal;
int nAssignmentCount = 0; // Number of assigned fields. This is the return value of this function. -1 in case of error.
int nConversionCount = 0; // Number of processed fields. Will be >= the number of assigned fields.
int nReadCount; // Temporary holder.
int nReadCountSum = 0; // Used to support the %n field.
const CharT* pFormatCurrent = pFormat; // Current position within entire fd string.
char* pArgumentCurrent = NULL; // Pointer to current va_list argument.
FormatData fd; //
int c = 0; // Temporary character.
uintmax_t uintMaxValue = 0; //
intmax_t intMaxValue = 0; //
long double ldValue; //
int bNegative; //
int bIntegerOverflow; //
int nBase; //
pReadFunction(kReadActionBegin, sizeof(*pFormat), pContext);
while(*pFormatCurrent)
{
CharT cFormat = *pFormatCurrent;
if(Isspace(cFormat))
{
do{
cFormat = *++pFormatCurrent;
}
while(Isspace(cFormat));
while(Isspace((CharT)(c = pReadFunction(kReadActionRead, 0, pContext))))
++nReadCountSum;
pReadFunction(kReadActionUnread, c, pContext);
continue;
}
if(cFormat != '%')
{
if((c = pReadFunction(kReadActionRead, 0, pContext)) != (int)cFormat)
{
pReadFunction(kReadActionUnread, c, pContext);
goto Done;
}
++nReadCountSum;
++pFormatCurrent;
continue;
}
pFormatCurrent = ReadFormat(pFormatCurrent, &fd);
if((fd.mnType == '%') || fd.mbSkipAssignment)
pArgumentCurrent = NULL;
else
pArgumentCurrent = (char*)va_arg(arguments, void*); // User arguments are always passed as pointers.
if((fd.mnType != 'n') && (pReadFunction(kReadActionGetLastError, 0, pContext) || pReadFunction(kReadActionGetAtEnd, 0, pContext)))
break;
switch (fd.mnType)
{
case '%':
{
while(Isspace((CharT)(c = pReadFunction(kReadActionRead, 0, pContext))))
++nReadCountSum;
if(c != '%')
{
pReadFunction(kReadActionUnread, c, pContext);
goto Done;
}
++nReadCountSum;
break;
}
case 'n':
{
if(pArgumentCurrent) // If we should write the value to a user argument...
{
switch (fd.mModifier)
{
// There are some other modifier types we could support here.
case kModifierMax_t: *(intmax_t*) pArgumentCurrent = (intmax_t) nReadCountSum; break;
case kModifierSize_t: *(size_t*) pArgumentCurrent = (size_t) nReadCountSum; break;
case kModifierPtrdiff_t:*(ptrdiff_t*)pArgumentCurrent = (ptrdiff_t)nReadCountSum; break;
case kModifierInt64: *(int64_t*) pArgumentCurrent = (int64_t) nReadCountSum; break;
case kModifierLongLong: *(long long*)pArgumentCurrent = (long long)nReadCountSum; break;
case kModifierInt32: *(int32_t*) pArgumentCurrent = (int32_t) nReadCountSum; break;
case kModifierLong: *(long*) pArgumentCurrent = (long) nReadCountSum; break;
case kModifierInt16:
case kModifierShort: *(short*) pArgumentCurrent = (short) nReadCountSum; break;
case kModifierInt8:
case kModifierChar: *(char*) pArgumentCurrent = (char) nReadCountSum; break;
case kModifierNone: *(int*) pArgumentCurrent = (int) nReadCountSum; break;
default: break;
}
}
continue;
}
case 'b': // 'b' means binary. This is a convenient extension that we provide.
case 'o':
case 'u':
case 'i':
case 'd':
case 'x':
case 'X':
{
// To consider: replace if/else usage below with a switch or something with less branching.
if(fd.mnType == 'b')
nBase = 2;
else if(fd.mnType == 'o')
nBase = 8;
else if(fd.mnType == 'u' || fd.mnType == 'd')
nBase = 10;
else if(fd.mnType == 'i')
nBase = 0;
else
nBase = 16;
switch (fd.mModifier)
{
case kModifierMax_t:
uintMaxValue = (uintmax_t) ReadUint64(pReadFunction, pContext, UINTMAX_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
case kModifierSize_t:
uintMaxValue = (size_t) ReadUint64(pReadFunction, pContext, SIZE_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
case kModifierPtrdiff_t:
uintMaxValue = (ptrdiff_t) ReadUint64(pReadFunction, pContext, PTRDIFF_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
case kModifierInt64:
case kModifierLongLong:
uintMaxValue = (unsigned long long)ReadUint64(pReadFunction, pContext, UINT64_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
case kModifierInt32:
case kModifierLong:
uintMaxValue = (unsigned long) ReadUint64(pReadFunction, pContext, UINT32_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
case kModifierInt16:
case kModifierShort:
uintMaxValue = (unsigned long) ReadUint64(pReadFunction, pContext, UINT16_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
case kModifierInt8:
case kModifierChar:
default:
uintMaxValue = (unsigned long) ReadUint64(pReadFunction, pContext, UINT8_MAX, nBase, fd.mnWidth, nReadCount, bNegative, bIntegerOverflow);
break;
}
if(!nReadCount)
goto Done;
if((fd.mnType == 'i' || fd.mnType == 'd')) // If using a signed type...
{
if(bNegative)
intMaxValue = -uintMaxValue;
else
intMaxValue = (intmax_t)uintMaxValue;
if(pArgumentCurrent) // If we should write the value to a user argument...
{
switch (fd.mModifier)
{
case kModifierMax_t: *(intmax_t*) pArgumentCurrent = (intmax_t) intMaxValue; break;
case kModifierSize_t: *(size_t*) pArgumentCurrent = (size_t) intMaxValue; break;
case kModifierPtrdiff_t:*(ptrdiff_t*) pArgumentCurrent = (ptrdiff_t) intMaxValue; break;
case kModifierInt64: *(int64_t*) pArgumentCurrent = (int64_t) intMaxValue; break;
case kModifierLongLong: *(long long*) pArgumentCurrent = intMaxValue; break;
case kModifierInt32: *(int32_t*) pArgumentCurrent = (int32_t) intMaxValue; break; // We assume sizeof long >= sizeof int32
case kModifierLong: *(long*) pArgumentCurrent = (long) intMaxValue; break;
case kModifierInt16:
case kModifierShort: *(short*) pArgumentCurrent = (short) intMaxValue; break;
case kModifierInt8:
case kModifierChar: *(char*) pArgumentCurrent = (char) intMaxValue; break;
case kModifierNone: *(int*) pArgumentCurrent = (int) intMaxValue; break;
default: /* This should never occur. Possibly assert false */ break;
}
nAssignmentCount++;
}
}
else
{
if(bNegative)
{
// It's a little odd to use a negative sign in front of an unsigned value, but it's valid C.
uintMaxValue = (uintmax_t)-(intmax_t)uintMaxValue;
}
// else leave as-is.
if(pArgumentCurrent) // If we should write the value to a user argument...
{
switch (fd.mModifier)
{
case kModifierMax_t: *(uintmax_t*) pArgumentCurrent = (uintmax_t) uintMaxValue; break;
case kModifierSize_t: *(size_t*) pArgumentCurrent = (size_t) uintMaxValue; break;
case kModifierPtrdiff_t:*(ptrdiff_t*) pArgumentCurrent = (ptrdiff_t) uintMaxValue; break;
case kModifierInt64: *(uint64_t*) pArgumentCurrent = (uint64_t) uintMaxValue; break;
case kModifierLongLong: *(unsigned long long*) pArgumentCurrent = uintMaxValue; break;
case kModifierInt32: *(uint32_t*) pArgumentCurrent = (uint32_t) uintMaxValue; break; // We assume sizeof ulong >= sizeof uint32
case kModifierLong: *(unsigned long*) pArgumentCurrent = (unsigned long) uintMaxValue; break;
case kModifierInt16:
case kModifierShort: *(unsigned short*) pArgumentCurrent = (unsigned short) uintMaxValue; break;
case kModifierInt8:
case kModifierChar: *(unsigned char*) pArgumentCurrent = (unsigned char) uintMaxValue; break;
case kModifierNone: *(unsigned int*) pArgumentCurrent = (unsigned int) uintMaxValue; break;
default: /* This should never occur. Possibly assert false */ break;
}
nAssignmentCount++;
}
}
nReadCountSum += nReadCount;
nConversionCount++;
break;
}
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'a':
case 'A':
{
ldValue = ReadDouble(pReadFunction, pContext, fd.mnWidth, fd.mDecimalPoint, nReadCount, bIntegerOverflow);
if(!nReadCount)
goto Done;
if(pArgumentCurrent) // If we should write the value to a user argument...
{
switch (fd.mModifier)
{
case kModifierLongDouble: *(long double*) pArgumentCurrent = ldValue; break;
case kModifierDouble: *(double*) pArgumentCurrent = (double)ldValue; break;
case kModifierNone: *(float*) pArgumentCurrent = (float) ldValue; break;
default: /* This should never occur. Possibly assert false */ break;
}
nAssignmentCount++;
}
nReadCountSum += nReadCount;
nConversionCount++;
break;
}
case 's':
case 'S':
{
c = pReadFunction(kReadActionRead, 0, pContext);
// We eat leading whitespace and then fall through to reading characters.
while(Isspace((CharT)c))
{
++nReadCountSum;
c = pReadFunction(kReadActionRead, 0, pContext);
}
pReadFunction(kReadActionUnread, c, pContext);
// Fall through, as %[] processing is the same as %s except %[] specifies a filter for what characters to accept or ignore.
}
case '[':
{
// The user can use %[ab ] to read chars 'a', 'b', ' ' into the string until some other char is encountered.
nReadCount = 0;
if(pArgumentCurrent) // If we should write the value to a user argument...
{
int stringTypeSize;
switch (fd.mModifier)
{
case kModifierInt8: // If the user specified %I8s or %I8S
case kModifierChar: // If the user specified %hs or %hS or kModifierWChar was chosen implicitly for other reasons.
stringTypeSize = 1;
break;
case kModifierInt16: // If the user specified %I16s or %I16S
stringTypeSize = 2;
break;
case kModifierInt32: // If the user specified %I32s or %I32S
stringTypeSize = 4;
break;
case kModifierWChar: // If the user specified %ls or %lS or kModifierWChar was chosen implicitly for other reasons.
stringTypeSize = sizeof(wchar_t);
break;
default: // If the user specified %I64s or %I64S or another invalid size.
//nAssignmentCount = -1; Should we do this?
goto Done;
}
if (!pReadFormatSpanFunction(fd, c, pReadFunction, pContext, stringTypeSize, pArgumentCurrent, nReadCount))
{
nAssignmentCount = -1;
goto Done;
}
if(!nReadCount)
{
pReadFunction(kReadActionUnread, c, pContext);
goto Done;
}
// 0-terminate the user's string.
switch (stringTypeSize)
{
case 1:
*((char*)pArgumentCurrent) = 0;
break;
case 2:
*((char16_t*)pArgumentCurrent) = 0;
break;
case 4:
*((char32_t*)pArgumentCurrent) = 0;
break;
}
nAssignmentCount++;
}
else
{
pReadFormatSpanFunction(fd, c, pReadFunction, pContext, -1, pArgumentCurrent, nReadCount);
if(!nReadCount)
{
pReadFunction(kReadActionUnread, c, pContext);
break;
}
}
if(fd.mnWidth >= 0)
pReadFunction(kReadActionUnread, c, pContext);
nReadCountSum += nReadCount;
nConversionCount++;
break;
}
case 'c':
case 'C': // Actually %C is not a standard scanf format.
{
// The user can specify %23c to read 23 chars (including spaces) into an array, with no 0-termination.
if(!fd.mbWidthSpecified)
fd.mnWidth = 1;
nReadCount = 0;
if(pArgumentCurrent) // If we should write the value to a user argument...
{
int charTypeSize;
switch (fd.mModifier)
{
case kModifierInt8: // If the user specified %I8c or %I8C
case kModifierChar: // If the user specified %hc or %hC or kModifierWChar was chosen implicitly for other reasons.
charTypeSize = 1;
break;
case kModifierInt16: // If the user specified %I16c or %I16C
charTypeSize = 2;
break;
case kModifierInt32: // If the user specified %I32c or %I32C
charTypeSize = 4;
break;
case kModifierWChar: // If the user specified %lc or %lC or kModifierWChar was chosen implicitly for other reasons.
charTypeSize = sizeof(wchar_t);
break;
default: // If the user specified %I64c or %I64C or another invalid size.
//nAssignmentCount = -1; Should we do this?
goto Done;
}
while(fd.mnWidth-- && ((c = pReadFunction(kReadActionRead, 0, pContext)) != -1))
{
switch (charTypeSize)
{
case 1:
// Applicable for 16 and 32 bit character string variant
#if EASTDC_SCANF_WARNINGS_ENABLED
//if(c > 127)
// ReportScanfWarning(loss of information);
#endif
*((char*)pArgumentCurrent) = (char)(uint8_t)(unsigned)c;
break;
case 2:
// Applicable for 8bit character string variant
// To do: Support UTF8 sequences.
// size_t charLength = UTF8CharSize(&c); Do this only for the first char.
// if(charLength > 1)
// read into buffer and do a single char Strlcpy from 8 bit to 16 bit.
// Applicable for 32bit character string variant
#if EASTDC_SCANF_WARNINGS_ENABLED
//if(c > 65535)
// ReportScanfWarning(loss of information);
#endif
*((char16_t*)pArgumentCurrent) = (char16_t)c;
break;
case 4:
// Applicable for 8bit character string variant
// To do: Support UTF8 sequences.
*((char32_t*)pArgumentCurrent) = (char32_t)c;
break;
}
++nReadCount;
}
if(!nReadCount)
goto Done;
nAssignmentCount++;
}
else // else ignore the field
{
while(fd.mnWidth-- && ((c = pReadFunction(kReadActionRead, 0, pContext)) != -1))
++nReadCount;
if(!nReadCount)
goto Done;
}
nReadCountSum += nReadCount;
nConversionCount++;
break;
}
case kFormatError:
default:
goto Done;
} // switch (fd.mnType)
} // while(*pFormatCurrent)
Done:
if((nConversionCount == 0) && pReadFunction(kReadActionGetLastError, 0, pContext))
nAssignmentCount = -1;
pReadFunction(kReadActionEnd, 0, pContext);
return nAssignmentCount;
}
private:
const CharT* ReadFormat(const CharT* pFormat, FormatData* pFormatData)
{
const CharT* pFormatCurrent = pFormat;
bool bModifierPresent = true; // True until proven false.
FormatData fd;
CharT c;
c = *++pFormatCurrent;
if(c == '%') // A %% sequence means to simply treat it as a literal '%'.
{
fd.mnType = '%';
*pFormatData = fd;
return ++pFormatCurrent;
}
if(Isdigit(c)) // If the user is specifying a field width...
{
// The standard doesn't say anything special about a field width of zero, so we allow it.
fd.mbWidthSpecified = 1;
fd.mnWidth = 0;
do{
fd.mnWidth = (int)((fd.mnWidth * 10) + (c - '0'));
c = *++pFormatCurrent;
} while(Isdigit(c));
}
else if(c == '*') // A * char after % means to skip the assignment but eat it from the source data.
{
fd.mbSkipAssignment = true;
c = *++pFormatCurrent;
}
// See if the user specified a modifier, such as h, hh, l, ll, or L.
switch (c)
{
case 'h': // handle h and hh
{
if(pFormatCurrent[1] == 'h') // If the fd is hh ...
{
fd.mModifier = kModifierChar; // Specifies that a following d, i, o, u, x, X, or n conversion specifier applies to an argument with type pointer to signed char or unsigned char.
c = *++pFormatCurrent;
}
else
fd.mModifier = kModifierShort; // Specifies that a following d, i, o, u, x, X, or n conversion specifier applies to an argument with type pointer to short int or unsigned short int.
break;
}
case 'l': // handle l and ll
{
if(pFormatCurrent[1] == 'l') // If the fd is ll ...
{
fd.mModifier = kModifierLongLong; // Specifies that a following d, i, o, u, x, X, or n conversion specifier applies to an argument with type pointer to long long int or unsigned long long int.
c = *++pFormatCurrent;
}
else
fd.mModifier = kModifierLong; // Specifies that a following d, i, o, u, x, X, or n conversion specifier applies to an argument with type pointer to long int or unsigned long int; that a following a, A, e, E, f, F, g, or G conversion specifier applies to an argument with type pointer to double; or that a following c, s, or [ conversion specifier applies to an argument with type pointer to wchar_t.
break;
}
case 'j':
// Specifies that a following d, i, o, u, x, or X conversion specifier applies to an intmax_t or uintmax_t argument; or that a following n conversion specifier applies to a pointer to an intmax_t argument.
fd.mModifier = kModifierMax_t;
break;
case 'z':
// Specifies that a following d, i, o, u, x, or X conversion specifier applies to a size_t or the corresponding signed integer type argument; or that a following n conversion specifier applies to a pointer to a signed integer type corresponding to size_t argument.
fd.mModifier = kModifierSize_t;
break;
case 't':
// Specifies that a following d, i, o, u, x, or X conversion specifier applies to a ptrdiff_t or the corresponding unsigned integer type argument; or that a following n conversion specifier applies to a pointer to a ptrdiff_t argument.
fd.mModifier = kModifierPtrdiff_t;
break;
case 'L':
// Specifies that a following a, A, e, E, f, F, g, or G conversion specifier applies to an argument with type pointer to long double.
fd.mModifier = kModifierLongDouble;
break;
case 'I': // We support Microsoft's extension sized fd specifiers.
if(pFormatCurrent[1] == '8') // If the user specified %I8 ...
{
fd.mModifier = kModifierInt8;
c = *++pFormatCurrent; // Account for the '8' part of I8. We'll account for the 'I' part below for all formats.
}
else if((pFormatCurrent[1] == '1') && (pFormatCurrent[2] == '6'))
{
fd.mModifier = kModifierInt16;
c = *(pFormatCurrent += 2);
}
else if((pFormatCurrent[1] == '3') && (pFormatCurrent[2] == '2'))
{
fd.mModifier = kModifierInt32;
c = *(pFormatCurrent += 2);
}
else if((pFormatCurrent[1] == '6') && (pFormatCurrent[2] == '4'))
{
fd.mModifier = kModifierInt64;
c = *(pFormatCurrent += 2); // Account for the '64' part of I64. We'll account for the 'I' part below for all formats.
}
else if((pFormatCurrent[1] == '1') && (pFormatCurrent[2] == '2') && (pFormatCurrent[3] == '8'))
{
fd.mModifier = kModifierInt128;
c = *(pFormatCurrent += 3);
}
else // Else the specified modifier was invalid.
{
fd.mnType = kFormatError;
*pFormatData = fd;
EA_FAIL_MSG("Scanf: Invalid %I modifier");
return ++pFormatCurrent;
}
break;
default:
bModifierPresent = false;
break;
}
if(bModifierPresent)
c = *++pFormatCurrent;
fd.mnType = (int)c;
switch (c)
{
case 'b': // 'b' means binary. This is a convenient extension that we provide.
case 'd':
case 'u':
case 'i':
case 'x':
case 'X':
case 'o':
if(fd.mModifier == kModifierLongDouble)
{
fd.mnType = kFormatError;
EA_FAIL_MSG("Scanf: Invalid %b/%d/%u/%i/%x/%o modifier");
}
break;
case 'c': // We accept %hc, %c, %lc, %I8c, %I16c, %I32c (regular, regular, wide, char, char16_t, char32_t)
case 'C': // We accept %hC, %C, %lC, %I8C, %I16C, %I32C (regular, wide, wide, char, char16_t, char32_t)
case 's': // We accept %hs, %s, %ls, %I8s, %I16s, %I32s (regular, regular, wide, char, char16_t, char32_t)
case 'S': // We accept %hS, %S, %lS, %I8s, %I16s, %I32s (regular, wide, wide, char, char16_t, char32_t)
{
// Microsoft's library goes against the C and C++ standard: %s is
// not interpreted to mean char string but instead is interpreted
// to be either char or wchar_t depending on what the output
// text fd is. This is non-standard but has the convenience
// of allowing users to migrate between char and wchar_t usage
// more easily. So we allow EASCANF_MS_STYLE_S_FORMAT to control this.
if(fd.mModifier == kModifierLong)
fd.mModifier = kModifierWChar;
else if(fd.mModifier == kModifierShort)
fd.mModifier = kModifierChar;
else if(fd.mModifier == kModifierNone)
{
#if EASCANF_MS_STYLE_S_FORMAT
if((c == 's') || (c == 'c'))
fd.mModifier = (sizeof(*pFormat) == sizeof(char)) ? kModifierChar : kModifierWChar;
else
fd.mModifier = (sizeof(*pFormat) == sizeof(char)) ? kModifierWChar : kModifierChar;
#else
if((c == 's') || (c == 'c'))
fd.mModifier = kModifierChar;
else
fd.mModifier = kModifierWChar;
#endif
}
else if((fd.mModifier < kModifierInt8) || (fd.mModifier > kModifierInt32)) // This expression assumes that Int8, Int16, Int32 are contiguous enum values.
{
fd.mnType = kFormatError;
EA_FAIL_MSG("Scanf: Invalid %s/%c modifier");
}
if((c == 's') || (c == 'S'))
{
// We make %s be a special case of %[] whereby all non-space characters are accepted.
// fd.mCharBitmap.SetAll();
// fd.mCharBitmap.Clear(0x09); // Set tab (0x09),
// fd.mCharBitmap.Clear(0x0a); // LF (0x0a),
// fd.mCharBitmap.Clear(0x0b); // VT (0x0b),
// fd.mCharBitmap.Clear(0x0c); // FF (0x0c),
// fd.mCharBitmap.Clear(0x0d); // CR (0x0d),
// fd.mCharBitmap.Clear(0x20); // space (0x20) to zero.
// Pre-calculated version of above:
fd.mCharBitmap.mBits[0] = 0xffffc1ff;
fd.mCharBitmap.mBits[1] = 0xfffffffe;
fd.mCharBitmap.mBits[2] = 0xffffffff;
fd.mCharBitmap.mBits[3] = 0xffffffff;
fd.mCharBitmap.mBits[4] = 0xffffffff;
fd.mCharBitmap.mBits[5] = 0xffffffff;
fd.mCharBitmap.mBits[6] = 0xffffffff;
fd.mCharBitmap.mBits[7] = 0xffffffff;
}
break;
}
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
case 'a':
case 'A':
// The C99 Standard, section 7.24.2.2, specifies that %l and %L are the only modifiers that
// affect floating point types. %f = float, %lf = double, %Lf = long double. It doesn't say
// what the expected result is for using other modifiers with floating point types. We can
// choose to ignore these types or yield an error. We give an error. The VC++ Standard
// Library has inconsistent behaviour: it ignores the h in %hf, but in the case of %llf it
// reinterprets the format to be %lli.
if(fd.mModifier == kModifierLong) // if %lf ...
fd.mModifier = kModifierDouble;
else if((fd.mModifier != kModifierLongDouble) && // if not %Lf ...
(fd.mModifier != kModifierNone))
{
fd.mnType = kFormatError;
EA_FAIL_MSG("Scanf: Invalid %e/%f/%g/%a modifier");
}
break;
case 'p':
if(sizeof(void*) == 2)
fd.mModifier = kModifierInt16;
else if(sizeof(void*) == 4)
fd.mModifier = kModifierInt32;
else
fd.mModifier = kModifierInt64;
fd.mnType = 'x';
break;
case '[':
{
// The C99 standard states (7.19.6.2 p12):
// Matches a non-empty sequence of bytes from a set of expected bytes (the scanset). The normal skip over
// white-space characters shall be suppressed in this case. The application shall ensure that the
// corresponding argument is a pointer to the initial byte of an array of char, signed char, or unsigned char
// large enough to accept the sequence and a terminating null byte, which shall be added automatically.
//
// If an l (ell) qualifier is present, the input is a sequence of characters that begins in the initial shift state.
// Each character in the sequence shall be converted to a wide character as if by a call to the mbrtowc() function,
// with the conversion state described by an mbstate_t object initialized to zero before the first character is converted.
// The application shall ensure that the corresponding argument is a pointer to an array of wchar_t large enough to
// accept the sequence and the terminating null wide character, which shall be added automatically.
//
// The conversion specification includes all subsequent bytes in the fd string up to and including the matching
// right square bracket (']'). The bytes between the square brackets (the scanlist) comprise the scanset,
// unless the byte after the left square bracket is a circumflex ('^'), in which case the scanset contains all
// bytes that do not appear in the scanlist between the circumflex and the right square bracket. If the conversion
// specification begins with "[]" or "[^]", the right square bracket is included in the scanlist and the next right
// square bracket is the matching right square bracket that ends the conversion specification; otherwise, the first
// right square bracket is the one that ends the conversion specification. If a '-' is in the scanlist and is not
// the first character, nor the second where the first character is a '^', nor the last character, the behavior is
// implementation-defined.
bool bInclusive = true;
if(fd.mModifier == kModifierShort)
fd.mModifier = kModifierChar;
else if(fd.mModifier == kModifierLong)
fd.mModifier = kModifierWChar;
else if(fd.mModifier == kModifierNone)
{
#if EASCANF_MS_STYLE_S_FORMAT
fd.mModifier = (sizeof(*pFormat) == sizeof(char)) ? kModifierChar : kModifierWChar;
#else
fd.mModifier = (sizeof(*pFormat) == sizeof(char16_t)) ? kModifierWChar : kModifierChar; //TODO: This condition seems odd, needs review.
#endif
}
else if((fd.mModifier < kModifierInt8) || (fd.mModifier > kModifierInt32)) // This expression assumes that Int8, Int16, Int32 are contiguous enum values.
{
fd.mnType = kFormatError;
EA_FAIL_MSG("Scanf: Invalid %[ modifier");
}
c = *++pFormatCurrent;
if(c == '^')
{
bInclusive = false;
c = *++pFormatCurrent;
}
if(c == ']') // The C99 standard requires that if there is an excluded ']' char, then it is the first char after [ or [^.
{
fd.mCharBitmap.Set((CharT)']');
c = *++pFormatCurrent;
}
// To do: We need to read UTF8 character sequences here instead of just ascii values.
EA_ASSERT((sizeof(CharT) != sizeof(char)) || ((uint8_t)(char)c < 128)); // A c >= 128 refers to a UTF8 sequence, which we don't yet support.
while(c && (c != ']')) // Walk through the characters until we encounter a closing ']' char. Use '-' char to indicate character ranges, as in "a-d"
{
fd.mCharBitmap.Set(c);
if((pFormatCurrent[1] == '-') && pFormatCurrent[2] && (pFormatCurrent[2] != ']')) // If we have a character range specifier...
{
while(++c <= pFormatCurrent[2])
fd.mCharBitmap.Set(c);
pFormatCurrent += 2;
}
c = *++pFormatCurrent;
}
if(c) // At this point, c should be ']'
{
if(!bInclusive)
fd.mCharBitmap.NegateAll();
}
else
{
fd.mnType = kFormatError;
EA_FAIL_MSG("Scanf: Missing format ] char");
}
break;
}
case 'n':
{
// The C99 standard states (7.19.6.2 p12): No input is consumed. The application shall ensure
// that the corresponding argument is a pointer to the integer into which shall be written the
// number of bytes read from the input so far by this call to the fscanf() functions.
// Execution of a %n conversion specification shall not increment the assignment nReadCount returned
// at the completion of execution of the function. No argument shall be converted, but one shall
// be consumed. If the conversion specification includes an assignment-suppressing character or
// a field width, the behavior is undefined.
break;
}
default:
{
fd.mnType = kFormatError;
EA_FAIL_MSG("Scanf: Invalid format.");
break;
}
}
*pFormatData = fd;
return ++pFormatCurrent;
}
uint64_t ReadUint64(ReadFunctionT pReadFunction, void* pContext,
uint64_t nMaxValue, int nBase, int nMaxFieldWidth,
int& nReadCount, int& bNegative, int& bIntegerOverflow)
{
ReadIntegerState state = kRISError;
uint64_t nValue = 0;
int nSpaceCount = 0;
const int kRISDone = kRISError | kRISEnd;
const int kRISSuccess = kRISAfterZero | kRISReadDigits | kRISEnd;
nReadCount = 0;
bNegative = 0;
bIntegerOverflow = 0;
if((nBase != 1) && (nBase <= 36) && (nMaxFieldWidth >= 1))
{
uint64_t nMaxValueCheck = 0; // This is what we compare nValue to as we build nValue. It is always equal to nValue / nBase. We need to do this because otherwise we'd compare overflowed values.
int c;
state = kRISLeadingSpace;
c = pReadFunction(kReadActionRead, 0, pContext);
nReadCount++;
if(nBase)
nMaxValueCheck = (nMaxValue / nBase);
while((c != kReadError) && (nReadCount <= nMaxFieldWidth) && ((state & kRISDone) == 0))
{
switch ((int)state)
{
case kRISLeadingSpace:
{
if(Isspace((CharT)c))
{
c = pReadFunction(kReadActionRead, 0, pContext);
nSpaceCount++;
// Stay in this state and read another char.
}
else
{
if(c == '-')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nReadCount++;
bNegative = 1;
}
else if(c == '+')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nReadCount++;
}
state = kRISZeroTest;
}
break;
}
case kRISZeroTest:
{
if(((nBase == 0) || (nBase == 16)) && (c == '0')) // If nBase == 0, then we should expect hex (0x1234) or octal (01234)
{
c = pReadFunction(kReadActionRead, 0, pContext);
nReadCount++;
state = kRISAfterZero;
}
else
{
if(nBase == 0) // If the base hasn't been determined yet (e.g. by leading "0x"), then it must be 10.
nBase = 10;
if(nMaxValueCheck == 0) // If this hasn't been set yet...
nMaxValueCheck = (nMaxValue / nBase);
state = kRISReadFirstDigit;
}
break;
}
case kRISAfterZero:
{
if((c == 'x') || (c == 'X'))
{
c = pReadFunction(kReadActionRead, 0, pContext);
nReadCount++;
nBase = 16;
state = kRISReadFirstDigit;
}
else
{
if(nBase == 0)
nBase = 8;
state = kRISReadDigits;
}
if(nMaxValueCheck == 0) // If this hasn't been set yet...
nMaxValueCheck = (nMaxValue / nBase);
break;
}
case kRISReadFirstDigit:
case kRISReadDigits:
{
const int cDigit = (c - '0');
if((unsigned)cDigit < 10) // If c is compatible with base 2, 8, 10 ...
{
if(cDigit >= nBase)
{
if(state == kRISReadDigits)
state = kRISEnd;
else
state = kRISError;
break;
}
c = cDigit;
}
else //Else we may have a hex digit, but we only pay attention to it if it's compatible with nBase (e.g. base 16).
{
int cHex; // Actually it might be something other than hex if we are using a screwy base such as 18.
CharT cLower;
// If the base is > 10 and if c is a char that can represent a digit in the base...
if((nBase > 10) && ((cLower = Tolower((CharT)c)) >= 'a') && ((cHex = (10 + (int)cLower - 'a')) < nBase))
c = cHex;
else
{
if(state == kRISReadDigits)
state = kRISEnd;
else
state = kRISError;
break;
}
}
if(nValue > nMaxValueCheck)
bIntegerOverflow = 1;
nValue *= nBase;
EA_ASSERT(c >= 0);
if((unsigned)c > (nMaxValue - nValue))
bIntegerOverflow = 1;
nValue += c;
state = kRISReadDigits;
c = pReadFunction(kReadActionRead, 0, pContext);
nReadCount++;
break;
}
} // switch()
} // while()
// Return the final char back to the stream. It will typically be a 0 (nul terminator) char.
pReadFunction(kReadActionUnread, c, pContext);
} // if()
if(state & kRISSuccess)
nReadCount += nSpaceCount - 1; // -1 because we un-read the last char above.
else
{
nValue = 0;
nReadCount = 0;
}
return nValue;
}
double ReadDouble(ReadFunctionT pReadFunction, void* pContext,
int nMaxFieldWidth, int cDecimalPoint, int& nReadCount, int& bOverflow)
{
int c;
DoubleValue doubleValue; // The string representation of the value, to be converted to actual value.
double dValue = 0.0;
int nSpaceCount = 0;
int nSignCount = 0; // There's supposed to be just zero or one of these.
int nFieldCount = 0;
int nExponent = 0;
int nExponentAdd = 0;
bool bNegative = false;
bool bExponentNegative = false;
ReadDoubleState state = kRDSLeadingSpace;
const int kRDSDone = kRDSError | kRDSEnd;
const int kRDSSuccess = kRDSSignificandLeading | kRDSIntegerDigits |
kRDSFractionLeading | kRDSFractionDigits |
kRDSExponentLeading | kRDSExponentDigits |
kRDSEnd;
nReadCount = 0;
bOverflow = 0;
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
while((c != kReadError) && (nFieldCount <= nMaxFieldWidth) && !(state & kRDSDone))
{
switch((int)state)
{
case kRDSLeadingSpace:
{
if(Isspace((CharT)c))
{
c = pReadFunction(kReadActionRead, 0, pContext);
nSpaceCount++;
break;
}
switch(c)
{
case '-':
bNegative = true;
// Fall through
case '+':
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
nSignCount++;
break;
case 'i': // Start of an "INF" or "INFINITY" sequence.
case 'I':
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSInfinity;
break;
case 'n': // Start of an "NAN" or "NAN(...)" sequence.
case 'N':
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSNAN;
break;
default:
state = kRDSSignificandBegin;
break;
}
break;
}
case kRDSSignificandBegin:
{
if(c == cDecimalPoint) // If there is no significand but there is instead the fraction-starting '.' char...
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSFractionBegin;
}
else if(c == '0')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSSignificandLeading; // We eat leading zeroes.
}
else if(Isdigit((CharT)c))
state = kRDSIntegerDigits;
else
state = kRDSError;
break;
}
case kRDSSignificandLeading:
{
if(c == '0')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
else
state = kRDSIntegerDigits;
break;
}
case kRDSIntegerDigits:
{
if(Isdigit((CharT)c))
{
if(doubleValue.mSigLen < kMaxSignificandDigits) // If we have any more room...
doubleValue.mSigStr[doubleValue.mSigLen++] = (char)c;
else
nExponentAdd++; // Lose significant digits but increase the exponent multiplier, so that the final result is close intended value, though chopped.
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
else
{
if(c == cDecimalPoint)
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSFractionDigits;
}
else
state = kRDSSignificandEnd;
}
break;
}
case kRDSFractionBegin:
{
if(Isdigit((CharT)c))
state = kRDSFractionDigits;
else
state = kRDSError;
break;
}
case kRDSFractionDigits:
{
if(Isdigit((CharT)c))
{
if(doubleValue.mSigLen < kMaxSignificandDigits) // If we have any more room...
{
nExponentAdd--; // Fractional digits reduce our multiplier.
if((c != '0') || doubleValue.mSigLen)
doubleValue.mSigStr[doubleValue.mSigLen++] = (char)c;
} // Else lose the remaining fractional part.
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
else
state = kRDSSignificandEnd;
break;
}
case kRDSSignificandEnd:
{
if(Toupper((CharT)c) == 'E')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSExponentBegin;
break;
}
state = kRDSEnd;
break;
}
case kRDSExponentBegin:
{
if(c == '+')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
else if(c == '-')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
bExponentNegative = 1;
}
state = kRDSExponentBeginDigits;
break;
}
case kRDSExponentBeginDigits:
{
if(c == '0')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
state = kRDSExponentLeading;
}
else if(Isdigit((CharT)c))
state = kRDSExponentDigits;
else
state = kRDSError;
break;
}
case kRDSExponentLeading:
{
if(c == '0')
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
else
state = kRDSExponentDigits;
break;
}
case kRDSExponentDigits:
{
if(Isdigit((CharT)c))
{
nExponent = (nExponent * 10) + (c - '0');
if(nExponent > kMaxDoubleExponent)
bOverflow = 1;
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
else
state = kRDSEnd;
break;
}
case kRDSInfinity:
{
// The C99 Standard specifies that we accept "INF" or "INFINITY", ignoring case.
int i = 1; // We have already matched the first 'I' char.
while((i < 8) && ((int)Toupper((CharT)c) == (int)"INFINITY"[i]))
{
i++;
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
}
if((i == 3) || (i == 8))
{
if(bNegative)
dValue = -kFloat64Infinity;
else
dValue = kFloat64Infinity;
nReadCount = nSpaceCount + nSignCount + i;
return dValue; // Question: Do some FPUs refuse to accept infinity as-is?
}
else
state = kRDSError;
break;
}
case kRDSNAN:
{
// The C99 Standard specifies that we accept "NAN" or "NAN(n-char-sequence)", ignoring case. The n-char-sequence is implementation-defined but is a string specifying a particular NAN.
int i = 1; // We have already matched the first 'N' char.
int j = 0;
//CharT pNANString[24]; pNANString[0] = 0;
while((i < 4) && ((int)Toupper((CharT)c) == (int)"NAN("[i]))
{
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
i++;
}
if((i == 3) || (i == 4))
{
if(i == 4)
{
while((j < 32) && (Isdigit((CharT)c) || Isalpha((CharT)c)))
{
//pNANString[j] = (char16_t)c;
c = pReadFunction(kReadActionRead, 0, pContext);
nFieldCount++;
j++;
}
if(c != ')')
{
state = kRDSError;
break;
}
else
j++;
}
// We currently ignore pNANString. To consider: Support some explicit NAN types based on the content of pNANString.
//pNANString[j] = 0;
if(bNegative)
dValue = -kFloat64NAN;
else
dValue = kFloat64NAN;
nReadCount = nSpaceCount + nSignCount + i + j;
return dValue; // Question: Don't some FPUs refuse to accept NANs as-is?
}
else
state = kRDSError;
break;
}
} // switch()
} // while()
// Return the final char back to the stream. It will typically be a 0 (nul terminator) char.
pReadFunction(kReadActionUnread, c, pContext);
if(state & kRDSSuccess)
{
nFieldCount--;
nReadCount = nFieldCount + nSpaceCount;
}
else
{
nFieldCount = 0;
nReadCount = 0;
}
if(bExponentNegative)
nExponent = -nExponent;
// We've got a mSigStr/mExponent that is something like "123"/0 for the case of "123"
// We've got a mSigStr/mExponent that is something like "123456"/-3 for the case of "123.456"
// We remove trailing zeroes until we have at most just one trailing zero.
int i = doubleValue.mSigLen - 1;
while((i > 0) && (doubleValue.mSigStr[i] == '0'))
{
nExponentAdd++;
i--;
}
if(i >= 0)
doubleValue.mSigLen = (int16_t)(i + 1);
else
{
// In this case we have no significand or a significand of all zeroes.
// Thus we have zero with some exponent, and the result is always zero,
// even if we had some kind of apparent exponent overflow.
bOverflow = false;
return bNegative ? -0.0 : 0.0; // Usage of -0.0 requires that precise floating point be enabled under VC++. We ensure that above via EA_ENABLE_PRECISE_FP().
// Alternative processing:
// doubleValue.mSigLen = 1;
// doubleValue.mSigStr[0] = '0'; // Leave nExponent as it is, which is probably zero.
}
doubleValue.mExponent = (int16_t)(nExponent + nExponentAdd);
if((doubleValue.mExponent < kMinDoubleExponent) || (doubleValue.mExponent > kMaxDoubleExponent))
bOverflow = 1;
// Note that it's still possible to have overflow if the exponent is present and less than max.
if(bOverflow)
{
if(bExponentNegative) // If the value is very small, return zero.
return 0.0;
else
{
// We don't set errno here; instead we set it in the caller.
// errno = ERANGE; // C99 standard, section 7.20.1.3-10
if(bNegative) // If the value is a very large negative value...
return -EASTDC_HUGE_VAL; // The C99 Standard (7.20.1.3-10) specifies that we return HUGE_VAL in the case of overflow.
else // Else the value is a very large positive value.
return EASTDC_HUGE_VAL;
}
}
dValue = doubleValue.ToDouble();
if(dValue > DBL_MAX) // If the value is denormalized too big...
{
// We don't set errno here; instead we set it in the caller.
//errno = ERANGE; // C99 standard, section 7.20.1.3-10
bOverflow = 1;
dValue = EASTDC_HUGE_VAL;
}
else if((dValue != 0.0) && (dValue < DBL_MIN)) // If the value is denormalized too small...
{
// We don't set errno here; instead we set it in the caller.
//errno = ERANGE; // C99 standard, section 7.20.1.3-10
bOverflow = 1;
//dValue = 0.0; // "If the result underflows (7.12.1), the functions return a value whose magnitude is no greater than the smallest normalized positive number in the return type; whether errno acquires the value ERANGE is implementation-defined."
}
if(bNegative)
dValue = -dValue;
return dValue;
}
};
bool ReadFormatSpan8(FormatData& fd, int& c, ReadFunction8 pReadFunction, void* pContext, int stringTypeSize, char*& pArgumentCurrent, int& nReadCount)
{
while(fd.mnWidth-- && ((c = pReadFunction(kReadActionRead, 0, pContext)) != -1) && fd.mCharBitmap.Get((char)c))
{
uint8_t c8 = (uint8_t)c; // It's easier to work with uint8_t instead of char, which might be signed.
switch (stringTypeSize)
{
case 1:
*((uint8_t*)pArgumentCurrent) = c8;
++pArgumentCurrent;
break;
case 2:
case 4:
{
if(c8 < 128)
{
if(stringTypeSize == 2)
*((char16_t*)pArgumentCurrent) = c8;
else
*((char32_t*)pArgumentCurrent) = c8;
}
else
{
// We need to convert from UTF8 to UCS here. However, this can be complicated because
// multiple UTF8 chars may correspond to a single UCS char. Luckily, the UTF8 format
// allows us to know how many chars are in a multi-byte sequence based on the char value.
char buffer[7];
const size_t utf8Len = utf8lengthTable[c8];
char16_t c16[2];
char32_t c32[2];
int result;
buffer[0] = (char)c8;
for(size_t i = 1; i < utf8Len; ++i)
{
c = pReadFunction(kReadActionRead, 0, pContext);
if(c < 0)
return false;
++nReadCount;
buffer[i] = (char)c;
}
if(stringTypeSize == 2)
result = Strlcpy(c16, buffer, 2, utf8Len);
else
result = Strlcpy(c32, buffer, 2, utf8Len);
if(result < 0) // If the UTF8 sequence was malformed...
return false;
if(stringTypeSize == 2)
*((char16_t*)pArgumentCurrent) = c16[0];
else
*((char32_t*)pArgumentCurrent) = c32[0];
}
pArgumentCurrent += stringTypeSize;
break;
}
}
++nReadCount;
}
return true;
}
bool ReadFormatSpan16(FormatData& fd, int& c, ReadFunction16 pReadFunction, void* pContext, int stringTypeSize, char*& pArgumentCurrent, int& nReadCount)
{
while (fd.mnWidth-- && ((c = pReadFunction(kReadActionRead, 0, pContext)) != -1) && fd.mCharBitmap.Get((char16_t)c))
{
char16_t c16 = (char16_t)(unsigned)c;
switch (stringTypeSize)
{
case 1:
// We need to convert from UCS2 to UTF8 here. One UCS2 char may convert to
// as many as six UTF8 chars (though usually no more than three).
// This Strlcpy (16 to 8) can never fail.
pArgumentCurrent += Strlcpy((char*)pArgumentCurrent, &c16, 7, 1);
break;
case 2:
*((char16_t*)pArgumentCurrent) = (char16_t)c16;
pArgumentCurrent += sizeof(char16_t);
break;
case 4:
*((char32_t*)pArgumentCurrent) = c16;
pArgumentCurrent += sizeof(char32_t);
break;
}
++nReadCount;
}
return true;
}
bool ReadFormatSpan32(FormatData& fd, int& c, ReadFunction32 pReadFunction, void* pContext, int stringTypeSize, char*& pArgumentCurrent, int& nReadCount)
{
while (fd.mnWidth-- && ((c = pReadFunction(kReadActionRead, 0, pContext)) != -1) && fd.mCharBitmap.Get((char32_t)c))
{
char32_t c32 = (char32_t)(unsigned)c;
switch (stringTypeSize)
{
case 1:
// We need to convert from UCS4 to UTF8 here. One UCS4 char may convert to
// as many as six UTF8 chars (though usually no more than three).
// This Strlcpy (32 to 8) can never fail.
pArgumentCurrent += Strlcpy((char*)pArgumentCurrent, &c32, 7, 1);
break;
case 2:
*((char16_t*)pArgumentCurrent) = (char16_t)c32;
pArgumentCurrent += sizeof(char16_t);
break;
case 4:
*((char32_t*)pArgumentCurrent) = c32;
pArgumentCurrent += sizeof(char32_t);
break;
}
++nReadCount;
}
return true;
}
typedef bool(*ReadFormatSpanFunction8)(FormatData& fd, int& c, ReadFunction8 pReadFunction, void* pContext, int stringTypeSize, char*& pArgumentCurrent, int& nReadCount);
typedef bool(*ReadFormatSpanFunction16)(FormatData& fd, int& c, ReadFunction16 pReadFunction, void* pContext, int stringTypeSize, char*& pArgumentCurrent, int& nReadCount);
typedef bool(*ReadFormatSpanFunction32)(FormatData& fd, int& c, ReadFunction32 pReadFunction, void* pContext, int stringTypeSize, char*& pArgumentCurrent, int& nReadCount);
int VscanfCore(ReadFunction8 pReadFunction8, void* pContext, const char* pFormat, va_list arguments)
{
VscanfUtil<ReadFunction8, ReadFormatSpanFunction8, char> scanner;
return scanner.VscanfCore(pReadFunction8, ReadFormatSpan8, pContext, pFormat, arguments);
}
int VscanfCore(ReadFunction16 pReadFunction16, void* pContext, const char16_t* pFormat, va_list arguments)
{
VscanfUtil<ReadFunction16, ReadFormatSpanFunction16, char16_t> scanner;
return scanner.VscanfCore(pReadFunction16, ReadFormatSpan16, pContext, pFormat, arguments);
}
int VscanfCore(ReadFunction32 pReadFunction32, void* pContext, const char32_t* pFormat, va_list arguments)
{
VscanfUtil<ReadFunction32, ReadFormatSpanFunction32, char32_t> scanner;
return scanner.VscanfCore(pReadFunction32, ReadFormatSpan32, pContext, pFormat, arguments);
}
// Undo the floating point precision statement we made above with EA_ENABLE_PRECISE_FP.
EA_RESTORE_PRECISE_FP()
} // namespace ScanfLocal
} // namespace StdC
} // namespace EA
EA_RESTORE_VC_WARNING()