Skip to content

Commit

Permalink
Floating point parsing support for scanf family (#924)
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusmoreira authored Nov 18, 2023
1 parent 8caf1b4 commit 3ac473d
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 4 deletions.
31 changes: 31 additions & 0 deletions examples/parsefloat.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include <stdio.h>

#define PARSE_AND_PRINT(type, scan_fmt, print_fmt, str) \
do { \
type val; int ret; \
ret = sscanf(str, scan_fmt, &val); \
printf("\"%s\" => " print_fmt " = %d\n", str, val, ret); \
} while (0)

int main()
{
PARSE_AND_PRINT(float, "%f", "%f", "0.3715");
PARSE_AND_PRINT(float, "%f", "%f", ".3715");
PARSE_AND_PRINT(float, "%f", "%f", "3715");
PARSE_AND_PRINT(float, "%f", "%f", "111.11");
PARSE_AND_PRINT(float, "%f", "%f", "-2.22");
PARSE_AND_PRINT(float, "%f", "%f", "Nan");
PARSE_AND_PRINT(float, "%f", "%f", "nAn(2)");
PARSE_AND_PRINT(float, "%f", "%f", "-NAN(_asdfZXCV1234_)");
PARSE_AND_PRINT(float, "%f", "%f", "-nan");
PARSE_AND_PRINT(float, "%f", "%f", "+nan");
PARSE_AND_PRINT(float, "%f", "%f", "inF");
PARSE_AND_PRINT(float, "%f", "%f", "iNfINiTy");
PARSE_AND_PRINT(float, "%f", "%f", "+inf");
PARSE_AND_PRINT(float, "%f", "%f", "-inf");
PARSE_AND_PRINT(float, "%f", "%f", "0X1.BC70A3D70A3D7P+6");
PARSE_AND_PRINT(float, "%f", "%f", "1.18973e+4932zzz");
PARSE_AND_PRINT(float, "%f", "%.10f", " -0.0000000123junk");
PARSE_AND_PRINT(float, "%f", "%f", "junk");
return 0;
}
199 changes: 195 additions & 4 deletions libc/stdio/vcscanf.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "libc/str/tpdecodecb.internal.h"
#include "libc/str/utf16.h"
#include "libc/sysv/errfuns.h"
#include "third_party/gdtoa/gdtoa.h"

#define READ \
({ \
Expand All @@ -35,6 +36,21 @@
c; \
})

#define FP_BUFFER_GROW 48
#define BUFFER \
({ \
int c = READ; \
if (fpbufcur >= fpbufsize - 1) { \
fpbufsize = fpbufsize + FP_BUFFER_GROW; \
fpbuf = realloc(fpbuf, fpbufsize); \
} \
if (c != -1) { \
fpbuf[fpbufcur++] = c; \
fpbuf[fpbufcur] = '\0'; \
} \
c; \
})

/**
* String / file / stream decoder.
*
Expand All @@ -61,6 +77,9 @@ int __vcscanf(int callback(void *), //
struct FreeMe *next;
void *ptr;
} *freeme = NULL;
unsigned char *fpbuf = NULL;
size_t fpbufsize;
size_t fpbufcur;
const unsigned char *p = (const unsigned char *)fmt;
int *n_ptr;
int items = 0;
Expand All @@ -85,8 +104,9 @@ int __vcscanf(int callback(void *), //
break;
case '%': {
uint128_t number;
void *buf;
unsigned char *buf;
size_t bufsize;
double fp;
unsigned width = 0;
unsigned char bits = 32;
unsigned char charbytes = sizeof(char);
Expand Down Expand Up @@ -209,6 +229,27 @@ int __vcscanf(int callback(void *), //
base = 10;
}
goto DecodeNumber;
case 'a':
case 'A':
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G': // floating point number
if (!(charbytes == sizeof(char) || charbytes == sizeof(wchar_t))) {
items = -1;
goto Done;
}
while (isspace(c)) {
c = READ;
}
fpbufsize = FP_BUFFER_GROW;
fpbuf = malloc(fpbufsize);
fpbufcur = 0;
fpbuf[fpbufcur++] = c;
fpbuf[fpbufcur] = '\0';
goto ConsumeFloatingPointNumber;
default:
items = einval();
goto Done;
Expand Down Expand Up @@ -294,6 +335,154 @@ 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) {
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 != ')');
if (c == ')') {
c = BUFFER;
}
goto GotFloatingPointNumber;
} else {
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') {
c = BUFFER;
} else {
goto Done;
}
} else {
goto Done;
}
} else {
goto Done;
}
} else {
goto Done;
}
} else {
if (c != -1 && unget) {
unget(c, arg);
}
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);
GotFloatingPointNumber:
fp = strtod((char *)fpbuf, NULL);
if (!discard) {
++items;
void *out = va_arg(va, void *);
if (charbytes == sizeof(char)) {
*(float *)out = (float)fp;
} else {
*(double *)out = (double)fp;
}
}
free(fpbuf);
fpbuf = NULL;
fpbufcur = fpbufsize = 0;
continue;
ReportConsumed:
n_ptr = va_arg(va, int *);
*n_ptr = consumed - 1; // minus lookahead
Expand Down Expand Up @@ -322,7 +511,7 @@ int __vcscanf(int callback(void *), //
}
if (c != -1 && j + !rawmode < bufsize && (rawmode || !isspace(c))) {
if (charbytes == 1) {
((unsigned char *)buf)[j++] = (unsigned char)c;
buf[j++] = (unsigned char)c;
c = READ;
} else if (tpdecodecb((wint_t *)&c, c, (void *)callback, arg) !=
-1) {
Expand All @@ -344,7 +533,7 @@ int __vcscanf(int callback(void *), //
goto Done;
} else if (!rawmode && j < bufsize) {
if (charbytes == sizeof(char)) {
((unsigned char *)buf)[j] = '\0';
buf[j] = '\0';
} else if (charbytes == sizeof(char16_t)) {
((char16_t *)buf)[j] = u'\0';
} else if (charbytes == sizeof(wchar_t)) {
Expand All @@ -356,8 +545,9 @@ int __vcscanf(int callback(void *), //
}
++items;
if (ismalloc) {
*va_arg(va, char **) = buf;
*va_arg(va, char **) = (void *) buf;
}
buf = NULL;
} else {
do {
if (isspace(c)) break;
Expand All @@ -378,5 +568,6 @@ int __vcscanf(int callback(void *), //
if (items == -1) free(entry->ptr);
free(entry);
}
if (fpbuf) free(fpbuf);
return items;
}
Loading

0 comments on commit 3ac473d

Please sign in to comment.