xref: /freebsd/contrib/one-true-awk/tran.c (revision 9768746b)
1 /****************************************************************
2 Copyright (C) Lucent Technologies 1997
3 All Rights Reserved
4 
5 Permission to use, copy, modify, and distribute this software and
6 its documentation for any purpose and without fee is hereby
7 granted, provided that the above copyright notice appear in all
8 copies and that both that the copyright notice and this
9 permission notice and warranty disclaimer appear in supporting
10 documentation, and that the name Lucent Technologies or any of
11 its entities not be used in advertising or publicity pertaining
12 to distribution of the software without specific, written prior
13 permission.
14 
15 LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
17 IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
18 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
20 IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
21 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
22 THIS SOFTWARE.
23 ****************************************************************/
24 
25 #define	DEBUG
26 #include <stdio.h>
27 #include <math.h>
28 #include <ctype.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include "awk.h"
32 
33 #define	FULLTAB	2	/* rehash when table gets this x full */
34 #define	GROWTAB 4	/* grow table by this factor */
35 
36 Array	*symtab;	/* main symbol table */
37 
38 char	**FS;		/* initial field sep */
39 char	**RS;		/* initial record sep */
40 char	**OFS;		/* output field sep */
41 char	**ORS;		/* output record sep */
42 char	**OFMT;		/* output format for numbers */
43 char	**CONVFMT;	/* format for conversions in getsval */
44 Awkfloat *NF;		/* number of fields in current record */
45 Awkfloat *NR;		/* number of current record */
46 Awkfloat *FNR;		/* number of current record in current file */
47 char	**FILENAME;	/* current filename argument */
48 Awkfloat *ARGC;		/* number of arguments from command line */
49 char	**SUBSEP;	/* subscript separator for a[i,j,k]; default \034 */
50 Awkfloat *RSTART;	/* start of re matched with ~; origin 1 (!) */
51 Awkfloat *RLENGTH;	/* length of same */
52 
53 Cell	*fsloc;		/* FS */
54 Cell	*nrloc;		/* NR */
55 Cell	*nfloc;		/* NF */
56 Cell	*fnrloc;	/* FNR */
57 Cell	*ofsloc;	/* OFS */
58 Cell	*orsloc;	/* ORS */
59 Cell	*rsloc;		/* RS */
60 Array	*ARGVtab;	/* symbol table containing ARGV[...] */
61 Array	*ENVtab;	/* symbol table containing ENVIRON[...] */
62 Cell	*rstartloc;	/* RSTART */
63 Cell	*rlengthloc;	/* RLENGTH */
64 Cell	*subseploc;	/* SUBSEP */
65 Cell	*symtabloc;	/* SYMTAB */
66 
67 Cell	*nullloc;	/* a guaranteed empty cell */
68 Node	*nullnode;	/* zero&null, converted into a node for comparisons */
69 Cell	*literal0;
70 
71 extern Cell **fldtab;
72 
73 static void
74 setfree(Cell *vp)
75 {
76 	if (&vp->sval == FS || &vp->sval == RS ||
77 	    &vp->sval == OFS || &vp->sval == ORS ||
78 	    &vp->sval == OFMT || &vp->sval == CONVFMT ||
79 	    &vp->sval == FILENAME || &vp->sval == SUBSEP)
80 		vp->tval |= DONTFREE;
81 	else
82 		vp->tval &= ~DONTFREE;
83 }
84 
85 void syminit(void)	/* initialize symbol table with builtin vars */
86 {
87 	literal0 = setsymtab("0", "0", 0.0, NUM|STR|CON|DONTFREE, symtab);
88 	/* this is used for if(x)... tests: */
89 	nullloc = setsymtab("$zero&null", "", 0.0, NUM|STR|CON|DONTFREE, symtab);
90 	nullnode = celltonode(nullloc, CCON);
91 
92 	fsloc = setsymtab("FS", " ", 0.0, STR|DONTFREE, symtab);
93 	FS = &fsloc->sval;
94 	rsloc = setsymtab("RS", "\n", 0.0, STR|DONTFREE, symtab);
95 	RS = &rsloc->sval;
96 	ofsloc = setsymtab("OFS", " ", 0.0, STR|DONTFREE, symtab);
97 	OFS = &ofsloc->sval;
98 	orsloc = setsymtab("ORS", "\n", 0.0, STR|DONTFREE, symtab);
99 	ORS = &orsloc->sval;
100 	OFMT = &setsymtab("OFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval;
101 	CONVFMT = &setsymtab("CONVFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval;
102 	FILENAME = &setsymtab("FILENAME", "", 0.0, STR|DONTFREE, symtab)->sval;
103 	nfloc = setsymtab("NF", "", 0.0, NUM, symtab);
104 	NF = &nfloc->fval;
105 	nrloc = setsymtab("NR", "", 0.0, NUM, symtab);
106 	NR = &nrloc->fval;
107 	fnrloc = setsymtab("FNR", "", 0.0, NUM, symtab);
108 	FNR = &fnrloc->fval;
109 	subseploc = setsymtab("SUBSEP", "\034", 0.0, STR|DONTFREE, symtab);
110 	SUBSEP = &subseploc->sval;
111 	rstartloc = setsymtab("RSTART", "", 0.0, NUM, symtab);
112 	RSTART = &rstartloc->fval;
113 	rlengthloc = setsymtab("RLENGTH", "", 0.0, NUM, symtab);
114 	RLENGTH = &rlengthloc->fval;
115 	symtabloc = setsymtab("SYMTAB", "", 0.0, ARR, symtab);
116 	free(symtabloc->sval);
117 	symtabloc->sval = (char *) symtab;
118 }
119 
120 void arginit(int ac, char **av)	/* set up ARGV and ARGC */
121 {
122 	Cell *cp;
123 	int i;
124 	char temp[50];
125 
126 	ARGC = &setsymtab("ARGC", "", (Awkfloat) ac, NUM, symtab)->fval;
127 	cp = setsymtab("ARGV", "", 0.0, ARR, symtab);
128 	ARGVtab = makesymtab(NSYMTAB);	/* could be (int) ARGC as well */
129 	free(cp->sval);
130 	cp->sval = (char *) ARGVtab;
131 	for (i = 0; i < ac; i++) {
132 		double result;
133 
134 		sprintf(temp, "%d", i);
135 		if (is_number(*av, & result))
136 			setsymtab(temp, *av, result, STR|NUM, ARGVtab);
137 		else
138 			setsymtab(temp, *av, 0.0, STR, ARGVtab);
139 		av++;
140 	}
141 }
142 
143 void envinit(char **envp)	/* set up ENVIRON variable */
144 {
145 	Cell *cp;
146 	char *p;
147 
148 	cp = setsymtab("ENVIRON", "", 0.0, ARR, symtab);
149 	ENVtab = makesymtab(NSYMTAB);
150 	free(cp->sval);
151 	cp->sval = (char *) ENVtab;
152 	for ( ; *envp; envp++) {
153 		double result;
154 
155 		if ((p = strchr(*envp, '=')) == NULL)
156 			continue;
157 		if( p == *envp ) /* no left hand side name in env string */
158 			continue;
159 		*p++ = 0;	/* split into two strings at = */
160 		if (is_number(p, & result))
161 			setsymtab(*envp, p, result, STR|NUM, ENVtab);
162 		else
163 			setsymtab(*envp, p, 0.0, STR, ENVtab);
164 		p[-1] = '=';	/* restore in case env is passed down to a shell */
165 	}
166 }
167 
168 Array *makesymtab(int n)	/* make a new symbol table */
169 {
170 	Array *ap;
171 	Cell **tp;
172 
173 	ap = (Array *) malloc(sizeof(*ap));
174 	tp = (Cell **) calloc(n, sizeof(*tp));
175 	if (ap == NULL || tp == NULL)
176 		FATAL("out of space in makesymtab");
177 	ap->nelem = 0;
178 	ap->size = n;
179 	ap->tab = tp;
180 	return(ap);
181 }
182 
183 void freesymtab(Cell *ap)	/* free a symbol table */
184 {
185 	Cell *cp, *temp;
186 	Array *tp;
187 	int i;
188 
189 	if (!isarr(ap))
190 		return;
191 	tp = (Array *) ap->sval;
192 	if (tp == NULL)
193 		return;
194 	for (i = 0; i < tp->size; i++) {
195 		for (cp = tp->tab[i]; cp != NULL; cp = temp) {
196 			xfree(cp->nval);
197 			if (freeable(cp))
198 				xfree(cp->sval);
199 			temp = cp->cnext;	/* avoids freeing then using */
200 			free(cp);
201 			tp->nelem--;
202 		}
203 		tp->tab[i] = NULL;
204 	}
205 	if (tp->nelem != 0)
206 		WARNING("can't happen: inconsistent element count freeing %s", ap->nval);
207 	free(tp->tab);
208 	free(tp);
209 }
210 
211 void freeelem(Cell *ap, const char *s)	/* free elem s from ap (i.e., ap["s"] */
212 {
213 	Array *tp;
214 	Cell *p, *prev = NULL;
215 	int h;
216 
217 	tp = (Array *) ap->sval;
218 	h = hash(s, tp->size);
219 	for (p = tp->tab[h]; p != NULL; prev = p, p = p->cnext)
220 		if (strcmp(s, p->nval) == 0) {
221 			if (prev == NULL)	/* 1st one */
222 				tp->tab[h] = p->cnext;
223 			else			/* middle somewhere */
224 				prev->cnext = p->cnext;
225 			if (freeable(p))
226 				xfree(p->sval);
227 			free(p->nval);
228 			free(p);
229 			tp->nelem--;
230 			return;
231 		}
232 }
233 
234 Cell *setsymtab(const char *n, const char *s, Awkfloat f, unsigned t, Array *tp)
235 {
236 	int h;
237 	Cell *p;
238 
239 	if (n != NULL && (p = lookup(n, tp)) != NULL) {
240 		DPRINTF("setsymtab found %p: n=%s s=\"%s\" f=%g t=%o\n",
241 			(void*)p, NN(p->nval), NN(p->sval), p->fval, p->tval);
242 		return(p);
243 	}
244 	p = (Cell *) malloc(sizeof(*p));
245 	if (p == NULL)
246 		FATAL("out of space for symbol table at %s", n);
247 	p->nval = tostring(n);
248 	p->sval = s ? tostring(s) : tostring("");
249 	p->fval = f;
250 	p->tval = t;
251 	p->csub = CUNK;
252 	p->ctype = OCELL;
253 	tp->nelem++;
254 	if (tp->nelem > FULLTAB * tp->size)
255 		rehash(tp);
256 	h = hash(n, tp->size);
257 	p->cnext = tp->tab[h];
258 	tp->tab[h] = p;
259 	DPRINTF("setsymtab set %p: n=%s s=\"%s\" f=%g t=%o\n",
260 		(void*)p, p->nval, p->sval, p->fval, p->tval);
261 	return(p);
262 }
263 
264 int hash(const char *s, int n)	/* form hash value for string s */
265 {
266 	unsigned hashval;
267 
268 	for (hashval = 0; *s != '\0'; s++)
269 		hashval = (*s + 31 * hashval);
270 	return hashval % n;
271 }
272 
273 void rehash(Array *tp)	/* rehash items in small table into big one */
274 {
275 	int i, nh, nsz;
276 	Cell *cp, *op, **np;
277 
278 	nsz = GROWTAB * tp->size;
279 	np = (Cell **) calloc(nsz, sizeof(*np));
280 	if (np == NULL)		/* can't do it, but can keep running. */
281 		return;		/* someone else will run out later. */
282 	for (i = 0; i < tp->size; i++) {
283 		for (cp = tp->tab[i]; cp; cp = op) {
284 			op = cp->cnext;
285 			nh = hash(cp->nval, nsz);
286 			cp->cnext = np[nh];
287 			np[nh] = cp;
288 		}
289 	}
290 	free(tp->tab);
291 	tp->tab = np;
292 	tp->size = nsz;
293 }
294 
295 Cell *lookup(const char *s, Array *tp)	/* look for s in tp */
296 {
297 	Cell *p;
298 	int h;
299 
300 	h = hash(s, tp->size);
301 	for (p = tp->tab[h]; p != NULL; p = p->cnext)
302 		if (strcmp(s, p->nval) == 0)
303 			return(p);	/* found it */
304 	return(NULL);			/* not found */
305 }
306 
307 Awkfloat setfval(Cell *vp, Awkfloat f)	/* set float val of a Cell */
308 {
309 	int fldno;
310 
311 	f += 0.0;		/* normalise negative zero to positive zero */
312 	if ((vp->tval & (NUM | STR)) == 0)
313 		funnyvar(vp, "assign to");
314 	if (isfld(vp)) {
315 		donerec = false;	/* mark $0 invalid */
316 		fldno = atoi(vp->nval);
317 		if (fldno > *NF)
318 			newfld(fldno);
319 		DPRINTF("setting field %d to %g\n", fldno, f);
320 	} else if (&vp->fval == NF) {
321 		donerec = false;	/* mark $0 invalid */
322 		setlastfld(f);
323 		DPRINTF("setting NF to %g\n", f);
324 	} else if (isrec(vp)) {
325 		donefld = false;	/* mark $1... invalid */
326 		donerec = true;
327 		savefs();
328 	} else if (vp == ofsloc) {
329 		if (!donerec)
330 			recbld();
331 	}
332 	if (freeable(vp))
333 		xfree(vp->sval); /* free any previous string */
334 	vp->tval &= ~(STR|CONVC|CONVO); /* mark string invalid */
335 	vp->fmt = NULL;
336 	vp->tval |= NUM;	/* mark number ok */
337 	if (f == -0)  /* who would have thought this possible? */
338 		f = 0;
339 	DPRINTF("setfval %p: %s = %g, t=%o\n", (void*)vp, NN(vp->nval), f, vp->tval);
340 	return vp->fval = f;
341 }
342 
343 void funnyvar(Cell *vp, const char *rw)
344 {
345 	if (isarr(vp))
346 		FATAL("can't %s %s; it's an array name.", rw, vp->nval);
347 	if (vp->tval & FCN)
348 		FATAL("can't %s %s; it's a function.", rw, vp->nval);
349 	WARNING("funny variable %p: n=%s s=\"%s\" f=%g t=%o",
350 		(void *)vp, vp->nval, vp->sval, vp->fval, vp->tval);
351 }
352 
353 char *setsval(Cell *vp, const char *s)	/* set string val of a Cell */
354 {
355 	char *t;
356 	int fldno;
357 	Awkfloat f;
358 
359 	DPRINTF("starting setsval %p: %s = \"%s\", t=%o, r,f=%d,%d\n",
360 		(void*)vp, NN(vp->nval), s, vp->tval, donerec, donefld);
361 	if ((vp->tval & (NUM | STR)) == 0)
362 		funnyvar(vp, "assign to");
363 	if (isfld(vp)) {
364 		donerec = false;	/* mark $0 invalid */
365 		fldno = atoi(vp->nval);
366 		if (fldno > *NF)
367 			newfld(fldno);
368 		DPRINTF("setting field %d to %s (%p)\n", fldno, s, (const void*)s);
369 	} else if (isrec(vp)) {
370 		donefld = false;	/* mark $1... invalid */
371 		donerec = true;
372 		savefs();
373 	} else if (vp == ofsloc) {
374 		if (!donerec)
375 			recbld();
376 	}
377 	t = s ? tostring(s) : tostring("");	/* in case it's self-assign */
378 	if (freeable(vp))
379 		xfree(vp->sval);
380 	vp->tval &= ~(NUM|CONVC|CONVO);
381 	vp->tval |= STR;
382 	vp->fmt = NULL;
383 	setfree(vp);
384 	DPRINTF("setsval %p: %s = \"%s (%p) \", t=%o r,f=%d,%d\n",
385 		(void*)vp, NN(vp->nval), t, (void*)t, vp->tval, donerec, donefld);
386 	vp->sval = t;
387 	if (&vp->fval == NF) {
388 		donerec = false;	/* mark $0 invalid */
389 		f = getfval(vp);
390 		setlastfld(f);
391 		DPRINTF("setting NF to %g\n", f);
392 	}
393 
394 	return(vp->sval);
395 }
396 
397 Awkfloat getfval(Cell *vp)	/* get float val of a Cell */
398 {
399 	if ((vp->tval & (NUM | STR)) == 0)
400 		funnyvar(vp, "read value of");
401 	if (isfld(vp) && !donefld)
402 		fldbld();
403 	else if (isrec(vp) && !donerec)
404 		recbld();
405 	if (!isnum(vp)) {	/* not a number */
406 		double fval;
407 		bool no_trailing;
408 
409 		if (is_valid_number(vp->sval, true, & no_trailing, & fval)) {
410 			vp->fval = fval;
411 			if (no_trailing && !(vp->tval&CON))
412 				vp->tval |= NUM;	/* make NUM only sparingly */
413 		} else
414 			vp->fval = 0.0;
415 	}
416 	DPRINTF("getfval %p: %s = %g, t=%o\n",
417 		(void*)vp, NN(vp->nval), vp->fval, vp->tval);
418 	return(vp->fval);
419 }
420 
421 static const char *get_inf_nan(double d)
422 {
423 	if (isinf(d)) {
424 		return (d < 0 ? "-inf" : "+inf");
425 	} else if (isnan(d)) {
426 		return (signbit(d) != 0 ? "-nan" : "+nan");
427 	} else
428 		return NULL;
429 }
430 
431 static char *get_str_val(Cell *vp, char **fmt)        /* get string val of a Cell */
432 {
433 	char s[256];
434 	double dtemp;
435 	const char *p;
436 
437 	if ((vp->tval & (NUM | STR)) == 0)
438 		funnyvar(vp, "read value of");
439 	if (isfld(vp) && ! donefld)
440 		fldbld();
441 	else if (isrec(vp) && ! donerec)
442 		recbld();
443 
444 	/*
445 	 * ADR: This is complicated and more fragile than is desirable.
446 	 * Retrieving a string value for a number associates the string
447 	 * value with the scalar.  Previously, the string value was
448 	 * sticky, meaning if converted via OFMT that became the value
449 	 * (even though POSIX wants it to be via CONVFMT). Or if CONVFMT
450 	 * changed after a string value was retrieved, the original value
451 	 * was maintained and used.  Also not per POSIX.
452 	 *
453 	 * We work around this design by adding two additional flags,
454 	 * CONVC and CONVO, indicating how the string value was
455 	 * obtained (via CONVFMT or OFMT) and _also_ maintaining a copy
456 	 * of the pointer to the xFMT format string used for the
457 	 * conversion.  This pointer is only read, **never** dereferenced.
458 	 * The next time we do a conversion, if it's coming from the same
459 	 * xFMT as last time, and the pointer value is different, we
460 	 * know that the xFMT format string changed, and we need to
461 	 * redo the conversion. If it's the same, we don't have to.
462 	 *
463 	 * There are also several cases where we don't do a conversion,
464 	 * such as for a field (see the checks below).
465 	 */
466 
467 	/* Don't duplicate the code for actually updating the value */
468 #define update_str_val(vp) \
469 	{ \
470 		if (freeable(vp)) \
471 			xfree(vp->sval); \
472 		if ((p = get_inf_nan(vp->fval)) != NULL) \
473 			strcpy(s, p); \
474 		else if (modf(vp->fval, &dtemp) == 0)	/* it's integral */ \
475 			snprintf(s, sizeof (s), "%.30g", vp->fval); \
476 		else \
477 			snprintf(s, sizeof (s), *fmt, vp->fval); \
478 		vp->sval = tostring(s); \
479 		vp->tval &= ~DONTFREE; \
480 		vp->tval |= STR; \
481 	}
482 
483 	if (isstr(vp) == 0) {
484 		update_str_val(vp);
485 		if (fmt == OFMT) {
486 			vp->tval &= ~CONVC;
487 			vp->tval |= CONVO;
488 		} else {
489 			/* CONVFMT */
490 			vp->tval &= ~CONVO;
491 			vp->tval |= CONVC;
492 		}
493 		vp->fmt = *fmt;
494 	} else if ((vp->tval & DONTFREE) != 0 || ! isnum(vp) || isfld(vp)) {
495 		goto done;
496 	} else if (isstr(vp)) {
497 		if (fmt == OFMT) {
498 			if ((vp->tval & CONVC) != 0
499 			    || ((vp->tval & CONVO) != 0 && vp->fmt != *fmt)) {
500 				update_str_val(vp);
501 				vp->tval &= ~CONVC;
502 				vp->tval |= CONVO;
503 				vp->fmt = *fmt;
504 			}
505 		} else {
506 			/* CONVFMT */
507 			if ((vp->tval & CONVO) != 0
508 			    || ((vp->tval & CONVC) != 0 && vp->fmt != *fmt)) {
509 				update_str_val(vp);
510 				vp->tval &= ~CONVO;
511 				vp->tval |= CONVC;
512 				vp->fmt = *fmt;
513 			}
514 		}
515 	}
516 done:
517 	DPRINTF("getsval %p: %s = \"%s (%p)\", t=%o\n",
518 		(void*)vp, NN(vp->nval), vp->sval, (void*)vp->sval, vp->tval);
519 	return(vp->sval);
520 }
521 
522 char *getsval(Cell *vp)       /* get string val of a Cell */
523 {
524       return get_str_val(vp, CONVFMT);
525 }
526 
527 char *getpssval(Cell *vp)     /* get string val of a Cell for print */
528 {
529       return get_str_val(vp, OFMT);
530 }
531 
532 
533 char *tostring(const char *s)	/* make a copy of string s */
534 {
535 	char *p = strdup(s);
536 	if (p == NULL)
537 		FATAL("out of space in tostring on %s", s);
538 	return(p);
539 }
540 
541 char *tostringN(const char *s, size_t n)	/* make a copy of string s */
542 {
543 	char *p;
544 
545 	p = (char *) malloc(n);
546 	if (p == NULL)
547 		FATAL("out of space in tostring on %s", s);
548 	strcpy(p, s);
549 	return(p);
550 }
551 
552 Cell *catstr(Cell *a, Cell *b) /* concatenate a and b */
553 {
554 	Cell *c;
555 	char *p;
556 	char *sa = getsval(a);
557 	char *sb = getsval(b);
558 	size_t l = strlen(sa) + strlen(sb) + 1;
559 	p = (char *) malloc(l);
560 	if (p == NULL)
561 		FATAL("out of space concatenating %s and %s", sa, sb);
562 	snprintf(p, l, "%s%s", sa, sb);
563 
564 	l++;	// add room for ' '
565 	char *newbuf = (char *) malloc(l);
566 	if (newbuf == NULL)
567 		FATAL("out of space concatenating %s and %s", sa, sb);
568 	// See string() in lex.c; a string "xx" is stored in the symbol
569 	// table as "xx ".
570 	snprintf(newbuf, l, "%s ", p);
571 	c = setsymtab(newbuf, p, 0.0, CON|STR|DONTFREE, symtab);
572 	free(p);
573 	free(newbuf);
574 	return c;
575 }
576 
577 char *qstring(const char *is, int delim)	/* collect string up to next delim */
578 {
579 	const char *os = is;
580 	int c, n;
581 	const uschar *s = (const uschar *) is;
582 	uschar *buf, *bp;
583 
584 	if ((buf = (uschar *) malloc(strlen(is)+3)) == NULL)
585 		FATAL( "out of space in qstring(%s)", s);
586 	for (bp = buf; (c = *s) != delim; s++) {
587 		if (c == '\n')
588 			SYNTAX( "newline in string %.20s...", os );
589 		else if (c != '\\')
590 			*bp++ = c;
591 		else {	/* \something */
592 			c = *++s;
593 			if (c == 0) {	/* \ at end */
594 				*bp++ = '\\';
595 				break;	/* for loop */
596 			}
597 			switch (c) {
598 			case '\\':	*bp++ = '\\'; break;
599 			case 'n':	*bp++ = '\n'; break;
600 			case 't':	*bp++ = '\t'; break;
601 			case 'b':	*bp++ = '\b'; break;
602 			case 'f':	*bp++ = '\f'; break;
603 			case 'r':	*bp++ = '\r'; break;
604 			case 'v':	*bp++ = '\v'; break;
605 			case 'a':	*bp++ = '\a'; break;
606 			default:
607 				if (!isdigit(c)) {
608 					*bp++ = c;
609 					break;
610 				}
611 				n = c - '0';
612 				if (isdigit(s[1])) {
613 					n = 8 * n + *++s - '0';
614 					if (isdigit(s[1]))
615 						n = 8 * n + *++s - '0';
616 				}
617 				*bp++ = n;
618 				break;
619 			}
620 		}
621 	}
622 	*bp++ = 0;
623 	return (char *) buf;
624 }
625 
626 const char *flags2str(int flags)
627 {
628 	static const struct ftab {
629 		const char *name;
630 		int value;
631 	} flagtab[] = {
632 		{ "NUM", NUM },
633 		{ "STR", STR },
634 		{ "DONTFREE", DONTFREE },
635 		{ "CON", CON },
636 		{ "ARR", ARR },
637 		{ "FCN", FCN },
638 		{ "FLD", FLD },
639 		{ "REC", REC },
640 		{ "CONVC", CONVC },
641 		{ "CONVO", CONVO },
642 		{ NULL, 0 }
643 	};
644 	static char buf[100];
645 	int i;
646 	char *cp = buf;
647 
648 	for (i = 0; flagtab[i].name != NULL; i++) {
649 		if ((flags & flagtab[i].value) != 0) {
650 			if (cp > buf)
651 				*cp++ = '|';
652 			strcpy(cp, flagtab[i].name);
653 			cp += strlen(cp);
654 		}
655 	}
656 
657 	return buf;
658 }
659