diff options
| author | Jacob S. Gordon | 2025-12-21 23:39:42 -0800 |
|---|---|---|
| committer | Paul Eggert | 2025-12-22 00:15:09 -0800 |
| commit | cff022f0c3632c2522e568bd5e96841c1a0271ef (patch) | |
| tree | 69f6254f17aa949d7c4ccd3c6b444f4866873400 /src | |
| parent | 1f7c804ab17a0a5b57cc3b32d44dc340caf92b0e (diff) | |
| download | emacs-cff022f0c3632c2522e568bd5e96841c1a0271ef.tar.gz emacs-cff022f0c3632c2522e568bd5e96841c1a0271ef.zip | |
Add binary format specifications '%b' and '%B'
These produce the binary representation of a number.
'%#b' and '%#B' prefix with '0b' and '0B', respectively.
(bug#79990)
* etc/NEWS: Announce change.
* doc/lispref/strings.texi (Formatting Strings): Describe new format
specs and add to comment on reconstructing the value with 'read'.
* src/editfns.c (format): Update doc string.
(styled_format): Add support for '%b' and '%B'. To remain
portable, avoid use of 'sprintf' by converting by hand.
* test/src/editfns-tests.el (format-binary-zero, format-binary-floats)
(format-binary-nonzero-integers): Add tests.
(read-large-integer): Add binary test cases.
Co-authored-by: Paul Eggert <eggert@cs.ucla.edu>
Diffstat (limited to 'src')
| -rw-r--r-- | src/editfns.c | 54 |
1 files changed, 42 insertions, 12 deletions
diff --git a/src/editfns.c b/src/editfns.c index b5ba3bde7f9..117722e0e09 100644 --- a/src/editfns.c +++ b/src/editfns.c | |||
| @@ -3349,6 +3349,8 @@ the next available argument, or the argument explicitly specified: | |||
| 3349 | 3349 | ||
| 3350 | %s means produce a string argument. Actually, produces any object with `princ'. | 3350 | %s means produce a string argument. Actually, produces any object with `princ'. |
| 3351 | %d or %i means produce a signed number in decimal. | 3351 | %d or %i means produce a signed number in decimal. |
| 3352 | %b means produce a number in binary. | ||
| 3353 | %B is like %b, but %#B uses upper case. | ||
| 3352 | %o means produce a number in octal. | 3354 | %o means produce a number in octal. |
| 3353 | %x means produce a number in hex. | 3355 | %x means produce a number in hex. |
| 3354 | %X is like %x, but uses upper case. | 3356 | %X is like %x, but uses upper case. |
| @@ -3382,8 +3384,9 @@ space inserts a space before any nonnegative number; these flags | |||
| 3382 | affect only numeric %-sequences, and the + flag takes precedence. | 3384 | affect only numeric %-sequences, and the + flag takes precedence. |
| 3383 | The - and 0 flags affect the width specifier, as described below. | 3385 | The - and 0 flags affect the width specifier, as described below. |
| 3384 | 3386 | ||
| 3385 | The # flag means to use an alternate display form for %o, %x, %X, %e, | 3387 | The # flag means to use an alternate display form for %b, %B, %o, %x, |
| 3386 | %f, and %g sequences: for %o, it ensures that the result begins with | 3388 | %X, %e, %f, and %g sequences: for %b and %B, it prefixes nonzero results |
| 3389 | with \"0b\" or \"0B\"; for %o, it ensures that the result begins with | ||
| 3387 | \"0\"; for %x and %X, it prefixes nonzero results with \"0x\" or \"0X\"; | 3390 | \"0\"; for %x and %X, it prefixes nonzero results with \"0x\" or \"0X\"; |
| 3388 | for %e and %f, it causes a decimal point to be included even if the | 3391 | for %e and %f, it causes a decimal point to be included even if the |
| 3389 | precision is zero; for %g, it causes a decimal point to be | 3392 | precision is zero; for %g, it causes a decimal point to be |
| @@ -3395,7 +3398,7 @@ produced representation. The padding, if any, normally goes on the | |||
| 3395 | left, but it goes on the right if the - flag is present. The padding | 3398 | left, but it goes on the right if the - flag is present. The padding |
| 3396 | character is normally a space, but it is 0 if the 0 flag is present. | 3399 | character is normally a space, but it is 0 if the 0 flag is present. |
| 3397 | The 0 flag is ignored if the - flag is present, or the format sequence | 3400 | The 0 flag is ignored if the - flag is present, or the format sequence |
| 3398 | is something other than %d, %o, %x, %e, %f, and %g. | 3401 | is something other than %d, %b, %o, %x, %e, %f, and %g. |
| 3399 | 3402 | ||
| 3400 | For %e and %f sequences, the number after the "." in the precision | 3403 | For %e and %f sequences, the number after the "." in the precision |
| 3401 | specifier says how many decimal places to show; if zero, the decimal | 3404 | specifier says how many decimal places to show; if zero, the decimal |
| @@ -3813,8 +3816,9 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 3813 | } | 3816 | } |
| 3814 | else if (! (conversion == 'c' || conversion == 'd' | 3817 | else if (! (conversion == 'c' || conversion == 'd' |
| 3815 | || float_conversion || conversion == 'i' | 3818 | || float_conversion || conversion == 'i' |
| 3816 | || conversion == 'o' || conversion == 'x' | 3819 | || conversion == 'o' |
| 3817 | || conversion == 'X')) | 3820 | || conversion == 'x' || conversion == 'X' |
| 3821 | || conversion == 'b' || conversion == 'B')) | ||
| 3818 | { | 3822 | { |
| 3819 | unsigned char *p = (unsigned char *) format - 1; | 3823 | unsigned char *p = (unsigned char *) format - 1; |
| 3820 | if (multibyte_format) | 3824 | if (multibyte_format) |
| @@ -3865,7 +3869,9 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 3865 | 3869 | ||
| 3866 | /* Characters to be inserted after spaces and before | 3870 | /* Characters to be inserted after spaces and before |
| 3867 | leading zeros. This can occur with bignums, since | 3871 | leading zeros. This can occur with bignums, since |
| 3868 | bignum_to_string does only leading '-'. */ | 3872 | bignum_to_string does only leading '-'. |
| 3873 | It can also occur with the conversion specs %b and %B, | ||
| 3874 | which sprintf might not support. */ | ||
| 3869 | char prefix[sizeof "-0x" - 1]; | 3875 | char prefix[sizeof "-0x" - 1]; |
| 3870 | int prefixlen = 0; | 3876 | int prefixlen = 0; |
| 3871 | 3877 | ||
| @@ -3882,6 +3888,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 3882 | conversions; for example, the min and max macros are | 3888 | conversions; for example, the min and max macros are |
| 3883 | not suitable here. */ | 3889 | not suitable here. */ |
| 3884 | ptrdiff_t sprintf_bytes; | 3890 | ptrdiff_t sprintf_bytes; |
| 3891 | bool nonzero_arg; | ||
| 3885 | if (float_conversion) | 3892 | if (float_conversion) |
| 3886 | { | 3893 | { |
| 3887 | /* Format as a long double if the arg is an integer | 3894 | /* Format as a long double if the arg is an integer |
| @@ -3949,20 +3956,28 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 3949 | bignum_arg: | 3956 | bignum_arg: |
| 3950 | { | 3957 | { |
| 3951 | int base = ((conversion == 'd' || conversion == 'i') ? 10 | 3958 | int base = ((conversion == 'd' || conversion == 'i') ? 10 |
| 3952 | : conversion == 'o' ? 8 : 16); | 3959 | : conversion == 'o' ? 8 |
| 3960 | : conversion == 'x' || conversion == 'X' ? 16 | ||
| 3961 | : 2); | ||
| 3953 | sprintf_bytes = bignum_bufsize (arg, base); | 3962 | sprintf_bytes = bignum_bufsize (arg, base); |
| 3954 | if (sprintf_bytes <= buf + bufsize - p) | 3963 | if (sprintf_bytes <= buf + bufsize - p) |
| 3955 | { | 3964 | { |
| 3956 | int signedbase = conversion == 'X' ? -base : base; | 3965 | int signedbase = conversion == 'X' ? -base : base; |
| 3957 | sprintf_bytes = bignum_to_c_string (p, sprintf_bytes, | 3966 | sprintf_bytes = bignum_to_c_string (p, sprintf_bytes, |
| 3958 | arg, signedbase); | 3967 | arg, signedbase); |
| 3968 | nonzero_arg = true; | ||
| 3969 | preformatted_fixnum_arg: | ||
| 3959 | bool negative = p[0] == '-'; | 3970 | bool negative = p[0] == '-'; |
| 3960 | prec = min (precision, sprintf_bytes - prefixlen); | 3971 | prec = min (precision, sprintf_bytes - prefixlen); |
| 3961 | prefix[prefixlen] = plus_flag ? '+' : ' '; | 3972 | prefix[prefixlen] = plus_flag ? '+' : ' '; |
| 3962 | prefixlen += (plus_flag | space_flag) & !negative; | 3973 | prefixlen += (plus_flag | space_flag) & !negative; |
| 3963 | prefix[prefixlen] = '0'; | 3974 | prefix[prefixlen] = '0'; |
| 3964 | prefix[prefixlen + 1] = conversion; | 3975 | prefix[prefixlen + 1] = conversion; |
| 3965 | prefixlen += sharp_flag && base == 16 ? 2 : 0; | 3976 | prefixlen += ((sharp_flag & nonzero_arg |
| 3977 | && ! (conversion == 'd' | ||
| 3978 | || conversion == 'i' | ||
| 3979 | || conversion == 'o')) | ||
| 3980 | ? 2 : 0); | ||
| 3966 | } | 3981 | } |
| 3967 | } | 3982 | } |
| 3968 | else if (conversion == 'd' || conversion == 'i') | 3983 | else if (conversion == 'd' || conversion == 'i') |
| @@ -4013,6 +4028,20 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4013 | } | 4028 | } |
| 4014 | } | 4029 | } |
| 4015 | p[0] = negative ? '-' : plus_flag ? '+' : ' '; | 4030 | p[0] = negative ? '-' : plus_flag ? '+' : ' '; |
| 4031 | |||
| 4032 | if (conversion == 'b' || conversion == 'B') | ||
| 4033 | { | ||
| 4034 | int bit_width = stdc_bit_width (x); | ||
| 4035 | char *bits = p + negative; | ||
| 4036 | int nbits = bit_width ? bit_width : prec < 0; | ||
| 4037 | for (int i = 0; i < nbits; i++) | ||
| 4038 | bits[i] = ((x >> (nbits - (i + 1))) & 1) + '0'; | ||
| 4039 | bits[nbits] = '\0'; | ||
| 4040 | sprintf_bytes = negative + nbits; | ||
| 4041 | nonzero_arg = x != 0; | ||
| 4042 | goto preformatted_fixnum_arg; | ||
| 4043 | } | ||
| 4044 | |||
| 4016 | bool signedp = negative | plus_flag | space_flag; | 4045 | bool signedp = negative | plus_flag | space_flag; |
| 4017 | sprintf_bytes = sprintf (p + signedp, convspec, prec, x); | 4046 | sprintf_bytes = sprintf (p + signedp, convspec, prec, x); |
| 4018 | sprintf_bytes += signedp; | 4047 | sprintf_bytes += signedp; |
| @@ -4053,12 +4082,13 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) | |||
| 4053 | 4082 | ||
| 4054 | if (convbytes <= buf + bufsize - p) | 4083 | if (convbytes <= buf + bufsize - p) |
| 4055 | { | 4084 | { |
| 4085 | /* Skip any sign and 0[bBxX] prefixes. */ | ||
| 4056 | bool signedp = p[0] == '-' || p[0] == '+' || p[0] == ' '; | 4086 | bool signedp = p[0] == '-' || p[0] == '+' || p[0] == ' '; |
| 4057 | int beglen = (signedp | 4087 | int beglen = (signedp |
| 4058 | + ((p[signedp] == '0' | 4088 | + ((!float_conversion |
| 4059 | && (p[signedp + 1] == 'x' | 4089 | && p[signedp] == '0' |
| 4060 | || p[signedp + 1] == 'X')) | 4090 | && p[signedp + 1] == conversion) |
| 4061 | ? 2 : 0)); | 4091 | ? 2 : 0)); |
| 4062 | eassert (prefixlen == 0 || beglen == 0 | 4092 | eassert (prefixlen == 0 || beglen == 0 |
| 4063 | || (beglen == 1 && p[0] == '-' | 4093 | || (beglen == 1 && p[0] == '-' |
| 4064 | && ! (prefix[0] == '-' || prefix[0] == '+' | 4094 | && ! (prefix[0] == '-' || prefix[0] == '+' |