I noticed that OpenBSD's fscanf doesn't yet support hex float strings, which are standardized in C99. I am using them in my application (which I would like to support OpenBSD), since the "%a" format specifier is a convenient way to preserve the exact floating point value.
strtod already supports parsing hex floats, so it is just the scanner in __svfscanf that needed changes. The implementation reuses the PFXOK and NZDIGITS flags from CT_INT scanning and follows similar logic to CT_INT. This required allocating new flag values for DPTOK and EXPOK. I did my best to follow style(9), but since the indentation level of this switch is so high, I found it difficult wrap lines nicely. I noticed that several existing lines broke the "space around binary operators" rule if the added space would require unnatural wrapping, so I did the same here. I wasn't sure which comments I should carry over from the CT_INT case (for example, above `case 'x':`), or if any of the other changes require additional comments. Please let me know if they do. diff --git lib/libc/stdio/vfscanf.c lib/libc/stdio/vfscanf.c index 5fb55d99e61..87134a9ef86 100644 --- lib/libc/stdio/vfscanf.c +++ lib/libc/stdio/vfscanf.c @@ -66,19 +66,19 @@ /* * The following are used in numeric conversions only: - * SIGNOK, HAVESIGN, NDIGITS, DPTOK, and EXPOK are for floating point; - * SIGNOK, HAVESIGN, NDIGITS, PFXOK, and NZDIGITS are for integral. + * DPTOK and EXPOK are for floating point; + * SIGNOK, HAVESIGN, NDIGITS, PFXOK, and NZDIGITS are for integral and floating + * point. */ #define SIGNOK 0x01000 /* +/- is (still) legal */ #define HAVESIGN 0x02000 /* sign detected */ #define NDIGITS 0x04000 /* no digits detected */ - -#define DPTOK 0x08000 /* (float) decimal point is still legal */ -#define EXPOK 0x10000 /* (float) exponent (e+3, etc) still legal */ - #define PFXOK 0x08000 /* 0x prefix is (still) legal */ #define NZDIGITS 0x10000 /* no zero digits detected */ +#define DPTOK 0x20000 /* (float) decimal point is still legal */ +#define EXPOK 0x40000 /* (float) exponent (e+3, etc) still legal */ + /* * Conversion types. */ @@ -770,7 +770,8 @@ literal: width = sizeof(buf) - 2; width++; #endif - flags |= SIGNOK | NDIGITS | DPTOK | EXPOK; + flags |= SIGNOK | NDIGITS | NZDIGITS | DPTOK | EXPOK; + base = 10; for (p = buf; width; width--) { c = *fp->_p; /* @@ -779,15 +780,36 @@ literal: */ switch (c) { - case '0': case '1': case '2': case '3': + case '0': + if ((flags&(NZDIGITS|NDIGITS|DPTOK)) == + (NZDIGITS|NDIGITS|DPTOK)) + flags |= PFXOK; + else + flags &= ~PFXOK; + flags &= + ~(SIGNOK | NZDIGITS | NDIGITS); + goto fok; + + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - flags &= ~(SIGNOK | NDIGITS); + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto fok; + + /* letters ok iff hex */ + case 'A': case 'B': case 'C': + case 'D': case 'F': + case 'a': case 'b': case 'c': + case 'd': case 'f': + if (base == 10) + break; /* not legal here */ + flags &= ~(SIGNOK | PFXOK | NDIGITS); goto fok; case '+': case '-': if (flags & SIGNOK) { flags &= ~SIGNOK; + flags |= HAVESIGN; goto fok; } break; @@ -799,11 +821,35 @@ literal: break; case 'e': case 'E': /* no exponent without some digits */ - if ((flags&(NDIGITS|EXPOK)) == EXPOK) { + if (base == 10 && + (flags&(NDIGITS|EXPOK)) == EXPOK) { flags = (flags & ~(EXPOK|DPTOK)) | SIGNOK | NDIGITS; goto fok; + } else if (base == 16) { + flags &= + ~(SIGNOK|PFXOK|NDIGITS); + goto fok; + } + break; + case 'p': case 'P': + /* no exponent without some digits */ + if (base == 16 && + (flags&(NDIGITS|EXPOK)) == EXPOK) { + flags = + (flags & ~(EXPOK|DPTOK)) | + SIGNOK | NDIGITS; + base = 10; + goto fok; + } + break; + case 'x': case 'X': + if ((flags & PFXOK) && p == + buf + 1 + !!(flags & HAVESIGN)) { + base = 16; + flags &= ~PFXOK; + goto fok; } break; }