Add OVERRIDES argument to prin1/prin1-to-string

* doc/lispref/streams.texi (Output Functions): Document it.
(Output Overrides): New node.

* src/process.c (Faccept_process_output):
* src/print.c (debug_print, print_error_message):
* src/pdumper.c (print_paths_to_root_1, decode_emacs_reloc):
* src/lread.c (readevalloop):
* src/eval.c (internal_lisp_condition_case):
* src/editfns.c (styled_format): Adjust prin1/prin1-to-string
callers.

* src/print.c (Fprin1): Take an OVERRIDES parameter.
(print_bind_overrides, print_bind_all_defaults): New functions.
(Fprin1_to_string): Take an OVERRIDES parameter.
This commit is contained in:
Lars Ingebrigtsen 2022-05-15 15:29:28 +02:00
parent 22873b5415
commit aa95b2a47d
10 changed files with 265 additions and 16 deletions

View file

@ -739,6 +739,7 @@ Reading and Printing Lisp Objects
* Output Functions:: Functions to print Lisp objects as text.
* Output Variables:: Variables that control what the printing
functions do.
* Output Overrides:: Overriding output variables.
Minibuffers

View file

@ -21,6 +21,7 @@ reading) or where to put it (if printing).
* Output Streams:: Various data types that can be used as output streams.
* Output Functions:: Functions to print Lisp objects as text.
* Output Variables:: Variables that control what the printing functions do.
* Output Overrides:: Overriding output variables.
@end menu
@node Streams Intro
@ -634,7 +635,7 @@ characters are used. @code{print} returns @var{object}. For example:
@end example
@end defun
@defun prin1 object &optional stream
@defun prin1 object &optional stream overrides
This function outputs the printed representation of @var{object} to
@var{stream}. It does not print newlines to separate output as
@code{print} does, but it does use quoting characters just like
@ -649,6 +650,10 @@ This function outputs the printed representation of @var{object} to
@result{} " came back"
@end group
@end example
If @var{overrides} is non-@code{nil}, it should either be @code{t}
(which tells @code{prin1} to use the defaults for all printer related
variables), or a list of settings. @xref{Output Overrides} for details.
@end defun
@defun princ object &optional stream
@ -694,7 +699,7 @@ newline character first, which enables you to display incomplete
lines.
@end defun
@defun prin1-to-string object &optional noescape
@defun prin1-to-string object &optional noescape overrides
@cindex object to string
This function returns a string containing the text that @code{prin1}
would have printed for the same argument.
@ -708,6 +713,10 @@ would have printed for the same argument.
(prin1-to-string (mark-marker))
@result{} "#<marker at 2773 in strings.texi>"
@end group
If @var{overrides} is non-@code{nil}, it should either be @code{t}
(which tells @code{prin1} to use the defaults for all printer related
variables), or a list of settings. @xref{Output Overrides} for details.
@end example
If @var{noescape} is non-@code{nil}, that inhibits use of quoting
@ -971,3 +980,93 @@ 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
@node Output Overrides
@section Overriding Output Variables
@xref{Output Functions} lists the numerous variables that controls how
the Emacs Lisp printer outputs data. These are generally available
for users to change, but sometimes you want to output data in the
default format. For instance, if you're storing Emacs Lisp data in a
file, you don't want that data to be shortened by a
@code{print-length} setting.
The @code{prin1} and @code{prin1-to-string} functions therefore have
an optional @var{overrides} argument. This variable can either be
@code{t} (which means that all printing variables should be the
default values), or a list of settings. Each element in the list can
either be @code{t} (which means ``reset to defaults'') or a pair where
the @code{car} is a symbol, and the @code{cdr} is the value.
For instance, this prints using nothing but defaults:
@lisp
(prin1 object nil t)
@end lisp
This prints @var{object} using the current printing settings, but
overrides @code{print-length} to 5:
@lisp
(prin1 object nil '((length . 5)))
@end lisp
And finally, this prints @var{object} using only default settings, but
overrides @code{print-length} to 5:
@lisp
(prin1 object nil '(t (length . 5)))
@end lisp
Below is a list of symbols that can be used, and which variables they
map to:
@table @code
@item length
This overrides @code{print-length}.
@item level
This overrides @code{print-level}.
@item circle
This overrides @code{print-circle}.
@item quoted
This overrides @code{print-quoted}.
@item escape-newlines
This overrides @code{print-escape-newlines}.
@item escape-control-characters
This overrides @code{print-escape-control-characters}.
@item escape-nonascii
This overrides @code{print-escape-nonascii}.
@item escape-multibyte
This overrides @code{print-escape-multibyte}.
@item charset-text-property
This overrides @code{print-charset-text-property}.
@item unreadeable-function
This overrides @code{print-unreadable-function}.
@item gensym
This overrides @code{print-gensym}.
@item continuous-numbering
This overrides @code{print-continuous-numbering}.
@item number-table
This overrides @code{print-number-table}.
@item float-format
This overrides @code{float-output-format}.
@item integers-as-characters
This overrides @code{print-integers-as-characters}.
@end table
In the future, more overrides may be offered that do not map directly
to a variable, but can only be used via this parameter.

View file

@ -1817,6 +1817,10 @@ functions.
* Lisp Changes in Emacs 29.1
+++
** 'prin1' and 'prin1-to-string' now takes an OVERRIDES parameter.
This parameter can be used to override printer settings.
+++
** New minor mode 'header-line-indent-mode'.
This is meant to be used in modes that have a header line that should

View file

@ -3327,7 +3327,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
if (EQ (arg, args[n]))
{
Lisp_Object noescape = conversion == 'S' ? Qnil : Qt;
spec->argument = arg = Fprin1_to_string (arg, noescape);
spec->argument = arg = Fprin1_to_string (arg, noescape, Qnil);
if (STRING_MULTIBYTE (arg) && ! multibyte)
{
multibyte = true;

View file

@ -1341,7 +1341,7 @@ internal_lisp_condition_case (Lisp_Object var, Lisp_Object bodyform,
&& (SYMBOLP (XCAR (tem))
|| CONSP (XCAR (tem))))))
error ("Invalid condition handler: %s",
SDATA (Fprin1_to_string (tem, Qt)));
SDATA (Fprin1_to_string (tem, Qt, Qnil)));
if (CONSP (tem) && EQ (XCAR (tem), QCsuccess))
success_handler = XCDR (tem);
else

View file

@ -2349,7 +2349,7 @@ readevalloop (Lisp_Object readcharfun,
{
Vvalues = Fcons (val, Vvalues);
if (EQ (Vstandard_output, Qt))
Fprin1 (val, Qnil);
Fprin1 (val, Qnil, Qnil);
else
Fprint (val, Qnil);
}

View file

@ -1383,7 +1383,7 @@ print_paths_to_root_1 (struct dump_context *ctx,
{
Lisp_Object referrer = XCAR (referrers);
referrers = XCDR (referrers);
Lisp_Object repr = Fprin1_to_string (referrer, Qnil);
Lisp_Object repr = Fprin1_to_string (referrer, Qnil, Qnil);
for (int i = 0; i < level; ++i)
putc (' ', stderr);
fwrite (SDATA (repr), 1, SBYTES (repr), stderr);
@ -3758,7 +3758,7 @@ decode_emacs_reloc (struct dump_context *ctx, Lisp_Object lreloc)
reloc.u.dump_offset = dump_recall_object (ctx, target_value);
if (reloc.u.dump_offset <= 0)
{
Lisp_Object repr = Fprin1_to_string (target_value, Qnil);
Lisp_Object repr = Fprin1_to_string (target_value, Qnil, Qnil);
error ("relocation target was not dumped: %s", SDATA (repr));
}
dump_check_dump_off (ctx, reloc.u.dump_offset);

View file

@ -620,7 +620,51 @@ If PRINTCHARFUN is omitted or nil, the value of `standard-output' is used. */)
return val;
}
DEFUN ("prin1", Fprin1, Sprin1, 1, 2, 0,
static void
print_bind_all_defaults (void)
{
for (Lisp_Object vars = Vprint__variable_mapping; !NILP (vars);
vars = XCDR (vars))
{
Lisp_Object elem = XCDR (XCAR (vars));
specbind (XCAR (elem), XCAR (XCDR (elem)));
}
}
static void
print_bind_overrides (Lisp_Object overrides)
{
if (EQ (overrides, Qt))
print_bind_all_defaults ();
else if (!CONSP (overrides))
xsignal (Qwrong_type_argument, Qconsp);
else
{
while (!NILP (overrides))
{
Lisp_Object setting = XCAR (overrides);
if (EQ (setting, Qt))
print_bind_all_defaults ();
else if (!CONSP (setting))
xsignal (Qwrong_type_argument, Qconsp);
else
{
Lisp_Object key = XCAR (setting),
value = XCDR (setting);
Lisp_Object map = Fassq (key, Vprint__variable_mapping);
if (NILP (map))
xsignal2 (Qwrong_type_argument, Qsymbolp, map);
specbind (XCAR (XCDR (map)), value);
}
if (!NILP (XCDR (overrides)) && !CONSP (XCDR (overrides)))
xsignal (Qwrong_type_argument, Qconsp);
overrides = XCDR (overrides);
}
}
}
DEFUN ("prin1", Fprin1, Sprin1, 1, 3, 0,
doc: /* Output the printed representation of OBJECT, any Lisp object.
Quoting characters are printed when needed to make output that `read'
can handle, whenever this is possible. For complex objects, the behavior
@ -642,21 +686,43 @@ of these:
- t, in which case the output is displayed in the echo area.
If PRINTCHARFUN is omitted, the value of `standard-output' (which see)
is used instead. */)
(Lisp_Object object, Lisp_Object printcharfun)
is used instead.
OVERRIDES should be a list of settings. An element in this list be
the symbol t, which means "use all the defaults". If not, an element
should be a pair, where the `car' or the pair is the setting, and the
`cdr' of the pair is the value of printer-related settings to use for
this `prin1' call.
For instance:
(prin1 object nil \\='((length . 100) (circle . t))).
See the manual entry `(elisp)Output Overrides' for a list of possible
values.
As a special case, OVERRIDES can also simply be the symbol t, which
means "use all the defaults". */)
(Lisp_Object object, Lisp_Object printcharfun, Lisp_Object overrides)
{
specpdl_ref count = SPECPDL_INDEX ();
if (NILP (printcharfun))
printcharfun = Vstandard_output;
if (!NILP (overrides))
print_bind_overrides (overrides);
PRINTPREPARE;
print (object, printcharfun, 1);
PRINTFINISH;
return object;
return unbind_to (count, object);
}
/* A buffer which is used to hold output being built by prin1-to-string. */
Lisp_Object Vprin1_to_string_buffer;
DEFUN ("prin1-to-string", Fprin1_to_string, Sprin1_to_string, 1, 2, 0,
DEFUN ("prin1-to-string", Fprin1_to_string, Sprin1_to_string, 1, 3, 0,
doc: /* Return a string containing the printed representation of OBJECT.
OBJECT can be any Lisp object. This function outputs quoting characters
when necessary to make output that `read' can handle, whenever possible,
@ -666,13 +732,18 @@ the behavior is controlled by `print-level' and `print-length', which see.
OBJECT is any of the Lisp data types: a number, a string, a symbol,
a list, a buffer, a window, a frame, etc.
See `prin1' for the meaning of OVERRIDES.
A printed representation of an object is text which describes that object. */)
(Lisp_Object object, Lisp_Object noescape)
(Lisp_Object object, Lisp_Object noescape, Lisp_Object overrides)
{
specpdl_ref count = SPECPDL_INDEX ();
specbind (Qinhibit_modification_hooks, Qt);
if (!NILP (overrides))
print_bind_overrides (overrides);
/* Save and restore this: we are altering a buffer
but we don't want to deactivate the mark just for that.
No need for specbind, since errors deactivate the mark. */
@ -847,7 +918,7 @@ append to existing target file. */)
void
debug_print (Lisp_Object arg)
{
Fprin1 (arg, Qexternal_debugging_output);
Fprin1 (arg, Qexternal_debugging_output, Qnil);
fputs ("\r\n", stderr);
}
@ -995,7 +1066,7 @@ print_error_message (Lisp_Object data, Lisp_Object stream, const char *context,
|| EQ (errname, Qend_of_file) || EQ (errname, Quser_error))
Fprinc (obj, stream);
else
Fprin1 (obj, stream);
Fprin1 (obj, stream, Qnil);
}
}
}
@ -2571,4 +2642,35 @@ be printed. */);
DEFSYM (Qprint_unreadable_function, "print-unreadable-function");
defsubr (&Sflush_standard_output);
DEFVAR_LISP ("print--variable-mapping", Vprint__variable_mapping,
doc: /* Mapping for print variables in `prin1'.
Do not modify this list. */);
Vprint__variable_mapping = Qnil;
Lisp_Object total[] = {
list3 (intern ("length"), intern ("print-length"), Qnil),
list3 (intern ("level"), intern ("print-level"), Qnil),
list3 (intern ("circle"), intern ("print-circle"), Qnil),
list3 (intern ("quoted"), intern ("print-quoted"), Qt),
list3 (intern ("escape-newlines"), intern ("print-escape-newlines"), Qnil),
list3 (intern ("escape-control-characters"),
intern ("print-escape-control-characters"), Qnil),
list3 (intern ("escape-nonascii"), intern ("print-escape-nonascii"), Qnil),
list3 (intern ("escape-multibyte"),
intern ("print-escape-multibyte"), Qnil),
list3 (intern ("charset-text-property"),
intern ("print-charset-text-property"), Qnil),
list3 (intern ("unreadeable-function"),
intern ("print-unreadable-function"), Qnil),
list3 (intern ("gensym"), intern ("print-gensym"), Qnil),
list3 (intern ("continuous-numbering"),
intern ("print-continuous-numbering"), Qnil),
list3 (intern ("number-table"), intern ("print-number-table"), Qnil),
list3 (intern ("float-format"), intern ("float-output-format"), Qnil),
list3 (intern ("integers-as-characters"),
intern ("print-integers-as-characters"), Qnil),
};
Vprint__variable_mapping = CALLMANY (Flist, total);
make_symbol_constant (intern_c_string ("print--variable-mapping"));
}

View file

@ -4779,7 +4779,7 @@ corresponding connection was closed. */)
SDATA (proc->name),
STRINGP (proc_thread_name)
? SDATA (proc_thread_name)
: SDATA (Fprin1_to_string (proc->thread, Qt)));
: SDATA (Fprin1_to_string (proc->thread, Qt, Qnil)));
}
}
else

View file

@ -425,5 +425,48 @@ otherwise, use a different charset."
(should (equal (prin1-to-string '\?bar) "\\?bar"))
(should (equal (prin1-to-string '\?bar?) "\\?bar?")))
(ert-deftest test-prin1-overrides ()
(with-temp-buffer
(let ((print-length 10))
(prin1 (make-list 20 t) (current-buffer) t)
(should (= print-length 10)))
(goto-char (point-min))
(should (= (length (read (current-buffer))) 20)))
(with-temp-buffer
(let ((print-length 10))
(prin1 (make-list 20 t) (current-buffer) '((length . 5)))
(should (= print-length 10)))
(goto-char (point-min))
(should (= (length (read (current-buffer))) 6)))
(with-temp-buffer
(let ((print-length 10))
(prin1 (make-list 20 t) (current-buffer) '(t (length . 5)))
(should (= print-length 10)))
(goto-char (point-min))
(should (= (length (read (current-buffer))) 6))))
(ert-deftest test-prin1-to-string-overrides ()
(let ((print-length 10))
(should
(= (length (car (read-from-string
(prin1-to-string (make-list 20 t) nil t))))
20)))
(let ((print-length 10))
(should
(= (length (car (read-from-string
(prin1-to-string (make-list 20 t) nil
'((length . 5))))))
6)))
(should-error (prin1-to-string 'foo nil 'a))
(should-error (prin1-to-string 'foo nil '(a)))
(should-error (prin1-to-string 'foo nil '(t . b)))
(should-error (prin1-to-string 'foo nil '(t b)))
(should-error (prin1-to-string 'foo nil '((a . b) b)))
(should-error (prin1-to-string 'foo nil '((length . 10) . b))))
(provide 'print-tests)
;;; print-tests.el ends here