1 /* SPDX-License-Identifier: GPL-2.0 */ 2 /* 3 * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> 4 */ 5 %{ 6 7 #include <ctype.h> 8 #include <stdarg.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <stdbool.h> 13 14 #include <xalloc.h> 15 #include "lkc.h" 16 #include "internal.h" 17 #include "preprocess.h" 18 19 #define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) 20 21 #define PRINTD 0x0001 22 #define DEBUG_PARSE 0x0002 23 24 int cdebug = PRINTD; 25 26 static void yyerror(const char *err); 27 static void zconfprint(const char *err, ...); 28 static void zconf_error(const char *err, ...); 29 static bool zconf_endtoken(const char *tokenname, 30 const char *expected_tokenname); 31 32 struct menu *current_menu, *current_entry, *current_choice; 33 34 %} 35 36 %union 37 { 38 char *string; 39 struct symbol *symbol; 40 struct expr *expr; 41 struct menu *menu; 42 enum symbol_type type; 43 enum variable_flavor flavor; 44 } 45 46 %token <string> T_HELPTEXT 47 %token <string> T_WORD 48 %token <string> T_WORD_QUOTE 49 %token T_BOOL 50 %token T_CHOICE 51 %token T_CLOSE_PAREN 52 %token T_COLON_EQUAL 53 %token T_COMMENT 54 %token T_CONFIG 55 %token T_DEFAULT 56 %token T_DEF_BOOL 57 %token T_DEF_TRISTATE 58 %token T_DEPENDS 59 %token T_ENDCHOICE 60 %token T_ENDIF 61 %token T_ENDMENU 62 %token T_HELP 63 %token T_HEX 64 %token T_IF 65 %token T_IMPLY 66 %token T_INT 67 %token T_MAINMENU 68 %token T_MENU 69 %token T_MENUCONFIG 70 %token T_MODULES 71 %token T_ON 72 %token T_OPEN_PAREN 73 %token T_PLUS_EQUAL 74 %token T_PROMPT 75 %token T_RANGE 76 %token T_SELECT 77 %token T_SOURCE 78 %token T_STRING 79 %token T_TRISTATE 80 %token T_VISIBLE 81 %token T_EOL 82 %token <string> T_ASSIGN_VAL 83 84 %left T_OR 85 %left T_AND 86 %left T_EQUAL T_UNEQUAL 87 %left T_LESS T_LESS_EQUAL T_GREATER T_GREATER_EQUAL 88 %nonassoc T_NOT 89 90 %type <symbol> nonconst_symbol 91 %type <symbol> symbol 92 %type <type> type default 93 %type <expr> expr 94 %type <expr> if_expr 95 %type <string> end 96 %type <menu> if_entry menu_entry choice_entry 97 %type <string> assign_val 98 %type <flavor> assign_op 99 100 %destructor { 101 fprintf(stderr, "%s:%d: missing end statement for this entry\n", 102 $$->filename, $$->lineno); 103 if (current_menu == $$) 104 menu_end_menu(); 105 } if_entry menu_entry choice_entry 106 107 %% 108 input: mainmenu_stmt stmt_list | stmt_list; 109 110 /* mainmenu entry */ 111 112 mainmenu_stmt: T_MAINMENU T_WORD_QUOTE T_EOL 113 { 114 menu_add_prompt(P_MENU, $2, NULL); 115 }; 116 117 stmt_list: 118 /* empty */ 119 | stmt_list assignment_stmt 120 | stmt_list choice_stmt 121 | stmt_list comment_stmt 122 | stmt_list config_stmt 123 | stmt_list if_stmt 124 | stmt_list menu_stmt 125 | stmt_list menuconfig_stmt 126 | stmt_list source_stmt 127 | stmt_list T_WORD error T_EOL { zconf_error("unknown statement \"%s\"", $2); } 128 | stmt_list error T_EOL { zconf_error("invalid statement"); } 129 ; 130 131 stmt_list_in_choice: 132 /* empty */ 133 | stmt_list_in_choice comment_stmt 134 | stmt_list_in_choice config_stmt 135 | stmt_list_in_choice if_stmt_in_choice 136 | stmt_list_in_choice error T_EOL { zconf_error("invalid statement"); } 137 ; 138 139 /* config/menuconfig entry */ 140 141 config_entry_start: T_CONFIG nonconst_symbol T_EOL 142 { 143 menu_add_entry($2); 144 printd(DEBUG_PARSE, "%s:%d:config %s\n", cur_filename, cur_lineno, $2->name); 145 }; 146 147 config_stmt: config_entry_start config_option_list 148 { 149 if (current_choice) { 150 if (!current_entry->prompt) { 151 fprintf(stderr, "%s:%d: error: choice member must have a prompt\n", 152 current_entry->filename, current_entry->lineno); 153 yynerrs++; 154 } 155 156 if (current_entry->sym->type != S_BOOLEAN) { 157 fprintf(stderr, "%s:%d: error: choice member must be bool\n", 158 current_entry->filename, current_entry->lineno); 159 yynerrs++; 160 } 161 162 list_add_tail(¤t_entry->sym->choice_link, 163 ¤t_choice->choice_members); 164 } 165 166 printd(DEBUG_PARSE, "%s:%d:endconfig\n", cur_filename, cur_lineno); 167 }; 168 169 menuconfig_entry_start: T_MENUCONFIG nonconst_symbol T_EOL 170 { 171 menu_add_entry($2); 172 printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", cur_filename, cur_lineno, $2->name); 173 }; 174 175 menuconfig_stmt: menuconfig_entry_start config_option_list 176 { 177 if (current_entry->prompt) 178 current_entry->prompt->type = P_MENU; 179 else 180 zconfprint("warning: menuconfig statement without prompt"); 181 printd(DEBUG_PARSE, "%s:%d:endconfig\n", cur_filename, cur_lineno); 182 }; 183 184 config_option_list: 185 /* empty */ 186 | config_option_list config_option 187 | config_option_list depends 188 | config_option_list help 189 ; 190 191 config_option: type prompt_stmt_opt T_EOL 192 { 193 menu_set_type($1); 194 printd(DEBUG_PARSE, "%s:%d:type(%u)\n", cur_filename, cur_lineno, $1); 195 }; 196 197 config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL 198 { 199 menu_add_prompt(P_PROMPT, $2, $3); 200 printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno); 201 }; 202 203 config_option: default expr if_expr T_EOL 204 { 205 menu_add_expr(P_DEFAULT, $2, $3); 206 if ($1 != S_UNKNOWN) 207 menu_set_type($1); 208 printd(DEBUG_PARSE, "%s:%d:default(%u)\n", cur_filename, cur_lineno, 209 $1); 210 }; 211 212 config_option: T_SELECT nonconst_symbol if_expr T_EOL 213 { 214 menu_add_symbol(P_SELECT, $2, $3); 215 printd(DEBUG_PARSE, "%s:%d:select\n", cur_filename, cur_lineno); 216 }; 217 218 config_option: T_IMPLY nonconst_symbol if_expr T_EOL 219 { 220 menu_add_symbol(P_IMPLY, $2, $3); 221 printd(DEBUG_PARSE, "%s:%d:imply\n", cur_filename, cur_lineno); 222 }; 223 224 config_option: T_RANGE symbol symbol if_expr T_EOL 225 { 226 menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,$2, $3), $4); 227 printd(DEBUG_PARSE, "%s:%d:range\n", cur_filename, cur_lineno); 228 }; 229 230 config_option: T_MODULES T_EOL 231 { 232 if (modules_sym) 233 zconf_error("symbol '%s' redefines option 'modules' already defined by symbol '%s'", 234 current_entry->sym->name, modules_sym->name); 235 modules_sym = current_entry->sym; 236 }; 237 238 /* choice entry */ 239 240 choice: T_CHOICE T_EOL 241 { 242 struct symbol *sym = sym_lookup(NULL, 0); 243 244 menu_add_entry(sym); 245 menu_set_type(S_BOOLEAN); 246 INIT_LIST_HEAD(¤t_entry->choice_members); 247 248 printd(DEBUG_PARSE, "%s:%d:choice\n", cur_filename, cur_lineno); 249 }; 250 251 choice_entry: choice choice_option_list 252 { 253 if (!current_entry->prompt) { 254 fprintf(stderr, "%s:%d: error: choice must have a prompt\n", 255 current_entry->filename, current_entry->lineno); 256 yynerrs++; 257 } 258 259 $$ = menu_add_menu(); 260 261 current_choice = current_entry; 262 }; 263 264 choice_end: end 265 { 266 current_choice = NULL; 267 268 if (zconf_endtoken($1, "choice")) { 269 menu_end_menu(); 270 printd(DEBUG_PARSE, "%s:%d:endchoice\n", cur_filename, cur_lineno); 271 } 272 }; 273 274 choice_stmt: choice_entry stmt_list_in_choice choice_end 275 ; 276 277 choice_option_list: 278 /* empty */ 279 | choice_option_list choice_option 280 | choice_option_list depends 281 | choice_option_list help 282 ; 283 284 choice_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL 285 { 286 menu_add_prompt(P_PROMPT, $2, $3); 287 printd(DEBUG_PARSE, "%s:%d:prompt\n", cur_filename, cur_lineno); 288 }; 289 290 choice_option: T_BOOL T_WORD_QUOTE if_expr T_EOL 291 { 292 menu_add_prompt(P_PROMPT, $2, $3); 293 printd(DEBUG_PARSE, "%s:%d:bool\n", cur_filename, cur_lineno); 294 }; 295 296 choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL 297 { 298 menu_add_symbol(P_DEFAULT, $2, $3); 299 printd(DEBUG_PARSE, "%s:%d:default\n", cur_filename, cur_lineno); 300 }; 301 302 type: 303 T_BOOL { $$ = S_BOOLEAN; } 304 | T_TRISTATE { $$ = S_TRISTATE; } 305 | T_INT { $$ = S_INT; } 306 | T_HEX { $$ = S_HEX; } 307 | T_STRING { $$ = S_STRING; } 308 309 default: 310 T_DEFAULT { $$ = S_UNKNOWN; } 311 | T_DEF_BOOL { $$ = S_BOOLEAN; } 312 | T_DEF_TRISTATE { $$ = S_TRISTATE; } 313 314 /* if entry */ 315 316 if_entry: T_IF expr T_EOL 317 { 318 printd(DEBUG_PARSE, "%s:%d:if\n", cur_filename, cur_lineno); 319 menu_add_entry(NULL); 320 menu_add_dep($2); 321 $$ = menu_add_menu(); 322 }; 323 324 if_end: end 325 { 326 if (zconf_endtoken($1, "if")) { 327 menu_end_menu(); 328 printd(DEBUG_PARSE, "%s:%d:endif\n", cur_filename, cur_lineno); 329 } 330 }; 331 332 if_stmt: if_entry stmt_list if_end 333 ; 334 335 if_stmt_in_choice: if_entry stmt_list_in_choice if_end 336 ; 337 338 /* menu entry */ 339 340 menu: T_MENU T_WORD_QUOTE T_EOL 341 { 342 menu_add_entry(NULL); 343 menu_add_prompt(P_MENU, $2, NULL); 344 printd(DEBUG_PARSE, "%s:%d:menu\n", cur_filename, cur_lineno); 345 }; 346 347 menu_entry: menu menu_option_list 348 { 349 $$ = menu_add_menu(); 350 }; 351 352 menu_end: end 353 { 354 if (zconf_endtoken($1, "menu")) { 355 menu_end_menu(); 356 printd(DEBUG_PARSE, "%s:%d:endmenu\n", cur_filename, cur_lineno); 357 } 358 }; 359 360 menu_stmt: menu_entry stmt_list menu_end 361 ; 362 363 menu_option_list: 364 /* empty */ 365 | menu_option_list visible 366 | menu_option_list depends 367 ; 368 369 source_stmt: T_SOURCE T_WORD_QUOTE T_EOL 370 { 371 printd(DEBUG_PARSE, "%s:%d:source %s\n", cur_filename, cur_lineno, $2); 372 zconf_nextfile($2); 373 free($2); 374 }; 375 376 /* comment entry */ 377 378 comment: T_COMMENT T_WORD_QUOTE T_EOL 379 { 380 menu_add_entry(NULL); 381 menu_add_prompt(P_COMMENT, $2, NULL); 382 printd(DEBUG_PARSE, "%s:%d:comment\n", cur_filename, cur_lineno); 383 }; 384 385 comment_stmt: comment comment_option_list 386 ; 387 388 comment_option_list: 389 /* empty */ 390 | comment_option_list depends 391 ; 392 393 /* help option */ 394 395 help_start: T_HELP T_EOL 396 { 397 printd(DEBUG_PARSE, "%s:%d:help\n", cur_filename, cur_lineno); 398 zconf_starthelp(); 399 }; 400 401 help: help_start T_HELPTEXT 402 { 403 if (current_entry->help) { 404 free(current_entry->help); 405 zconfprint("warning: '%s' defined with more than one help text -- only the last one will be used", 406 current_entry->sym->name ?: "<choice>"); 407 } 408 409 /* Is the help text empty or all whitespace? */ 410 if ($2[strspn($2, " \f\n\r\t\v")] == '\0') 411 zconfprint("warning: '%s' defined with blank help text", 412 current_entry->sym->name ?: "<choice>"); 413 414 current_entry->help = $2; 415 }; 416 417 /* depends option */ 418 419 depends: T_DEPENDS T_ON expr T_EOL 420 { 421 menu_add_dep($3); 422 printd(DEBUG_PARSE, "%s:%d:depends on\n", cur_filename, cur_lineno); 423 }; 424 425 /* visibility option */ 426 visible: T_VISIBLE if_expr T_EOL 427 { 428 menu_add_visibility($2); 429 }; 430 431 /* prompt statement */ 432 433 prompt_stmt_opt: 434 /* empty */ 435 | T_WORD_QUOTE if_expr 436 { 437 menu_add_prompt(P_PROMPT, $1, $2); 438 }; 439 440 end: T_ENDMENU T_EOL { $$ = "menu"; } 441 | T_ENDCHOICE T_EOL { $$ = "choice"; } 442 | T_ENDIF T_EOL { $$ = "if"; } 443 ; 444 445 if_expr: /* empty */ { $$ = NULL; } 446 | T_IF expr { $$ = $2; } 447 ; 448 449 expr: symbol { $$ = expr_alloc_symbol($1); } 450 | symbol T_LESS symbol { $$ = expr_alloc_comp(E_LTH, $1, $3); } 451 | symbol T_LESS_EQUAL symbol { $$ = expr_alloc_comp(E_LEQ, $1, $3); } 452 | symbol T_GREATER symbol { $$ = expr_alloc_comp(E_GTH, $1, $3); } 453 | symbol T_GREATER_EQUAL symbol { $$ = expr_alloc_comp(E_GEQ, $1, $3); } 454 | symbol T_EQUAL symbol { $$ = expr_alloc_comp(E_EQUAL, $1, $3); } 455 | symbol T_UNEQUAL symbol { $$ = expr_alloc_comp(E_UNEQUAL, $1, $3); } 456 | T_OPEN_PAREN expr T_CLOSE_PAREN { $$ = $2; } 457 | T_NOT expr { $$ = expr_alloc_one(E_NOT, $2); } 458 | expr T_OR expr { $$ = expr_alloc_two(E_OR, $1, $3); } 459 | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); } 460 ; 461 462 /* For symbol definitions, selects, etc., where quotes are not accepted */ 463 nonconst_symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); }; 464 465 symbol: nonconst_symbol 466 | T_WORD_QUOTE { $$ = sym_lookup($1, SYMBOL_CONST); free($1); } 467 ; 468 469 /* assignment statement */ 470 471 assignment_stmt: T_WORD assign_op assign_val T_EOL { variable_add($1, $3, $2); free($1); free($3); } 472 473 assign_op: 474 T_EQUAL { $$ = VAR_RECURSIVE; } 475 | T_COLON_EQUAL { $$ = VAR_SIMPLE; } 476 | T_PLUS_EQUAL { $$ = VAR_APPEND; } 477 ; 478 479 assign_val: 480 /* empty */ { $$ = xstrdup(""); }; 481 | T_ASSIGN_VAL 482 ; 483 484 %% 485 486 /** 487 * choice_check_sanity - check sanity of a choice member 488 * 489 * @menu: menu of the choice member 490 * 491 * Return: -1 if an error is found, 0 otherwise. 492 */ 493 static int choice_check_sanity(const struct menu *menu) 494 { 495 struct property *prop; 496 int ret = 0; 497 498 for (prop = menu->sym->prop; prop; prop = prop->next) { 499 if (prop->type == P_DEFAULT) { 500 fprintf(stderr, "%s:%d: error: %s", 501 prop->filename, prop->lineno, 502 "defaults for choice values not supported\n"); 503 ret = -1; 504 } 505 506 if (prop->menu != menu && prop->type == P_PROMPT && 507 prop->menu->parent != menu->parent) { 508 fprintf(stderr, "%s:%d: error: %s", 509 prop->filename, prop->lineno, 510 "choice value has a prompt outside its choice group\n"); 511 ret = -1; 512 } 513 } 514 515 return ret; 516 } 517 518 void conf_parse(const char *name) 519 { 520 struct menu *menu; 521 522 autoconf_cmd = str_new(); 523 524 str_printf(&autoconf_cmd, "\ndeps_config := \\\n"); 525 526 zconf_initscan(name); 527 528 _menu_init(); 529 530 if (getenv("ZCONF_DEBUG")) 531 yydebug = 1; 532 yyparse(); 533 534 str_printf(&autoconf_cmd, 535 "\n" 536 "$(autoconfig): $(deps_config)\n" 537 "$(deps_config): ;\n"); 538 539 env_write_dep(&autoconf_cmd); 540 541 /* Variables are expanded in the parse phase. We can free them here. */ 542 variable_all_del(); 543 544 if (yynerrs) 545 exit(1); 546 if (!modules_sym) 547 modules_sym = &symbol_no; 548 549 if (!menu_has_prompt(&rootmenu)) { 550 current_entry = &rootmenu; 551 menu_add_prompt(P_MENU, "Main menu", NULL); 552 } 553 554 menu_finalize(); 555 556 menu_for_each_entry(menu) { 557 struct menu *child; 558 559 if (menu->sym && sym_check_deps(menu->sym)) 560 yynerrs++; 561 562 if (menu->sym && sym_is_choice(menu->sym)) { 563 menu_for_each_sub_entry(child, menu) 564 if (child->sym && choice_check_sanity(child)) 565 yynerrs++; 566 } 567 } 568 569 if (yynerrs) 570 exit(1); 571 conf_set_changed(true); 572 } 573 574 static bool zconf_endtoken(const char *tokenname, 575 const char *expected_tokenname) 576 { 577 if (strcmp(tokenname, expected_tokenname)) { 578 zconf_error("unexpected '%s' within %s block", 579 tokenname, expected_tokenname); 580 yynerrs++; 581 return false; 582 } 583 if (strcmp(current_menu->filename, cur_filename)) { 584 zconf_error("'%s' in different file than '%s'", 585 tokenname, expected_tokenname); 586 fprintf(stderr, "%s:%d: location of the '%s'\n", 587 current_menu->filename, current_menu->lineno, 588 expected_tokenname); 589 yynerrs++; 590 return false; 591 } 592 return true; 593 } 594 595 static void zconfprint(const char *err, ...) 596 { 597 va_list ap; 598 599 fprintf(stderr, "%s:%d: ", cur_filename, cur_lineno); 600 va_start(ap, err); 601 vfprintf(stderr, err, ap); 602 va_end(ap); 603 fprintf(stderr, "\n"); 604 } 605 606 static void zconf_error(const char *err, ...) 607 { 608 va_list ap; 609 610 yynerrs++; 611 fprintf(stderr, "%s:%d: ", cur_filename, cur_lineno); 612 va_start(ap, err); 613 vfprintf(stderr, err, ap); 614 va_end(ap); 615 fprintf(stderr, "\n"); 616 } 617 618 static void yyerror(const char *err) 619 { 620 fprintf(stderr, "%s:%d: %s\n", cur_filename, cur_lineno, err); 621 } 622 623 static void print_quoted_string(FILE *out, const char *str) 624 { 625 const char *p; 626 int len; 627 628 putc('"', out); 629 while ((p = strchr(str, '"'))) { 630 len = p - str; 631 if (len) 632 fprintf(out, "%.*s", len, str); 633 fputs("\\\"", out); 634 str = p + 1; 635 } 636 fputs(str, out); 637 putc('"', out); 638 } 639 640 static void print_symbol(FILE *out, const struct menu *menu) 641 { 642 struct symbol *sym = menu->sym; 643 struct property *prop; 644 645 if (sym_is_choice(sym)) 646 fprintf(out, "\nchoice\n"); 647 else 648 fprintf(out, "\nconfig %s\n", sym->name); 649 switch (sym->type) { 650 case S_BOOLEAN: 651 fputs(" bool\n", out); 652 break; 653 case S_TRISTATE: 654 fputs(" tristate\n", out); 655 break; 656 case S_STRING: 657 fputs(" string\n", out); 658 break; 659 case S_INT: 660 fputs(" integer\n", out); 661 break; 662 case S_HEX: 663 fputs(" hex\n", out); 664 break; 665 default: 666 fputs(" ???\n", out); 667 break; 668 } 669 for (prop = sym->prop; prop; prop = prop->next) { 670 if (prop->menu != menu) 671 continue; 672 switch (prop->type) { 673 case P_PROMPT: 674 fputs(" prompt ", out); 675 print_quoted_string(out, prop->text); 676 if (!expr_is_yes(prop->visible.expr)) { 677 fputs(" if ", out); 678 expr_fprint(prop->visible.expr, out); 679 } 680 fputc('\n', out); 681 break; 682 case P_DEFAULT: 683 fputs( " default ", out); 684 expr_fprint(prop->expr, out); 685 if (!expr_is_yes(prop->visible.expr)) { 686 fputs(" if ", out); 687 expr_fprint(prop->visible.expr, out); 688 } 689 fputc('\n', out); 690 break; 691 case P_SELECT: 692 fputs( " select ", out); 693 expr_fprint(prop->expr, out); 694 fputc('\n', out); 695 break; 696 case P_IMPLY: 697 fputs( " imply ", out); 698 expr_fprint(prop->expr, out); 699 fputc('\n', out); 700 break; 701 case P_RANGE: 702 fputs( " range ", out); 703 expr_fprint(prop->expr, out); 704 fputc('\n', out); 705 break; 706 case P_MENU: 707 fputs( " menu ", out); 708 print_quoted_string(out, prop->text); 709 fputc('\n', out); 710 break; 711 default: 712 fprintf(out, " unknown prop %d!\n", prop->type); 713 break; 714 } 715 } 716 if (menu->help) { 717 int len = strlen(menu->help); 718 while (menu->help[--len] == '\n') 719 menu->help[len] = 0; 720 fprintf(out, " help\n%s\n", menu->help); 721 } 722 } 723 724 void zconfdump(FILE *out) 725 { 726 struct property *prop; 727 struct symbol *sym; 728 struct menu *menu; 729 730 menu = rootmenu.list; 731 while (menu) { 732 if ((sym = menu->sym)) 733 print_symbol(out, menu); 734 else if ((prop = menu->prompt)) { 735 switch (prop->type) { 736 case P_COMMENT: 737 fputs("\ncomment ", out); 738 print_quoted_string(out, prop->text); 739 fputs("\n", out); 740 break; 741 case P_MENU: 742 fputs("\nmenu ", out); 743 print_quoted_string(out, prop->text); 744 fputs("\n", out); 745 break; 746 default: 747 ; 748 } 749 if (!expr_is_yes(prop->visible.expr)) { 750 fputs(" depends ", out); 751 expr_fprint(prop->visible.expr, out); 752 fputc('\n', out); 753 } 754 } 755 756 if (menu->list) 757 menu = menu->list; 758 else if (menu->next) 759 menu = menu->next; 760 else while ((menu = menu->parent)) { 761 if (menu->prompt && menu->prompt->type == P_MENU) 762 fputs("\nendmenu\n", out); 763 if (menu->next) { 764 menu = menu->next; 765 break; 766 } 767 } 768 } 769 } 770