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;
}