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