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