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