1 /* $NetBSD: chared.c,v 1.40 2014/06/18 18:12:28 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Christos Zoulas of Cornell University. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include "config.h" 36 #if !defined(lint) && !defined(SCCSID) 37 #if 0 38 static char sccsid[] = "@(#)chared.c 8.1 (Berkeley) 6/4/93"; 39 #else 40 __RCSID("$NetBSD: chared.c,v 1.40 2014/06/18 18:12:28 christos Exp $"); 41 #endif 42 #endif /* not lint && not SCCSID */ 43 #include <sys/cdefs.h> 44 __FBSDID("$FreeBSD$"); 45 46 /* 47 * chared.c: Character editor utilities 48 */ 49 #include <stdlib.h> 50 #include "el.h" 51 52 private void ch__clearmacro (EditLine *); 53 54 /* value to leave unused in line buffer */ 55 #define EL_LEAVE 2 56 57 /* cv_undo(): 58 * Handle state for the vi undo command 59 */ 60 protected void 61 cv_undo(EditLine *el) 62 { 63 c_undo_t *vu = &el->el_chared.c_undo; 64 c_redo_t *r = &el->el_chared.c_redo; 65 size_t size; 66 67 /* Save entire line for undo */ 68 size = (size_t)(el->el_line.lastchar - el->el_line.buffer); 69 vu->len = (ssize_t)size; 70 vu->cursor = (int)(el->el_line.cursor - el->el_line.buffer); 71 (void)memcpy(vu->buf, el->el_line.buffer, size * sizeof(*vu->buf)); 72 73 /* save command info for redo */ 74 r->count = el->el_state.doingarg ? el->el_state.argument : 0; 75 r->action = el->el_chared.c_vcmd.action; 76 r->pos = r->buf; 77 r->cmd = el->el_state.thiscmd; 78 r->ch = el->el_state.thisch; 79 } 80 81 /* cv_yank(): 82 * Save yank/delete data for paste 83 */ 84 protected void 85 cv_yank(EditLine *el, const Char *ptr, int size) 86 { 87 c_kill_t *k = &el->el_chared.c_kill; 88 89 (void)memcpy(k->buf, ptr, (size_t)size * sizeof(*k->buf)); 90 k->last = k->buf + size; 91 } 92 93 94 /* c_insert(): 95 * Insert num characters 96 */ 97 protected void 98 c_insert(EditLine *el, int num) 99 { 100 Char *cp; 101 102 if (el->el_line.lastchar + num >= el->el_line.limit) { 103 if (!ch_enlargebufs(el, (size_t)num)) 104 return; /* can't go past end of buffer */ 105 } 106 107 if (el->el_line.cursor < el->el_line.lastchar) { 108 /* if I must move chars */ 109 for (cp = el->el_line.lastchar; cp >= el->el_line.cursor; cp--) 110 cp[num] = *cp; 111 } 112 el->el_line.lastchar += num; 113 } 114 115 116 /* c_delafter(): 117 * Delete num characters after the cursor 118 */ 119 protected void 120 c_delafter(EditLine *el, int num) 121 { 122 123 if (el->el_line.cursor + num > el->el_line.lastchar) 124 num = (int)(el->el_line.lastchar - el->el_line.cursor); 125 126 if (el->el_map.current != el->el_map.emacs) { 127 cv_undo(el); 128 cv_yank(el, el->el_line.cursor, num); 129 } 130 131 if (num > 0) { 132 Char *cp; 133 134 for (cp = el->el_line.cursor; cp <= el->el_line.lastchar; cp++) 135 *cp = cp[num]; 136 137 el->el_line.lastchar -= num; 138 } 139 } 140 141 142 /* c_delafter1(): 143 * Delete the character after the cursor, do not yank 144 */ 145 protected void 146 c_delafter1(EditLine *el) 147 { 148 Char *cp; 149 150 for (cp = el->el_line.cursor; cp <= el->el_line.lastchar; cp++) 151 *cp = cp[1]; 152 153 el->el_line.lastchar--; 154 } 155 156 157 /* c_delbefore(): 158 * Delete num characters before the cursor 159 */ 160 protected void 161 c_delbefore(EditLine *el, int num) 162 { 163 164 if (el->el_line.cursor - num < el->el_line.buffer) 165 num = (int)(el->el_line.cursor - el->el_line.buffer); 166 167 if (el->el_map.current != el->el_map.emacs) { 168 cv_undo(el); 169 cv_yank(el, el->el_line.cursor - num, num); 170 } 171 172 if (num > 0) { 173 Char *cp; 174 175 for (cp = el->el_line.cursor - num; 176 cp <= el->el_line.lastchar; 177 cp++) 178 *cp = cp[num]; 179 180 el->el_line.lastchar -= num; 181 } 182 } 183 184 185 /* c_delbefore1(): 186 * Delete the character before the cursor, do not yank 187 */ 188 protected void 189 c_delbefore1(EditLine *el) 190 { 191 Char *cp; 192 193 for (cp = el->el_line.cursor - 1; cp <= el->el_line.lastchar; cp++) 194 *cp = cp[1]; 195 196 el->el_line.lastchar--; 197 } 198 199 200 /* ce__isword(): 201 * Return if p is part of a word according to emacs 202 */ 203 protected int 204 ce__isword(Int p) 205 { 206 return Isalnum(p) || Strchr(STR("*?_-.[]~="), p) != NULL; 207 } 208 209 210 /* cv__isword(): 211 * Return if p is part of a word according to vi 212 */ 213 protected int 214 cv__isword(Int p) 215 { 216 if (Isalnum(p) || p == '_') 217 return 1; 218 if (Isgraph(p)) 219 return 2; 220 return 0; 221 } 222 223 224 /* cv__isWord(): 225 * Return if p is part of a big word according to vi 226 */ 227 protected int 228 cv__isWord(Int p) 229 { 230 return !Isspace(p); 231 } 232 233 234 /* c__prev_word(): 235 * Find the previous word 236 */ 237 protected Char * 238 c__prev_word(Char *p, Char *low, int n, int (*wtest)(Int)) 239 { 240 p--; 241 242 while (n--) { 243 while ((p >= low) && !(*wtest)(*p)) 244 p--; 245 while ((p >= low) && (*wtest)(*p)) 246 p--; 247 } 248 249 /* cp now points to one character before the word */ 250 p++; 251 if (p < low) 252 p = low; 253 /* cp now points where we want it */ 254 return p; 255 } 256 257 258 /* c__next_word(): 259 * Find the next word 260 */ 261 protected Char * 262 c__next_word(Char *p, Char *high, int n, int (*wtest)(Int)) 263 { 264 while (n--) { 265 while ((p < high) && !(*wtest)(*p)) 266 p++; 267 while ((p < high) && (*wtest)(*p)) 268 p++; 269 } 270 if (p > high) 271 p = high; 272 /* p now points where we want it */ 273 return p; 274 } 275 276 /* cv_next_word(): 277 * Find the next word vi style 278 */ 279 protected Char * 280 cv_next_word(EditLine *el, Char *p, Char *high, int n, int (*wtest)(Int)) 281 { 282 int test; 283 284 while (n--) { 285 test = (*wtest)(*p); 286 while ((p < high) && (*wtest)(*p) == test) 287 p++; 288 /* 289 * vi historically deletes with cw only the word preserving the 290 * trailing whitespace! This is not what 'w' does.. 291 */ 292 if (n || el->el_chared.c_vcmd.action != (DELETE|INSERT)) 293 while ((p < high) && Isspace(*p)) 294 p++; 295 } 296 297 /* p now points where we want it */ 298 if (p > high) 299 return high; 300 else 301 return p; 302 } 303 304 305 /* cv_prev_word(): 306 * Find the previous word vi style 307 */ 308 protected Char * 309 cv_prev_word(Char *p, Char *low, int n, int (*wtest)(Int)) 310 { 311 int test; 312 313 p--; 314 while (n--) { 315 while ((p > low) && Isspace(*p)) 316 p--; 317 test = (*wtest)(*p); 318 while ((p >= low) && (*wtest)(*p) == test) 319 p--; 320 } 321 p++; 322 323 /* p now points where we want it */ 324 if (p < low) 325 return low; 326 else 327 return p; 328 } 329 330 331 /* cv_delfini(): 332 * Finish vi delete action 333 */ 334 protected void 335 cv_delfini(EditLine *el) 336 { 337 int size; 338 int action = el->el_chared.c_vcmd.action; 339 340 if (action & INSERT) 341 el->el_map.current = el->el_map.key; 342 343 if (el->el_chared.c_vcmd.pos == 0) 344 /* sanity */ 345 return; 346 347 size = (int)(el->el_line.cursor - el->el_chared.c_vcmd.pos); 348 if (size == 0) 349 size = 1; 350 el->el_line.cursor = el->el_chared.c_vcmd.pos; 351 if (action & YANK) { 352 if (size > 0) 353 cv_yank(el, el->el_line.cursor, size); 354 else 355 cv_yank(el, el->el_line.cursor + size, -size); 356 } else { 357 if (size > 0) { 358 c_delafter(el, size); 359 re_refresh_cursor(el); 360 } else { 361 c_delbefore(el, -size); 362 el->el_line.cursor += size; 363 } 364 } 365 el->el_chared.c_vcmd.action = NOP; 366 } 367 368 369 /* cv__endword(): 370 * Go to the end of this word according to vi 371 */ 372 protected Char * 373 cv__endword(Char *p, Char *high, int n, int (*wtest)(Int)) 374 { 375 int test; 376 377 p++; 378 379 while (n--) { 380 while ((p < high) && Isspace(*p)) 381 p++; 382 383 test = (*wtest)(*p); 384 while ((p < high) && (*wtest)(*p) == test) 385 p++; 386 } 387 p--; 388 return p; 389 } 390 391 /* ch_init(): 392 * Initialize the character editor 393 */ 394 protected int 395 ch_init(EditLine *el) 396 { 397 c_macro_t *ma = &el->el_chared.c_macro; 398 399 el->el_line.buffer = el_malloc(EL_BUFSIZ * 400 sizeof(*el->el_line.buffer)); 401 if (el->el_line.buffer == NULL) 402 return -1; 403 404 (void) memset(el->el_line.buffer, 0, EL_BUFSIZ * 405 sizeof(*el->el_line.buffer)); 406 el->el_line.cursor = el->el_line.buffer; 407 el->el_line.lastchar = el->el_line.buffer; 408 el->el_line.limit = &el->el_line.buffer[EL_BUFSIZ - EL_LEAVE]; 409 410 el->el_chared.c_undo.buf = el_malloc(EL_BUFSIZ * 411 sizeof(*el->el_chared.c_undo.buf)); 412 if (el->el_chared.c_undo.buf == NULL) 413 return -1; 414 (void) memset(el->el_chared.c_undo.buf, 0, EL_BUFSIZ * 415 sizeof(*el->el_chared.c_undo.buf)); 416 el->el_chared.c_undo.len = -1; 417 el->el_chared.c_undo.cursor = 0; 418 el->el_chared.c_redo.buf = el_malloc(EL_BUFSIZ * 419 sizeof(*el->el_chared.c_redo.buf)); 420 if (el->el_chared.c_redo.buf == NULL) 421 return -1; 422 el->el_chared.c_redo.pos = el->el_chared.c_redo.buf; 423 el->el_chared.c_redo.lim = el->el_chared.c_redo.buf + EL_BUFSIZ; 424 el->el_chared.c_redo.cmd = ED_UNASSIGNED; 425 426 el->el_chared.c_vcmd.action = NOP; 427 el->el_chared.c_vcmd.pos = el->el_line.buffer; 428 429 el->el_chared.c_kill.buf = el_malloc(EL_BUFSIZ * 430 sizeof(*el->el_chared.c_kill.buf)); 431 if (el->el_chared.c_kill.buf == NULL) 432 return -1; 433 (void) memset(el->el_chared.c_kill.buf, 0, EL_BUFSIZ * 434 sizeof(*el->el_chared.c_kill.buf)); 435 el->el_chared.c_kill.mark = el->el_line.buffer; 436 el->el_chared.c_kill.last = el->el_chared.c_kill.buf; 437 el->el_chared.c_resizefun = NULL; 438 el->el_chared.c_resizearg = NULL; 439 el->el_chared.c_aliasfun = NULL; 440 el->el_chared.c_aliasarg = NULL; 441 442 el->el_map.current = el->el_map.key; 443 444 el->el_state.inputmode = MODE_INSERT; /* XXX: save a default */ 445 el->el_state.doingarg = 0; 446 el->el_state.metanext = 0; 447 el->el_state.argument = 1; 448 el->el_state.lastcmd = ED_UNASSIGNED; 449 450 ma->level = -1; 451 ma->offset = 0; 452 ma->macro = el_malloc(EL_MAXMACRO * sizeof(*ma->macro)); 453 if (ma->macro == NULL) 454 return -1; 455 return 0; 456 } 457 458 /* ch_reset(): 459 * Reset the character editor 460 */ 461 protected void 462 ch_reset(EditLine *el, int mclear) 463 { 464 el->el_line.cursor = el->el_line.buffer; 465 el->el_line.lastchar = el->el_line.buffer; 466 467 el->el_chared.c_undo.len = -1; 468 el->el_chared.c_undo.cursor = 0; 469 470 el->el_chared.c_vcmd.action = NOP; 471 el->el_chared.c_vcmd.pos = el->el_line.buffer; 472 473 el->el_chared.c_kill.mark = el->el_line.buffer; 474 475 el->el_map.current = el->el_map.key; 476 477 el->el_state.inputmode = MODE_INSERT; /* XXX: save a default */ 478 el->el_state.doingarg = 0; 479 el->el_state.metanext = 0; 480 el->el_state.argument = 1; 481 el->el_state.lastcmd = ED_UNASSIGNED; 482 483 el->el_history.eventno = 0; 484 485 if (mclear) 486 ch__clearmacro(el); 487 } 488 489 private void 490 ch__clearmacro(EditLine *el) 491 { 492 c_macro_t *ma = &el->el_chared.c_macro; 493 while (ma->level >= 0) 494 el_free(ma->macro[ma->level--]); 495 } 496 497 /* ch_enlargebufs(): 498 * Enlarge line buffer to be able to hold twice as much characters. 499 * Returns 1 if successful, 0 if not. 500 */ 501 protected int 502 ch_enlargebufs(EditLine *el, size_t addlen) 503 { 504 size_t sz, newsz; 505 Char *newbuffer, *oldbuf, *oldkbuf; 506 507 sz = (size_t)(el->el_line.limit - el->el_line.buffer + EL_LEAVE); 508 newsz = sz * 2; 509 /* 510 * If newly required length is longer than current buffer, we need 511 * to make the buffer big enough to hold both old and new stuff. 512 */ 513 if (addlen > sz) { 514 while(newsz - sz < addlen) 515 newsz *= 2; 516 } 517 518 /* 519 * Reallocate line buffer. 520 */ 521 newbuffer = el_realloc(el->el_line.buffer, newsz * sizeof(*newbuffer)); 522 if (!newbuffer) 523 return 0; 524 525 /* zero the newly added memory, leave old data in */ 526 (void) memset(&newbuffer[sz], 0, (newsz - sz) * sizeof(*newbuffer)); 527 528 oldbuf = el->el_line.buffer; 529 530 el->el_line.buffer = newbuffer; 531 el->el_line.cursor = newbuffer + (el->el_line.cursor - oldbuf); 532 el->el_line.lastchar = newbuffer + (el->el_line.lastchar - oldbuf); 533 /* don't set new size until all buffers are enlarged */ 534 el->el_line.limit = &newbuffer[sz - EL_LEAVE]; 535 536 /* 537 * Reallocate kill buffer. 538 */ 539 newbuffer = el_realloc(el->el_chared.c_kill.buf, newsz * 540 sizeof(*newbuffer)); 541 if (!newbuffer) 542 return 0; 543 544 /* zero the newly added memory, leave old data in */ 545 (void) memset(&newbuffer[sz], 0, (newsz - sz) * sizeof(*newbuffer)); 546 547 oldkbuf = el->el_chared.c_kill.buf; 548 549 el->el_chared.c_kill.buf = newbuffer; 550 el->el_chared.c_kill.last = newbuffer + 551 (el->el_chared.c_kill.last - oldkbuf); 552 el->el_chared.c_kill.mark = el->el_line.buffer + 553 (el->el_chared.c_kill.mark - oldbuf); 554 555 /* 556 * Reallocate undo buffer. 557 */ 558 newbuffer = el_realloc(el->el_chared.c_undo.buf, 559 newsz * sizeof(*newbuffer)); 560 if (!newbuffer) 561 return 0; 562 563 /* zero the newly added memory, leave old data in */ 564 (void) memset(&newbuffer[sz], 0, (newsz - sz) * sizeof(*newbuffer)); 565 el->el_chared.c_undo.buf = newbuffer; 566 567 newbuffer = el_realloc(el->el_chared.c_redo.buf, 568 newsz * sizeof(*newbuffer)); 569 if (!newbuffer) 570 return 0; 571 el->el_chared.c_redo.pos = newbuffer + 572 (el->el_chared.c_redo.pos - el->el_chared.c_redo.buf); 573 el->el_chared.c_redo.lim = newbuffer + 574 (el->el_chared.c_redo.lim - el->el_chared.c_redo.buf); 575 el->el_chared.c_redo.buf = newbuffer; 576 577 if (!hist_enlargebuf(el, sz, newsz)) 578 return 0; 579 580 /* Safe to set enlarged buffer size */ 581 el->el_line.limit = &el->el_line.buffer[newsz - EL_LEAVE]; 582 if (el->el_chared.c_resizefun) 583 (*el->el_chared.c_resizefun)(el, el->el_chared.c_resizearg); 584 return 1; 585 } 586 587 /* ch_end(): 588 * Free the data structures used by the editor 589 */ 590 protected void 591 ch_end(EditLine *el) 592 { 593 el_free(el->el_line.buffer); 594 el->el_line.buffer = NULL; 595 el->el_line.limit = NULL; 596 el_free(el->el_chared.c_undo.buf); 597 el->el_chared.c_undo.buf = NULL; 598 el_free(el->el_chared.c_redo.buf); 599 el->el_chared.c_redo.buf = NULL; 600 el->el_chared.c_redo.pos = NULL; 601 el->el_chared.c_redo.lim = NULL; 602 el->el_chared.c_redo.cmd = ED_UNASSIGNED; 603 el_free(el->el_chared.c_kill.buf); 604 el->el_chared.c_kill.buf = NULL; 605 ch_reset(el, 1); 606 el_free(el->el_chared.c_macro.macro); 607 el->el_chared.c_macro.macro = NULL; 608 } 609 610 611 /* el_insertstr(): 612 * Insert string at cursorI 613 */ 614 public int 615 FUN(el,insertstr)(EditLine *el, const Char *s) 616 { 617 size_t len; 618 619 if (s == NULL || (len = Strlen(s)) == 0) 620 return -1; 621 if (el->el_line.lastchar + len >= el->el_line.limit) { 622 if (!ch_enlargebufs(el, len)) 623 return -1; 624 } 625 626 c_insert(el, (int)len); 627 while (*s) 628 *el->el_line.cursor++ = *s++; 629 return 0; 630 } 631 632 633 /* el_deletestr(): 634 * Delete num characters before the cursor 635 */ 636 public void 637 el_deletestr(EditLine *el, int n) 638 { 639 if (n <= 0) 640 return; 641 642 if (el->el_line.cursor < &el->el_line.buffer[n]) 643 return; 644 645 c_delbefore(el, n); /* delete before dot */ 646 el->el_line.cursor -= n; 647 if (el->el_line.cursor < el->el_line.buffer) 648 el->el_line.cursor = el->el_line.buffer; 649 } 650 651 /* el_cursor(): 652 * Move the cursor to the left or the right of the current position 653 */ 654 public int 655 el_cursor(EditLine *el, int n) 656 { 657 if (n == 0) 658 goto out; 659 660 el->el_line.cursor += n; 661 662 if (el->el_line.cursor < el->el_line.buffer) 663 el->el_line.cursor = el->el_line.buffer; 664 if (el->el_line.cursor > el->el_line.lastchar) 665 el->el_line.cursor = el->el_line.lastchar; 666 out: 667 return (int)(el->el_line.cursor - el->el_line.buffer); 668 } 669 670 /* c_gets(): 671 * Get a string 672 */ 673 protected int 674 c_gets(EditLine *el, Char *buf, const Char *prompt) 675 { 676 Char ch; 677 ssize_t len; 678 Char *cp = el->el_line.buffer; 679 680 if (prompt) { 681 len = (ssize_t)Strlen(prompt); 682 (void)memcpy(cp, prompt, (size_t)len * sizeof(*cp)); 683 cp += len; 684 } 685 len = 0; 686 687 for (;;) { 688 el->el_line.cursor = cp; 689 *cp = ' '; 690 el->el_line.lastchar = cp + 1; 691 re_refresh(el); 692 693 if (FUN(el,getc)(el, &ch) != 1) { 694 ed_end_of_file(el, 0); 695 len = -1; 696 break; 697 } 698 699 switch (ch) { 700 701 case 0010: /* Delete and backspace */ 702 case 0177: 703 if (len == 0) { 704 len = -1; 705 break; 706 } 707 cp--; 708 continue; 709 710 case 0033: /* ESC */ 711 case '\r': /* Newline */ 712 case '\n': 713 buf[len] = ch; 714 break; 715 716 default: 717 if (len >= (ssize_t)(EL_BUFSIZ - 16)) 718 terminal_beep(el); 719 else { 720 buf[len++] = ch; 721 *cp++ = ch; 722 } 723 continue; 724 } 725 break; 726 } 727 728 el->el_line.buffer[0] = '\0'; 729 el->el_line.lastchar = el->el_line.buffer; 730 el->el_line.cursor = el->el_line.buffer; 731 return (int)len; 732 } 733 734 735 /* c_hpos(): 736 * Return the current horizontal position of the cursor 737 */ 738 protected int 739 c_hpos(EditLine *el) 740 { 741 Char *ptr; 742 743 /* 744 * Find how many characters till the beginning of this line. 745 */ 746 if (el->el_line.cursor == el->el_line.buffer) 747 return 0; 748 else { 749 for (ptr = el->el_line.cursor - 1; 750 ptr >= el->el_line.buffer && *ptr != '\n'; 751 ptr--) 752 continue; 753 return (int)(el->el_line.cursor - ptr - 1); 754 } 755 } 756 757 protected int 758 ch_resizefun(EditLine *el, el_zfunc_t f, void *a) 759 { 760 el->el_chared.c_resizefun = f; 761 el->el_chared.c_resizearg = a; 762 return 0; 763 } 764 765 protected int 766 ch_aliasfun(EditLine *el, el_afunc_t f, void *a) 767 { 768 el->el_chared.c_aliasfun = f; 769 el->el_chared.c_aliasarg = a; 770 return 0; 771 } 772