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