Subject: bug#44155: Print integers as characters

From: Juri Linkov <[email-masked]>
Date: Sun, 25 Oct 2020 21:09:07 +0200

>> +        if (EQ (Vinteger_output_format, Qt) && CHARACTERP (obj) && c < 4194176)
>                                                                           ^^^^^^^
>
> Please use MAX_5_BYTE_CHAR here.  Or, better yet, CHAR_BYTE8_P.

Thanks, fixed.

> And, btw, why not allow raw bytes here as well? is there some problem?

Because of ambiguity, both these return the same value:

(read (concat "?" (string 128)))     => 128
(read (concat "?" (string 4194176))) => 128

>> +            print_string (Fchar_to_string (obj), printcharfun);
>
> Why are you using print_string here instead of printchar?  IOW, what
> is the difference between printing a backslash and printing any other
> character, that you can use printchar for the former, but not for the
> latter?

It was needed in earlier versions, but not now; fixed.

>> +        else if (INTEGERP (Vinteger_output_format)
>> +                 && XFIXNUM (Vinteger_output_format) == 16 && c >= 0)
>
> If you really want to allow Vinteger_output_format to be a bignum, you
> cannot use XFIXNUM with it, you need to use integer_to_intmax or
> somesuch.  Otherwise, you should use FIXNUMP instead of INTEGERP.

Fixed.

>> +  DEFVAR_LISP ("integer-output-format", Vinteger_output_format,
>> +	       doc: /* The format used to print integers.
>> +When 't', print integers as characters.
>              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> But only integers that are small enough, yes?

Fixed the docstring as well.

>> +When a number 16, print numbers in hex format.
>
> This immediately begs the question: why cannot the value be 8 or 2?

Because octal and binary are not so widely used as hex.
But variable makes room for further improvements to later support
octal and binary too, and maybe string formats like in float-output-format.

> P.S. This will eventually need a NEWS entry.

And also updates in the Info manual will be in the final version of the patch.

diff --git a/src/print.c b/src/print.c
index 53aa353769..b04d5023f8 100644
--- a/src/print.c
+++ b/src/print.c
@@ -1908,8 +1908,30 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
     {
     case_Lisp_Int:
       {
-	int len = sprintf (buf, "%"pI"d", XFIXNUM (obj));
-	strout (buf, len, len, printcharfun);
+        int c = XFIXNUM (obj);
+        intmax_t i;
+
+        if (EQ (Vinteger_output_format, Qt) && CHARACTERP (obj) && ! CHAR_BYTE8_P (c))
+          {
+            printchar ('?', printcharfun);
+            if (escapeflag
+                && (c == ';' || c == '(' || c == ')' || c == '{' || c == '}'
+                    || c == '[' || c == ']' || c == '\"' || c == '\'' || c == '\\'))
+              printchar ('\\', printcharfun);
+            printchar (c, printcharfun);
+          }
+        else if (INTEGERP (Vinteger_output_format)
+                 && integer_to_intmax (Vinteger_output_format, &i)
+                 && i == 16 && XFIXNUM (obj) >= 0)
+          {
+            int len = sprintf (buf, "#x%"pI"x", (EMACS_UINT) XFIXNUM (obj));
+            strout (buf, len, len, printcharfun);
+          }
+        else
+          {
+            int len = sprintf (buf, "%"pI"d", XFIXNUM (obj));
+            strout (buf, len, len, printcharfun);
+          }
       }
       break;
 
@@ -2247,6 +2269,13 @@ syms_of_print (void)
 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 characters.
+When a number 16, print non-negative numbers in hex format.
+Otherwise, print integers in decimal format.  */);
+  Vinteger_output_format = Qnil;
+
   DEFVAR_LISP ("print-length", Vprint_length,
 	       doc: /* Maximum length of list to print before abbreviating.
 A value of nil means no limit.  See also `eval-expression-print-length'.  */);