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