master xplshn/aruu / cmd / posix / awk / tran.c
  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 "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) /* cant 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 its 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 its 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 its the same, we dont have to
498 *
499 * there are also several cases where we dont do a conversion,
500 * such as for a field (see the checks below)
501 */
502
503  /* dont 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) /* its 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}