1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2005-2018. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24 
25 #include "erl_printf_term.h"
26 #include "sys.h"
27 #include "big.h"
28 #include "erl_map.h"
29 #include "erl_binary.h"
30 
31 #define PRINT_CHAR(CNT, FN, ARG, C)					\
32 do {									\
33     int res__ = erts_printf_char((FN), (ARG), (C));			\
34     if (res__ < 0)							\
35 	return res__;							\
36     (CNT) += res__;							\
37 } while (0)
38 
39 #define PRINT_STRING(CNT, FN, ARG, STR)					\
40 do {									\
41     int res__ = erts_printf_string((FN), (ARG), (STR));			\
42     if (res__ < 0)							\
43 	return res__;							\
44     (CNT) += res__;							\
45 } while (0)
46 
47 #define PRINT_BUF(CNT, FN, ARG, BUF, LEN)				\
48 do {									\
49     int res__ = erts_printf_buf((FN), (ARG), (char*)(BUF), (LEN));	\
50     if (res__ < 0)							\
51 	return res__;							\
52     (CNT) += res__;							\
53 } while (0)
54 
55 #define PRINT_POINTER(CNT, FN, ARG, PTR)				\
56 do {									\
57     int res__ = erts_printf_pointer((FN), (ARG), (void *) (PTR));	\
58     if (res__ < 0)							\
59 	return res__;							\
60     (CNT) += res__;							\
61 } while (0)
62 
63 #define PRINT_UWORD(CNT, FN, ARG, C, P, W, I)				\
64 do {									\
65     int res__ = erts_printf_uword((FN), (ARG), (C), (P), (W), (I));	\
66     if (res__ < 0)							\
67 	return res__;							\
68     (CNT) += res__;							\
69 } while (0)
70 
71 #define PRINT_SWORD(CNT, FN, ARG, C, P, W, I)				\
72 do {									\
73     int res__ = erts_printf_sword((FN), (ARG), (C), (P), (W), (I));	\
74     if (res__ < 0)							\
75 	return res__;							\
76     (CNT) += res__;							\
77 } while (0)
78 
79 #define PRINT_DOUBLE(CNT, FN, ARG, C, P, W, I)				\
80 do {									\
81     int res__ = erts_printf_double((FN), (ARG), (C), (P), (W), (I));	\
82     if (res__ < 0)							\
83 	return res__;							\
84     (CNT) += res__;							\
85 } while (0)
86 
87 /* CTYPE macros */
88 
89 #define LATIN1
90 
91 #define IS_DIGIT(c)  ((c) >= '0' && (c) <= '9')
92 #ifdef LATIN1
93 #define IS_LOWER(c)  (((c) >= 'a' && (c) <= 'z') \
94 		      || ((c) >= 128+95 && (c) <= 255 && (c) != 247))
95 #define IS_UPPER(c)  (((c) >= 'A' && (c) <= 'Z') \
96 		      || ((c) >= 128+64 && (c) <= 128+94 && (c) != 247-32))
97 #else
98 #define IS_LOWER(c)  ((c) >= 'a' && (c) <= 'z')
99 #define IS_UPPER(c)  ((c) >= 'A' && (c) <= 'Z')
100 #endif
101 
102 #define IS_ALNUM(c)  (IS_DIGIT(c) || IS_LOWER(c) || IS_UPPER(c))
103 
104 /* We don't include 160 (non-breaking space). */
105 #define IS_SPACE(c)  (c == ' ' || c == '\n' || c == '\t' || c == '\r')
106 
107 #ifdef LATIN1
108 #define IS_CNTRL(c)  ((c) < ' ' || (c) == 127 \
109 		      || ((c) >= 128 && (c) < 128+32))
110 #else
111 /* Treat all non-ASCII as control characters */
112 #define IS_CNTRL(c)  ((c) < ' ' || (c) >= 127)
113 #endif
114 
115 #define IS_PRINT(c)  (!IS_CNTRL(c))
116 
117 /* return 0 if list is not a non-empty flat list of printable characters */
118 
119 static int
is_printable_string(Eterm list)120 is_printable_string(Eterm list) {
121     int len = 0;
122     int c;
123 
124     while(is_list(list)) {
125 	Eterm* consp = list_val(list);
126 	Eterm hd = CAR(consp);
127 
128 	if (!is_byte(hd))
129 	    return 0;
130 	c = signed_val(hd);
131 	/* IS_PRINT || IS_SPACE would be another way to put it */
132 	if (IS_CNTRL(c) && !IS_SPACE(c))
133 	   return 0;
134 	len++;
135 	list = CDR(consp);
136     }
137     if (is_nil(list))
138 	return len;
139     return 0;
140 }
141 
is_printable_ascii(byte * bytep,Uint bytesize,Uint bitoffs)142 static int is_printable_ascii(byte* bytep, Uint bytesize, Uint bitoffs)
143 {
144     if (!bitoffs) {
145 	while (bytesize--) {
146 	    if (*bytep < ' ' || *bytep >= 127)
147 		return 0;
148 	    bytep++;
149 	}
150     } else {
151 	while (bytesize--) {
152 	    byte octet = (bytep[0] << bitoffs) | (bytep[1] >> (8-bitoffs));
153 	    if (octet < ' ' || octet >= 127)
154 		return 0;
155 	    bytep++;
156 	}
157     }
158     return 1;
159 }
160 
161 /* print a atom doing what quoting is necessary */
print_atom_name(fmtfn_t fn,void * arg,Eterm atom,long * dcount)162 static int print_atom_name(fmtfn_t fn, void* arg, Eterm atom, long *dcount)
163 {
164     int n, i;
165     int res;
166     int need_quote;
167     int pos;
168     byte *s;
169     byte *cpos;
170     int c;
171 
172     res = 0;
173     i = atom_val(atom);
174 
175     if ((i < 0) || (i >= atom_table_size()) ||  (atom_tab(i) == NULL)) {
176 	PRINT_STRING(res, fn, arg, "<bad atom index: ");
177 	PRINT_SWORD(res, fn, arg, 'd', 0, 1, (ErlPfSWord) i);
178 	PRINT_CHAR(res, fn, arg, '>');
179 	return res;
180     }
181 
182     s = atom_tab(i)->name;
183     n = atom_tab(i)->len;
184 
185     *dcount -= atom_tab(i)->len;
186 
187     if (n == 0) {
188 	PRINT_STRING(res, fn, arg, "''");
189 	return res;
190     }
191 
192 
193     need_quote = 0;
194     cpos = s;
195     pos = n - 1;
196 
197     c = *cpos++;
198     if (!IS_LOWER(c))
199 	need_quote++;
200     else {
201 	while (pos--) {
202 	    c = *cpos++;
203 	    if (!IS_ALNUM(c) && (c != '_')) {
204 		need_quote++;
205 		break;
206 	    }
207 	}
208     }
209     cpos = s;
210     pos = n;
211     if (need_quote)
212 	PRINT_CHAR(res, fn, arg, '\'');
213     while(pos--) {
214 	c = *cpos++;
215 	switch(c) {
216 	case '\'': PRINT_STRING(res, fn, arg, "\\'"); break;
217 	case '\\': PRINT_STRING(res, fn, arg, "\\\\"); break;
218 	case '\n': PRINT_STRING(res, fn, arg, "\\n"); break;
219 	case '\f': PRINT_STRING(res, fn, arg, "\\f"); break;
220 	case '\t': PRINT_STRING(res, fn, arg, "\\t"); break;
221 	case '\r': PRINT_STRING(res, fn, arg, "\\r"); break;
222 	case '\b': PRINT_STRING(res, fn, arg, "\\b"); break;
223 	case '\v': PRINT_STRING(res, fn, arg, "\\v"); break;
224 	default:
225 	    if (IS_CNTRL(c)) {
226 		PRINT_CHAR(res, fn, arg, '\\');
227 		PRINT_UWORD(res, fn, arg, 'o', 1, 3, (ErlPfUWord) c);
228 	    }
229 	    else
230 		PRINT_CHAR(res, fn, arg, (char) c);
231 	    break;
232 	}
233     }
234     if (need_quote)
235 	PRINT_CHAR(res, fn, arg, '\'');
236     return res;
237 }
238 
239 
240 #define PRT_BAR                ((Eterm) 0)
241 #define PRT_COMMA              ((Eterm) 1)
242 #define PRT_CLOSE_LIST         ((Eterm) 2)
243 #define PRT_CLOSE_TUPLE        ((Eterm) 3)
244 #define PRT_ASSOC              ((Eterm) 4)
245 #define PRT_TERM               ((Eterm) 5)
246 #define PRT_ONE_CONS           ((Eterm) 6)
247 #define PRT_PATCH_FUN_SIZE     ((Eterm) 7)
248 #define PRT_LAST_ARRAY_ELEMENT ((Eterm) 8) /* Note! Must be last... */
249 
250 #if 0
251 static char *format_binary(Uint16 x, char *b) {
252     int z;
253     b[16] = '\0';
254     for (z = 0; z < 16; z++) {
255 	b[15-z] = ((x>>z) & 0x1) ? '1' : '0';
256     }
257     return b;
258 }
259 #endif
260 
261 static int
print_term(fmtfn_t fn,void * arg,Eterm obj,long * dcount)262 print_term(fmtfn_t fn, void* arg, Eterm obj, long *dcount) {
263     DECLARE_WSTACK(s);
264     int res;
265     int i;
266     Eterm val;
267     Uint32 *ref_num;
268     union {
269 	UWord word;
270 	Eterm* ptr;
271     }popped;
272     Eterm* nobj;
273     Wterm wobj;
274 
275     res = 0;
276 
277     goto L_jump_start;
278 
279  L_outer_loop:
280     while (!WSTACK_ISEMPTY(s)) {
281 	switch (val = WSTACK_POP(s)) {
282 	case PRT_COMMA:
283 	    PRINT_CHAR(res, fn, arg, ',');
284 	    goto L_outer_loop;
285 	case PRT_BAR:
286 	    PRINT_CHAR(res, fn, arg, '|');
287 	    goto L_outer_loop;
288 	case PRT_CLOSE_LIST:
289 	    PRINT_CHAR(res, fn, arg, ']');
290 	    goto L_outer_loop;
291 	case PRT_CLOSE_TUPLE:
292 	    PRINT_CHAR(res, fn, arg, '}');
293 	    goto L_outer_loop;
294 	case PRT_ASSOC:
295 	    PRINT_STRING(res, fn, arg, "=>");
296 	    goto L_outer_loop;
297 	default:
298 	    popped.word = WSTACK_POP(s);
299 
300 	    switch (val) {
301 	    case PRT_TERM:
302 		obj = (Eterm) popped.word;
303 		break;
304 	    case PRT_ONE_CONS:
305 		obj = (Eterm) popped.word;
306 	    L_print_one_cons:
307 		{
308 		    Eterm* cons = list_val(obj);
309 		    Eterm tl;
310 
311 		    obj = CAR(cons);
312 		    tl = CDR(cons);
313 		    if (is_not_nil(tl)) {
314 			if (is_list(tl)) {
315 			    WSTACK_PUSH3(s, tl, PRT_ONE_CONS, PRT_COMMA);
316 			} else {
317 			    WSTACK_PUSH3(s, tl, PRT_TERM, PRT_BAR);
318 			}
319 		    }
320 		}
321 		break;
322 	    case PRT_LAST_ARRAY_ELEMENT:
323 		obj = *popped.ptr;
324 		break;
325 	    default:		/* PRT_LAST_ARRAY_ELEMENT+1 and upwards */
326 		obj = *popped.ptr;
327 	        WSTACK_PUSH3(s, (UWord) (popped.ptr + 1), val-1, PRT_COMMA);
328 		break;
329 	    }
330 	    break;
331 	}
332 
333     L_jump_start:
334 
335 	if ((*dcount)-- <= 0)
336 	    goto L_done;
337 
338 	if (is_CP(obj)) {
339 	    PRINT_STRING(res, fn, arg, "<cp/header:");
340 	    PRINT_POINTER(res, fn, arg, cp_val(obj));
341 	    PRINT_CHAR(res, fn, arg, '>');
342 	    goto L_done;
343 	}
344 	wobj = (Wterm)obj;
345 	switch (tag_val_def(wobj)) {
346 	case NIL_DEF:
347 	    PRINT_STRING(res, fn, arg, "[]");
348 	    break;
349 	case ATOM_DEF: {
350 	    int tres = print_atom_name(fn, arg, obj, dcount);
351 	    if (tres < 0) {
352 		res = tres;
353 		goto L_done;
354 	    }
355 	    res += tres;
356 	    if (*dcount <= 0)
357 		goto L_done;
358 	    break;
359 	}
360 	case SMALL_DEF:
361 	    PRINT_SWORD(res, fn, arg, 'd', 0, 1, (ErlPfSWord) signed_val(obj));
362 	    break;
363 	case BIG_DEF: {
364 	    int print_res;
365 	    char def_buf[64];
366 	    char *buf, *big_str;
367 	    Uint sz = (Uint) big_integer_estimate(wobj, 10);
368 	    sz++;
369 	    if (sz <= 64)
370 		buf = &def_buf[0];
371 	    else
372 		buf = erts_alloc(ERTS_ALC_T_TMP, sz);
373 	    big_str = erts_big_to_string(wobj, 10, buf, sz);
374 	    print_res = erts_printf_string(fn, arg, big_str);
375 	    if (buf != &def_buf[0])
376 		erts_free(ERTS_ALC_T_TMP, (void *) buf);
377 	    if (print_res < 0) {
378 		res = print_res;
379 		goto L_done;
380 	    }
381 	    res += print_res;
382 	    break;
383 	}
384 	case REF_DEF:
385             if (!ERTS_IS_CRASH_DUMPING)
386                 erts_magic_ref_save_bin(obj);
387             /* fall through... */
388 	case EXTERNAL_REF_DEF:
389 	    PRINT_STRING(res, fn, arg, "#Ref<");
390 	    PRINT_UWORD(res, fn, arg, 'u', 0, 1,
391 			(ErlPfUWord) ref_channel_no(wobj));
392 	    ref_num = ref_numbers(wobj);
393 	    for (i = ref_no_numbers(wobj)-1; i >= 0; i--) {
394 		PRINT_CHAR(res, fn, arg, '.');
395 		PRINT_UWORD(res, fn, arg, 'u', 0, 1, (ErlPfUWord) ref_num[i]);
396 	    }
397 	    PRINT_CHAR(res, fn, arg, '>');
398 	    break;
399 	case PID_DEF:
400 	case EXTERNAL_PID_DEF:
401 	    PRINT_CHAR(res, fn, arg, '<');
402 	    PRINT_UWORD(res, fn, arg, 'u', 0, 1,
403 			(ErlPfUWord) pid_channel_no(wobj));
404 	    PRINT_CHAR(res, fn, arg, '.');
405 	    PRINT_UWORD(res, fn, arg, 'u', 0, 1,
406 			(ErlPfUWord) pid_number(wobj));
407 	    PRINT_CHAR(res, fn, arg, '.');
408 	    PRINT_UWORD(res, fn, arg, 'u', 0, 1,
409 			(ErlPfUWord) pid_serial(wobj));
410 	    PRINT_CHAR(res, fn, arg, '>');
411 	    break;
412 	case PORT_DEF:
413 	case EXTERNAL_PORT_DEF:
414 	    PRINT_STRING(res, fn, arg, "#Port<");
415 	    PRINT_UWORD(res, fn, arg, 'u', 0, 1,
416 			(ErlPfUWord) port_channel_no(wobj));
417 	    PRINT_CHAR(res, fn, arg, '.');
418 	    PRINT_UWORD(res, fn, arg, 'u', 0, 1,
419 			(ErlPfUWord) port_number(wobj));
420 	    PRINT_CHAR(res, fn, arg, '>');
421 	    break;
422 	case LIST_DEF:
423 	    if (is_printable_string(obj)) {
424 		int c;
425 		PRINT_CHAR(res, fn, arg, '"');
426 		nobj = list_val(obj);
427 		while (1) {
428 		    if ((*dcount)-- <= 0)
429 			goto L_done;
430 		    c = signed_val(*nobj++);
431 		    if (c == '\n')
432 			PRINT_STRING(res, fn, arg, "\\n");
433 		    else {
434 			if (c == '"')
435 			    PRINT_CHAR(res, fn, arg, '\\');
436 			PRINT_CHAR(res, fn, arg, (char) c);
437 		    }
438 		    if (is_not_list(*nobj))
439 			break;
440 		    nobj = list_val(*nobj);
441 		}
442 		PRINT_CHAR(res, fn, arg, '"');
443 	    } else {
444 		PRINT_CHAR(res, fn, arg, '[');
445 		WSTACK_PUSH(s,PRT_CLOSE_LIST);
446 		goto L_print_one_cons;
447 	    }
448 	    break;
449 	case TUPLE_DEF:
450 	    nobj = tuple_val(wobj);	/* pointer to arity */
451 	    i = arityval(*nobj);	/* arity */
452 	    PRINT_CHAR(res, fn, arg, '{');
453 	    WSTACK_PUSH(s,PRT_CLOSE_TUPLE);
454 	    ++nobj;
455 	    if (i > 0) {
456 		WSTACK_PUSH2(s, (UWord) nobj, PRT_LAST_ARRAY_ELEMENT+i-1);
457 	    }
458 	    break;
459 	case FLOAT_DEF: {
460 	    FloatDef ff;
461 	    GET_DOUBLE(wobj, ff);
462 	    PRINT_DOUBLE(res, fn, arg, 'e', 6, 0, ff.fd);
463 	}
464 	    break;
465 	case BINARY_DEF:
466 	    {
467 		byte* bytep;
468 		Uint bytesize = binary_size(obj);
469 		Uint bitoffs;
470 		Uint bitsize;
471 		byte octet;
472 		ERTS_GET_BINARY_BYTES(obj, bytep, bitoffs, bitsize);
473 
474 		if (bitsize || !bytesize
475 		    || !is_printable_ascii(bytep, bytesize, bitoffs)) {
476 		    int is_first = 1;
477 		    PRINT_STRING(res, fn, arg, "<<");
478 		    while (bytesize) {
479 			if (is_first)
480 			    is_first = 0;
481 			else
482 			    PRINT_CHAR(res, fn, arg, ',');
483 			if (bitoffs)
484 			    octet = (bytep[0] << bitoffs) | (bytep[1] >> (8-bitoffs));
485 			else
486 			    octet = bytep[0];
487 			PRINT_UWORD(res, fn, arg, 'u', 0, 1, octet);
488 			++bytep;
489 			--bytesize;
490 		    }
491 		    if (bitsize) {
492 			Uint bits = bitoffs + bitsize;
493 			octet = bytep[0];
494 			if (bits < 8)
495 			    octet >>= 8 - bits;
496 			else if (bits > 8) {
497 			    bits -= 8;  /* bits in last byte */
498 			    octet <<= bits;
499 			    octet |= bytep[1] >> (8 - bits);
500 			}
501 			octet &= (1 << bitsize) - 1;
502 			if (is_first)
503 			    is_first = 0;
504 			else
505 			    PRINT_CHAR(res, fn, arg, ',');
506 			PRINT_UWORD(res, fn, arg, 'u', 0, 1, octet);
507 			PRINT_CHAR(res, fn, arg, ':');
508 			PRINT_UWORD(res, fn, arg, 'u', 0, 1, bitsize);
509 		    }
510 		    PRINT_STRING(res, fn, arg, ">>");
511 		}
512 		else {
513 		    PRINT_STRING(res, fn, arg, "<<\"");
514 		    while (bytesize) {
515 			if (bitoffs)
516 			    octet = (bytep[0] << bitoffs) | (bytep[1] >> (8-bitoffs));
517 			else
518 			    octet = bytep[0];
519 			if (octet == '"')
520 			    PRINT_CHAR(res, fn, arg, '\\');
521 			PRINT_CHAR(res, fn, arg, octet);
522 			++bytep;
523 			--bytesize;
524 		    }
525 		    PRINT_STRING(res, fn, arg, "\">>");
526 		}
527 	    }
528 	    break;
529 	case EXPORT_DEF:
530 	    {
531 		Export* ep = *((Export **) (export_val(wobj) + 1));
532 		Atom* module = atom_tab(atom_val(ep->info.mfa.module));
533 		Atom* name = atom_tab(atom_val(ep->info.mfa.function));
534 
535 		PRINT_STRING(res, fn, arg, "fun ");
536 		PRINT_BUF(res, fn, arg, module->name, module->len);
537 		PRINT_CHAR(res, fn, arg, ':');
538 		PRINT_BUF(res, fn, arg, name->name, name->len);
539 		PRINT_CHAR(res, fn, arg, '/');
540 		PRINT_SWORD(res, fn, arg, 'd', 0, 1,
541 			    (ErlPfSWord) ep->info.mfa.arity);
542 	    }
543 	    break;
544 	case FUN_DEF:
545 	    {
546 		ErlFunThing *funp = (ErlFunThing *) fun_val(wobj);
547 		Atom *ap = atom_tab(atom_val(funp->fe->module));
548 
549 		PRINT_STRING(res, fn, arg, "#Fun<");
550 		PRINT_BUF(res, fn, arg, ap->name, ap->len);
551 		PRINT_CHAR(res, fn, arg, '.');
552 		PRINT_SWORD(res, fn, arg, 'd', 0, 1,
553 			    (ErlPfSWord) funp->fe->old_index);
554 		PRINT_CHAR(res, fn, arg, '.');
555 		PRINT_SWORD(res, fn, arg, 'd', 0, 1,
556 			    (ErlPfSWord) funp->fe->old_uniq);
557 		PRINT_CHAR(res, fn, arg, '>');
558 	    }
559 	    break;
560 	case MAP_DEF:
561             if (is_flatmap(wobj)) {
562                 Uint n;
563                 Eterm *ks, *vs;
564                 flatmap_t *mp = (flatmap_t *)flatmap_val(wobj);
565                 n  = flatmap_get_size(mp);
566                 ks = flatmap_get_keys(mp);
567                 vs = flatmap_get_values(mp);
568 
569                 PRINT_CHAR(res, fn, arg, '#');
570                 PRINT_CHAR(res, fn, arg, '{');
571                 WSTACK_PUSH(s, PRT_CLOSE_TUPLE);
572                 if (n > 0) {
573                     n--;
574                     WSTACK_PUSH5(s, vs[n], PRT_TERM, PRT_ASSOC, ks[n], PRT_TERM);
575                     while (n--) {
576                         WSTACK_PUSH6(s, PRT_COMMA, vs[n], PRT_TERM, PRT_ASSOC,
577                                 ks[n], PRT_TERM);
578                     }
579                 }
580             } else {
581                 Uint n, mapval;
582                 Eterm *head;
583                 head = hashmap_val(wobj);
584                 mapval = MAP_HEADER_VAL(*head);
585                 switch (MAP_HEADER_TYPE(*head)) {
586                 case MAP_HEADER_TAG_HAMT_HEAD_ARRAY:
587                 case MAP_HEADER_TAG_HAMT_HEAD_BITMAP:
588                     PRINT_STRING(res, fn, arg, "#<");
589                     PRINT_UWORD(res, fn, arg, 'x', 0, 1, mapval);
590                     PRINT_STRING(res, fn, arg, ">{");
591                     WSTACK_PUSH(s,PRT_CLOSE_TUPLE);
592                     n = hashmap_bitcount(mapval);
593                     ASSERT(n < 17);
594                     head += 2;
595                     if (n > 0) {
596                         n--;
597                         WSTACK_PUSH(s, head[n]);
598                         WSTACK_PUSH(s, PRT_TERM);
599                         while (n--) {
600                             WSTACK_PUSH(s, PRT_COMMA);
601                             WSTACK_PUSH(s, head[n]);
602                             WSTACK_PUSH(s, PRT_TERM);
603                         }
604                     }
605                     break;
606                 case MAP_HEADER_TAG_HAMT_NODE_BITMAP:
607                     n = hashmap_bitcount(mapval);
608                     head++;
609                     PRINT_CHAR(res, fn, arg, '<');
610                     PRINT_UWORD(res, fn, arg, 'x', 0, 1, mapval);
611                     PRINT_STRING(res, fn, arg, ">{");
612                     WSTACK_PUSH(s,PRT_CLOSE_TUPLE);
613                     ASSERT(n < 17);
614                     if (n > 0) {
615                         n--;
616                         WSTACK_PUSH(s, head[n]);
617                         WSTACK_PUSH(s, PRT_TERM);
618                         while (n--) {
619                             WSTACK_PUSH(s, PRT_COMMA);
620                             WSTACK_PUSH(s, head[n]);
621                             WSTACK_PUSH(s, PRT_TERM);
622                         }
623                     }
624                     break;
625                 }
626             }
627             break;
628 	case MATCHSTATE_DEF:
629 	    PRINT_STRING(res, fn, arg, "#MatchState");
630 	    break;
631         default:
632 	    PRINT_STRING(res, fn, arg, "<unknown:");
633 	    PRINT_POINTER(res, fn, arg, wobj);
634 	    PRINT_CHAR(res, fn, arg, '>');
635 	    break;
636 	}
637     }
638 
639  L_done:
640     DESTROY_WSTACK(s);
641     return res;
642 }
643 
644 
645 int
erts_printf_term(fmtfn_t fn,void * arg,ErlPfEterm term,long precision)646 erts_printf_term(fmtfn_t fn, void* arg, ErlPfEterm term, long precision) {
647     int res;
648     ERTS_CT_ASSERT(sizeof(ErlPfEterm) == sizeof(Eterm));
649 
650     res = print_term(fn, arg, (Eterm)term, &precision);
651     if (res < 0)
652 	return res;
653     if (precision <= 0)
654 	PRINT_STRING(res, fn, arg, "... ");
655     return res;
656 }
657