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