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