Add new variable print-unreadable-function

* doc/lispref/streams.texi (Output Variables): Document it.

* src/print.c (print_vectorlike): Use the variable.
(syms_of_print): New variable print-unreadable-function
(bug#52566).
This commit is contained in:
Lars Ingebrigtsen 2022-01-22 15:06:33 +01:00
parent 7e596463be
commit e4d2a7894b
4 changed files with 308 additions and 220 deletions

View file

@ -872,6 +872,32 @@ If non-@code{nil}, this variable enables detection of circular and
shared structure in printing. @xref{Circular Objects}.
@end defvar
@defvar print-unreadable-function
By default, Emacs prints unreadable objects as @samp{#<...>"}. For
instance:
@example
(prin1-to-string (make-marker))
@result{} "#<marker in no buffer>"
@end example
If this variable is non-@code{nil}, it should be a function that will
be called to handle printing of these objects. The first argument is
the object, and the second argument is the @var{noescape} flag used by
the printing functions (@pxref{Output Functions}).
The function should return either @code{nil} (print nothing), or a
string (which will be printed), or any other object (which means that
the object should be printed normally). For instance:
@example
(let ((print-unreadable-function
(lambda (object escape) "hello")))
(prin1-to-string (make-marker)))
@result{} "hello"
@end example
@end defvar
@defvar print-gensym
If non-@code{nil}, this variable enables detection of uninterned symbols
(@pxref{Creating Symbols}) in printing. When this is enabled,

View file

@ -977,12 +977,16 @@ functions.
* Lisp Changes in Emacs 29.1
--
+++
** New variable 'print-unreadable-function'.
This variable allows changing how Emacs prints unreadable objects.
---
** The variable 'polling-period' now accepts floating point values.
This means Emacs can now poll for input during Lisp execution more
frequently than once in a second.
--
---
** New function 'bidi-string-strip-control-characters'.
This utility function is meant for displaying strings when it's
essential that there's no bidirectional context.

View file

@ -1387,6 +1387,7 @@ static bool
print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
char *buf)
{
/* First do all the vectorlike types that have a readable syntax. */
switch (PSEUDOVECTOR_TYPE (XVECTOR (obj)))
{
case PVEC_BIGNUM:
@ -1398,77 +1399,7 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
strout (str, len, len, printcharfun);
SAFE_FREE ();
}
break;
case PVEC_MARKER:
print_c_string ("#<marker ", printcharfun);
/* Do you think this is necessary? */
if (XMARKER (obj)->insertion_type != 0)
print_c_string ("(moves after insertion) ", printcharfun);
if (! XMARKER (obj)->buffer)
print_c_string ("in no buffer", printcharfun);
else
{
int len = sprintf (buf, "at %"pD"d in ", marker_position (obj));
strout (buf, len, len, printcharfun);
print_string (BVAR (XMARKER (obj)->buffer, name), printcharfun);
}
printchar ('>', printcharfun);
break;
case PVEC_OVERLAY:
print_c_string ("#<overlay ", printcharfun);
if (! XMARKER (OVERLAY_START (obj))->buffer)
print_c_string ("in no buffer", printcharfun);
else
{
int len = sprintf (buf, "from %"pD"d to %"pD"d in ",
marker_position (OVERLAY_START (obj)),
marker_position (OVERLAY_END (obj)));
strout (buf, len, len, printcharfun);
print_string (BVAR (XMARKER (OVERLAY_START (obj))->buffer, name),
printcharfun);
}
printchar ('>', printcharfun);
break;
case PVEC_USER_PTR:
{
print_c_string ("#<user-ptr ", printcharfun);
int i = sprintf (buf, "ptr=%p finalizer=%p",
XUSER_PTR (obj)->p,
XUSER_PTR (obj)->finalizer);
strout (buf, i, i, printcharfun);
printchar ('>', printcharfun);
}
break;
case PVEC_FINALIZER:
print_c_string ("#<finalizer", printcharfun);
if (NILP (XFINALIZER (obj)->function))
print_c_string (" used", printcharfun);
printchar ('>', printcharfun);
break;
case PVEC_MISC_PTR:
{
/* This shouldn't happen in normal usage, but let's
print it anyway for the benefit of the debugger. */
int i = sprintf (buf, "#<ptr %p>", xmint_pointer (obj));
strout (buf, i, i, printcharfun);
}
break;
case PVEC_PROCESS:
if (escapeflag)
{
print_c_string ("#<process ", printcharfun);
print_string (XPROCESS (obj)->name, printcharfun);
printchar ('>', printcharfun);
}
else
print_string (XPROCESS (obj)->name, printcharfun);
break;
return true;
case PVEC_BOOL_VECTOR:
{
@ -1513,70 +1444,7 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
print_c_string (" ...", printcharfun);
printchar ('\"', printcharfun);
}
break;
case PVEC_SUBR:
print_c_string ("#<subr ", printcharfun);
print_c_string (XSUBR (obj)->symbol_name, printcharfun);
printchar ('>', printcharfun);
break;
case PVEC_XWIDGET:
#ifdef HAVE_XWIDGETS
{
if (NILP (XXWIDGET (obj)->buffer))
print_c_string ("#<killed xwidget>", printcharfun);
else
{
#ifdef USE_GTK
int len = sprintf (buf, "#<xwidget %u %p>",
XXWIDGET (obj)->xwidget_id,
XXWIDGET (obj)->widget_osr);
#else
int len = sprintf (buf, "#<xwidget %u %p>",
XXWIDGET (obj)->xwidget_id,
XXWIDGET (obj)->xwWidget);
#endif
strout (buf, len, len, printcharfun);
}
break;
}
#else
emacs_abort ();
#endif
case PVEC_XWIDGET_VIEW:
print_c_string ("#<xwidget view", printcharfun);
printchar ('>', printcharfun);
break;
case PVEC_WINDOW:
{
int len = sprintf (buf, "#<window %"pI"d",
XWINDOW (obj)->sequence_number);
strout (buf, len, len, printcharfun);
if (BUFFERP (XWINDOW (obj)->contents))
{
print_c_string (" on ", printcharfun);
print_string (BVAR (XBUFFER (XWINDOW (obj)->contents), name),
printcharfun);
}
printchar ('>', printcharfun);
}
break;
case PVEC_TERMINAL:
{
struct terminal *t = XTERMINAL (obj);
int len = sprintf (buf, "#<terminal %d", t->id);
strout (buf, len, len, printcharfun);
if (t->name)
{
print_c_string (" on ", printcharfun);
print_c_string (t->name, printcharfun);
}
printchar ('>', printcharfun);
}
break;
return true;
case PVEC_HASH_TABLE:
{
@ -1649,6 +1517,253 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
print_c_string ("))", printcharfun);
}
return true;
case PVEC_RECORD:
{
ptrdiff_t size = PVSIZE (obj);
/* Don't print more elements than the specified maximum. */
ptrdiff_t n
= (FIXNATP (Vprint_length) && XFIXNAT (Vprint_length) < size
? XFIXNAT (Vprint_length) : size);
print_c_string ("#s(", printcharfun);
for (ptrdiff_t i = 0; i < n; i ++)
{
if (i) printchar (' ', printcharfun);
print_object (AREF (obj, i), printcharfun, escapeflag);
}
if (n < size)
print_c_string (" ...", printcharfun);
printchar (')', printcharfun);
}
return true;
case PVEC_SUB_CHAR_TABLE:
case PVEC_COMPILED:
case PVEC_CHAR_TABLE:
case PVEC_NORMAL_VECTOR:
{
ptrdiff_t size = ASIZE (obj);
if (COMPILEDP (obj))
{
printchar ('#', printcharfun);
size &= PSEUDOVECTOR_SIZE_MASK;
}
if (CHAR_TABLE_P (obj) || SUB_CHAR_TABLE_P (obj))
{
/* Print a char-table as if it were a vector,
lumping the parent and default slots in with the
character slots. But add #^ as a prefix. */
/* Make each lowest sub_char_table start a new line.
Otherwise we'll make a line extremely long, which
results in slow redisplay. */
if (SUB_CHAR_TABLE_P (obj)
&& XSUB_CHAR_TABLE (obj)->depth == 3)
printchar ('\n', printcharfun);
print_c_string ("#^", printcharfun);
if (SUB_CHAR_TABLE_P (obj))
printchar ('^', printcharfun);
size &= PSEUDOVECTOR_SIZE_MASK;
}
if (size & PSEUDOVECTOR_FLAG)
return false;
printchar ('[', printcharfun);
int idx = SUB_CHAR_TABLE_P (obj) ? SUB_CHAR_TABLE_OFFSET : 0;
Lisp_Object tem;
ptrdiff_t real_size = size;
/* For a sub char-table, print heading non-Lisp data first. */
if (SUB_CHAR_TABLE_P (obj))
{
int i = sprintf (buf, "%d %d", XSUB_CHAR_TABLE (obj)->depth,
XSUB_CHAR_TABLE (obj)->min_char);
strout (buf, i, i, printcharfun);
}
/* Don't print more elements than the specified maximum. */
if (FIXNATP (Vprint_length)
&& XFIXNAT (Vprint_length) < size)
size = XFIXNAT (Vprint_length);
for (int i = idx; i < size; i++)
{
if (i) printchar (' ', printcharfun);
tem = AREF (obj, i);
print_object (tem, printcharfun, escapeflag);
}
if (size < real_size)
print_c_string (" ...", printcharfun);
printchar (']', printcharfun);
}
return true;
default:
break;
}
/* Then do all the pseudovector types that don't have a readable
syntax. First check whether this is handled by
`print-unreadable-function'. */
if (!NILP (Vprint_unreadable_function)
&& FUNCTIONP (Vprint_unreadable_function))
{
ptrdiff_t count = SPECPDL_INDEX ();
/* Bind `print-unreadable-function' to nil to avoid accidental
infinite recursion in the function called. */
Lisp_Object func = Vprint_unreadable_function;
specbind (Qprint_unreadable_function, Qnil);
Lisp_Object result = CALLN (Ffuncall, func, obj,
escapeflag? Qt: Qnil);
unbind_to (count, Qnil);
if (!NILP (result))
{
if (STRINGP (result))
print_string (result, printcharfun);
/* It's handled, so stop processing here. */
return true;
}
}
/* Not handled; print unreadable object. */
switch (PSEUDOVECTOR_TYPE (XVECTOR (obj)))
{
case PVEC_MARKER:
print_c_string ("#<marker ", printcharfun);
/* Do you think this is necessary? */
if (XMARKER (obj)->insertion_type != 0)
print_c_string ("(moves after insertion) ", printcharfun);
if (! XMARKER (obj)->buffer)
print_c_string ("in no buffer", printcharfun);
else
{
int len = sprintf (buf, "at %"pD"d in ", marker_position (obj));
strout (buf, len, len, printcharfun);
print_string (BVAR (XMARKER (obj)->buffer, name), printcharfun);
}
printchar ('>', printcharfun);
break;
case PVEC_OVERLAY:
print_c_string ("#<overlay ", printcharfun);
if (! XMARKER (OVERLAY_START (obj))->buffer)
print_c_string ("in no buffer", printcharfun);
else
{
int len = sprintf (buf, "from %"pD"d to %"pD"d in ",
marker_position (OVERLAY_START (obj)),
marker_position (OVERLAY_END (obj)));
strout (buf, len, len, printcharfun);
print_string (BVAR (XMARKER (OVERLAY_START (obj))->buffer, name),
printcharfun);
}
printchar ('>', printcharfun);
break;
case PVEC_USER_PTR:
{
print_c_string ("#<user-ptr ", printcharfun);
int i = sprintf (buf, "ptr=%p finalizer=%p",
XUSER_PTR (obj)->p,
XUSER_PTR (obj)->finalizer);
strout (buf, i, i, printcharfun);
printchar ('>', printcharfun);
}
break;
case PVEC_FINALIZER:
print_c_string ("#<finalizer", printcharfun);
if (NILP (XFINALIZER (obj)->function))
print_c_string (" used", printcharfun);
printchar ('>', printcharfun);
break;
case PVEC_MISC_PTR:
{
/* This shouldn't happen in normal usage, but let's
print it anyway for the benefit of the debugger. */
int i = sprintf (buf, "#<ptr %p>", xmint_pointer (obj));
strout (buf, i, i, printcharfun);
}
break;
case PVEC_PROCESS:
if (escapeflag)
{
print_c_string ("#<process ", printcharfun);
print_string (XPROCESS (obj)->name, printcharfun);
printchar ('>', printcharfun);
}
else
print_string (XPROCESS (obj)->name, printcharfun);
break;
case PVEC_SUBR:
print_c_string ("#<subr ", printcharfun);
print_c_string (XSUBR (obj)->symbol_name, printcharfun);
printchar ('>', printcharfun);
break;
case PVEC_XWIDGET:
#ifdef HAVE_XWIDGETS
{
if (NILP (XXWIDGET (obj)->buffer))
print_c_string ("#<killed xwidget>", printcharfun);
else
{
#ifdef USE_GTK
int len = sprintf (buf, "#<xwidget %u %p>",
XXWIDGET (obj)->xwidget_id,
XXWIDGET (obj)->widget_osr);
#else
int len = sprintf (buf, "#<xwidget %u %p>",
XXWIDGET (obj)->xwidget_id,
XXWIDGET (obj)->xwWidget);
#endif
strout (buf, len, len, printcharfun);
}
break;
}
#else
emacs_abort ();
#endif
case PVEC_XWIDGET_VIEW:
print_c_string ("#<xwidget view", printcharfun);
printchar ('>', printcharfun);
break;
case PVEC_WINDOW:
{
int len = sprintf (buf, "#<window %"pI"d",
XWINDOW (obj)->sequence_number);
strout (buf, len, len, printcharfun);
if (BUFFERP (XWINDOW (obj)->contents))
{
print_c_string (" on ", printcharfun);
print_string (BVAR (XBUFFER (XWINDOW (obj)->contents), name),
printcharfun);
}
printchar ('>', printcharfun);
}
break;
case PVEC_TERMINAL:
{
struct terminal *t = XTERMINAL (obj);
int len = sprintf (buf, "#<terminal %d", t->id);
strout (buf, len, len, printcharfun);
if (t->name)
{
print_c_string (" on ", printcharfun);
print_c_string (t->name, printcharfun);
}
printchar ('>', printcharfun);
}
break;
case PVEC_BUFFER:
@ -1756,89 +1871,6 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
printchar ('>', printcharfun);
break;
case PVEC_RECORD:
{
ptrdiff_t size = PVSIZE (obj);
/* Don't print more elements than the specified maximum. */
ptrdiff_t n
= (FIXNATP (Vprint_length) && XFIXNAT (Vprint_length) < size
? XFIXNAT (Vprint_length) : size);
print_c_string ("#s(", printcharfun);
for (ptrdiff_t i = 0; i < n; i ++)
{
if (i) printchar (' ', printcharfun);
print_object (AREF (obj, i), printcharfun, escapeflag);
}
if (n < size)
print_c_string (" ...", printcharfun);
printchar (')', printcharfun);
}
break;
case PVEC_SUB_CHAR_TABLE:
case PVEC_COMPILED:
case PVEC_CHAR_TABLE:
case PVEC_NORMAL_VECTOR:
{
ptrdiff_t size = ASIZE (obj);
if (COMPILEDP (obj))
{
printchar ('#', printcharfun);
size &= PSEUDOVECTOR_SIZE_MASK;
}
if (CHAR_TABLE_P (obj) || SUB_CHAR_TABLE_P (obj))
{
/* Print a char-table as if it were a vector,
lumping the parent and default slots in with the
character slots. But add #^ as a prefix. */
/* Make each lowest sub_char_table start a new line.
Otherwise we'll make a line extremely long, which
results in slow redisplay. */
if (SUB_CHAR_TABLE_P (obj)
&& XSUB_CHAR_TABLE (obj)->depth == 3)
printchar ('\n', printcharfun);
print_c_string ("#^", printcharfun);
if (SUB_CHAR_TABLE_P (obj))
printchar ('^', printcharfun);
size &= PSEUDOVECTOR_SIZE_MASK;
}
if (size & PSEUDOVECTOR_FLAG)
return false;
printchar ('[', printcharfun);
int idx = SUB_CHAR_TABLE_P (obj) ? SUB_CHAR_TABLE_OFFSET : 0;
Lisp_Object tem;
ptrdiff_t real_size = size;
/* For a sub char-table, print heading non-Lisp data first. */
if (SUB_CHAR_TABLE_P (obj))
{
int i = sprintf (buf, "%d %d", XSUB_CHAR_TABLE (obj)->depth,
XSUB_CHAR_TABLE (obj)->min_char);
strout (buf, i, i, printcharfun);
}
/* Don't print more elements than the specified maximum. */
if (FIXNATP (Vprint_length)
&& XFIXNAT (Vprint_length) < size)
size = XFIXNAT (Vprint_length);
for (int i = idx; i < size; i++)
{
if (i) printchar (' ', printcharfun);
tem = AREF (obj, i);
print_object (tem, printcharfun, escapeflag);
}
if (size < real_size)
print_c_string (" ...", printcharfun);
printchar (']', printcharfun);
}
break;
#ifdef HAVE_MODULES
case PVEC_MODULE_FUNCTION:
{
@ -2464,4 +2496,19 @@ priorities. Values other than nil or t are also treated as
print_prune_charset_plist = Qnil;
staticpro (&print_prune_charset_plist);
DEFVAR_LISP ("print-unreadable-function", Vprint_unreadable_function,
doc: /* Function called when printing unreadable objects.
By default, Emacs printing functions (like `prin1') print unreadable
objects like \"#<...>\", where \"...\" describes the object (for
instance, \"#<marker in no buffer>\"). If this variable is non-nil,
it should be a function which will be called to print the object instead.
It will be called with two arguments: The object to be printed, and
noescape (see `prin1-to-string'). If this function returns nil, the
object will be printed as normal. If it returns a string, that string
will then be printed. If the function returns anything else, the
object will not be printed. */);
Vprint_unreadable_function = Qnil;
DEFSYM (Qprint_unreadable_function, "print-unreadable-function");
}

View file

@ -406,5 +406,16 @@ otherwise, use a different charset."
(should (equal printed-nonprints
"(55296 57343 778 65535 8194 8204)"))))
(ert-deftest test-unreadable ()
(should (equal (prin1-to-string (make-marker)) "#<marker in no buffer>"))
(let ((print-unreadable-function
(lambda (_object _escape)
"hello")))
(should (equal (prin1-to-string (make-marker)) "hello")))
(let ((print-unreadable-function
(lambda (_object _escape)
t)))
(should (equal (prin1-to-string (make-marker)) ""))))
(provide 'print-tests)
;;; print-tests.el ends here