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 <string.h> 28 #include <ctype.h> 29 #include <errno.h> 30 #include <stdlib.h> 31 #include <stdarg.h> 32 #include "awk.h" 33 #include "ytab.h" 34 35 FILE *infile = NULL; 36 char *file = ""; 37 char *record; 38 int recsize = RECSIZE; 39 char *fields; 40 int fieldssize = RECSIZE; 41 42 Cell **fldtab; /* pointers to Cells */ 43 char inputFS[100] = " "; 44 45 #define MAXFLD 2 46 int nfields = MAXFLD; /* last allocated slot for $i */ 47 48 int donefld; /* 1 = implies rec broken into fields */ 49 int donerec; /* 1 = record is valid (no flds have changed) */ 50 51 int lastfld = 0; /* last used field */ 52 int argno = 1; /* current input argument number */ 53 extern Awkfloat *ARGC; 54 55 static Cell dollar0 = { OCELL, CFLD, NULL, "", 0.0, REC|STR|DONTFREE }; 56 static Cell dollar1 = { OCELL, CFLD, NULL, "", 0.0, FLD|STR|DONTFREE }; 57 58 void recinit(unsigned int n) 59 { 60 if ( (record = (char *) malloc(n)) == NULL 61 || (fields = (char *) malloc(n+1)) == NULL 62 || (fldtab = (Cell **) malloc((nfields+1) * sizeof(Cell *))) == NULL 63 || (fldtab[0] = (Cell *) malloc(sizeof(Cell))) == NULL ) 64 FATAL("out of space for $0 and fields"); 65 *record = '\0'; 66 *fldtab[0] = dollar0; 67 fldtab[0]->sval = record; 68 fldtab[0]->nval = tostring("0"); 69 makefields(1, nfields); 70 } 71 72 void makefields(int n1, int n2) /* create $n1..$n2 inclusive */ 73 { 74 char temp[50]; 75 int i; 76 77 for (i = n1; i <= n2; i++) { 78 fldtab[i] = (Cell *) malloc(sizeof (struct Cell)); 79 if (fldtab[i] == NULL) 80 FATAL("out of space in makefields %d", i); 81 *fldtab[i] = dollar1; 82 sprintf(temp, "%d", i); 83 fldtab[i]->nval = tostring(temp); 84 } 85 } 86 87 void initgetrec(void) 88 { 89 int i; 90 char *p; 91 92 for (i = 1; i < *ARGC; i++) { 93 p = getargv(i); /* find 1st real filename */ 94 if (p == NULL || *p == '\0') { /* deleted or zapped */ 95 argno++; 96 continue; 97 } 98 if (!isclvar(p)) { 99 setsval(lookup("FILENAME", symtab), p); 100 return; 101 } 102 setclvar(p); /* a commandline assignment before filename */ 103 argno++; 104 } 105 infile = stdin; /* no filenames, so use stdin */ 106 } 107 108 static int firsttime = 1; 109 110 int getrec(char **pbuf, int *pbufsize, int isrecord) /* get next input record */ 111 { /* note: cares whether buf == record */ 112 int c; 113 char *buf = *pbuf; 114 uschar saveb0; 115 int bufsize = *pbufsize, savebufsize = bufsize; 116 117 if (firsttime) { 118 firsttime = 0; 119 initgetrec(); 120 } 121 dprintf( ("RS=<%s>, FS=<%s>, ARGC=%g, FILENAME=%s\n", 122 *RS, *FS, *ARGC, *FILENAME) ); 123 if (isrecord) { 124 donefld = 0; 125 donerec = 1; 126 } 127 saveb0 = buf[0]; 128 buf[0] = 0; 129 while (argno < *ARGC || infile == stdin) { 130 dprintf( ("argno=%d, file=|%s|\n", argno, file) ); 131 if (infile == NULL) { /* have to open a new file */ 132 file = getargv(argno); 133 if (file == NULL || *file == '\0') { /* deleted or zapped */ 134 argno++; 135 continue; 136 } 137 if (isclvar(file)) { /* a var=value arg */ 138 setclvar(file); 139 argno++; 140 continue; 141 } 142 *FILENAME = file; 143 dprintf( ("opening file %s\n", file) ); 144 if (*file == '-' && *(file+1) == '\0') 145 infile = stdin; 146 else if ((infile = fopen(file, "r")) == NULL) 147 FATAL("can't open file %s", file); 148 setfval(fnrloc, 0.0); 149 } 150 c = readrec(&buf, &bufsize, infile); 151 if (c != 0 || buf[0] != '\0') { /* normal record */ 152 if (isrecord) { 153 if (freeable(fldtab[0])) 154 xfree(fldtab[0]->sval); 155 fldtab[0]->sval = buf; /* buf == record */ 156 fldtab[0]->tval = REC | STR | DONTFREE; 157 if (is_number(fldtab[0]->sval)) { 158 fldtab[0]->fval = atof(fldtab[0]->sval); 159 fldtab[0]->tval |= NUM; 160 } 161 } 162 setfval(nrloc, nrloc->fval+1); 163 setfval(fnrloc, fnrloc->fval+1); 164 *pbuf = buf; 165 *pbufsize = bufsize; 166 return 1; 167 } 168 /* EOF arrived on this file; set up next */ 169 if (infile != stdin) 170 fclose(infile); 171 infile = NULL; 172 argno++; 173 } 174 buf[0] = saveb0; 175 *pbuf = buf; 176 *pbufsize = savebufsize; 177 return 0; /* true end of file */ 178 } 179 180 void nextfile(void) 181 { 182 if (infile != NULL && infile != stdin) 183 fclose(infile); 184 infile = NULL; 185 argno++; 186 } 187 188 int readrec(char **pbuf, int *pbufsize, FILE *inf) /* read one record into buf */ 189 { 190 int sep, c; 191 char *rr, *buf = *pbuf; 192 int bufsize = *pbufsize; 193 194 if (strlen(*FS) >= sizeof(inputFS)) 195 FATAL("field separator %.10s... is too long", *FS); 196 /*fflush(stdout); avoids some buffering problem but makes it 25% slower*/ 197 strcpy(inputFS, *FS); /* for subsequent field splitting */ 198 if ((sep = **RS) == 0) { 199 sep = '\n'; 200 while ((c=getc(inf)) == '\n' && c != EOF) /* skip leading \n's */ 201 ; 202 if (c != EOF) 203 ungetc(c, inf); 204 } 205 for (rr = buf; ; ) { 206 for (; (c=getc(inf)) != sep && c != EOF; ) { 207 if (rr-buf+1 > bufsize) 208 if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 1")) 209 FATAL("input record `%.30s...' too long", buf); 210 *rr++ = c; 211 } 212 if (**RS == sep || c == EOF) 213 break; 214 if ((c = getc(inf)) == '\n' || c == EOF) /* 2 in a row */ 215 break; 216 if (!adjbuf(&buf, &bufsize, 2+rr-buf, recsize, &rr, "readrec 2")) 217 FATAL("input record `%.30s...' too long", buf); 218 *rr++ = '\n'; 219 *rr++ = c; 220 } 221 if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 3")) 222 FATAL("input record `%.30s...' too long", buf); 223 *rr = 0; 224 dprintf( ("readrec saw <%s>, returns %d\n", buf, c == EOF && rr == buf ? 0 : 1) ); 225 *pbuf = buf; 226 *pbufsize = bufsize; 227 return c == EOF && rr == buf ? 0 : 1; 228 } 229 230 char *getargv(int n) /* get ARGV[n] */ 231 { 232 Cell *x; 233 char *s, temp[50]; 234 extern Array *ARGVtab; 235 236 sprintf(temp, "%d", n); 237 if (lookup(temp, ARGVtab) == NULL) 238 return NULL; 239 x = setsymtab(temp, "", 0.0, STR, ARGVtab); 240 s = getsval(x); 241 dprintf( ("getargv(%d) returns |%s|\n", n, s) ); 242 return s; 243 } 244 245 void setclvar(char *s) /* set var=value from s */ 246 { 247 char *p; 248 Cell *q; 249 250 for (p=s; *p != '='; p++) 251 ; 252 *p++ = 0; 253 p = qstring(p, '\0'); 254 q = setsymtab(s, p, 0.0, STR, symtab); 255 setsval(q, p); 256 if (is_number(q->sval)) { 257 q->fval = atof(q->sval); 258 q->tval |= NUM; 259 } 260 dprintf( ("command line set %s to |%s|\n", s, p) ); 261 } 262 263 264 void fldbld(void) /* create fields from current record */ 265 { 266 /* this relies on having fields[] the same length as $0 */ 267 /* the fields are all stored in this one array with \0's */ 268 /* possibly with a final trailing \0 not associated with any field */ 269 char *r, *fr, sep; 270 Cell *p; 271 int i, j, n; 272 273 if (donefld) 274 return; 275 if (!isstr(fldtab[0])) 276 getsval(fldtab[0]); 277 r = fldtab[0]->sval; 278 n = strlen(r); 279 if (n > fieldssize) { 280 xfree(fields); 281 if ((fields = (char *) malloc(n+2)) == NULL) /* possibly 2 final \0s */ 282 FATAL("out of space for fields in fldbld %d", n); 283 fieldssize = n; 284 } 285 fr = fields; 286 i = 0; /* number of fields accumulated here */ 287 strcpy(inputFS, *FS); 288 if (strlen(inputFS) > 1) { /* it's a regular expression */ 289 i = refldbld(r, inputFS); 290 } else if ((sep = *inputFS) == ' ') { /* default whitespace */ 291 for (i = 0; ; ) { 292 while (*r == ' ' || *r == '\t' || *r == '\n') 293 r++; 294 if (*r == 0) 295 break; 296 i++; 297 if (i > nfields) 298 growfldtab(i); 299 if (freeable(fldtab[i])) 300 xfree(fldtab[i]->sval); 301 fldtab[i]->sval = fr; 302 fldtab[i]->tval = FLD | STR | DONTFREE; 303 do 304 *fr++ = *r++; 305 while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0'); 306 *fr++ = 0; 307 } 308 *fr = 0; 309 } else if ((sep = *inputFS) == 0) { /* new: FS="" => 1 char/field */ 310 for (i = 0; *r != 0; r++) { 311 char buf[2]; 312 i++; 313 if (i > nfields) 314 growfldtab(i); 315 if (freeable(fldtab[i])) 316 xfree(fldtab[i]->sval); 317 buf[0] = *r; 318 buf[1] = 0; 319 fldtab[i]->sval = tostring(buf); 320 fldtab[i]->tval = FLD | STR; 321 } 322 *fr = 0; 323 } else if (*r != 0) { /* if 0, it's a null field */ 324 /* subtlecase : if length(FS) == 1 && length(RS > 0) 325 * \n is NOT a field separator (cf awk book 61,84). 326 * this variable is tested in the inner while loop. 327 */ 328 int rtest = '\n'; /* normal case */ 329 if (strlen(*RS) > 0) 330 rtest = '\0'; 331 for (;;) { 332 i++; 333 if (i > nfields) 334 growfldtab(i); 335 if (freeable(fldtab[i])) 336 xfree(fldtab[i]->sval); 337 fldtab[i]->sval = fr; 338 fldtab[i]->tval = FLD | STR | DONTFREE; 339 while (*r != sep && *r != rtest && *r != '\0') /* \n is always a separator */ 340 *fr++ = *r++; 341 *fr++ = 0; 342 if (*r++ == 0) 343 break; 344 } 345 *fr = 0; 346 } 347 if (i > nfields) 348 FATAL("record `%.30s...' has too many fields; can't happen", r); 349 cleanfld(i+1, lastfld); /* clean out junk from previous record */ 350 lastfld = i; 351 donefld = 1; 352 for (j = 1; j <= lastfld; j++) { 353 p = fldtab[j]; 354 if(is_number(p->sval)) { 355 p->fval = atof(p->sval); 356 p->tval |= NUM; 357 } 358 } 359 setfval(nfloc, (Awkfloat) lastfld); 360 if (dbg) { 361 for (j = 0; j <= lastfld; j++) { 362 p = fldtab[j]; 363 printf("field %d (%s): |%s|\n", j, p->nval, p->sval); 364 } 365 } 366 } 367 368 void cleanfld(int n1, int n2) /* clean out fields n1 .. n2 inclusive */ 369 { /* nvals remain intact */ 370 Cell *p; 371 int i; 372 373 for (i = n1; i <= n2; i++) { 374 p = fldtab[i]; 375 if (freeable(p)) 376 xfree(p->sval); 377 p->sval = ""; 378 p->tval = FLD | STR | DONTFREE; 379 } 380 } 381 382 void newfld(int n) /* add field n after end of existing lastfld */ 383 { 384 if (n > nfields) 385 growfldtab(n); 386 cleanfld(lastfld+1, n); 387 lastfld = n; 388 setfval(nfloc, (Awkfloat) n); 389 } 390 391 Cell *fieldadr(int n) /* get nth field */ 392 { 393 if (n < 0) 394 FATAL("trying to access out of range field %d", n); 395 if (n > nfields) /* fields after NF are empty */ 396 growfldtab(n); /* but does not increase NF */ 397 return(fldtab[n]); 398 } 399 400 void growfldtab(int n) /* make new fields up to at least $n */ 401 { 402 int nf = 2 * nfields; 403 size_t s; 404 405 if (n > nf) 406 nf = n; 407 s = (nf+1) * (sizeof (struct Cell *)); /* freebsd: how much do we need? */ 408 if (s / sizeof(struct Cell *) - 1 == nf) /* didn't overflow */ 409 fldtab = (Cell **) realloc(fldtab, s); 410 else /* overflow sizeof int */ 411 xfree(fldtab); /* make it null */ 412 if (fldtab == NULL) 413 FATAL("out of space creating %d fields", nf); 414 makefields(nfields+1, nf); 415 nfields = nf; 416 } 417 418 int refldbld(const char *rec, const char *fs) /* build fields from reg expr in FS */ 419 { 420 /* this relies on having fields[] the same length as $0 */ 421 /* the fields are all stored in this one array with \0's */ 422 char *fr; 423 int i, tempstat, n; 424 fa *pfa; 425 426 n = strlen(rec); 427 if (n > fieldssize) { 428 xfree(fields); 429 if ((fields = (char *) malloc(n+1)) == NULL) 430 FATAL("out of space for fields in refldbld %d", n); 431 fieldssize = n; 432 } 433 fr = fields; 434 *fr = '\0'; 435 if (*rec == '\0') 436 return 0; 437 pfa = makedfa(fs, 1); 438 dprintf( ("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs) ); 439 tempstat = pfa->initstat; 440 for (i = 1; ; i++) { 441 if (i > nfields) 442 growfldtab(i); 443 if (freeable(fldtab[i])) 444 xfree(fldtab[i]->sval); 445 fldtab[i]->tval = FLD | STR | DONTFREE; 446 fldtab[i]->sval = fr; 447 dprintf( ("refldbld: i=%d\n", i) ); 448 if (nematch(pfa, rec)) { 449 pfa->initstat = 2; /* horrible coupling to b.c */ 450 dprintf( ("match %s (%d chars)\n", patbeg, patlen) ); 451 strncpy(fr, rec, patbeg-rec); 452 fr += patbeg - rec + 1; 453 *(fr-1) = '\0'; 454 rec = patbeg + patlen; 455 } else { 456 dprintf( ("no match %s\n", rec) ); 457 strcpy(fr, rec); 458 pfa->initstat = tempstat; 459 break; 460 } 461 } 462 return i; 463 } 464 465 void recbld(void) /* create $0 from $1..$NF if necessary */ 466 { 467 int i; 468 char *r, *p; 469 470 if (donerec == 1) 471 return; 472 r = record; 473 for (i = 1; i <= *NF; i++) { 474 p = getsval(fldtab[i]); 475 if (!adjbuf(&record, &recsize, 1+strlen(p)+r-record, recsize, &r, "recbld 1")) 476 FATAL("created $0 `%.30s...' too long", record); 477 while ((*r = *p++) != 0) 478 r++; 479 if (i < *NF) { 480 if (!adjbuf(&record, &recsize, 2+strlen(*OFS)+r-record, recsize, &r, "recbld 2")) 481 FATAL("created $0 `%.30s...' too long", record); 482 for (p = *OFS; (*r = *p++) != 0; ) 483 r++; 484 } 485 } 486 if (!adjbuf(&record, &recsize, 2+r-record, recsize, &r, "recbld 3")) 487 FATAL("built giant record `%.30s...'", record); 488 *r = '\0'; 489 dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]) ); 490 491 if (freeable(fldtab[0])) 492 xfree(fldtab[0]->sval); 493 fldtab[0]->tval = REC | STR | DONTFREE; 494 fldtab[0]->sval = record; 495 496 dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void*)fldtab[0]) ); 497 dprintf( ("recbld = |%s|\n", record) ); 498 donerec = 1; 499 } 500 501 int errorflag = 0; 502 503 void yyerror(const char *s) 504 { 505 SYNTAX("%s", s); 506 } 507 508 void SYNTAX(const char *fmt, ...) 509 { 510 extern char *cmdname, *curfname; 511 static int been_here = 0; 512 va_list varg; 513 514 if (been_here++ > 2) 515 return; 516 fprintf(stderr, "%s: ", cmdname); 517 va_start(varg, fmt); 518 vfprintf(stderr, fmt, varg); 519 va_end(varg); 520 fprintf(stderr, " at source line %d", lineno); 521 if (curfname != NULL) 522 fprintf(stderr, " in function %s", curfname); 523 if (compile_time == 1 && cursource() != NULL) 524 fprintf(stderr, " source file %s", cursource()); 525 fprintf(stderr, "\n"); 526 errorflag = 2; 527 eprint(); 528 } 529 530 void fpecatch(int n) 531 { 532 FATAL("floating point exception %d", n); 533 } 534 535 extern int bracecnt, brackcnt, parencnt; 536 537 void bracecheck(void) 538 { 539 int c; 540 static int beenhere = 0; 541 542 if (beenhere++) 543 return; 544 while ((c = input()) != EOF && c != '\0') 545 bclass(c); 546 bcheck2(bracecnt, '{', '}'); 547 bcheck2(brackcnt, '[', ']'); 548 bcheck2(parencnt, '(', ')'); 549 } 550 551 void bcheck2(int n, int c1, int c2) 552 { 553 if (n == 1) 554 fprintf(stderr, "\tmissing %c\n", c2); 555 else if (n > 1) 556 fprintf(stderr, "\t%d missing %c's\n", n, c2); 557 else if (n == -1) 558 fprintf(stderr, "\textra %c\n", c2); 559 else if (n < -1) 560 fprintf(stderr, "\t%d extra %c's\n", -n, c2); 561 } 562 563 void FATAL(const char *fmt, ...) 564 { 565 extern char *cmdname; 566 va_list varg; 567 568 fflush(stdout); 569 fprintf(stderr, "%s: ", cmdname); 570 va_start(varg, fmt); 571 vfprintf(stderr, fmt, varg); 572 va_end(varg); 573 error(); 574 if (dbg > 1) /* core dump if serious debugging on */ 575 abort(); 576 exit(2); 577 } 578 579 void WARNING(const char *fmt, ...) 580 { 581 extern char *cmdname; 582 va_list varg; 583 584 fflush(stdout); 585 fprintf(stderr, "%s: ", cmdname); 586 va_start(varg, fmt); 587 vfprintf(stderr, fmt, varg); 588 va_end(varg); 589 error(); 590 } 591 592 void error() 593 { 594 extern Node *curnode; 595 596 fprintf(stderr, "\n"); 597 if (compile_time != 2 && NR && *NR > 0) { 598 fprintf(stderr, " input record number %d", (int) (*FNR)); 599 if (strcmp(*FILENAME, "-") != 0) 600 fprintf(stderr, ", file %s", *FILENAME); 601 fprintf(stderr, "\n"); 602 } 603 if (compile_time != 2 && curnode) 604 fprintf(stderr, " source line number %d", curnode->lineno); 605 else if (compile_time != 2 && lineno) 606 fprintf(stderr, " source line number %d", lineno); 607 if (compile_time == 1 && cursource() != NULL) 608 fprintf(stderr, " source file %s", cursource()); 609 fprintf(stderr, "\n"); 610 eprint(); 611 } 612 613 void eprint(void) /* try to print context around error */ 614 { 615 char *p, *q; 616 int c; 617 static int been_here = 0; 618 extern char ebuf[], *ep; 619 620 if (compile_time == 2 || compile_time == 0 || been_here++ > 0 || ebuf == ep) 621 return; 622 p = ep - 1; 623 if (p > ebuf && *p == '\n') 624 p--; 625 for ( ; p > ebuf && *p != '\n' && *p != '\0'; p--) 626 ; 627 while (*p == '\n') 628 p++; 629 fprintf(stderr, " context is\n\t"); 630 for (q=ep-1; q>=p && *q!=' ' && *q!='\t' && *q!='\n'; q--) 631 ; 632 for ( ; p < q; p++) 633 if (*p) 634 putc(*p, stderr); 635 fprintf(stderr, " >>> "); 636 for ( ; p < ep; p++) 637 if (*p) 638 putc(*p, stderr); 639 fprintf(stderr, " <<< "); 640 if (*ep) 641 while ((c = input()) != '\n' && c != '\0' && c != EOF) { 642 putc(c, stderr); 643 bclass(c); 644 } 645 putc('\n', stderr); 646 ep = ebuf; 647 } 648 649 void bclass(int c) 650 { 651 switch (c) { 652 case '{': bracecnt++; break; 653 case '}': bracecnt--; break; 654 case '[': brackcnt++; break; 655 case ']': brackcnt--; break; 656 case '(': parencnt++; break; 657 case ')': parencnt--; break; 658 } 659 } 660 661 double errcheck(double x, const char *s) 662 { 663 664 if (errno == EDOM) { 665 errno = 0; 666 WARNING("%s argument out of domain", s); 667 x = 1; 668 } else if (errno == ERANGE) { 669 errno = 0; 670 WARNING("%s result out of range", s); 671 x = 1; 672 } 673 return x; 674 } 675 676 int isclvar(const char *s) /* is s of form var=something ? */ 677 { 678 const char *os = s; 679 680 if (!isalpha((uschar) *s) && *s != '_') 681 return 0; 682 for ( ; *s; s++) 683 if (!(isalnum((uschar) *s) || *s == '_')) 684 break; 685 return *s == '=' && s > os && *(s+1) != '='; 686 } 687 688 /* strtod is supposed to be a proper test of what's a valid number */ 689 /* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */ 690 /* wrong: violates 4.10.1.4 of ansi C standard */ 691 692 #include <math.h> 693 int is_number(const char *s) 694 { 695 double r; 696 char *ep; 697 errno = 0; 698 r = strtod(s, &ep); 699 if (ep == s || r == HUGE_VAL || errno == ERANGE) 700 return 0; 701 while (*ep == ' ' || *ep == '\t' || *ep == '\n') 702 ep++; 703 if (*ep == '\0') 704 return 1; 705 else 706 return 0; 707 } 708