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