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