Reduce integer-output-format to print-integers-as-characters

The variable now only controls whether characters are printed, not
the radix.  Control chars are printed in human-readable syntax
only when special escapes such as ?\n are available.  Spaces,
formatting and combining chars are excluded (bug#44155).
Done in collaboration with Juri Linkov.

* src/character.c (graphic_base_p):
* src/print.c (named_escape): New functions.
(print_object): Change semantics as described above.
(syms_of_print): Rename integer-output-format.  Update doc string.
* doc/lispref/streams.texi (Output Variables):
* etc/NEWS:
* test/src/print-tests.el (print-integers-as-characters):
Rename and update according to new semantics.  The test now passes.
This commit is contained in:
Mattias Engdegård 2020-11-02 23:37:16 +01:00
parent 527413fb2f
commit 0c5eb1c7e7
6 changed files with 102 additions and 52 deletions

View file

@ -903,10 +903,16 @@ in the C function @code{sprintf}. For further restrictions on what
you can use, see the variable's documentation string.
@end defvar
@defvar integer-output-format
This variable specifies how to print integer numbers. The default is
@code{nil}, meaning use the decimal format. When bound to @code{t},
print integers as characters when an integer represents a character
(@pxref{Basic Char Syntax}). When bound to the number @code{16},
print non-negative integers in the hexadecimal format.
@defvar print-integers-as-characters
When this variable is non-@code{nil}, integers that represent
graphic base characters will be printed using Lisp character syntax
(@pxref{Basic Char Syntax}). Other numbers are printed the usual way.
For example, the list @code{(4 65 -1 10)} would be printed as
@samp{(4 ?A -1 ?\n)}.
More precisely, values printed in character syntax are those
representing characters belonging to the Unicode general categories
Letter, Number, Punctuation, Symbol and Private-use
(@pxref{Character Properties}), as well as the control characters
having their own escape syntax such as newline.
@end defvar

View file

@ -1711,12 +1711,6 @@ ledit.el, lmenu.el, lucid.el and old-whitespace.el.
* Lisp Changes in Emacs 28.1
** New variable 'integer-output-format' determines how to print integer values.
When this variable is bound to the value 't', integers are printed by
printing functions as characters when an integer represents a character.
When bound to the number 16, non-negative integers are printed in the
hexadecimal format.
+++
** 'define-globalized-minor-mode' now takes a ':predicate' parameter.
This can be used to control which major modes the minor mode should be
@ -1909,6 +1903,11 @@ file can affect code in another. For details, see the manual section
'replace-regexp-in-string', 'catch', 'throw', 'error', 'signal'
and 'play-sound-file'.
+++
** New variable 'print-integers-as-characters' modifies integer printing.
If this variable is non-nil, character syntax is used for printing
numbers when this makes sense, such as '?A' for 65.
* Changes in Emacs 28.1 on Non-Free Operating Systems

View file

@ -982,6 +982,27 @@ printablep (int c)
|| gen_cat == UNICODE_CATEGORY_Cn)); /* unassigned */
}
/* Return true if C is graphic character that can be printed independently. */
bool
graphic_base_p (int c)
{
Lisp_Object category = CHAR_TABLE_REF (Vunicode_category_table, c);
if (! FIXNUMP (category))
return false;
EMACS_INT gen_cat = XFIXNUM (category);
return (!(gen_cat == UNICODE_CATEGORY_Mn /* mark, nonspacing */
|| gen_cat == UNICODE_CATEGORY_Mc /* mark, combining */
|| gen_cat == UNICODE_CATEGORY_Me /* mark, enclosing */
|| gen_cat == UNICODE_CATEGORY_Zs /* separator, space */
|| gen_cat == UNICODE_CATEGORY_Zl /* separator, line */
|| gen_cat == UNICODE_CATEGORY_Zp /* separator, paragraph */
|| gen_cat == UNICODE_CATEGORY_Cc /* other, control */
|| gen_cat == UNICODE_CATEGORY_Cs /* other, surrogate */
|| gen_cat == UNICODE_CATEGORY_Cf /* other, format */
|| gen_cat == UNICODE_CATEGORY_Cn)); /* other, unassigned */
}
/* Return true if C is a horizontal whitespace character, as defined
by https://www.unicode.org/reports/tr18/tr18-19.html#blank. */
bool

View file

@ -583,6 +583,7 @@ extern bool alphanumericp (int);
extern bool graphicp (int);
extern bool printablep (int);
extern bool blankp (int);
extern bool graphic_base_p (int);
/* Look up the element in char table OBJ at index CH, and return it as
an integer. If the element is not a character, return CH itself. */

View file

@ -1848,6 +1848,24 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
return true;
}
static char
named_escape (int i)
{
switch (i)
{
case '\b': return 'b';
case '\t': return 't';
case '\n': return 'n';
case '\f': return 'f';
case '\r': return 'r';
case ' ': return 's';
/* \a, \v, \e and \d are excluded from printing as escapes since
they are somewhat rare as characters and more likely to be
plain integers. */
}
return 0;
}
static void
print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
{
@ -1908,29 +1926,30 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
{
case_Lisp_Int:
{
int c;
intmax_t i;
EMACS_INT i = XFIXNUM (obj);
char escaped_name;
if (EQ (Vinteger_output_format, Qt) && CHARACTERP (obj)
&& (c = XFIXNUM (obj)))
if (print_integers_as_characters && i >= 0 && i <= MAX_UNICODE_CHAR
&& ((escaped_name = named_escape (i))
|| graphic_base_p (i)))
{
printchar ('?', printcharfun);
if (escapeflag
&& (c == ';' || c == '(' || c == ')' || c == '{' || c == '}'
|| c == '[' || c == ']' || c == '\"' || c == '\'' || c == '\\'))
if (escaped_name)
{
printchar ('\\', printcharfun);
i = escaped_name;
}
else if (escapeflag
&& (i == ';' || i == '\"' || i == '\'' || i == '\\'
|| i == '(' || i == ')'
|| i == '{' || i == '}'
|| i == '[' || i == ']'))
printchar ('\\', printcharfun);
printchar (c, printcharfun);
}
else if (INTEGERP (Vinteger_output_format)
&& integer_to_intmax (Vinteger_output_format, &i)
&& i == 16 && !NILP (Fnatnump (obj)))
{
int len = sprintf (buf, "#x%"pI"x", (EMACS_UINT) XFIXNUM (obj));
strout (buf, len, len, printcharfun);
printchar (i, printcharfun);
}
else
{
int len = sprintf (buf, "%"pI"d", XFIXNUM (obj));
int len = sprintf (buf, "%"pI"d", i);
strout (buf, len, len, printcharfun);
}
}
@ -2270,12 +2289,13 @@ A value of nil means to use the shortest notation
that represents the number without losing information. */);
Vfloat_output_format = Qnil;
DEFVAR_LISP ("integer-output-format", Vinteger_output_format,
doc: /* The format used to print integers.
When t, print characters from integers that represent a character.
When a number 16, print non-negative integers in the hexadecimal format.
Otherwise, by default print integers in the decimal format. */);
Vinteger_output_format = Qnil;
DEFVAR_BOOL ("print-integers-as-characters", print_integers_as_characters,
doc: /* Non-nil means integers are printed using characters syntax.
Only independent graphic characters, and control characters with named
escape sequences such as newline, are printed this way. Other
integers, including those corresponding to raw bytes, are printed
as numbers the usual way. */);
print_integers_as_characters = Qnil;
DEFVAR_LISP ("print-length", Vprint_length,
doc: /* Maximum length of list to print before abbreviating.

View file

@ -383,25 +383,28 @@ otherwise, use a different charset."
(let ((print-length 1))
(format "%S" h))))))
(print-tests--deftest print-integer-output-format ()
(print-tests--deftest print-integers-as-characters ()
;; Bug#44155.
(let ((integer-output-format t)
(syms (list ?? ?\; ?\( ?\) ?\{ ?\} ?\[ ?\] ?\" ?\' ?\\ )))
(should (equal (read (print-tests--prin1-to-string syms)) syms))
(should (equal (print-tests--prin1-to-string syms)
(concat "(" (mapconcat #'prin1-char syms " ") ")"))))
(let ((integer-output-format t)
(syms (list -1 0 1 ?\120 4194175 4194176 (max-char) (1+ (max-char)))))
(should (equal (read (print-tests--prin1-to-string syms)) syms)))
(let ((integer-output-format 16)
(syms (list -1 0 1 most-positive-fixnum (1+ most-positive-fixnum))))
(should (equal (read (print-tests--prin1-to-string syms)) syms))
(should (equal (print-tests--prin1-to-string syms)
(concat "(" (mapconcat
(lambda (i)
(if (and (>= i 0) (<= i most-positive-fixnum))
(format "#x%x" i) (format "%d" i)))
syms " ") ")")))))
(let* ((print-integers-as-characters t)
(chars '(?? ?\; ?\( ?\) ?\{ ?\} ?\[ ?\] ?\" ?\' ?\\ ?f ?~ 32
?\n ?\r ?\t ?\b ?\f ?\a ?\v ?\e ?\d))
(nums '(-1 -65 0 1 31 #x80 #x9f #x110000 #x3fff80 #x3fffff))
(nonprints '(#xd800 #xdfff #x030a #xffff #x2002 #x200c))
(printed-chars (print-tests--prin1-to-string chars))
(printed-nums (print-tests--prin1-to-string nums))
(printed-nonprints (print-tests--prin1-to-string nonprints)))
(should (equal (read printed-chars) chars))
(should (equal
printed-chars
(concat
"(?? ?\\; ?\\( ?\\) ?\\{ ?\\} ?\\[ ?\\] ?\\\" ?\\' ?\\\\"
" ?f ?~ ?Á ?\\s ?\\n ?\\r ?\\t ?\\b ?\\f 7 11 27 127)")))
(should (equal (read printed-nums) nums))
(should (equal printed-nums
"(-1 -65 0 1 31 128 159 1114112 4194176 4194303)"))
(should (equal (read printed-nonprints) nonprints))
(should (equal printed-nonprints
"(55296 57343 778 65535 8194 8204)"))))
(provide 'print-tests)
;;; print-tests.el ends here