emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] master 0dd1bbb 1/2: Implement field numbers in format stri


From: Philipp Stephani
Subject: [Emacs-diffs] master 0dd1bbb 1/2: Implement field numbers in format strings
Date: Thu, 1 Jun 2017 18:29:59 -0400 (EDT)

branch: master
commit 0dd1bbb0bb228acab21b8e16f2f2a0b5a17b19ab
Author: Philipp Stephani <address@hidden>
Commit: Philipp Stephani <address@hidden>

    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.
---
 doc/lispref/strings.texi    | 31 ++++++++++++++++++++++---
 etc/NEWS                    |  3 +++
 lisp/emacs-lisp/bytecomp.el | 11 ++++++---
 src/editfns.c               | 55 ++++++++++++++++++++++++++++++++++++++-------
 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 9436a96..526b1fb 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:
   (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
@@ -961,6 +962,25 @@ operation} error.
 @end group
 @end example
 
address@hidden 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
address@hidden  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:
+
address@hidden
+(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.
+
 @cindex field width
 @cindex padding
   A specification can have a @dfn{width}, which is a decimal number
@@ -996,9 +1016,14 @@ is not truncated.
 @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
diff --git a/etc/NEWS b/etc/NEWS
index 055de8c..1b098f9 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -368,6 +368,9 @@ libraries: 'find-library-other-window' and 
'find-library-other-frame'.
 ** 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".
+
 
 * Editing Changes in Emacs 26.1
 
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 12a7d4a..e5b9b47 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -1375,10 +1375,15 @@ extra args."
     (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)
diff --git a/src/editfns.c b/src/editfns.c
index 89a6724..44341ce 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/>.  */
 #include <float.h>
 #include <limits.h>
 
+#include <c-ctype.h>
 #include <intprops.h>
 #include <stdlib.h>
 #include <strftime.h>
@@ -3856,7 +3857,7 @@ The first argument is a format control string.
 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.
@@ -3873,13 +3874,17 @@ the next available argument:
 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
@@ -4032,14 +4037,19 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool 
message)
        {
          /* 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.
@@ -4048,6 +4058,29 @@ 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;
+            }
+
          bool minus_flag = false;
          bool  plus_flag = false;
          bool space_flag = false;
@@ -4090,7 +4123,13 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool 
message)
          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))
diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el
index 8019eb0..f76c6c9 100644
--- a/test/src/editfns-tests.el
+++ b/test/src/editfns-tests.el
@@ -177,4 +177,22 @@
            (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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]