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