xref: /openbsd-src/usr.bin/awk/tran.c (revision 78fec973f57e9fc9edd564490c79661460ad807b)
1 /*	$OpenBSD: tran.c,v 1.35 2022/06/03 19:46:09 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("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 (isfld(vp)) {
353 		donerec = false;	/* mark $0 invalid */
354 		fldno = atoi(vp->nval);
355 		if (fldno > *NF)
356 			newfld(fldno);
357 		DPRINTF("setting field %d to %s (%p)\n", fldno, s, (const void*)s);
358 	} else if (isrec(vp)) {
359 		donefld = false;	/* mark $1... invalid */
360 		donerec = true;
361 		savefs();
362 	} else if (vp == ofsloc) {
363 		if (!donerec)
364 			recbld();
365 	}
366 	t = s ? tostring(s) : tostring("");	/* in case it's self-assign */
367 	if (freeable(vp))
368 		xfree(vp->sval);
369 	vp->tval &= ~(NUM|DONTFREE|CONVC|CONVO);
370 	vp->tval |= STR;
371 	vp->fmt = NULL;
372 	DPRINTF("setsval %p: %s = \"%s (%p) \", t=%o r,f=%d,%d\n",
373 		(void*)vp, NN(vp->nval), t, (void*)t, vp->tval, donerec, donefld);
374 	vp->sval = t;
375 	if (&vp->fval == NF) {
376 		donerec = false;	/* mark $0 invalid */
377 		f = getfval(vp);
378 		setlastfld(f);
379 		DPRINTF("setting NF to %g\n", f);
380 	}
381 
382 	return(vp->sval);
383 }
384 
385 Awkfloat getfval(Cell *vp)	/* get float val of a Cell */
386 {
387 	if ((vp->tval & (NUM | STR)) == 0)
388 		funnyvar(vp, "read value of");
389 	if (isfld(vp) && !donefld)
390 		fldbld();
391 	else if (isrec(vp) && !donerec)
392 		recbld();
393 	if (!isnum(vp)) {	/* not a number */
394 		double fval;
395 		bool no_trailing;
396 
397 		if (is_valid_number(vp->sval, true, & no_trailing, & fval)) {
398 			vp->fval = fval;
399 			if (no_trailing && !(vp->tval&CON))
400 				vp->tval |= NUM;	/* make NUM only sparingly */
401 		} else
402 			vp->fval = 0.0;
403 	}
404 	DPRINTF("getfval %p: %s = %g, t=%o\n",
405 		(void*)vp, NN(vp->nval), vp->fval, vp->tval);
406 	return(vp->fval);
407 }
408 
409 static const char *get_inf_nan(double d)
410 {
411 	if (isinf(d)) {
412 		return (d < 0 ? "-inf" : "+inf");
413 	} else if (isnan(d)) {
414 		return (signbit(d) != 0 ? "-nan" : "+nan");
415 	} else
416 		return NULL;
417 }
418 
419 static char *get_str_val(Cell *vp, char **fmt)        /* get string val of a Cell */
420 {
421 	int n;
422 	double dtemp;
423 	const char *p;
424 
425 	if ((vp->tval & (NUM | STR)) == 0)
426 		funnyvar(vp, "read value of");
427 	if (isfld(vp) && ! donefld)
428 		fldbld();
429 	else if (isrec(vp) && ! donerec)
430 		recbld();
431 
432 	/*
433 	 * ADR: This is complicated and more fragile than is desirable.
434 	 * Retrieving a string value for a number associates the string
435 	 * value with the scalar.  Previously, the string value was
436 	 * sticky, meaning if converted via OFMT that became the value
437 	 * (even though POSIX wants it to be via CONVFMT). Or if CONVFMT
438 	 * changed after a string value was retrieved, the original value
439 	 * was maintained and used.  Also not per POSIX.
440 	 *
441 	 * We work around this design by adding two additional flags,
442 	 * CONVC and CONVO, indicating how the string value was
443 	 * obtained (via CONVFMT or OFMT) and _also_ maintaining a copy
444 	 * of the pointer to the xFMT format string used for the
445 	 * conversion.  This pointer is only read, **never** dereferenced.
446 	 * The next time we do a conversion, if it's coming from the same
447 	 * xFMT as last time, and the pointer value is different, we
448 	 * know that the xFMT format string changed, and we need to
449 	 * redo the conversion. If it's the same, we don't have to.
450 	 *
451 	 * There are also several cases where we don't do a conversion,
452 	 * such as for a field (see the checks below).
453 	 */
454 
455 	/* Don't duplicate the code for actually updating the value */
456 #define update_str_val(vp) \
457 	{ \
458 		if (freeable(vp)) \
459 			xfree(vp->sval); \
460 		if ((p = get_inf_nan(vp->fval)) != NULL) \
461 			n = (vp->sval = strdup(p)) ? 0 : -1; \
462 		else if (modf(vp->fval, &dtemp) == 0)	/* it's integral */ \
463 			n = asprintf(&vp->sval, "%.30g", vp->fval); \
464 		else \
465 			n = asprintf(&vp->sval, *fmt, vp->fval); \
466 		if (n == -1) \
467 			FATAL("out of space in get_str_val"); \
468 		vp->tval &= ~DONTFREE; \
469 		vp->tval |= STR; \
470 	}
471 
472 	if (isstr(vp) == 0) {
473 		update_str_val(vp);
474 		if (fmt == OFMT) {
475 			vp->tval &= ~CONVC;
476 			vp->tval |= CONVO;
477 		} else {
478 			/* CONVFMT */
479 			vp->tval &= ~CONVO;
480 			vp->tval |= CONVC;
481 		}
482 		vp->fmt = *fmt;
483 	} else if ((vp->tval & DONTFREE) != 0 || ! isnum(vp) || isfld(vp)) {
484 		goto done;
485 	} else if (isstr(vp)) {
486 		if (fmt == OFMT) {
487 			if ((vp->tval & CONVC) != 0
488 			    || ((vp->tval & CONVO) != 0 && vp->fmt != *fmt)) {
489 				update_str_val(vp);
490 				vp->tval &= ~CONVC;
491 				vp->tval |= CONVO;
492 				vp->fmt = *fmt;
493 			}
494 		} else {
495 			/* CONVFMT */
496 			if ((vp->tval & CONVO) != 0
497 			    || ((vp->tval & CONVC) != 0 && vp->fmt != *fmt)) {
498 				update_str_val(vp);
499 				vp->tval &= ~CONVO;
500 				vp->tval |= CONVC;
501 				vp->fmt = *fmt;
502 			}
503 		}
504 	}
505 done:
506 	DPRINTF("getsval %p: %s = \"%s (%p)\", t=%o\n",
507 		(void*)vp, NN(vp->nval), vp->sval, (void*)vp->sval, vp->tval);
508 	return(vp->sval);
509 }
510 
511 char *getsval(Cell *vp)       /* get string val of a Cell */
512 {
513       return get_str_val(vp, CONVFMT);
514 }
515 
516 char *getpssval(Cell *vp)     /* get string val of a Cell for print */
517 {
518       return get_str_val(vp, OFMT);
519 }
520 
521 
522 char *tostring(const char *s)	/* make a copy of string s */
523 {
524 	char *p = strdup(s);
525 	if (p == NULL)
526 		FATAL("out of space in tostring on %s", s);
527 	return(p);
528 }
529 
530 char *tostringN(const char *s, size_t n)	/* make a copy of string s */
531 {
532 	char *p;
533 
534 	p = (char *) malloc(n);
535 	if (p == NULL)
536 		FATAL("out of space in tostringN %zu", n);
537 	if (strlcpy(p, s, n) >= n)
538 		FATAL("out of space in tostringN on %s", s);
539 	return(p);
540 }
541 
542 Cell *catstr(Cell *a, Cell *b) /* concatenate a and b */
543 {
544 	Cell *c;
545 	char *p;
546 	char *sa = getsval(a);
547 	char *sb = getsval(b);
548 	size_t l = strlen(sa) + strlen(sb) + 1;
549 	p = (char *) malloc(l);
550 	if (p == NULL)
551 		FATAL("out of space concatenating %s and %s", sa, sb);
552 	snprintf(p, l, "%s%s", sa, sb);
553 
554 	l++;	// add room for ' '
555 	char *newbuf = (char *) malloc(l);
556 	if (newbuf == NULL)
557 		FATAL("out of space concatenating %s and %s", sa, sb);
558 	// See string() in lex.c; a string "xx" is stored in the symbol
559 	// table as "xx ".
560 	snprintf(newbuf, l, "%s ", p);
561 	c = setsymtab(newbuf, p, 0.0, CON|STR|DONTFREE, symtab);
562 	free(p);
563 	free(newbuf);
564 	return c;
565 }
566 
567 char *qstring(const char *is, int delim)	/* collect string up to next delim */
568 {
569 	const char *os = is;
570 	int c, n;
571 	const uschar *s = (const uschar *) is;
572 	uschar *buf, *bp;
573 
574 	if ((buf = (uschar *) malloc(strlen(is)+3)) == NULL)
575 		FATAL( "out of space in qstring(%s)", s);
576 	for (bp = buf; (c = *s) != delim; s++) {
577 		if (c == '\n')
578 			SYNTAX( "newline in string %.20s...", os );
579 		else if (c != '\\')
580 			*bp++ = c;
581 		else {	/* \something */
582 			c = *++s;
583 			if (c == 0) {	/* \ at end */
584 				*bp++ = '\\';
585 				break;	/* for loop */
586 			}
587 			switch (c) {
588 			case '\\':	*bp++ = '\\'; break;
589 			case 'n':	*bp++ = '\n'; break;
590 			case 't':	*bp++ = '\t'; break;
591 			case 'b':	*bp++ = '\b'; break;
592 			case 'f':	*bp++ = '\f'; break;
593 			case 'r':	*bp++ = '\r'; break;
594 			case 'v':	*bp++ = '\v'; break;
595 			case 'a':	*bp++ = '\a'; break;
596 			default:
597 				if (!isdigit(c)) {
598 					*bp++ = c;
599 					break;
600 				}
601 				n = c - '0';
602 				if (isdigit(s[1])) {
603 					n = 8 * n + *++s - '0';
604 					if (isdigit(s[1]))
605 						n = 8 * n + *++s - '0';
606 				}
607 				*bp++ = n;
608 				break;
609 			}
610 		}
611 	}
612 	*bp++ = 0;
613 	return (char *) buf;
614 }
615 
616 const char *flags2str(int flags)
617 {
618 	static const struct ftab {
619 		const char *name;
620 		int value;
621 	} flagtab[] = {
622 		{ "NUM", NUM },
623 		{ "STR", STR },
624 		{ "DONTFREE", DONTFREE },
625 		{ "CON", CON },
626 		{ "ARR", ARR },
627 		{ "FCN", FCN },
628 		{ "FLD", FLD },
629 		{ "REC", REC },
630 		{ "CONVC", CONVC },
631 		{ "CONVO", CONVO },
632 		{ NULL, 0 }
633 	};
634 	static char buf[100];
635 	int i, len;
636 	char *cp = buf;
637 
638 	for (i = 0; flagtab[i].name != NULL; i++) {
639 		if ((flags & flagtab[i].value) != 0) {
640 			len = snprintf(cp, sizeof(buf) - (cp - buf),
641 			    "%s%s", cp > buf ? "|" : "", flagtab[i].name);
642 			if (len < 0 || len >= sizeof(buf) - (cp - buf))
643 				FATAL("out of space in flags2str");
644 			cp += len;
645 		}
646 	}
647 
648 	return buf;
649 }
650