A field number explicitly specifies the argument to be formatted.
This is especially important for potential localization work, since
grammars of various languages dictate different word orders.
* src/editfns.c (Fformat): Update documentation.
(styled_format): Implement field numbers.
* doc/lispref/strings.texi (Formatting Strings): Document field numbers.
* lisp/emacs-lisp/bytecomp.el (byte-compile-format-warn): Adapt.
* test/src/editfns-tests.el (format-with-field): New unit test.
(format "%s" @var{arbitrary-string})
@end example
- If @var{string} contains more than one format specification, the
+ 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
@var{objects}. Thus, the first format specification in @var{string}
uses the first such value, the second format specification uses the
@end group
@end example
+@cindex field numbers in format spec
+ A specification can have a @dfn{field number}, which is a decimal
+number after the initial @samp{%}, followed by a literal dollar sign
+@samp{$}. If you provide a field number, then the argument to be
+printed corresponds to the given field number instead of the next
+argument. Field numbers start at 1.
+
+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"
+@end example
+
+You can't use field numbers in a @samp{%%} specification.
+
@cindex field width
@cindex padding
A specification can have a @dfn{width}, which is a decimal number
@end group
@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
- Immediately after the @samp{%} and before the optional width
-specifier, you can also put certain @dfn{flag characters}.
+ 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.
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
** The new variable 'display-raw-bytes-as-hex' allows to change the
display of raw bytes from octal to hex.
+** You can now provide explicit field numbers in format specifiers.
+For example, '(format "%2$s %1$s" 1 2)' produces "2 1".
+
\f
* Editing Changes in Emacs 26.1
(let ((nfields (with-temp-buffer
(insert (nth 1 form))
(goto-char (point-min))
- (let ((n 0))
+ (let ((i 0) (n 0))
(while (re-search-forward "%." nil t)
- (unless (eq ?% (char-after (1+ (match-beginning 0))))
- (setq n (1+ n))))
+ (backward-char)
+ (unless (eq ?% (char-after))
+ (setq i (if (looking-at "\\([0-9]+\\)\\$")
+ (string-to-number (match-string 1) 10)
+ (1+ i))
+ n (max n i)))
+ (forward-char))
n)))
(nargs (- (length form) 2)))
(unless (= nargs nfields)
#include <float.h>
#include <limits.h>
+#include <c-ctype.h>
#include <intprops.h>
#include <stdlib.h>
#include <strftime.h>
The other arguments are substituted into it to make the result, a string.
The format control string may contain %-sequences meaning to substitute
-the next available argument:
+the next available argument, or the argument explicitly specified:
%s means print a string argument. Actually, prints any object, with `princ'.
%d means print as signed number in decimal.
The argument used for %d, %o, %x, %e, %f, %g or %c must be a number.
Use %% to put a single % into the output.
-A %-sequence may contain optional flag, width, and precision
-specifiers, as follows:
+A %-sequence may contain optional field number, flag, width, and
+precision specifiers, as follows:
- %<flags><width><precision>character
+ %<field><flags><width><precision>character
-where flags is [+ #-0]+, width is [0-9]+, and precision is a literal
-period "." followed by [0-9]+
+where field is [0-9]+ followed by a literal dollar "$", flags is
+[+ #-0]+, width is [0-9]+, and precision is a literal period "."
+followed by [0-9]+.
+
+If field is given, it must be a one-based argument number; the given
+argument is substituted instead of the next one.
The + flag character inserts a + before any positive number, while a
space inserts a space before any positive number; these flags only
{
/* General format specifications look like
- '%' [flags] [field-width] [precision] format
+ '%' [field-number] [flags] [field-width] [precision] format
where
+ field-number ::= [0-9]+ '$'
flags ::= [-+0# ]+
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 a field-width is specified, it specifies to which width
the output should be padded with blanks, if the output
string is shorter than field-width.
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;
+ }
+
bool minus_flag = false;
bool plus_flag = false;
bool space_flag = false;
memset (&discarded[format0 - format_start], 1,
format - format0 - (conversion == '%'));
if (conversion == '%')
- goto copy_char;
+ {
+ if (has_field)
+ /* FIXME: `error' doesn't appear to support `%%'. */
+ error ("Field number specified together with `%c' conversion",
+ '%');
+ goto copy_char;
+ }
++n;
if (! (n < nargs))
(format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" nil
(concat (make-string 2048 ?X) "0")))))
+(ert-deftest format-with-field ()
+ (should (equal (format "First argument %2$s, then %s, then %1$s" 1 2 3)
+ "First argument 2, then 3, then 1"))
+ (should (equal (format "a %2$s %d %1$d %2$S %d %d b" 11 "22" 33 44)
+ "a 22 33 11 \"22\" 33 44 b"))
+ (should (equal (format "a %08$s %s b" 1 2 3 4 5 6 7 8 9) "a 8 9 b"))
+ (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 %%$")))
+ (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"))))
+
;;; editfns-tests.el ends here