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