vcscanf: parse floating point numbers

The vcscanf function attempts to recognize the floating point number
and then uses the strtod function to calculate its numeric value.
It begins by recognizing an optional plus or minus sign, followed by
either hexadecimal notation or NaN and infinity constants.
Then it looks for an optional integer part, a point or comma,
a fractional part, and an optional exponent symbol.
If it finds an exponent, it recognizes the optional sign and digits.

Every time a character is accepted, it is also buffered.
The accumulated buffer is passed to strtod when recognition is finished
and the output is assigned to the output pointer of appropriate length.
This only happens if not in discard mode.
Finally, the buffer is freed and its state is reset.

References:

https://en.cppreference.com/w/c/string/byte/strtof
This commit is contained in:
Matheus Afonso Martins Moreira 2023-11-02 06:29:15 -03:00
parent a2225a2d85
commit 99c5006ea9

View file

@ -332,6 +332,150 @@ int __vcscanf(int callback(void *), //
goto Done;
}
continue;
ConsumeFloatingPointNumber:
if (c == '+' || c == '-') {
c = BUFFER;
}
bool hexadecimal = false;
if (c == '0') {
c = BUFFER;
if (c == 'x' || c == 'X') {
c = BUFFER;
hexadecimal = true;
goto BufferFloatingPointNumber;
} else if (c == -1) {
fp = strtod((char *) buf, NULL);
goto GotFloatingPointNumber;
} else {
goto BufferFloatingPointNumber;
}
} else if (c == 'n' || c == 'N') {
c = BUFFER;
if (c == 'a' || c == 'A') {
c = BUFFER;
if (c == 'n' || c == 'N') {
c = BUFFER;
if (c == '(') {
c = BUFFER;
do {
bool isdigit = c >= '0' && c <= '9';
bool isletter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
if (!(c == '_' || isdigit || isletter)) {
goto Done;
}
} while ((c = BUFFER) != -1 && c != ')');
fp = strtod((char *) buf, NULL);
goto GotFloatingPointNumber;
} else {
fp = strtod((char *) buf, NULL);
goto GotFloatingPointNumber;
}
} else {
goto Done;
}
} else {
goto Done;
}
} else if (c == 'i' || c == 'I') {
c = BUFFER;
if (c == 'n' || c == 'N') {
c = BUFFER;
if (c == 'f' || c == 'F') {
c = BUFFER;
if (c == 'i' || c == 'I') {
c = BUFFER;
if (c == 'n' || c == 'N') {
c = BUFFER;
if (c == 'i' || c == 'I') {
c = BUFFER;
if (c == 't' || c == 'T') {
c = BUFFER;
if (c == 'y' || c == 'Y') {
} else {
goto Done;
}
} else {
goto Done;
}
} else {
goto Done;
}
} else {
goto Done;
}
} else {
if (c != -1 && unget) {
unget(c, arg);
}
fp = strtod((char *) buf, NULL);
goto GotFloatingPointNumber;
}
} else {
goto Done;
}
} else {
goto Done;
}
}
BufferFloatingPointNumber:
enum { INTEGER, FRACTIONAL, SIGN, EXPONENT } state = INTEGER;
do {
bool isdecdigit = c >= '0' && c <= '9';
bool ishexdigit = (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
bool ispoint = c == '.' || c == ',';
bool isdecexp = c == 'e' || c == 'E';
bool ishexp = c == 'p' || c == 'P';
bool issign = c == '+' || c == '-';
switch (state) {
case INTEGER:
case FRACTIONAL:
if (isdecdigit || (hexadecimal && ishexdigit)) {
goto Continue;
} else if (state == INTEGER && ispoint) {
state = FRACTIONAL;
goto Continue;
} else if (isdecexp || (hexadecimal && ishexp)) {
state = SIGN;
goto Continue;
} else goto Break;
case SIGN:
if (issign) {
state = EXPONENT;
goto Continue;
}
state = EXPONENT;
// fallthrough
case EXPONENT:
if (isdecdigit) {
goto Continue;
} else goto Break;
default:
goto Break;
}
Continue:
continue;
Break:
if (c != -1 && unget) {
unget(c, arg);
}
break;
} while ((c = BUFFER) != -1);
fp = strtod((char *)buf, NULL);
GotFloatingPointNumber:
if (!discard) {
++items;
void *out = va_arg(va, void *);
if (charbytes == sizeof(char)) {
*(float *)out = (float)fp;
} else {
*(double *)out = (double)fp;
}
}
free(buf);
buf = NULL;
bufcur = bufsize = 0;
continue;
ReportConsumed:
n_ptr = va_arg(va, int *);
*n_ptr = consumed - 1; // minus lookahead