From 6b7edbf14be8b18e0781559345edda7346a1e363 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 31 May 2017 22:09:39 -0700 Subject: [PATCH 2/2] Minor improvements to format field numbers * src/editfns.c (styled_format): Allow field numbers in a %% spec. No need for a special diagnostic for field numbers greater than PTRDIFF_MAX. Reword diagnostic for field 0. * test/src/editfns-tests.el (format-with-field): Adjust to match. --- doc/lispref/strings.texi | 119 +++++++++++++++++++++------------------------- etc/NEWS | 2 +- src/editfns.c | 56 ++++++++-------------- test/src/editfns-tests.el | 10 ++-- 4 files changed, 79 insertions(+), 108 deletions(-) diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 9bf52f2..7577410 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -864,15 +864,6 @@ Formatting Strings (format "%s" @var{arbitrary-string}) @end example - If @var{string} contains more than one format specification and none -of the format specifications contain an explicit field number, the -format specifications correspond to successive values from address@hidden Thus, the first format specification in @var{string} -uses the first such value, the second format specification uses the -second such value, and so on. Any extra format specifications (those -for which there are no corresponding values) cause an error. Any -extra values to be formatted are ignored. - Certain format specifications require values of particular types. If you supply a value that doesn't fit the requirements, an error is signaled. @@ -962,68 +953,33 @@ Formatting Strings @end group @end example + By default, format specifications correspond to successive values from address@hidden Thus, the first format specification in @var{string} +uses the first such value, the second format specification uses the +second such value, and so on. Any extra format specifications (those +for which there are no corresponding values) cause an error. Any +extra values to be formatted are ignored. + @cindex field number - A specification can have a @dfn{field number}, which is a decimal -number after the initial @samp{%}, followed by a literal dollar sign address@hidden If you provide a field numer, then the argument to be -printed corresponds to the given field number instead of the next -argument. Field numbers start at 1. + A format specification can have a @dfn{field number}, which is a +decimal number immediately after the initial @samp{%}, followed by a +literal dollar sign @samp{$}. It causes the format specification to +convert the argument with the given number instead of the next +argument. Argument 1 is the argument just after the format. -You can mix specifications with and without field numbers. A + You can mix specifications with and without field numbers. A specification without a field number that follows a specification with a field number will convert the argument after the one specified by the field number: @example -(format "First argument %2$s, then %s, then %1$s" 1 2 3) - @result{} "First argument 2, then 3, then 1" address@hidden example - -You can't use field numbers in a @samp{%%} specification. - address@hidden field width address@hidden padding - A specification can have a @dfn{width}, which is a decimal number -between the @samp{%} and the specification character. If the printed -representation of the object contains fewer characters than this -width, @code{format} extends it with padding. The width specifier is -ignored for the @samp{%%} specification. Any padding introduced by -the width specifier normally consists of spaces inserted on the left: - address@hidden -(format "%5d is padded on the left with spaces" 123) - @result{} " 123 is padded on the left with spaces" address@hidden example - address@hidden -If the width is too small, @code{format} does not truncate the -object's printed representation. Thus, you can use a width to specify -a minimum spacing between columns with no risk of losing information. -In the following two examples, @samp{%7s} specifies a minimum width -of 7. In the first case, the string inserted in place of @samp{%7s} -has only 3 letters, and needs 4 blank spaces as padding. In the -second case, the string @code{"specification"} is 13 letters wide but -is not truncated. - address@hidden address@hidden -(format "The word '%7s' has %d letters in it." - "foo" (length "foo")) - @result{} "The word ' foo' has 3 letters in it." -(format "The word '%7s' has %d letters in it." - "specification" (length "specification")) - @result{} "The word 'specification' has 13 letters in it." address@hidden group +(format "Argument %2$s, then %s, then %1$s" "x" "y" "z") + @result{} "Argument y, then z, then x" @end example -If you want to use both a field number and a width, place the field -number before the width. For example, in @samp{%2$7s}, @samp{2} is -the field number and @samp{7} is the width. - @cindex flags in format specifications - After the @samp{%} and before the optional width specifier, you can -also put certain @dfn{flag characters}. The flag characters need to -come directly after a potential field number. + After the @samp{%} and any field number, you can put certain address@hidden characters}. The flag @samp{+} inserts a plus sign before a positive number, so that it always has a sign. A space character as flag inserts a space @@ -1048,8 +1004,8 @@ Formatting Strings These specification characters accept the @samp{0} flag, but still pad with @emph{spaces}. - The flag @samp{-} causes the padding inserted by the width -specifier, if any, to be inserted on the right rather than the left. + The flag @samp{-} causes any padding inserted by the width, +if specified, to be inserted on the right rather than the left. If both @samp{-} and @samp{0} are present, the @samp{0} flag is ignored. @@ -1067,9 +1023,44 @@ Formatting Strings @end group @end example address@hidden field width address@hidden padding + A specification can have a @dfn{width}, which is a decimal number +that appears after any field number and flags. If the printed +representation of the object contains fewer characters than this +width, @code{format} extends it with padding. The width is +ignored for the @samp{%%} specification. Any padding introduced by +the width normally consists of spaces inserted on the left: + address@hidden +(format "%5d is padded on the left with spaces" 123) + @result{} " 123 is padded on the left with spaces" address@hidden example + address@hidden +If the width is too small, @code{format} does not truncate the +object's printed representation. Thus, you can use a width to specify +a minimum spacing between columns with no risk of losing information. +In the following two examples, @samp{%7s} specifies a minimum width +of 7. In the first case, the string inserted in place of @samp{%7s} +has only 3 letters, and needs 4 blank spaces as padding. In the +second case, the string @code{"specification"} is 13 letters wide but +is not truncated. + address@hidden address@hidden +(format "The word '%7s' has %d letters in it." + "foo" (length "foo")) + @result{} "The word ' foo' has 3 letters in it." +(format "The word '%7s' has %d letters in it." + "specification" (length "specification")) + @result{} "The word 'specification' has 13 letters in it." address@hidden group address@hidden example + @cindex precision in format specifications All the specification characters allow an optional @dfn{precision} -before the character (after the width, if present). The precision is +after the field number, flags and width, if present. The precision is a decimal-point @samp{.} followed by a digit-string. For the floating-point specifications (@samp{%e} and @samp{%f}), the precision specifies how many digits following the decimal point to diff --git a/etc/NEWS b/etc/NEWS index e31c0c7..dfe81cd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -365,7 +365,7 @@ large integers from being displayed as characters. libraries: 'find-library-other-window' and 'find-library-other-frame'. ** You can now provide explicit field numbers in format specifiers. -For example, '(format "%2$s %1$s" 1 2)' produces "2 1". +For example, '(format "%2$s %1$s" "X" "Y")' produces "Y X". * Editing Changes in Emacs 26.1 diff --git a/src/editfns.c b/src/editfns.c index 44341ce..98187df 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -4046,9 +4046,8 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) field-width ::= [0-9]+ precision ::= '.' [0-9]* - If a field-number is specified, it specifies the argument - number to substitute. Otherwise, the next argument is - taken. + If present, a field-number specifies the argument number + to substitute. Otherwise, the next argument is taken. If a field-width is specified, it specifies to which width the output should be padded with blanks, if the output @@ -4058,28 +4057,20 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) digits to print after the '.' for floats, or the max. number of chars to print from a string. */ - char *field_end; - uintmax_t raw_field = strtoumax (format, &field_end, 10); - bool has_field = false; - if (c_isdigit (*format) && *field_end == '$') - { - if (raw_field < 1 || raw_field >= PTRDIFF_MAX) - { - /* doprnt doesn't support %.*s, so we need to copy - the field number string. */ - ptrdiff_t length = field_end - format; - eassert (length > 0); - eassert (length < PTRDIFF_MAX); - char *field = SAFE_ALLOCA (length + 1); - memcpy (field, format, length); - field[length] = '\0'; - error ("Invalid field number `%s'", field); - } - has_field = true; - /* n is incremented below. */ - n = raw_field - 1; - format = field_end + 1; - } + uintmax_t num; + char *num_end; + if (c_isdigit (*format)) + { + num = strtoumax (format, &num_end, 10); + if (*num_end == '$') + { + if (num == 0) + error ("Invalid format field number 0"); + n = min (num, PTRDIFF_MAX); + n--; + format = num_end + 1; + } + } bool minus_flag = false; bool plus_flag = false; @@ -4104,11 +4095,10 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) space_flag &= ! plus_flag; zero_flag &= ! minus_flag; - char *num_end; - uintmax_t raw_field_width = strtoumax (format, &num_end, 10); - if (max_bufsize <= raw_field_width) + num = strtoumax (format, &num_end, 10); + if (max_bufsize <= num) string_overflow (); - ptrdiff_t field_width = raw_field_width; + ptrdiff_t field_width = num; bool precision_given = *num_end == '.'; uintmax_t precision = (precision_given @@ -4123,13 +4113,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) memset (&discarded[format0 - format_start], 1, format - format0 - (conversion == '%')); if (conversion == '%') - { - if (has_field) - /* FIXME: `error' doesn't appear to support `%%'. */ - error ("Field number specified together with `%c' conversion", - '%'); - goto copy_char; - } + goto copy_char; ++n; if (! (n < nargs)) diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el index f76c6c9..c5923aa 100644 --- a/test/src/editfns-tests.el +++ b/test/src/editfns-tests.el @@ -186,13 +186,9 @@ transpose-test-get-byte-positions (should (equal (should-error (format "a %999999$s b" 11)) '(error "Not enough arguments for format string"))) (should (equal (should-error (format "a %$s b" 11)) - ;; FIXME: there shouldn't be two % in the error - ;; string! - '(error "Invalid format operation %%$"))) + '(error "Invalid format operation %$"))) (should (equal (should-error (format "a %0$s b" 11)) - '(error "Invalid field number `0'"))) - (should (equal - (should-error (format "a %1$% %s b" 11)) - '(error "Field number specified together with `%' conversion")))) + '(error "Invalid format field number 0"))) + (should (equal (format "a %1$% %s b" 11) "a % 11 b"))) ;;; editfns-tests.el ends here -- 2.9.4