aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Stephani2017-06-01 00:09:43 +0200
committerPhilipp Stephani2017-06-02 00:22:13 +0200
commit0dd1bbb0bb228acab21b8e16f2f2a0b5a17b19ab (patch)
tree279fbd070724c1d04945b69db32eb69957274e72
parent404273aeacba39833ae3a38ce6764cc7a636e9d9 (diff)
downloademacs-0dd1bbb0bb228acab21b8e16f2f2a0b5a17b19ab.tar.gz
emacs-0dd1bbb0bb228acab21b8e16f2f2a0b5a17b19ab.zip
Implement field numbers in format strings
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.
-rw-r--r--doc/lispref/strings.texi31
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/emacs-lisp/bytecomp.el11
-rw-r--r--src/editfns.c55
-rw-r--r--test/src/editfns-tests.el18
5 files changed, 104 insertions, 14 deletions
diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
index 9436a96ead4..526b1fb4ebc 100644
--- a/doc/lispref/strings.texi
+++ b/doc/lispref/strings.texi
@@ -864,7 +864,8 @@ below, as the first argument, and the string as the second, like this:
864 (format "%s" @var{arbitrary-string}) 864 (format "%s" @var{arbitrary-string})
865@end example 865@end example
866 866
867 If @var{string} contains more than one format specification, the 867 If @var{string} contains more than one format specification and none
868of the format specifications contain an explicit field number, the
868format specifications correspond to successive values from 869format specifications correspond to successive values from
869@var{objects}. Thus, the first format specification in @var{string} 870@var{objects}. Thus, the first format specification in @var{string}
870uses the first such value, the second format specification uses the 871uses the first such value, the second format specification uses the
@@ -961,6 +962,25 @@ operation} error.
961@end group 962@end group
962@end example 963@end example
963 964
965@cindex field numbers in format spec
966 A specification can have a @dfn{field number}, which is a decimal
967number after the initial @samp{%}, followed by a literal dollar sign
968@samp{$}. If you provide a field number, then the argument to be
969printed corresponds to the given field number instead of the next
970argument. Field numbers start at 1.
971
972You can mix specifications with and without field numbers. A
973specification without a field number that follows a specification with
974a field number will convert the argument after the one specified by
975the field number:
976
977@example
978(format "First argument %2$s, then %s, then %1$s" 1 2 3)
979 @result{} "First argument 2, then 3, then 1"
980@end example
981
982You can't use field numbers in a @samp{%%} specification.
983
964@cindex field width 984@cindex field width
965@cindex padding 985@cindex padding
966 A specification can have a @dfn{width}, which is a decimal number 986 A specification can have a @dfn{width}, which is a decimal number
@@ -996,9 +1016,14 @@ is not truncated.
996@end group 1016@end group
997@end example 1017@end example
998 1018
1019If you want to use both a field number and a width, place the field
1020number before the width. For example, in @samp{%2$7s}, @samp{2} is
1021the field number and @samp{7} is the width.
1022
999@cindex flags in format specifications 1023@cindex flags in format specifications
1000 Immediately after the @samp{%} and before the optional width 1024 After the @samp{%} and before the optional width specifier, you can
1001specifier, you can also put certain @dfn{flag characters}. 1025also put certain @dfn{flag characters}. The flag characters need to
1026come directly after a potential field number.
1002 1027
1003 The flag @samp{+} inserts a plus sign before a positive number, so 1028 The flag @samp{+} inserts a plus sign before a positive number, so
1004that it always has a sign. A space character as flag inserts a space 1029that it always has a sign. A space character as flag inserts a space
diff --git a/etc/NEWS b/etc/NEWS
index 055de8ca9e8..1b098f98425 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -368,6 +368,9 @@ libraries: 'find-library-other-window' and 'find-library-other-frame'.
368** The new variable 'display-raw-bytes-as-hex' allows to change the 368** The new variable 'display-raw-bytes-as-hex' allows to change the
369display of raw bytes from octal to hex. 369display of raw bytes from octal to hex.
370 370
371** You can now provide explicit field numbers in format specifiers.
372For example, '(format "%2$s %1$s" 1 2)' produces "2 1".
373
371 374
372* Editing Changes in Emacs 26.1 375* Editing Changes in Emacs 26.1
373 376
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 12a7d4afc2a..e5b9b47b1d0 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -1375,10 +1375,15 @@ extra args."
1375 (let ((nfields (with-temp-buffer 1375 (let ((nfields (with-temp-buffer
1376 (insert (nth 1 form)) 1376 (insert (nth 1 form))
1377 (goto-char (point-min)) 1377 (goto-char (point-min))
1378 (let ((n 0)) 1378 (let ((i 0) (n 0))
1379 (while (re-search-forward "%." nil t) 1379 (while (re-search-forward "%." nil t)
1380 (unless (eq ?% (char-after (1+ (match-beginning 0)))) 1380 (backward-char)
1381 (setq n (1+ n)))) 1381 (unless (eq ?% (char-after))
1382 (setq i (if (looking-at "\\([0-9]+\\)\\$")
1383 (string-to-number (match-string 1) 10)
1384 (1+ i))
1385 n (max n i)))
1386 (forward-char))
1382 n))) 1387 n)))
1383 (nargs (- (length form) 2))) 1388 (nargs (- (length form) 2)))
1384 (unless (= nargs nfields) 1389 (unless (= nargs nfields)
diff --git a/src/editfns.c b/src/editfns.c
index 89a67241044..44341cef2d3 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -48,6 +48,7 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
48#include <float.h> 48#include <float.h>
49#include <limits.h> 49#include <limits.h>
50 50
51#include <c-ctype.h>
51#include <intprops.h> 52#include <intprops.h>
52#include <stdlib.h> 53#include <stdlib.h>
53#include <strftime.h> 54#include <strftime.h>
@@ -3856,7 +3857,7 @@ The first argument is a format control string.
3856The other arguments are substituted into it to make the result, a string. 3857The other arguments are substituted into it to make the result, a string.
3857 3858
3858The format control string may contain %-sequences meaning to substitute 3859The format control string may contain %-sequences meaning to substitute
3859the next available argument: 3860the next available argument, or the argument explicitly specified:
3860 3861
3861%s means print a string argument. Actually, prints any object, with `princ'. 3862%s means print a string argument. Actually, prints any object, with `princ'.
3862%d means print as signed number in decimal. 3863%d means print as signed number in decimal.
@@ -3873,13 +3874,17 @@ the next available argument:
3873The argument used for %d, %o, %x, %e, %f, %g or %c must be a number. 3874The argument used for %d, %o, %x, %e, %f, %g or %c must be a number.
3874Use %% to put a single % into the output. 3875Use %% to put a single % into the output.
3875 3876
3876A %-sequence may contain optional flag, width, and precision 3877A %-sequence may contain optional field number, flag, width, and
3877specifiers, as follows: 3878precision specifiers, as follows:
3878 3879
3879 %<flags><width><precision>character 3880 %<field><flags><width><precision>character
3880 3881
3881where flags is [+ #-0]+, width is [0-9]+, and precision is a literal 3882where field is [0-9]+ followed by a literal dollar "$", flags is
3882period "." followed by [0-9]+ 3883[+ #-0]+, width is [0-9]+, and precision is a literal period "."
3884followed by [0-9]+.
3885
3886If field is given, it must be a one-based argument number; the given
3887argument is substituted instead of the next one.
3883 3888
3884The + flag character inserts a + before any positive number, while a 3889The + flag character inserts a + before any positive number, while a
3885space inserts a space before any positive number; these flags only 3890space inserts a space before any positive number; these flags only
@@ -4032,14 +4037,19 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4032 { 4037 {
4033 /* General format specifications look like 4038 /* General format specifications look like
4034 4039
4035 '%' [flags] [field-width] [precision] format 4040 '%' [field-number] [flags] [field-width] [precision] format
4036 4041
4037 where 4042 where
4038 4043
4044 field-number ::= [0-9]+ '$'
4039 flags ::= [-+0# ]+ 4045 flags ::= [-+0# ]+
4040 field-width ::= [0-9]+ 4046 field-width ::= [0-9]+
4041 precision ::= '.' [0-9]* 4047 precision ::= '.' [0-9]*
4042 4048
4049 If a field-number is specified, it specifies the argument
4050 number to substitute. Otherwise, the next argument is
4051 taken.
4052
4043 If a field-width is specified, it specifies to which width 4053 If a field-width is specified, it specifies to which width
4044 the output should be padded with blanks, if the output 4054 the output should be padded with blanks, if the output
4045 string is shorter than field-width. 4055 string is shorter than field-width.
@@ -4048,6 +4058,29 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4048 digits to print after the '.' for floats, or the max. 4058 digits to print after the '.' for floats, or the max.
4049 number of chars to print from a string. */ 4059 number of chars to print from a string. */
4050 4060
4061 char *field_end;
4062 uintmax_t raw_field = strtoumax (format, &field_end, 10);
4063 bool has_field = false;
4064 if (c_isdigit (*format) && *field_end == '$')
4065 {
4066 if (raw_field < 1 || raw_field >= PTRDIFF_MAX)
4067 {
4068 /* doprnt doesn't support %.*s, so we need to copy
4069 the field number string. */
4070 ptrdiff_t length = field_end - format;
4071 eassert (length > 0);
4072 eassert (length < PTRDIFF_MAX);
4073 char *field = SAFE_ALLOCA (length + 1);
4074 memcpy (field, format, length);
4075 field[length] = '\0';
4076 error ("Invalid field number `%s'", field);
4077 }
4078 has_field = true;
4079 /* n is incremented below. */
4080 n = raw_field - 1;
4081 format = field_end + 1;
4082 }
4083
4051 bool minus_flag = false; 4084 bool minus_flag = false;
4052 bool plus_flag = false; 4085 bool plus_flag = false;
4053 bool space_flag = false; 4086 bool space_flag = false;
@@ -4090,7 +4123,13 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
4090 memset (&discarded[format0 - format_start], 1, 4123 memset (&discarded[format0 - format_start], 1,
4091 format - format0 - (conversion == '%')); 4124 format - format0 - (conversion == '%'));
4092 if (conversion == '%') 4125 if (conversion == '%')
4093 goto copy_char; 4126 {
4127 if (has_field)
4128 /* FIXME: `error' doesn't appear to support `%%'. */
4129 error ("Field number specified together with `%c' conversion",
4130 '%');
4131 goto copy_char;
4132 }
4094 4133
4095 ++n; 4134 ++n;
4096 if (! (n < nargs)) 4135 if (! (n < nargs))
diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el
index 8019eb03838..f76c6c9fd36 100644
--- a/test/src/editfns-tests.el
+++ b/test/src/editfns-tests.el
@@ -177,4 +177,22 @@
177 (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" nil 177 (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" nil
178 (concat (make-string 2048 ?X) "0"))))) 178 (concat (make-string 2048 ?X) "0")))))
179 179
180(ert-deftest format-with-field ()
181 (should (equal (format "First argument %2$s, then %s, then %1$s" 1 2 3)
182 "First argument 2, then 3, then 1"))
183 (should (equal (format "a %2$s %d %1$d %2$S %d %d b" 11 "22" 33 44)
184 "a 22 33 11 \"22\" 33 44 b"))
185 (should (equal (format "a %08$s %s b" 1 2 3 4 5 6 7 8 9) "a 8 9 b"))
186 (should (equal (should-error (format "a %999999$s b" 11))
187 '(error "Not enough arguments for format string")))
188 (should (equal (should-error (format "a %$s b" 11))
189 ;; FIXME: there shouldn't be two % in the error
190 ;; string!
191 '(error "Invalid format operation %%$")))
192 (should (equal (should-error (format "a %0$s b" 11))
193 '(error "Invalid field number `0'")))
194 (should (equal
195 (should-error (format "a %1$% %s b" 11))
196 '(error "Field number specified together with `%' conversion"))))
197
180;;; editfns-tests.el ends here 198;;; editfns-tests.el ends here