C2x scanf %b support

ISO C2x defines scanf %b for input of binary integers (with an
optional 0b or 0B prefix).  Implement such support, along with the
corresponding SCNb* macros in <inttypes.h>.  Unlike the support for
binary integers with 0b or 0B prefix with scanf %i, this is supported
in all versions of scanf (independent of the standards mode used for
compilation), because there are no backwards compatibility concerns
(%b wasn't previously a supported format) the way there were for %i.

Tested for x86_64 and x86.
This commit is contained in:
Joseph Myers 2023-06-19 19:40:34 +00:00
parent 5f83b2674e
commit 2d88df5411
5 changed files with 248 additions and 9 deletions

7
NEWS
View File

@ -17,9 +17,12 @@ Major new features:
wcstoull_l, wcstoimax, wcstoumax, wcstoq, wcstouq. Similarly, the wcstoull_l, wcstoimax, wcstoumax, wcstoq, wcstouq. Similarly, the
following functions support binary integers prefixed by 0b or 0B as following functions support binary integers prefixed by 0b or 0B as
input to the %i format: fscanf, scanf, sscanf, vscanf, vsscanf, input to the %i format: fscanf, scanf, sscanf, vscanf, vsscanf,
vfscanf, fwscanf, wscanf, swscanf, vfwscanf, vwscanf, vswscanf. vfscanf, fwscanf, wscanf, swscanf, vfwscanf, vwscanf, vswscanf; those
functions also support the %b format for binary integers, with or
without such a prefix and independent of standards mode.
* PRIb* and PRIB* macros from C2X have been added to <inttypes.h>. * PRIb*, PRIB* and SCNb* macros from C2X have been added to
<inttypes.h>.
* printf-family functions now support the wN format length modifiers for * printf-family functions now support the wN format length modifiers for
arguments of type intN_t, int_leastN_t, uintN_t or uint_leastN_t (for arguments of type intN_t, int_leastN_t, uintN_t or uint_leastN_t (for

View File

@ -3545,6 +3545,10 @@ Matches an optionally signed integer in any of the formats that the C
language defines for specifying an integer constant. @xref{Numeric language defines for specifying an integer constant. @xref{Numeric
Input Conversions}. Input Conversions}.
@item @samp{%b}
Matches an unsigned integer written in binary radix. This is an ISO
C2X feature. @xref{Numeric Input Conversions}.
@item @samp{%o} @item @samp{%o}
Matches an unsigned integer written in octal radix. Matches an unsigned integer written in octal radix.
@xref{Numeric Input Conversions}. @xref{Numeric Input Conversions}.
@ -3652,11 +3656,13 @@ For example, any of the strings @samp{10}, @samp{0xa}, or @samp{012}
could be read in as integers under the @samp{%i} conversion. Each of could be read in as integers under the @samp{%i} conversion. Each of
these specifies a number with decimal value @code{10}. these specifies a number with decimal value @code{10}.
The @samp{%o}, @samp{%u}, and @samp{%x} conversions match unsigned The @samp{%b}, @samp{%o}, @samp{%u}, and @samp{%x} conversions match unsigned
integers in octal, decimal, and hexadecimal radices, respectively. The integers in binary, octal, decimal, and hexadecimal radices, respectively. The
syntax that is recognized is the same as that for the @code{strtoul} syntax that is recognized is the same as that for the @code{strtoul}
function (@pxref{Parsing of Integers}) with the appropriate value function (@pxref{Parsing of Integers}) with the appropriate value
(@code{8}, @code{10}, or @code{16}) for the @var{base} argument. (@code{2}, @code{8}, @code{10}, or @code{16}) for the @var{base}
argument. The @samp{%b} conversion accepts an optional leading
@samp{0b} or @samp{0B} in all standards modes.
The @samp{%X} conversion is identical to the @samp{%x} conversion. They The @samp{%X} conversion is identical to the @samp{%x} conversion. They
both permit either uppercase or lowercase letters to be used as digits. both permit either uppercase or lowercase letters to be used as digits.

View File

@ -16,10 +16,12 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
#include <inttypes.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <wchar.h> #include <wchar.h>
#include <libc-diag.h>
#include <support/check.h> #include <support/check.h>
#include <support/xstdio.h> #include <support/xstdio.h>
@ -176,6 +178,195 @@ one_check (const CHAR *s, int expected, char expected_c)
TEST_COMPARE (ret_c, expected_c); TEST_COMPARE (ret_c, expected_c);
} }
/* GCC does not know the %b format before GCC 12. */
DIAG_PUSH_NEEDS_COMMENT;
#if !__GNUC_PREREQ (12, 0)
DIAG_IGNORE_NEEDS_COMMENT (11, "-Wformat");
DIAG_IGNORE_NEEDS_COMMENT (11, "-Wformat-extra-args");
#endif
static void
one_check_b (const CHAR *s, int expected, char expected_c)
{
int ret;
FILE *fp;
unsigned int ret_i;
unsigned long int ret_l;
unsigned long long int ret_ll;
char ret_c;
fp = xfopen (INFILE, "w");
ret = FNX (fput, s) (s, fp);
TEST_VERIFY_EXIT (0 <= ret);
xfclose (fp);
ret = FNX (s, scanf) (s, L_("%b %c"), &ret_i, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_i, (unsigned int) expected);
TEST_COMPARE (ret_c, expected_c);
fp = xfopen (INFILE, "r");
ret = FNX (f, scanf) (fp, L_("%b %c"), &ret_i, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_i, (unsigned int) expected);
TEST_COMPARE (ret_c, expected_c);
xfclose (fp);
fp = xfreopen (INFILE, "r", stdin);
ret = FNX (, scanf) (L_("%b %c"), &ret_i, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_i, (unsigned int) expected);
TEST_COMPARE (ret_c, expected_c);
ret = wrap_vsscanf (s, L_("%b %c"), &ret_i, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_i, (unsigned int) expected);
TEST_COMPARE (ret_c, expected_c);
fp = xfopen (INFILE, "r");
ret = wrap_vfscanf (fp, L_("%b %c"), &ret_i, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_i, (unsigned int) expected);
TEST_COMPARE (ret_c, expected_c);
xfclose (fp);
fp = xfreopen (INFILE, "r", stdin);
ret = wrap_vscanf (L_("%b %c"), &ret_i, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_i, (unsigned int) expected);
TEST_COMPARE (ret_c, expected_c);
ret = FNX (s, scanf) (s, L_("%lb %c"), &ret_l, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_l, (unsigned long int) expected);
TEST_COMPARE (ret_c, expected_c);
fp = xfopen (INFILE, "r");
ret = FNX (f, scanf) (fp, L_("%lb %c"), &ret_l, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_l, (unsigned long int) expected);
TEST_COMPARE (ret_c, expected_c);
xfclose (fp);
fp = xfreopen (INFILE, "r", stdin);
ret = FNX (, scanf) (L_("%lb %c"), &ret_l, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_l, (unsigned long int) expected);
TEST_COMPARE (ret_c, expected_c);
ret = wrap_vsscanf (s, L_("%lb %c"), &ret_l, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_l, (unsigned long int) expected);
TEST_COMPARE (ret_c, expected_c);
fp = xfopen (INFILE, "r");
ret = wrap_vfscanf (fp, L_("%lb %c"), &ret_l, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_l, (unsigned long int) expected);
TEST_COMPARE (ret_c, expected_c);
xfclose (fp);
fp = xfreopen (INFILE, "r", stdin);
ret = wrap_vscanf (L_("%lb %c"), &ret_l, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_l, (unsigned long int) expected);
TEST_COMPARE (ret_c, expected_c);
ret = FNX (s, scanf) (s, L_("%llb %c"), &ret_ll, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_ll, (unsigned long long int) expected);
TEST_COMPARE (ret_c, expected_c);
fp = xfopen (INFILE, "r");
ret = FNX (f, scanf) (fp, L_("%llb %c"), &ret_ll, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_ll, (unsigned long long int) expected);
TEST_COMPARE (ret_c, expected_c);
xfclose (fp);
fp = xfreopen (INFILE, "r", stdin);
ret = FNX (, scanf) (L_("%llb %c"), &ret_ll, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_ll, (unsigned long long int) expected);
TEST_COMPARE (ret_c, expected_c);
ret = wrap_vsscanf (s, L_("%llb %c"), &ret_ll, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_ll, (unsigned long long int) expected);
TEST_COMPARE (ret_c, expected_c);
fp = xfopen (INFILE, "r");
ret = wrap_vfscanf (fp, L_("%llb %c"), &ret_ll, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_ll, (unsigned long long int) expected);
TEST_COMPARE (ret_c, expected_c);
xfclose (fp);
fp = xfreopen (INFILE, "r", stdin);
ret = wrap_vscanf (L_("%llb %c"), &ret_ll, &ret_c);
TEST_COMPARE (ret, 2);
TEST_COMPARE (ret_ll, (unsigned long long int) expected);
TEST_COMPARE (ret_c, expected_c);
}
#define CHECK_SCNB(TYPE, MACRO, S, EXPECTED, EXPECTED_C) \
do \
{ \
int ret; \
FILE *fp; \
TYPE ret_t; \
char ret_c; \
fp = xfopen (INFILE, "w"); \
ret = FNX (fput, s) (S, fp); \
TEST_VERIFY_EXIT (0 <= ret); \
xfclose (fp); \
ret = FNX (s, scanf) (S, L_("%") MACRO " %c", &ret_t, &ret_c); \
TEST_COMPARE (ret, 2); \
TEST_COMPARE (ret_t, EXPECTED); \
TEST_COMPARE (ret_c, EXPECTED_C); \
fp = xfopen (INFILE, "r"); \
ret = FNX (f, scanf) (fp, L_("%") MACRO " %c", &ret_t, &ret_c); \
TEST_COMPARE (ret, 2); \
TEST_COMPARE (ret_t, EXPECTED); \
TEST_COMPARE (ret_c, EXPECTED_C); \
xfclose (fp); \
fp = xfreopen (INFILE, "r", stdin); \
ret = FNX (, scanf) (L_("%") MACRO " %c", &ret_t, &ret_c); \
TEST_COMPARE (ret, 2); \
TEST_COMPARE (ret_t, EXPECTED); \
TEST_COMPARE (ret_c, EXPECTED_C); \
ret = wrap_vsscanf (S, L_("%") MACRO " %c", &ret_t, &ret_c); \
TEST_COMPARE (ret, 2); \
TEST_COMPARE (ret_t, EXPECTED); \
TEST_COMPARE (ret_c, EXPECTED_C); \
fp = xfopen (INFILE, "r"); \
ret = wrap_vfscanf (fp, L_("%") MACRO " %c", &ret_t, &ret_c); \
TEST_COMPARE (ret, 2); \
TEST_COMPARE (ret_t, EXPECTED); \
TEST_COMPARE (ret_c, EXPECTED_C); \
xfclose (fp); \
fp = xfreopen (INFILE, "r", stdin); \
ret = wrap_vscanf (L_("%") MACRO " %c", &ret_t, &ret_c); \
TEST_COMPARE (ret, 2); \
TEST_COMPARE (ret_t, EXPECTED); \
TEST_COMPARE (ret_c, EXPECTED_C); \
} \
while (0)
static void
one_check_scnb (const CHAR *s, int expected, char expected_c)
{
#if TEST_C2X || defined _GNU_SOURCE
CHECK_SCNB (uint8_t, SCNb8, s, (uint8_t) expected, expected_c);
CHECK_SCNB (uint16_t, SCNb16, s, (uint16_t) expected, expected_c);
CHECK_SCNB (uint32_t, SCNb32, s, (uint32_t) expected, expected_c);
CHECK_SCNB (uint64_t, SCNb64, s, (uint64_t) expected, expected_c);
CHECK_SCNB (uint_least8_t, SCNbLEAST8, s, (uint_least8_t) expected,
expected_c);
CHECK_SCNB (uint_least16_t, SCNbLEAST16, s, (uint_least16_t) expected,
expected_c);
CHECK_SCNB (uint_least32_t, SCNbLEAST32, s, (uint_least32_t) expected,
expected_c);
CHECK_SCNB (uint_least64_t, SCNbLEAST64, s, (uint_least64_t) expected,
expected_c);
CHECK_SCNB (uint_fast8_t, SCNbFAST8, s, (uint_fast8_t) expected, expected_c);
CHECK_SCNB (uint_fast16_t, SCNbFAST16, s, (uint_fast16_t) expected,
expected_c);
CHECK_SCNB (uint_fast32_t, SCNbFAST32, s, (uint_fast32_t) expected,
expected_c);
CHECK_SCNB (uint_fast64_t, SCNbFAST64, s, (uint_fast64_t) expected,
expected_c);
CHECK_SCNB (uintmax_t, SCNbMAX, s, (uintmax_t) expected, expected_c);
CHECK_SCNB (uintptr_t, SCNbPTR, s, (uintptr_t) expected, expected_c);
#endif
}
DIAG_POP_NEEDS_COMMENT;
static int static int
do_test (void) do_test (void)
{ {
@ -183,6 +374,18 @@ do_test (void)
one_check (L_("0B101 x"), 5, 'x'); one_check (L_("0B101 x"), 5, 'x');
one_check (L_("-0b11111 y"), -31, 'y'); one_check (L_("-0b11111 y"), -31, 'y');
one_check (L_("-0B11111 y"), -31, 'y'); one_check (L_("-0B11111 y"), -31, 'y');
one_check_b (L_("0b101 x"), 5, 'x');
one_check_b (L_("0B101 x"), 5, 'x');
one_check_b (L_("-0b11111 y"), -31, 'y');
one_check_b (L_("-0B11111 y"), -31, 'y');
one_check_b (L_("101 x"), 5, 'x');
one_check_b (L_("-11111 y"), -31, 'y');
one_check_scnb (L_("0b101 x"), 5, 'x');
one_check_scnb (L_("0B101 x"), 5, 'x');
one_check_scnb (L_("-0b11111 y"), -31, 'y');
one_check_scnb (L_("-0B11111 y"), -31, 'y');
one_check_scnb (L_("101 x"), 5, 'x');
one_check_scnb (L_("-11111 y"), -31, 'y');
return 0; return 0;
} }

View File

@ -1381,6 +1381,10 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
base = 8; base = 8;
goto number; goto number;
case L_('b'): /* Binary integer. */
base = 2;
goto number;
case L_('u'): /* Unsigned decimal integer. */ case L_('u'): /* Unsigned decimal integer. */
base = 10; base = 10;
goto number; goto number;
@ -1428,10 +1432,11 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
c = inchar (); c = inchar ();
} }
} }
else if ((mode_flags & SCANF_ISOC23_BIN_CST) != 0 else if (width != 0
&& base == 0 && TOLOWER (c) == L_('b')
&& width != 0 && (base == 2
&& TOLOWER (c) == L_('b')) || ((mode_flags & SCANF_ISOC23_BIN_CST) != 0
&& base == 0)))
{ {
base = 2; base = 2;
if (width > 0) if (width > 0)

View File

@ -302,6 +302,28 @@ typedef wchar_t __gwchar_t;
# define SCNxPTR __PRIPTR_PREFIX "x" # define SCNxPTR __PRIPTR_PREFIX "x"
/* Binary notation. */
# if __GLIBC_USE (ISOC2X)
# define SCNb8 "hhb"
# define SCNb16 "hb"
# define SCNb32 "b"
# define SCNb64 __PRI64_PREFIX "b"
# define SCNbLEAST8 "hhb"
# define SCNbLEAST16 "hb"
# define SCNbLEAST32 "b"
# define SCNbLEAST64 __PRI64_PREFIX "b"
# define SCNbFAST8 "hhb"
# define SCNbFAST16 __PRIPTR_PREFIX "b"
# define SCNbFAST32 __PRIPTR_PREFIX "b"
# define SCNbFAST64 __PRI64_PREFIX "b"
# define SCNbMAX __PRI64_PREFIX "b"
# define SCNbPTR __PRIPTR_PREFIX "b"
# endif
__BEGIN_DECLS __BEGIN_DECLS
#if __WORDSIZE == 64 #if __WORDSIZE == 64