diff options
| author | Philipp Stephani | 2017-06-01 00:09:43 +0200 |
|---|---|---|
| committer | Philipp Stephani | 2017-06-02 00:22:13 +0200 |
| commit | 0dd1bbb0bb228acab21b8e16f2f2a0b5a17b19ab (patch) | |
| tree | 279fbd070724c1d04945b69db32eb69957274e72 | |
| parent | 404273aeacba39833ae3a38ce6764cc7a636e9d9 (diff) | |
| download | emacs-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.texi | 31 | ||||
| -rw-r--r-- | etc/NEWS | 3 | ||||
| -rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 11 | ||||
| -rw-r--r-- | src/editfns.c | 55 | ||||
| -rw-r--r-- | test/src/editfns-tests.el | 18 |
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 |
| 868 | of the format specifications contain an explicit field number, the | ||
| 868 | format specifications correspond to successive values from | 869 | format 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} |
| 870 | uses the first such value, the second format specification uses the | 871 | uses 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 | ||
| 967 | number after the initial @samp{%}, followed by a literal dollar sign | ||
| 968 | @samp{$}. If you provide a field number, then the argument to be | ||
| 969 | printed corresponds to the given field number instead of the next | ||
| 970 | argument. Field numbers start at 1. | ||
| 971 | |||
| 972 | You can mix specifications with and without field numbers. A | ||
| 973 | specification without a field number that follows a specification with | ||
| 974 | a field number will convert the argument after the one specified by | ||
| 975 | the 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 | |||
| 982 | You 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 | ||
| 1019 | If you want to use both a field number and a width, place the field | ||
| 1020 | number before the width. For example, in @samp{%2$7s}, @samp{2} is | ||
| 1021 | the 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 |
| 1001 | specifier, you can also put certain @dfn{flag characters}. | 1025 | also put certain @dfn{flag characters}. The flag characters need to |
| 1026 | come 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 |
| 1004 | that it always has a sign. A space character as flag inserts a space | 1029 | that it always has a sign. A space character as flag inserts a space |
| @@ -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 |
| 369 | display of raw bytes from octal to hex. | 369 | display of raw bytes from octal to hex. |
| 370 | 370 | ||
| 371 | ** You can now provide explicit field numbers in format specifiers. | ||
| 372 | For 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. | |||
| 3856 | The other arguments are substituted into it to make the result, a string. | 3857 | The other arguments are substituted into it to make the result, a string. |
| 3857 | 3858 | ||
| 3858 | The format control string may contain %-sequences meaning to substitute | 3859 | The format control string may contain %-sequences meaning to substitute |
| 3859 | the next available argument: | 3860 | the 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: | |||
| 3873 | The argument used for %d, %o, %x, %e, %f, %g or %c must be a number. | 3874 | The argument used for %d, %o, %x, %e, %f, %g or %c must be a number. |
| 3874 | Use %% to put a single % into the output. | 3875 | Use %% to put a single % into the output. |
| 3875 | 3876 | ||
| 3876 | A %-sequence may contain optional flag, width, and precision | 3877 | A %-sequence may contain optional field number, flag, width, and |
| 3877 | specifiers, as follows: | 3878 | precision specifiers, as follows: |
| 3878 | 3879 | ||
| 3879 | %<flags><width><precision>character | 3880 | %<field><flags><width><precision>character |
| 3880 | 3881 | ||
| 3881 | where flags is [+ #-0]+, width is [0-9]+, and precision is a literal | 3882 | where field is [0-9]+ followed by a literal dollar "$", flags is |
| 3882 | period "." followed by [0-9]+ | 3883 | [+ #-0]+, width is [0-9]+, and precision is a literal period "." |
| 3884 | followed by [0-9]+. | ||
| 3885 | |||
| 3886 | If field is given, it must be a one-based argument number; the given | ||
| 3887 | argument is substituted instead of the next one. | ||
| 3883 | 3888 | ||
| 3884 | The + flag character inserts a + before any positive number, while a | 3889 | The + flag character inserts a + before any positive number, while a |
| 3885 | space inserts a space before any positive number; these flags only | 3890 | space 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 |