mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2025-01-02 11:21:42 +00:00
Make printing mostly non-recursive (bug#55481)
Introduce explicit stacks for traversing common data types during printing: conses, vectors, records, byte-code, hash-tables and char-tables, all previously traversed using recursion in C. This greatly reduces the risk of crashing Emacs from C stack overflow when printing deeply nested data. * src/print.c (Fprinc, print, PRINT_CIRCLE_CANDIDATE_P): Special-case Fprinc with a plain string argument to eliminate the need for keeping track of print_depth during the preprocessing phase. This also improves performance. (struct print_pp_entry, struct print_pp_stack, ppstack) (grow_pp_stack, pp_stack_push_value, pp_stack_push_values) (pp_stack_empty_p, pp_stack_pop): New stack for preprocessing. (print_preprocess): Make mostly nonrecursive, except for string properties. (enum print_entry_type, struct print_stack_entry) (struct print_stack, prstack, grow_print_stack) (print_stack_push, print_stack_push_vector): New stack for printing. (print_vectorlike, print_object): Make mostly nonrecursive, except for string properties and some less heavily used types. * test/src/print-tests.el (print-deeply-nested): New test.
This commit is contained in:
parent
24f7719cb6
commit
97400c4c24
814
src/print.c
814
src/print.c
@ -834,7 +834,13 @@ is used instead. */)
|
||||
if (NILP (printcharfun))
|
||||
printcharfun = Vstandard_output;
|
||||
PRINTPREPARE;
|
||||
print (object, printcharfun, 0);
|
||||
if (STRINGP (object)
|
||||
&& !string_intervals (object)
|
||||
&& NILP (Vprint_continuous_numbering))
|
||||
/* fast path for plain strings */
|
||||
print_string (object, printcharfun);
|
||||
else
|
||||
print (object, printcharfun, 0);
|
||||
PRINTFINISH;
|
||||
return object;
|
||||
}
|
||||
@ -1249,7 +1255,6 @@ print (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
{
|
||||
/* Construct Vprint_number_table.
|
||||
This increments print_number_index for the objects added. */
|
||||
print_depth = 0;
|
||||
print_preprocess (obj);
|
||||
|
||||
if (HASH_TABLE_P (Vprint_number_table))
|
||||
@ -1273,10 +1278,7 @@ print (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
}
|
||||
|
||||
#define PRINT_CIRCLE_CANDIDATE_P(obj) \
|
||||
((STRINGP (obj) \
|
||||
&& (string_intervals (obj) \
|
||||
|| print_depth > 1 \
|
||||
|| !NILP (Vprint_continuous_numbering))) \
|
||||
(STRINGP (obj) \
|
||||
|| CONSP (obj) \
|
||||
|| (VECTORLIKEP (obj) \
|
||||
&& (VECTORP (obj) || COMPILEDP (obj) \
|
||||
@ -1287,6 +1289,78 @@ print (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
&& SYMBOLP (obj) \
|
||||
&& !SYMBOL_INTERNED_P (obj)))
|
||||
|
||||
/* The print preprocess stack, used to traverse data structures. */
|
||||
|
||||
struct print_pp_entry {
|
||||
ptrdiff_t n; /* number of values, or 0 if a single value */
|
||||
union {
|
||||
Lisp_Object value; /* when n = 0 */
|
||||
Lisp_Object *values; /* when n > 0 */
|
||||
} u;
|
||||
};
|
||||
|
||||
struct print_pp_stack {
|
||||
struct print_pp_entry *stack; /* base of stack */
|
||||
ptrdiff_t size; /* allocated size in entries */
|
||||
ptrdiff_t sp; /* current number of entries */
|
||||
};
|
||||
|
||||
static struct print_pp_stack ppstack = {NULL, 0, 0};
|
||||
|
||||
NO_INLINE static void
|
||||
grow_pp_stack (void)
|
||||
{
|
||||
struct print_pp_stack *ps = &ppstack;
|
||||
eassert (ps->sp == ps->size);
|
||||
ps->stack = xpalloc (ps->stack, &ps->size, 1, -1, sizeof *ps->stack);
|
||||
eassert (ps->sp < ps->size);
|
||||
}
|
||||
|
||||
static inline void
|
||||
pp_stack_push_value (Lisp_Object value)
|
||||
{
|
||||
if (ppstack.sp >= ppstack.size)
|
||||
grow_pp_stack ();
|
||||
ppstack.stack[ppstack.sp++] = (struct print_pp_entry){.n = 0,
|
||||
.u.value = value};
|
||||
}
|
||||
|
||||
static inline void
|
||||
pp_stack_push_values (Lisp_Object *values, ptrdiff_t n)
|
||||
{
|
||||
eassume (n >= 0);
|
||||
if (n == 0)
|
||||
return;
|
||||
if (ppstack.sp >= ppstack.size)
|
||||
grow_pp_stack ();
|
||||
ppstack.stack[ppstack.sp++] = (struct print_pp_entry){.n = n,
|
||||
.u.values = values};
|
||||
}
|
||||
|
||||
static inline bool
|
||||
pp_stack_empty_p (void)
|
||||
{
|
||||
return ppstack.sp <= 0;
|
||||
}
|
||||
|
||||
static inline Lisp_Object
|
||||
pp_stack_pop (void)
|
||||
{
|
||||
eassume (!pp_stack_empty_p ());
|
||||
struct print_pp_entry *e = &ppstack.stack[ppstack.sp - 1];
|
||||
if (e->n == 0) /* single value */
|
||||
{
|
||||
--ppstack.sp;
|
||||
return e->u.value;
|
||||
}
|
||||
/* Array of values: pop them left to right, which seems to be slightly
|
||||
faster than right to left. */
|
||||
e->n--;
|
||||
if (e->n == 0)
|
||||
--ppstack.sp; /* last value consumed */
|
||||
return (++e->u.values)[-1];
|
||||
}
|
||||
|
||||
/* Construct Vprint_number_table for the print-circle feature
|
||||
according to the structure of OBJ. OBJ itself and all its elements
|
||||
will be added to Vprint_number_table recursively if it is a list,
|
||||
@ -1298,86 +1372,81 @@ print (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
static void
|
||||
print_preprocess (Lisp_Object obj)
|
||||
{
|
||||
int i;
|
||||
ptrdiff_t size;
|
||||
int loop_count = 0;
|
||||
Lisp_Object halftail;
|
||||
|
||||
eassert (!NILP (Vprint_circle));
|
||||
ptrdiff_t base_sp = ppstack.sp;
|
||||
|
||||
print_depth++;
|
||||
halftail = obj;
|
||||
|
||||
loop:
|
||||
if (PRINT_CIRCLE_CANDIDATE_P (obj))
|
||||
for (;;)
|
||||
{
|
||||
if (!HASH_TABLE_P (Vprint_number_table))
|
||||
Vprint_number_table = CALLN (Fmake_hash_table, QCtest, Qeq);
|
||||
|
||||
Lisp_Object num = Fgethash (obj, Vprint_number_table, Qnil);
|
||||
if (!NILP (num)
|
||||
/* If Vprint_continuous_numbering is non-nil and OBJ is a gensym,
|
||||
always print the gensym with a number. This is a special for
|
||||
the lisp function byte-compile-output-docform. */
|
||||
|| (!NILP (Vprint_continuous_numbering)
|
||||
&& SYMBOLP (obj)
|
||||
&& !SYMBOL_INTERNED_P (obj)))
|
||||
{ /* OBJ appears more than once. Let's remember that. */
|
||||
if (!FIXNUMP (num))
|
||||
{
|
||||
print_number_index++;
|
||||
/* Negative number indicates it hasn't been printed yet. */
|
||||
Fputhash (obj, make_fixnum (- print_number_index),
|
||||
Vprint_number_table);
|
||||
}
|
||||
print_depth--;
|
||||
return;
|
||||
}
|
||||
else
|
||||
/* OBJ is not yet recorded. Let's add to the table. */
|
||||
Fputhash (obj, Qt, Vprint_number_table);
|
||||
|
||||
switch (XTYPE (obj))
|
||||
if (PRINT_CIRCLE_CANDIDATE_P (obj))
|
||||
{
|
||||
case Lisp_String:
|
||||
/* A string may have text properties, which can be circular. */
|
||||
traverse_intervals_noorder (string_intervals (obj),
|
||||
print_preprocess_string, NULL);
|
||||
break;
|
||||
if (!HASH_TABLE_P (Vprint_number_table))
|
||||
Vprint_number_table = CALLN (Fmake_hash_table, QCtest, Qeq);
|
||||
|
||||
case Lisp_Cons:
|
||||
/* Use HALFTAIL and LOOP_COUNT to detect circular lists,
|
||||
just as in print_object. */
|
||||
if (loop_count && EQ (obj, halftail))
|
||||
break;
|
||||
print_preprocess (XCAR (obj));
|
||||
obj = XCDR (obj);
|
||||
loop_count++;
|
||||
if (!(loop_count & 1))
|
||||
halftail = XCDR (halftail);
|
||||
goto loop;
|
||||
|
||||
case Lisp_Vectorlike:
|
||||
size = ASIZE (obj);
|
||||
if (size & PSEUDOVECTOR_FLAG)
|
||||
size &= PSEUDOVECTOR_SIZE_MASK;
|
||||
for (i = (SUB_CHAR_TABLE_P (obj)
|
||||
? SUB_CHAR_TABLE_OFFSET : 0); i < size; i++)
|
||||
print_preprocess (AREF (obj, i));
|
||||
if (HASH_TABLE_P (obj))
|
||||
{ /* For hash tables, the key_and_value slot is past
|
||||
`size' because it needs to be marked specially in case
|
||||
the table is weak. */
|
||||
struct Lisp_Hash_Table *h = XHASH_TABLE (obj);
|
||||
print_preprocess (h->key_and_value);
|
||||
Lisp_Object num = Fgethash (obj, Vprint_number_table, Qnil);
|
||||
if (!NILP (num)
|
||||
/* If Vprint_continuous_numbering is non-nil and OBJ is a gensym,
|
||||
always print the gensym with a number. This is a special for
|
||||
the lisp function byte-compile-output-docform. */
|
||||
|| (!NILP (Vprint_continuous_numbering)
|
||||
&& SYMBOLP (obj)
|
||||
&& !SYMBOL_INTERNED_P (obj)))
|
||||
{ /* OBJ appears more than once. Let's remember that. */
|
||||
if (!FIXNUMP (num))
|
||||
{
|
||||
print_number_index++;
|
||||
/* Negative number indicates it hasn't been printed yet. */
|
||||
Fputhash (obj, make_fixnum (- print_number_index),
|
||||
Vprint_number_table);
|
||||
}
|
||||
}
|
||||
break;
|
||||
else
|
||||
{
|
||||
/* OBJ is not yet recorded. Let's add to the table. */
|
||||
Fputhash (obj, Qt, Vprint_number_table);
|
||||
|
||||
default:
|
||||
break;
|
||||
switch (XTYPE (obj))
|
||||
{
|
||||
case Lisp_String:
|
||||
/* A string may have text properties,
|
||||
which can be circular. */
|
||||
traverse_intervals_noorder (string_intervals (obj),
|
||||
print_preprocess_string, NULL);
|
||||
break;
|
||||
|
||||
case Lisp_Cons:
|
||||
if (!NILP (XCDR (obj)))
|
||||
pp_stack_push_value (XCDR (obj));
|
||||
obj = XCAR (obj);
|
||||
continue;
|
||||
|
||||
case Lisp_Vectorlike:
|
||||
{
|
||||
struct Lisp_Vector *vec = XVECTOR (obj);
|
||||
ptrdiff_t size = ASIZE (obj);
|
||||
if (size & PSEUDOVECTOR_FLAG)
|
||||
size &= PSEUDOVECTOR_SIZE_MASK;
|
||||
ptrdiff_t start = (SUB_CHAR_TABLE_P (obj)
|
||||
? SUB_CHAR_TABLE_OFFSET : 0);
|
||||
pp_stack_push_values (vec->contents + start, size - start);
|
||||
if (HASH_TABLE_P (obj))
|
||||
{
|
||||
struct Lisp_Hash_Table *h = XHASH_TABLE (obj);
|
||||
obj = h->key_and_value;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ppstack.sp <= base_sp)
|
||||
break;
|
||||
obj = pp_stack_pop ();
|
||||
}
|
||||
print_depth--;
|
||||
}
|
||||
|
||||
DEFUN ("print--preprocess", Fprint_preprocess, Sprint_preprocess, 1, 1, 0,
|
||||
@ -1569,162 +1638,6 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
|
||||
}
|
||||
return true;
|
||||
|
||||
case PVEC_HASH_TABLE:
|
||||
{
|
||||
struct Lisp_Hash_Table *h = XHASH_TABLE (obj);
|
||||
/* Implement a readable output, e.g.:
|
||||
#s(hash-table size 2 test equal data (k1 v1 k2 v2)) */
|
||||
/* Always print the size. */
|
||||
int len = sprintf (buf, "#s(hash-table size %"pD"d",
|
||||
HASH_TABLE_SIZE (h));
|
||||
strout (buf, len, len, printcharfun);
|
||||
|
||||
if (!NILP (h->test.name))
|
||||
{
|
||||
print_c_string (" test ", printcharfun);
|
||||
print_object (h->test.name, printcharfun, escapeflag);
|
||||
}
|
||||
|
||||
if (!NILP (h->weak))
|
||||
{
|
||||
print_c_string (" weakness ", printcharfun);
|
||||
print_object (h->weak, printcharfun, escapeflag);
|
||||
}
|
||||
|
||||
print_c_string (" rehash-size ", printcharfun);
|
||||
print_object (Fhash_table_rehash_size (obj),
|
||||
printcharfun, escapeflag);
|
||||
|
||||
print_c_string (" rehash-threshold ", printcharfun);
|
||||
print_object (Fhash_table_rehash_threshold (obj),
|
||||
printcharfun, escapeflag);
|
||||
|
||||
if (h->purecopy)
|
||||
{
|
||||
print_c_string (" purecopy ", printcharfun);
|
||||
print_object (h->purecopy ? Qt : Qnil, printcharfun, escapeflag);
|
||||
}
|
||||
|
||||
print_c_string (" data ", printcharfun);
|
||||
|
||||
/* Print the data here as a plist. */
|
||||
ptrdiff_t real_size = HASH_TABLE_SIZE (h);
|
||||
ptrdiff_t size = h->count;
|
||||
|
||||
/* Don't print more elements than the specified maximum. */
|
||||
if (FIXNATP (Vprint_length) && XFIXNAT (Vprint_length) < size)
|
||||
size = XFIXNAT (Vprint_length);
|
||||
|
||||
printchar ('(', printcharfun);
|
||||
ptrdiff_t j = 0;
|
||||
for (ptrdiff_t i = 0; i < real_size; i++)
|
||||
{
|
||||
Lisp_Object key = HASH_KEY (h, i);
|
||||
if (!EQ (key, Qunbound))
|
||||
{
|
||||
if (j++) printchar (' ', printcharfun);
|
||||
print_object (key, printcharfun, escapeflag);
|
||||
printchar (' ', printcharfun);
|
||||
print_object (HASH_VALUE (h, i), printcharfun, escapeflag);
|
||||
if (j == size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j < h->count)
|
||||
{
|
||||
if (j)
|
||||
printchar (' ', printcharfun);
|
||||
print_c_string ("...", printcharfun);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -2103,32 +2016,118 @@ named_escape (int i)
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum print_entry_type {
|
||||
PE_list, /* print rest of list */
|
||||
PE_rbrac, /* print ")" */
|
||||
PE_vector, /* print rest of vector */
|
||||
PE_hash, /* print rest of hash data */
|
||||
};
|
||||
|
||||
struct print_stack_entry {
|
||||
enum print_entry_type type;
|
||||
union {
|
||||
struct {
|
||||
Lisp_Object last; /* cons whose car was just printed */
|
||||
ptrdiff_t idx; /* index of next element */
|
||||
intmax_t maxlen; /* max length (from Vprint_length) */
|
||||
/* state for Brent cycle detection */
|
||||
Lisp_Object tortoise; /* slow pointer */
|
||||
ptrdiff_t n; /* tortoise step countdown */
|
||||
ptrdiff_t m; /* tortoise step period */
|
||||
} list;
|
||||
struct {
|
||||
Lisp_Object obj; /* object to print after " . " */
|
||||
} dotted_cdr;
|
||||
struct {
|
||||
Lisp_Object obj; /* vector object */
|
||||
ptrdiff_t size; /* length of vector */
|
||||
ptrdiff_t idx; /* index of next element */
|
||||
const char *end; /* string to print at end */
|
||||
bool truncated; /* whether to print "..." before end */
|
||||
} vector;
|
||||
struct {
|
||||
Lisp_Object obj; /* hash-table object */
|
||||
ptrdiff_t nobjs; /* number of keys and values to print */
|
||||
ptrdiff_t idx; /* index of key-value pair */
|
||||
ptrdiff_t printed; /* number of keys and values printed */
|
||||
bool truncated; /* whether to print "..." before end */
|
||||
} hash;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct print_stack {
|
||||
struct print_stack_entry *stack; /* base of stack */
|
||||
ptrdiff_t size; /* allocated size in entries */
|
||||
ptrdiff_t sp; /* current number of entries */
|
||||
};
|
||||
|
||||
static struct print_stack prstack = {NULL, 0, 0};
|
||||
|
||||
NO_INLINE static void
|
||||
grow_print_stack (void)
|
||||
{
|
||||
struct print_stack *ps = &prstack;
|
||||
eassert (ps->sp == ps->size);
|
||||
ps->stack = xpalloc (ps->stack, &ps->size, 1, -1, sizeof *ps->stack);
|
||||
eassert (ps->sp < ps->size);
|
||||
}
|
||||
|
||||
static inline void
|
||||
print_stack_push (struct print_stack_entry e)
|
||||
{
|
||||
if (prstack.sp >= prstack.size)
|
||||
grow_print_stack ();
|
||||
prstack.stack[prstack.sp++] = e;
|
||||
}
|
||||
|
||||
static void
|
||||
print_stack_push_vector (const char *lbrac, const char *rbrac,
|
||||
Lisp_Object obj, ptrdiff_t start, ptrdiff_t size,
|
||||
Lisp_Object printcharfun)
|
||||
{
|
||||
print_c_string (lbrac, printcharfun);
|
||||
|
||||
ptrdiff_t print_size = ((FIXNATP (Vprint_length)
|
||||
&& XFIXNAT (Vprint_length) < size)
|
||||
? XFIXNAT (Vprint_length) : size);
|
||||
print_stack_push ((struct print_stack_entry){
|
||||
.type = PE_vector,
|
||||
.u.vector.obj = obj,
|
||||
.u.vector.size = print_size,
|
||||
.u.vector.idx = start,
|
||||
.u.vector.end = rbrac,
|
||||
.u.vector.truncated = (print_size < size),
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
{
|
||||
ptrdiff_t base_depth = print_depth;
|
||||
ptrdiff_t base_sp = prstack.sp;
|
||||
char buf[max (sizeof "from..to..in " + 2 * INT_STRLEN_BOUND (EMACS_INT),
|
||||
max (sizeof " . #" + INT_STRLEN_BOUND (intmax_t),
|
||||
max ((sizeof " with data 0x"
|
||||
+ (sizeof (uintmax_t) * CHAR_BIT + 4 - 1) / 4),
|
||||
40)))];
|
||||
current_thread->stack_top = buf;
|
||||
|
||||
print_obj:
|
||||
maybe_quit ();
|
||||
|
||||
/* Detect circularities and truncate them. */
|
||||
if (NILP (Vprint_circle))
|
||||
{
|
||||
/* Simple but incomplete way. */
|
||||
int i;
|
||||
|
||||
if (print_depth >= PRINT_CIRCLE)
|
||||
error ("Apparently circular structure being printed");
|
||||
|
||||
for (i = 0; i < print_depth; i++)
|
||||
for (int i = 0; i < print_depth; i++)
|
||||
if (BASE_EQ (obj, being_printed[i]))
|
||||
{
|
||||
int len = sprintf (buf, "#%d", i);
|
||||
strout (buf, len, len, printcharfun);
|
||||
return;
|
||||
goto next_obj;
|
||||
}
|
||||
being_printed[print_depth] = obj;
|
||||
}
|
||||
@ -2152,7 +2151,7 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
/* Just print #n# if OBJ has already been printed. */
|
||||
int len = sprintf (buf, "#%"pI"d#", n);
|
||||
strout (buf, len, len, printcharfun);
|
||||
return;
|
||||
goto next_obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2226,7 +2225,8 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
for (i = 0, i_byte = 0; i_byte < size_byte;)
|
||||
{
|
||||
/* Here, we must convert each multi-byte form to the
|
||||
corresponding character code before handing it to printchar. */
|
||||
corresponding character code before handing it to
|
||||
printchar. */
|
||||
int c = fetch_string_char_advance (obj, &i, &i_byte);
|
||||
|
||||
maybe_quit ();
|
||||
@ -2246,7 +2246,8 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
else if (multibyte
|
||||
&& ! ASCII_CHAR_P (c) && print_escape_multibyte)
|
||||
{
|
||||
/* When requested, print multibyte chars using hex escapes. */
|
||||
/* When requested, print multibyte chars using
|
||||
hex escapes. */
|
||||
char outbuf[sizeof "\\x" + INT_STRLEN_BOUND (c)];
|
||||
int len = sprintf (outbuf, "\\x%04x", c + 0u);
|
||||
strout (outbuf, len, len, printcharfun);
|
||||
@ -2357,14 +2358,22 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
&& EQ (XCAR (obj), Qquote))
|
||||
{
|
||||
printchar ('\'', printcharfun);
|
||||
print_object (XCAR (XCDR (obj)), printcharfun, escapeflag);
|
||||
obj = XCAR (XCDR (obj));
|
||||
--print_depth; /* tail recursion */
|
||||
goto print_obj;
|
||||
}
|
||||
else if (print_quoted && CONSP (XCDR (obj)) && NILP (XCDR (XCDR (obj)))
|
||||
&& EQ (XCAR (obj), Qfunction))
|
||||
{
|
||||
print_c_string ("#'", printcharfun);
|
||||
print_object (XCAR (XCDR (obj)), printcharfun, escapeflag);
|
||||
obj = XCAR (XCDR (obj));
|
||||
--print_depth; /* tail recursion */
|
||||
goto print_obj;
|
||||
}
|
||||
/* FIXME: Do we really need the new_backquote_output gating of
|
||||
special syntax for comma and comma-at? There is basically no
|
||||
benefit from it at all, and it would be nice to get rid of
|
||||
the recursion here without additional complexity. */
|
||||
else if (print_quoted && CONSP (XCDR (obj)) && NILP (XCDR (XCDR (obj)))
|
||||
&& EQ (XCAR (obj), Qbackquote))
|
||||
{
|
||||
@ -2374,9 +2383,9 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
new_backquote_output--;
|
||||
}
|
||||
else if (print_quoted && CONSP (XCDR (obj)) && NILP (XCDR (XCDR (obj)))
|
||||
&& new_backquote_output
|
||||
&& (EQ (XCAR (obj), Qcomma)
|
||||
|| EQ (XCAR (obj), Qcomma_at)))
|
||||
|| EQ (XCAR (obj), Qcomma_at))
|
||||
&& new_backquote_output)
|
||||
{
|
||||
print_object (XCAR (obj), printcharfun, false);
|
||||
new_backquote_output--;
|
||||
@ -2386,70 +2395,135 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
else
|
||||
{
|
||||
printchar ('(', printcharfun);
|
||||
|
||||
/* Negative values of print-length are invalid in CL.
|
||||
Treat them like nil, as CMUCL does. */
|
||||
intmax_t print_length = (FIXNATP (Vprint_length)
|
||||
? XFIXNAT (Vprint_length)
|
||||
: INTMAX_MAX);
|
||||
Lisp_Object objtail = Qnil;
|
||||
intmax_t i = 0;
|
||||
FOR_EACH_TAIL_SAFE (obj)
|
||||
if (print_length == 0)
|
||||
print_c_string ("...)", printcharfun);
|
||||
else
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
printchar (' ', printcharfun);
|
||||
|
||||
if (!NILP (Vprint_circle))
|
||||
{
|
||||
/* With the print-circle feature. */
|
||||
Lisp_Object num = Fgethash (obj, Vprint_number_table,
|
||||
Qnil);
|
||||
if (FIXNUMP (num))
|
||||
{
|
||||
print_c_string (". ", printcharfun);
|
||||
print_object (obj, printcharfun, escapeflag);
|
||||
goto end_of_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (print_length <= i)
|
||||
{
|
||||
print_c_string ("...", printcharfun);
|
||||
goto end_of_list;
|
||||
}
|
||||
|
||||
i++;
|
||||
print_object (XCAR (obj), printcharfun, escapeflag);
|
||||
objtail = XCDR (obj);
|
||||
print_stack_push ((struct print_stack_entry){
|
||||
.type = PE_list,
|
||||
.u.list.last = obj,
|
||||
.u.list.maxlen = print_length,
|
||||
.u.list.idx = 1,
|
||||
.u.list.tortoise = obj,
|
||||
.u.list.n = 2,
|
||||
.u.list.m = 2,
|
||||
});
|
||||
/* print the car */
|
||||
obj = XCAR (obj);
|
||||
goto print_obj;
|
||||
}
|
||||
|
||||
/* OBJTAIL non-nil here means it's the end of a dotted list
|
||||
or FOR_EACH_TAIL_SAFE detected a circular list. */
|
||||
if (!NILP (objtail))
|
||||
{
|
||||
print_c_string (" . ", printcharfun);
|
||||
|
||||
if (CONSP (objtail) && NILP (Vprint_circle))
|
||||
{
|
||||
int len = sprintf (buf, "#%"PRIdMAX, i >> 1);
|
||||
strout (buf, len, len, printcharfun);
|
||||
goto end_of_list;
|
||||
}
|
||||
|
||||
print_object (objtail, printcharfun, escapeflag);
|
||||
}
|
||||
|
||||
end_of_list:
|
||||
printchar (')', printcharfun);
|
||||
}
|
||||
break;
|
||||
|
||||
case Lisp_Vectorlike:
|
||||
/* First do all the vectorlike types that have a readable syntax. */
|
||||
switch (PSEUDOVECTOR_TYPE (XVECTOR (obj)))
|
||||
{
|
||||
case PVEC_NORMAL_VECTOR:
|
||||
{
|
||||
print_stack_push_vector ("[", "]", obj, 0, ASIZE (obj),
|
||||
printcharfun);
|
||||
goto next_obj;
|
||||
}
|
||||
case PVEC_RECORD:
|
||||
{
|
||||
print_stack_push_vector ("#s(", ")", obj, 0, PVSIZE (obj),
|
||||
printcharfun);
|
||||
goto next_obj;
|
||||
}
|
||||
case PVEC_COMPILED:
|
||||
{
|
||||
print_stack_push_vector ("#[", "]", obj, 0, PVSIZE (obj),
|
||||
printcharfun);
|
||||
goto next_obj;
|
||||
}
|
||||
case PVEC_CHAR_TABLE:
|
||||
{
|
||||
print_stack_push_vector ("#^[", "]", obj, 0, PVSIZE (obj),
|
||||
printcharfun);
|
||||
goto next_obj;
|
||||
}
|
||||
case PVEC_SUB_CHAR_TABLE:
|
||||
{
|
||||
/* Make each lowest sub_char_table start a new line.
|
||||
Otherwise we'll make a line extremely long, which
|
||||
results in slow redisplay. */
|
||||
if (XSUB_CHAR_TABLE (obj)->depth == 3)
|
||||
printchar ('\n', printcharfun);
|
||||
print_c_string ("#^^[", printcharfun);
|
||||
int n = sprintf (buf, "%d %d",
|
||||
XSUB_CHAR_TABLE (obj)->depth,
|
||||
XSUB_CHAR_TABLE (obj)->min_char);
|
||||
strout (buf, n, n, printcharfun);
|
||||
print_stack_push_vector ("", "]", obj,
|
||||
SUB_CHAR_TABLE_OFFSET, PVSIZE (obj),
|
||||
printcharfun);
|
||||
goto next_obj;
|
||||
}
|
||||
case PVEC_HASH_TABLE:
|
||||
{
|
||||
struct Lisp_Hash_Table *h = XHASH_TABLE (obj);
|
||||
/* Implement a readable output, e.g.:
|
||||
#s(hash-table size 2 test equal data (k1 v1 k2 v2)) */
|
||||
/* Always print the size. */
|
||||
int len = sprintf (buf, "#s(hash-table size %"pD"d",
|
||||
HASH_TABLE_SIZE (h));
|
||||
strout (buf, len, len, printcharfun);
|
||||
|
||||
if (!NILP (h->test.name))
|
||||
{
|
||||
print_c_string (" test ", printcharfun);
|
||||
print_object (h->test.name, printcharfun, escapeflag);
|
||||
}
|
||||
|
||||
if (!NILP (h->weak))
|
||||
{
|
||||
print_c_string (" weakness ", printcharfun);
|
||||
print_object (h->weak, printcharfun, escapeflag);
|
||||
}
|
||||
|
||||
print_c_string (" rehash-size ", printcharfun);
|
||||
print_object (Fhash_table_rehash_size (obj),
|
||||
printcharfun, escapeflag);
|
||||
|
||||
print_c_string (" rehash-threshold ", printcharfun);
|
||||
print_object (Fhash_table_rehash_threshold (obj),
|
||||
printcharfun, escapeflag);
|
||||
|
||||
if (h->purecopy)
|
||||
print_c_string (" purecopy t", printcharfun);
|
||||
|
||||
print_c_string (" data (", printcharfun);
|
||||
|
||||
ptrdiff_t size = h->count;
|
||||
/* Don't print more elements than the specified maximum. */
|
||||
if (FIXNATP (Vprint_length) && XFIXNAT (Vprint_length) < size)
|
||||
size = XFIXNAT (Vprint_length);
|
||||
|
||||
print_stack_push ((struct print_stack_entry){
|
||||
.type = PE_hash,
|
||||
.u.hash.obj = obj,
|
||||
.u.hash.nobjs = size * 2,
|
||||
.u.hash.idx = 0,
|
||||
.u.hash.printed = 0,
|
||||
.u.hash.truncated = (size < h->count),
|
||||
});
|
||||
goto next_obj;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (print_vectorlike (obj, printcharfun, escapeflag, buf))
|
||||
break;
|
||||
FALLTHROUGH;
|
||||
|
||||
default:
|
||||
{
|
||||
int len;
|
||||
@ -2464,10 +2538,160 @@ print_object (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag)
|
||||
print_c_string ((" Save your buffers immediately"
|
||||
" and please report this bug>"),
|
||||
printcharfun);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
print_depth--;
|
||||
|
||||
next_obj:
|
||||
if (prstack.sp > base_sp)
|
||||
{
|
||||
/* Handle a continuation on the print stack. */
|
||||
struct print_stack_entry *e = &prstack.stack[prstack.sp - 1];
|
||||
switch (e->type)
|
||||
{
|
||||
case PE_list:
|
||||
{
|
||||
/* after "(" ELEM (* " " ELEM) */
|
||||
Lisp_Object next = XCDR (e->u.list.last);
|
||||
if (NILP (next))
|
||||
{
|
||||
/* end of list: print ")" */
|
||||
printchar (')', printcharfun);
|
||||
--prstack.sp;
|
||||
--print_depth;
|
||||
goto next_obj;
|
||||
}
|
||||
else if (CONSP (next))
|
||||
{
|
||||
if (!NILP (Vprint_circle))
|
||||
{
|
||||
/* With the print-circle feature. */
|
||||
Lisp_Object num = Fgethash (next, Vprint_number_table,
|
||||
Qnil);
|
||||
if (FIXNUMP (num))
|
||||
{
|
||||
print_c_string (" . ", printcharfun);
|
||||
obj = next;
|
||||
e->type = PE_rbrac;
|
||||
goto print_obj;
|
||||
}
|
||||
}
|
||||
|
||||
/* list continues: print " " ELEM ... */
|
||||
|
||||
printchar (' ', printcharfun);
|
||||
|
||||
/* FIXME: We wouldn't need to keep track of idx if we
|
||||
count down maxlen instead, and maintain a separate
|
||||
tortoise index if required. */
|
||||
if (e->u.list.idx >= e->u.list.maxlen)
|
||||
{
|
||||
print_c_string ("...)", printcharfun);
|
||||
--prstack.sp;
|
||||
--print_depth;
|
||||
goto next_obj;
|
||||
}
|
||||
|
||||
e->u.list.last = next;
|
||||
e->u.list.idx++;
|
||||
e->u.list.n--;
|
||||
if (e->u.list.n == 0)
|
||||
{
|
||||
/* Double tortoise update period and teleport it. */
|
||||
e->u.list.m <<= 1;
|
||||
e->u.list.n = e->u.list.m;
|
||||
e->u.list.tortoise = next;
|
||||
}
|
||||
else if (BASE_EQ (next, e->u.list.tortoise))
|
||||
{
|
||||
/* FIXME: This #N tail index is bug-compatible with
|
||||
previous implementations but actually nonsense;
|
||||
see bug#55395. */
|
||||
int len = sprintf (buf, ". #%" PRIdMAX ")",
|
||||
(e->u.list.idx >> 1) - 1);
|
||||
strout (buf, len, len, printcharfun);
|
||||
--prstack.sp;
|
||||
--print_depth;
|
||||
goto next_obj;
|
||||
}
|
||||
obj = XCAR (next);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* non-nil ending: print " . " ELEM ")" */
|
||||
print_c_string (" . ", printcharfun);
|
||||
obj = next;
|
||||
e->type = PE_rbrac;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PE_rbrac:
|
||||
printchar (')', printcharfun);
|
||||
--prstack.sp;
|
||||
--print_depth;
|
||||
goto next_obj;
|
||||
|
||||
case PE_vector:
|
||||
if (e->u.vector.idx >= e->u.vector.size)
|
||||
{
|
||||
if (e->u.vector.truncated)
|
||||
{
|
||||
if (e->u.vector.idx > 0)
|
||||
printchar (' ', printcharfun);
|
||||
print_c_string ("...", printcharfun);
|
||||
}
|
||||
print_c_string (e->u.vector.end, printcharfun);
|
||||
--prstack.sp;
|
||||
--print_depth;
|
||||
goto next_obj;
|
||||
}
|
||||
if (e->u.vector.idx > 0)
|
||||
printchar (' ', printcharfun);
|
||||
obj = AREF (e->u.vector.obj, e->u.vector.idx);
|
||||
e->u.vector.idx++;
|
||||
break;
|
||||
|
||||
case PE_hash:
|
||||
if (e->u.hash.printed >= e->u.hash.nobjs)
|
||||
{
|
||||
if (e->u.hash.truncated)
|
||||
{
|
||||
if (e->u.hash.printed)
|
||||
printchar (' ', printcharfun);
|
||||
print_c_string ("...", printcharfun);
|
||||
}
|
||||
print_c_string ("))", printcharfun);
|
||||
--prstack.sp;
|
||||
--print_depth;
|
||||
goto next_obj;
|
||||
}
|
||||
|
||||
if (e->u.hash.printed)
|
||||
printchar (' ', printcharfun);
|
||||
|
||||
struct Lisp_Hash_Table *h = XHASH_TABLE (e->u.hash.obj);
|
||||
if ((e->u.hash.printed & 1) == 0)
|
||||
{
|
||||
Lisp_Object key;
|
||||
ptrdiff_t idx = e->u.hash.idx;
|
||||
while (BASE_EQ ((key = HASH_KEY (h, idx)), Qunbound))
|
||||
idx++;
|
||||
e->u.hash.idx = idx;
|
||||
obj = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = HASH_VALUE (h, e->u.hash.idx);
|
||||
e->u.hash.idx++;
|
||||
}
|
||||
e->u.hash.printed++;
|
||||
break;
|
||||
}
|
||||
goto print_obj;
|
||||
}
|
||||
eassert (print_depth == base_depth);
|
||||
}
|
||||
|
||||
|
||||
|
@ -468,5 +468,21 @@ otherwise, use a different charset."
|
||||
(should-error (prin1-to-string 'foo nil '((a . b) b)))
|
||||
(should-error (prin1-to-string 'foo nil '((length . 10) . b))))
|
||||
|
||||
(ert-deftest print-deeply-nested ()
|
||||
;; Check that we can print a deeply nested data structure correctly.
|
||||
(let ((print-circle t))
|
||||
(let ((levels 10000)
|
||||
(x 'a)
|
||||
(prefix nil)
|
||||
(suffix nil))
|
||||
(dotimes (_ levels)
|
||||
(setq x (list (vector (record 'r x))))
|
||||
(push "([#s(r " prefix)
|
||||
(push ")])" suffix))
|
||||
(let ((expected (concat (apply #'concat prefix)
|
||||
"a"
|
||||
(apply #'concat suffix))))
|
||||
(should (equal (prin1-to-string x) expected))))))
|
||||
|
||||
(provide 'print-tests)
|
||||
;;; print-tests.el ends here
|
||||
|
Loading…
Reference in New Issue
Block a user