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