From e52eb215cb7dcace3a97180dbc744522db98f4d0 Mon Sep 17 00:00:00 2001 From: Graham Dobbins Date: Fri, 31 Mar 2017 00:16:17 -0400 Subject: [PATCH] Add new %'d specifier to format. * src/editfns.c (styled_format): Add the new functionality. * test/src/editfns-tests.el: Add tests for new functionality. * doc/lispref/strings.texi: Document new specifier. --- doc/lispref/strings.texi | 16 ++++++ etc/NEWS | 5 ++ src/editfns.c | 125 ++++++++++++++++++++++++++++++++++++++++++++-- test/src/editfns-tests.el | 14 ++++++ 4 files changed, 157 insertions(+), 3 deletions(-) diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index ae2b31c541..9d5786e26b 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -1030,8 +1030,24 @@ Formatting Strings If both @samp{-} and @samp{0} are present, the @samp{0} flag is ignored. + The flag @samp{'} only has defined behavior when combined with the address@hidden specifier. It causes the number to be printed in groups with +width determined by @code{format-digit-grouping} and separated by the +character @code{format-digit-separator}. This action takes place +before any padding is applied. This flag is typically used to achieve +more human readable representations of numbers such as address@hidden"1,234,567"}. + @example @group +(format "%'d" 123456789) + @result{} "123,456,789" + +(let ((format-digit-grouping 4) + (format-digit-separator ?.)) + (format "%'d" 123456789)) + @result{} "1.2345.6789" + (format "%06d is padded on the left with zeros" 123) @result{} "000123 is padded on the left with zeros" diff --git a/etc/NEWS b/etc/NEWS index cd98f53399..5105904687 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1055,6 +1055,11 @@ its window gets deleted by 'delete-other-windows'. *** New command 'window-swap-states' swaps the states of two live windows. ++++ +*** 'format' now accepts the specifier "%'d" to format integers into +groups of size 'format-digit-grouping' with separator +'format-digit-separator' which default to 3 and comma respectively. + * Changes in Emacs 26.1 on Non-Free Operating Systems diff --git a/src/editfns.c b/src/editfns.c index 2dafd8e7b1..eb7d1a4829 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -3884,7 +3884,7 @@ specifiers, as follows: %character -where flags is [+ #-0]+, width is [0-9]+, and precision is a literal +where flags is [+ #-0\\=']+, width is [0-9]+, and precision is a literal period "." followed by [0-9]+ The + flag character inserts a + before any positive number, while a @@ -3900,6 +3900,12 @@ the precision is zero; for %g, it causes a decimal point to be included even if the the precision is zero, and also forces trailing zeros after the decimal point to be left in place. +The behavior of the \\=' flag is only defined for %d. The argument is +printed in groupings whose size is dictated by `format-digit-grouping' +and are separated by `format-digit-separator'. This is typically used +to print more human readable representations of numbers like +"1,000." This action takes place before any padding is done. + The width specifier supplies a lower limit for the length of the printed representation. The padding, if any, normally goes on the left, but it goes on the right if the - flag is present. The padding @@ -4042,7 +4048,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) where - flags ::= [-+0# ]+ + flags ::= [-+0# ']+ field-width ::= [0-9]+ precision ::= '.' [0-9]* @@ -4059,6 +4065,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) bool space_flag = false; bool sharp_flag = false; bool zero_flag = false; + bool apos_flag = false; for (; ; format++) { @@ -4069,6 +4076,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) case ' ': space_flag = true; continue; case '#': sharp_flag = true; continue; case '0': zero_flag = true; continue; + case '\'': apos_flag = true; continue; } break; } @@ -4274,7 +4282,10 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) precision is no more than USEFUL_PRECISION_MAX. On all practical hosts, %f is the worst case. */ SPRINTF_BUFSIZE = - sizeof "-." + (LDBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX, + max (sizeof "-." + (LDBL_MAX_10_EXP + 1) + USEFUL_PRECISION_MAX, + (INT_BUFSIZE_BOUND (printmax_t) + + (INT_STRLEN_BOUND (printmax_t) - 2) + * MAX_MULTIBYTE_LENGTH)), /* Length of pM (that is, of pMd without the trailing "d"). */ @@ -4381,6 +4392,100 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message) } } sprintf_bytes = sprintf (sprintf_buf, convspec, prec, x); + + if (apos_flag) + { + CHECK_CHARACTER (Vformat_digit_separator); + int separator = XFASTINT (Vformat_digit_separator); + + int separator_size = CHAR_BYTES (separator); + if (separator_size != 1 && !multibyte) + { + multibyte = true; + goto retry; + } + + ptrdiff_t beg_pos = 0; + for (; !('0' <= sprintf_buf[beg_pos] + && sprintf_buf[beg_pos] <= '9'); + ++beg_pos); + + Lisp_Object real_grouping = Vformat_digit_grouping; + EMACS_INT grouping; + ptrdiff_t separator_count; + + if (CONSP (real_grouping)) + { + EMACS_INT temp_bytes = sprintf_bytes; + EMACS_INT temp_grouping; + separator_count = 0; + FOR_EACH_TAIL (real_grouping) + { + CHECK_RANGED_INTEGER (XCAR (real_grouping), + 1, MOST_POSITIVE_FIXNUM); + temp_grouping = XINT (XCAR (real_grouping)); + if (temp_bytes > beg_pos + temp_grouping) + { + temp_bytes -= temp_grouping; + ++separator_count; + if (NILP (XCDR (real_grouping))) + { + separator_count + += ((temp_bytes - beg_pos - 1) + / temp_grouping); + } + } + else + break; + } + real_grouping = Vformat_digit_grouping; + grouping = XINT (XCAR (real_grouping)); + } + else + { + CHECK_RANGED_INTEGER (real_grouping, + 1, MOST_POSITIVE_FIXNUM); + grouping = XINT (real_grouping); + separator_count = (sprintf_bytes - beg_pos - 1) / grouping; + } + separator_count *= separator_size; + + EMACS_INT group_count = -2; + + for (ptrdiff_t i = sprintf_bytes + 1; i > beg_pos; --i) + { + sprintf_buf[i + separator_count] = sprintf_buf[i]; + ++group_count; + + if (group_count == grouping) + { + separator_count -= separator_size; + + if (separator_size == 1) + sprintf_buf[i + separator_count] = separator; + else + { + CHAR_STRING (separator, + (unsigned char *) + (sprintf_buf + i + separator_count)); + nchars -= (separator_size - 1); + } + + if (CONSP (real_grouping) + && !NILP (XCDR (real_grouping))) + { + real_grouping = XCDR (real_grouping); + CHECK_RANGED_INTEGER + (XCAR (real_grouping), + 1, MOST_POSITIVE_FIXNUM); + grouping = XINT (XCAR (real_grouping)); + } + + group_count = 0; + sprintf_bytes += separator_size; + } + } + } } else { @@ -5171,6 +5276,20 @@ functions if all the text being accessed has this property. */); DEFVAR_LISP ("operating-system-release", Voperating_system_release, doc: /* The release of the operating system Emacs is running on. */); + DEFVAR_LISP ("format-digit-grouping", + Vformat_digit_grouping, + doc: /* Number of digits in each group of a formatted +number in `format'. If a list, the first number applies to the least +significant grouping and so on, with the last number applying to all +remaining groupings. */); + Vformat_digit_grouping = make_number (3); + + DEFVAR_LISP ("format-digit-separator", + Vformat_digit_separator, + doc: /* Character to use as grouping separator for +formatted numbers in `format'. */); + Vformat_digit_separator = make_number (','); + defsubr (&Spropertize); defsubr (&Schar_equal); defsubr (&Sgoto_char); diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el index 14124ef85f..231c72ead6 100644 --- a/test/src/editfns-tests.el +++ b/test/src/editfns-tests.el @@ -136,4 +136,18 @@ transpose-test-get-byte-positions (ert-deftest format-c-float () (should-error (format "%c" 0.5))) +(ert-deftest format-quote-d () + (let ((format-digit-grouping 3) + (format-digit-separator ?,)) + (should (string= (format "%'d" 123456789) "123,456,789")) + (should (string= (format "%+'d" 123456789) "+123,456,789")) + (should (string= (format "% 'd" 123456789) " 123,456,789")) + (should (string= (format "%0'15d" 123456789) "0000123,456,789")) + (let ((format-digit-grouping 1)) + (should (string= (format "%'d" -123456789) "-1,2,3,4,5,6,7,8,9"))) + (let ((format-digit-grouping '(3 2))) + (should (string= (format "%'d" 123456789) "12,34,56,789"))) + (let ((format-digit-separator ?Ĭ)) + (should (string= (format "%'d" 123456789) "123Ĭ456Ĭ789"))))) + ;;; editfns-tests.el ends here -- 2.12.1