1 /******************************************************************************
2
3 #### # # ###### # # ####
4 # # # # # # # #
5 #### ###### ##### # # #
6 # # # # # # ### #
7 # # # # # # # ### # #
8 #### # # ###### ###### ###### ### ####
9
10 ******************************************************************************/
11 /* This file is part of MAPMAKER 3.0b, Copyright 1987-1992, Whitehead Institute
12 for Biomedical Research. All rights reserved. See READ.ME for license. */
13
14 #define INC_LIB
15 #define INC_MISC
16 #define INC_HELP_DEFS
17 #include "system.h"
18 #include "shell.h"
19 #include "table.h"
20
21 char *com, *args; /* available as globals to the command procedures */
22 char *uncrunched_args;
23 int com_num, num_args;
24 char *((*prompt)());
25 char *default_prompt();
26 bool (*quit_save_hook)();
27 bool more_mode;
28
29 /***** defs for internal use only *****/
30 #define UNHELPFUL (-1) /* as a help_entry */
31 #define HELPLESS ((long) -1) /* as a help_key */
32 #define MAX_SUCCESSIVE_FAILURES 15
33
34 #define MAX_NUM_MENUS 20
35 #define MAX_MENU_ENTRIES 20
36 #define MENU_ENTRY_LEN 20
37
38 COMMAND **cmd; /* [command#] => ptr to a COMMAND struct */
39 MENU **menu; /* [menu#] => ptr to a menu struct */
40 int num_menus;
41 int command_num; /* num commands in cmd */
42 int help_entries; /* number of entries in help_file */
43 char **topic_name; /* [topic_num] set by mktopic() */
44 int *topic_code; /* [topic_num] set by mktopic() */
45 long *topic_help_key; /* [topic_num] set by mktopic() */
46 char **tokens; /* [token_num] used as a temp by the parser */
47 char **remaining; /* ditto */
48 bool *matched; /* [command_num] parser temp */
49 int num_matched;
50 bool wizard_mode;
51 char *save_args_ptr; /* the arg list, saved so the ptr can be bashed */
52 char *save_uncrunched_args;
53 TABLE *cmd_history;
54 int autosave; /* saves state on exit if TRUE */
55 FILE *help_file; /* file that contains all the help summaries */
56 bool user_is_amused; /* two state vars for keep_user_amused */
57
58 bool shell_uses_wimp_cmds, help_uses_wimp_help;
59 bool inhibit_menus;
60 /* If shell_uses_wimp_cmds==TRUE, a command entered to the prompt will
61 invoke its WIMP version, if one is available, otherwise its normal
62 text version will be run. help_uses_wimp_help is similar. The WIMP
63 code should only try to run a command when inhibit_menus==FALSE. */
64
65 int cmd_history_num;
66 bool photo_update_top_hook;
67 void (*photo_banner_hook)();
68 char *the_program, *the_version, *the_copyright;
69
70 int alias_match();
71 int abbrev_match();
72 int com_matches();
73 int abbrev_matches();
74 int try_to_match();
75
76 void expand_history_references();
77 char *centering();
78
79 #define NO_HELP_FILE "\
80 Can't find help file - detailed help information is not available.\n\
81 See installation instructions for details.\n"
82
83 #define NO_HELP_KEY "No additional information available.\n"
84
85 #define SURROGATE_ABOUT "\
86 This program is freely redistributable under certain conditions and is\n\
87 licensed free of charge for non-commercial applications. No warranty of any\n\
88 type is provided. See the License Agreement for details.\n"
89
90 #define TYPE_HELP_PLEASE "\
91 Type 'help' for help.\n\
92 Type 'about' for license, non-warranty, and support information.\n"
93
banner()94 void banner()
95 {
96 char line[81];
97
98 print(
99 "************************************************************************\n");
100 print(
101 "* Welcome to: *\n");
102
103 print(centering("",FALSE));
104 print(centering(the_program,FALSE));
105 if (!nullstr(the_version))
106 { sf(line,"(version %s)",the_version); print(centering(line,FALSE)); }
107 print(centering("",FALSE));
108 sf(line,"Copyright %s, Whitehead Institute for Biomedical Research",
109 the_copyright); print(centering(line,FALSE));
110 if (gnu_copyright(line)) print(centering(line,TRUE));
111
112 print(
113 "************************************************************************\n");
114
115 nl();
116 print(TYPE_HELP_PLEASE);
117 if (help_file==NULL) print(NO_HELP_FILE);
118 }
119
120
get_version(version_filename)121 char *get_version(version_filename)
122 char *version_filename;
123 {
124 FILE *fp;
125 char str[TOKLEN+1], *p, *version, name[PATH_LENGTH+1];
126
127 if (nullstr(version_filename)) return(NULL);
128 fp=NULL; version=NULL;
129
130 run {
131 strcpy(name,version_filename);
132 if (!make_filename_in_dir(name,FORCE_EXTENSION,WRS(".v"),
133 FORCE_DIR,CODE_DIR)) send(CANTOPEN);
134 fp=open_file(name,READ);
135 finput(fp,ln_,MAXLINE); p=ln_; /* don't hack &ln_ */
136 close_file(fp);
137 if (stoken(&p,sREQUIRED,str)) version=mkstrcpy(str);
138 } when_aborting {
139 close_file(fp); /* Don't bother relaying messages */
140 }
141 return(version);
142 }
143
144
photo_banner()145 void photo_banner()
146 {
147 char ver[81];
148 /* Thu Apr 13 05:31:11 EDT 1989
149 123456789012345678901234567890123456789012345678901234567890123456789012 */
150 lib_puts(photo,
151 "************************************************************************\n");
152 sf(ps,"* Output from: %24s *\n",time_string());
153 lib_puts(photo,ps);
154
155 lib_puts(photo,centering("",FALSE));
156 lib_puts(photo,centering(the_program,FALSE));
157 sf(ver,"(version %s)",the_version); lib_puts(photo,centering(ver,FALSE));
158 lib_puts(photo,centering("",FALSE));
159 lib_puts(photo,
160 "************************************************************************\n");
161 lib_puts(photo,"\n");
162 if (photo_banner_hook!=NULL) (*photo_banner_hook)(photo);
163 }
164
165
166 int prev_spaces=2;
167
centering(str,lineup)168 char *centering(str,lineup) /* into global ps */
169 char *str;
170 bool lineup;
171 {
172 int spaces, i;
173 if (!lineup) spaces= 36-(len(str)/2); else spaces=prev_spaces;
174 prev_spaces= spaces;
175 strcpy(ps,
176 "* *\n");
177 for (i=0; str[i]!='\0'; i++) ps[spaces+i]=str[i];
178 return(ps);
179 }
180
181
shell_init(program,version,copyright,help_filename)182 void shell_init(program,version,copyright,help_filename)
183 char *program, *version, *copyright, *help_filename;
184 /* help_filename must be side-effectable */
185 {
186 int i;
187 char *full_name[PATH_LENGTH+1];
188
189 array(cmd, MAX_COMMANDS, COMMAND*);
190 array(matched, MAX_COMMANDS, bool);
191 array(topic_name, MAX_COM_TOPICS+1, char*);
192 array(topic_code, MAX_COM_TOPICS+1, int);
193 array(topic_help_key, MAX_COM_TOPICS+1, long);
194 matrix(tokens, MAX_COM_TOKENS, TOKLEN+1, char);
195 array(remaining, MAX_COM_TOKENS+1, char*);
196 array(args, ARGS_LEN+1, char);
197 array(uncrunched_args, ARGS_LEN+1, char);
198 save_args_ptr= args; save_uncrunched_args= uncrunched_args;
199
200 the_program= mkstrcpy(program);
201 the_version= mkstrcpy(version);
202 the_copyright= mkstrcpy(copyright);
203
204 for (i=1; i<MAX_COM_TOPICS+1; i++)
205 { topic_name[i]=NULL; topic_code[i]=0; topic_help_key[i]=HELPLESS; }
206 topic_name[0]= mkstrcpy("TOPIC ZERO");
207 topic_code[0]=0; topic_help_key[0]=HELPLESS;
208
209 cmd_history= allocate_table(21,250,CANT_EXPAND,INDEX_BY_NUMBER);
210 cmd_history_num= next_entry_number(cmd_history);
211
212 command_num=0;
213 help_entries=0;
214 wizard_mode=FALSE;
215 photo_update_top_hook=TRUE;
216 photo_banner_hook=NULL;
217 prompt=default_prompt;
218 inhibit_menus=TRUE;
219 quit_save_hook=NULL;
220 user_is_amused=FALSE;
221
222 parray(menu, MAX_NUM_MENUS, MENU);
223 for (i=0; i<MAX_NUM_MENUS; i++)
224 array(menu[i]->title,MENU_ENTRY_LEN+1,char);
225 shell_uses_wimp_cmds= FALSE;
226 help_uses_wimp_help= FALSE;
227
228 help_file=NULL;
229 if (make_filename_in_dir(help_filename,FORCE_EXTENSION,WRS(HELP_EXT),
230 DEFAULT_DIR,CODE_DIR)) {
231 run help_file=open_file(help_filename,READ);
232 except_when(CANTOPEN) {}
233 }
234 }
235
236
default_prompt(s)237 char *default_prompt(s)
238 char *s;
239 { sf(s,"\n%d> ",cmd_history_num+1); return(s); }
240
241
mktopic(num,nam,code,description_index)242 void mktopic(num,nam,code,description_index)
243 int num;
244 char *nam;
245 int code;
246 long description_index;
247 {
248 if (num<=0 || num>MAX_COM_TOPICS || len(nam)>MAX_TOPIC_LEN) send(CRASH);
249 topic_name[num]=mkstrcpy(nam); topic_code[num]=code;
250 topic_help_key[num]= description_index;
251 }
252
253
mkcommand(name,abbrev,func,code)254 int mkcommand(name,abbrev,func,code)
255 char *name, *abbrev;
256 void (*func)();
257 int code;
258 {
259 char *str, **toks, c;
260 int i, n_tokens;
261
262 single(cmd[command_num], COMMAND);
263 str= mkstrcpy(name); crunch(str); truncstr(str,MAX_COM_NAME_LEN);
264 cmd[command_num]->name= mkstrcpy(str); /* str itself => toks, below */
265
266 array(toks,MAX_COM_TOKENS,char*);
267 for (i=0; i<MAX_COM_TOKENS; i++) toks[i]=NULL;
268 toks[0]= str;
269
270 for (i=0, n_tokens=1; str[i]!='\0'; i++)
271 if ((c=str[i])==' ' || c=='-' || c=='_') {
272 str[i]= '\0';
273 if (n_tokens<MAX_COM_TOKENS) toks[n_tokens++]= &str[i+1];
274 else { cmd[command_num]->name[i]= '\0'; break; }
275 }
276
277 cmd[command_num]->tokens= toks;
278 cmd[command_num]->num_tokens= n_tokens;
279 cmd[command_num]->procedure= func;
280 cmd[command_num]->code= code;
281
282 nstrcpy(cmd[command_num]->abbreviation,abbrev,MAX_ABBREV_LEN);
283 crunch(cmd[command_num]->abbreviation);
284
285 cmd[command_num]->topic= 0;
286 cmd[command_num]->help_key= HELPLESS;
287 cmd[command_num]->help_entry= UNHELPFUL;
288 cmd[command_num]->cmd_help= NULL;
289 cmd[command_num]->args_help=NULL;
290 cmd[command_num]->num_args= -1;
291 cmd[command_num]->num_args_prefix= 0;
292
293 cmd[command_num]->wimp_procedure= NULL;
294 cmd[command_num]->status_function= NULL;
295 cmd[command_num]->wimp_help= NULL;
296 cmd[command_num]->wimp_menu_num= -1;
297 cmd[command_num]->menu_entry= NULL;
298 cmd[command_num]->wimp_shortcut= '\0';
299
300 return(command_num++);
301 }
302
303
mkhelp(cmd_name,abbrev,description_index,num_args_prefix,num_args,code,topic,description,arguments,defaults)304 void mkhelp(cmd_name,abbrev,description_index,num_args_prefix,num_args,
305 code,topic,description,arguments,defaults)
306 char *cmd_name, *abbrev;
307 long description_index;
308 int num_args_prefix, num_args;
309 int topic, code;
310 char *description, *arguments, *defaults;
311 {
312 int i, old_wiz;
313 char *rest;
314
315 old_wiz=wizard_mode; wizard_mode=TRUE;
316 i=parser(cmd_name,&rest,TRUE);
317 wizard_mode= old_wiz;
318
319 if (i<0 || !nullstr(rest)) {
320 if (code!=HLP) {
321 sf(ps,"warning: attempt to make help for non-command '%s'\n",
322 cmd_name); pr();
323 return;
324 } else i=mkcommand(cmd_name,abbrev,NULL,HLP);
325 }
326
327 if (!streq(cmd[i]->name,cmd_name)) {
328 sf(ps,"warning: names disagree for command '%s'\n",cmd_name);
329 pr();
330 }
331 if (code!=cmd[i]->code) {
332 sf(ps,"warning: type codes disagree for command '%s'\n",cmd_name);
333 pr();
334 }
335 if (!streq(cmd[i]->abbreviation,abbrev)) {
336 sf(ps,"warning: abbreviations disagree for command '%s'\n",cmd_name);
337 pr();
338 }
339
340 array(cmd[i]->cmd_help, MAX_COM_HELP_LEN+1,char);
341 array(cmd[i]->def_help, MAX_ARG_HELP_LEN+1,char);
342 array(cmd[i]->args_help,MAX_ARG_HELP_LEN+1,char);
343
344 nstrcpy(cmd[i]->cmd_help,description,MAX_COM_HELP_LEN);
345 nstrcpy(cmd[i]->args_help,arguments,MAX_ARG_HELP_LEN);
346 nstrcpy(cmd[i]->def_help,defaults,MAX_ARG_HELP_LEN);
347
348 cmd[i]->code= code;
349 cmd[i]->topic= topic;
350 cmd[i]->help_key= description_index;
351 cmd[i]->help_entry= help_entries;
352 cmd[i]->num_args= num_args;
353 cmd[i]->num_args_prefix= num_args_prefix;
354
355 ++help_entries;
356 }
357
358
valid_name(str)359 bool valid_name(str) /* checks the syntax of names */
360 char *str;
361 {
362 int i; char *token;
363
364 if (!is_a_token(str)) return(FALSE);
365 if (strin(NAME_TAG_CHARS,str[0])) str++;
366 if (!strin(NAME_FIRST_CHARS,str[0])) return(FALSE);
367 for(i=1; str[i]!='\0'; i++) if (!strin(NAME_CHARS,str[i])) return(FALSE);
368 return(TRUE);
369 }
370
371
null_command()372 void null_command() { send(CRASH); }
373
374
375
376 /**************************** The Command Parser ****************************/
377
parser(line,rest,help_ok)378 int parser(line,rest,help_ok)
379 char *line;
380 char **rest; /* side-effected */
381 bool help_ok;
382 {
383 int i, j, n_tokens, last_match;
384 char *foo;
385 extern char **tokens, **remaining; /* temps */
386 extern int *matched, num_matched;
387
388 for (i=0; i<command_num; i++) matched[i]= FALSE;
389 num_matched= 0;
390 if (rest==(char**)NULL) rest= &foo;
391
392 remaining[0]= line;
393 for (n_tokens=0; n_tokens<MAX_COM_TOKENS; n_tokens++)
394 if (stoken(&line,sREQUIRED,tokens[n_tokens])) {
395 remaining[n_tokens+1]= line;
396 if (remaining[n_tokens+1][0]==' ')
397 remaining[n_tokens+1]+=1;
398 } else break;
399
400 for (i=0; i<command_num; i++) /* try to exactly match an abbreviation */
401 if (allowed_cmd(i,help_ok) && xstreq(tokens[0],cmd[i]->abbreviation))
402 { matched[i]=TRUE; num_matched=1; *rest=remaining[1]; return(i); }
403
404 try_to_match(tokens,n_tokens,3,3,matched,&num_matched,&last_match,help_ok);
405 if (num_matched==1) { *rest=remaining[3]; return(last_match); }
406
407 try_to_match(tokens,n_tokens,2,2,matched,&num_matched,&last_match,help_ok);
408 if (num_matched==1) { *rest=remaining[2]; return(last_match); }
409 try_to_match(tokens,n_tokens,2,3,matched,&num_matched,&last_match,help_ok);
410 if (num_matched==1) { *rest=remaining[2]; return(last_match); }
411
412 try_to_match(tokens,n_tokens,1,1,matched,&num_matched,&last_match,help_ok);
413 if (num_matched==1) { *rest=remaining[1]; return(last_match); }
414 try_to_match(tokens,n_tokens,1,2,matched,&num_matched,&last_match,help_ok);
415 if (num_matched==1) { *rest=remaining[1]; return(last_match); }
416 try_to_match(tokens,n_tokens,1,3,matched,&num_matched,&last_match,help_ok);
417 if (num_matched==1) { *rest=remaining[1]; return(last_match); }
418
419 *rest= remaining[0];
420 return(-1);
421 }
422
423
print_parser_results(rest,help_ok)424 void print_parser_results(rest,help_ok) /* uses the global state */
425 char *rest;
426 bool help_ok;
427 {
428 int j;
429
430 if (num_matched==1) {
431 for (j=0; j<command_num; j++)
432 if (matched[j]) sf(ps,"matched command '%s'\n",cmd[j]->name);
433 pr();
434
435 } else if (num_matched>1) {
436 sf(ps,"ambiguous command%s: could be any of the following:\n",
437 (help_ok ? " or help topic":"")); pr();
438 for (j=0; j<command_num; j++)
439 if (matched[j]) { print("\t"); print(cmd[j]->name); nl();
440 }
441
442 } else { /* num_matched==0 */
443 sf(ps,"unrecognized command%s: '",(help_ok ? " or help topic":""));
444 pr();
445 if (len(rest)>LINE-25) strcpy(&rest[LINE-28],"...");
446 print(rest); print("'\n");
447 }
448 }
449
450
451 /* Try to match n_to_try the input tokens against all n_word_commands, and
452 set com_match, n_matched and last_match accordingly. Return TRUE only if a
453 unique match was found. */
454
try_to_match(token,n_tokens,n_to_try,n_word_command,com_match,n_matched,last_match,allow_help_only_stuff)455 int try_to_match(token,n_tokens,n_to_try,n_word_command,
456 com_match,n_matched,last_match,allow_help_only_stuff)
457 char **token;
458 int n_tokens, n_to_try, n_word_command;
459 int *com_match, *n_matched, *last_match, allow_help_only_stuff;
460 {
461 int exact, i;
462
463 if (n_tokens<n_to_try) return(FALSE);
464 for (i=0; i<command_num; i++)
465 if (allowed_cmd(i,allow_help_only_stuff) &&
466 cmd[i]->num_tokens==n_word_command &&
467 com_matches(token,cmd[i]->tokens,n_to_try,&exact)) {
468 *last_match= i;
469 if (!com_match[i])
470 { com_match[i]= TRUE; ++*n_matched; }
471 if (exact && n_to_try==n_word_command) return(TRUE);
472 }
473 if (*n_matched==1) return(TRUE); else return(FALSE);
474 }
475
476
com_matches(in_tokens,com_tokens,num_to_match,exact)477 int com_matches(in_tokens,com_tokens,num_to_match,exact)
478 char **in_tokens, **com_tokens;
479 int num_to_match, *exact;
480 {
481 int i,j;
482
483 for (i=0, *exact=TRUE; i<num_to_match; i++)
484 if (!matches(in_tokens[i],com_tokens[i])) return(FALSE);
485 else if (istrlen(in_tokens[i])!=istrlen(com_tokens[i])) *exact=FALSE;
486
487 return(TRUE);
488 }
489
490
491 /********************************* The Shell *********************************/
492
493
494 #define HIST_RANGE "command history number is out of range"
495
expand_history_references(line)496 void expand_history_references(line) /* sends an error if need be */
497 char *line; /* line IS side-effected */
498 {
499 char *str, *save;
500 int num, first, foo, i;
501
502 /* The cmd_history num will NOT have been incremented between the call
503 to prompt and here! Also remember that 1+ that number is printed. */
504
505 save=line;
506 if (!itoken(&line,iREQUIRED,&num)) return;
507 for (i=0; save+i!=line; i++) save[i]=' ';
508
509 if (get_numbered_entry(num-1,&str,cmd_history)) {
510 /* maxstrins(line," ",MAXLINE); */
511 maxstrins(line,str,MAXLINE);
512 despace(line);
513 print("="); if (cmd_history_num>9) print("=");
514 if (cmd_history_num>99) print("="); print("> ");
515 print(line); nl();
516 expand_history_references(line);
517
518 } else {
519 /* THIS IS A KLUDGE, and results in a 'statement not reached' error. */
520 for(Te=cmd_history->list; Te!=NULL; Te=Te->next)
521 { first=Te->id.num; break; } /*NOTREACHED*/
522 if (cmd_history_num>1)
523 sf(ps,"%s\nUse a number from %d to %d.",HIST_RANGE,
524 first+1,cmd_history_num);
525 else if (cmd_history_num==1)
526 sf(ps,"%s\nThe only valid command number yet is 1.",HIST_RANGE);
527 else if (cmd_history_num==0)
528 sf(ps,"%s\nNo commands have yet been entered.",HIST_RANGE);
529 error(ps); return; /* never returns from here */
530 }
531 }
532
533
534 #define nopunt failures++; break;
535 #define punt done=TRUE; break;
536 #define toleft if (cursor!=0) nl();
537
command_loop()538 void command_loop()
539 {
540 char *rest;
541 int done, failures, foo, prev_lvl= -1;
542 void (*func)();
543
544 done=FALSE; failures=0;
545 do {
546 /* run { */
547 if ((msg=setjmp(stk[lvl_plus_plus()]))==0) {
548 do {
549 if (prev_lvl<0) prev_lvl=lvl;
550 else if (prev_lvl!=lvl) {
551 fprintf(stderr,"Internal Error: stack level changed\n");
552 prev_lvl= lvl;
553 }
554 if (log_open) fflush(photo);
555
556 inhibit_menus= FALSE;
557 cmd_history_num= next_entry_number(cmd_history);
558 uncrunched_args= save_uncrunched_args;
559 input((*prompt)(ps),uncrunched_args,ARGS_LEN-1);
560 despace(uncrunched_args); /* should already be filtered */
561
562 if (!nullstr(uncrunched_args)) {
563 expand_history_references(uncrunched_args);
564 put_numbered_entry(uncrunched_args,cmd_history,&foo);
565
566 if ((com_num=parser(uncrunched_args,&rest,FALSE))>=0) {
567 uncrunched_args=rest; args=save_args_ptr;
568 strcpy(args,uncrunched_args); lowercase(args);
569 if (wimp && shell_uses_wimp_cmds &&
570 cmd[com_num]->wimp_procedure!=NULL)
571 func=cmd[com_num]->wimp_procedure;
572 else func=cmd[com_num]->procedure;
573 if (func==NULL) send(CRASH);
574 com=cmd[com_num]->name;
575 num_args=0; inhibit_menus=TRUE;
576 (*func)(); /* Run the command */
577 failures=0;
578 } else print_parser_results(rest,FALSE); /* error msg */
579 }
580 } while(--lvl>10000);
581
582 /* } except { */
583 } else switch(msg) {
584 when INTERRUPT:
585 print("\n*** break ***\n");
586 if (redirecting_input) {
587 print("\n\t...run cancelled...\n");
588 redirect_input(NULL,FALSE);
589 }
590 nopunt;
591
592 when NOMEMORY: print("\n*** out of memory ***\n"); nopunt;
593 when MATHERROR: print("\n*** floating point error ***"); nopunt;
594 when ENDOINPUT: toleft; print("\n\t...end of input...\n\n"); punt;
595 when QUIT: toleft; print("\n\t...goodbye...\n\n"); punt;
596 when SOFTABORT: nopunt;
597
598 when IOERROR:
599 when CRASH:
600 when SYSERROR:
601 when CANTOPEN:
602 when ENDOFILE:
603 default: verbose_untrapped_msg(); nopunt;
604 }
605
606 if (failures>MAX_SUCCESSIVE_FAILURES) {
607 fprintf(stderr,"\n*** too many successive errors: aborting ***\n");
608 done= TRUE;
609 }
610 } while(!done);
611 }
612
613
614
615 /************************ Useful things for commands ************************/
616
617
abort_command()618 void abort_command() { send(SOFTABORT); }
619
620
error(errmsg)621 void error(errmsg) /* guaranteed not to use ps */
622 char *errmsg;
623 { print("error: "); print(errmsg); nl(); abort_command(); }
624
625
maybe_set_bool(var)626 void maybe_set_bool(var)
627 bool *var;
628 {
629 char temp[TOKLEN+1];
630
631 if (stoken(&args,sREQUIRED,temp)) {
632 if (streq(temp,"on")) *var= TRUE;
633 else if(streq(temp,"off")) *var=FALSE;
634 else set_usage_error("either 'on' or 'off'");
635 }
636 sf(ps,"'%s' is %s.\n",com, *var ? "on" : "off"); pr();
637 }
638
639
maybe_set_real(var,lbound,hbound,fmt)640 void maybe_set_real(var,lbound,hbound,fmt)
641 real *var, lbound, hbound;
642 real fmt;
643 {
644 real temp;
645
646 if (!nullstr(args)) {
647 if (!rtoken(&args,rREQUIRED,&temp) || !rrange(&temp,lbound,hbound)) {
648 sf(ps,"a real number from %s to %s",rs(fmt,lbound),rs(fmt,hbound));
649 set_usage_error(ps);
650 } else *var= temp;
651 }
652 sf(ps,"'%s' is %s\n",com,rs(fmt,*var)); pr();
653 }
654
655
maybe_set_long(var,lbound,hbound)656 void maybe_set_long(var,lbound,hbound)
657 long *var, lbound, hbound;
658 {
659 long temp;
660
661 if (!nullstr(args)) {
662 if (!ltoken(&args,lREQUIRED,&temp) ||
663 !lrange(&temp,lbound,hbound)) {
664 sf(ps,"an integer number from %ld to %ld",lbound,hbound);
665 set_usage_error(ps);
666 } else *var= temp;
667 }
668 sf(ps,"'%s' is %ld\n",com,*var); pr();
669 }
670
671
maybe_set_int(var,lbound,hbound)672 void maybe_set_int(var,lbound,hbound)
673 int *var, lbound, hbound;
674 {
675 int temp;
676
677 if (!nullstr(args)) {
678 if (!itoken(&args,iREQUIRED,&temp) ||
679 !irange(&temp,lbound,hbound)) {
680 sf(ps,"an integer number from %d to %d",lbound,hbound);
681 set_usage_error(ps);
682 } else *var= temp;
683 }
684 sf(ps,"'%s' is %d\n",com,*var); pr();
685 }
686
687
set_usage_error(com_args)688 void set_usage_error(com_args) /* guaranteed not to use ps */
689 char *com_args;
690 { print("error: illegal value for '"); print(com); print("'\n");
691 if (cmd[com_num]->args_help!=NULL)
692 { print("correct value: "); print(cmd[com_num]->args_help); print("\n"); }
693 print("type '"); print(com);
694 print("' alone to display current value\n");
695 print("type 'help "); print(com); print("' for details\n");
696 abort_command();
697 }
698
699
usage_error(num)700 void usage_error(num)
701 int num; /* num args given, maybe <0 */
702 {
703 if (cmd[com_num]->num_args==0)
704 sf(ps,"error: The '%s' command takes no arguments.\n",com);
705 else
706 sf(ps,"error: Missing%s argument(s) for the '%s' command.\n",
707 (num!=0 ? " or invalid":""),com);
708 pr();
709
710 if (cmd[com_num]->num_args!=0) {
711 if (!nullstr(cmd[com_num]->args_help))
712 { sf(ps,"expected: %s\n",cmd[com_num]->args_help); pr(); }
713 if (!nullstr(cmd[com_num]->def_help))
714 { sf(ps,"default%s %s\n",(cmd[com_num]->num_args==1 ? ": ":"s:"),
715 cmd[com_num]->def_help); pr(); }
716 }
717
718 if (cmd[com_num]->help_key!=HELPLESS)
719 { sf(ps,"type 'help %s' for details.\n",com); pr(); }
720
721 abort_command();
722 }
723
724
725 #define TOOMANY \
726 "error: Too many arguments for the '%s' command\n(%s%d argument%s expected).\n"
727
nomore_args(n)728 void nomore_args(n)
729 int n; /* for now, a dummy arg */
730 {
731 int mode, num;
732 num= cmd[com_num]->num_args;
733 mode=cmd[com_num]->num_args_prefix;
734 if (nullstr(args)) return; /* all is OK, otherwise... */
735
736 if (num==0)
737 sf(ps,"error: The '%s' command takes no arguments.\n",com);
738 else if (num>0)
739 sf(ps,TOOMANY,com,(mode==EXACTLY ? "" : "up to "),num,maybe_s(num));
740 else /* num<0 */ sf(ps,"error: Too many arguments.\n");
741 pr();
742
743 if (!nullstr(cmd[com_num]->args_help))
744 { print("expected: "); print(cmd[com_num]->args_help); nl(); }
745
746 if (!nullstr(cmd[com_num]->def_help))
747 { sf(ps,"default%s: %s\n",maybe_s(num),cmd[com_num]->args_help); pr(); }
748
749 if (cmd[com_num]->help_key!=HELPLESS)
750 { sf(ps,"Type 'help %s' for details.\n",com); pr(); }
751
752 abort_command();
753 }
754
755
more_args(num)756 void more_args(num)
757 int num; /* num args given, maybe <0 */
758 {
759 int mode, want;
760 want= cmd[com_num]->num_args;
761 mode=cmd[com_num]->num_args_prefix;
762
763 if (!nullstr(args)) usage_error(-1); /* instead */
764
765 sf(ps,"error: Missing argument%s.\n",maybe_s(want-num)); pr();
766 sf(ps,"The '%s' command requires %s%d argument%s.\n",com,
767 mode==EXACTLY ? "" : "at least ",want,maybe_s(want)); pr();
768
769 if (!nullstr(cmd[com_num]->args_help))
770 { print("expected: "); print(cmd[com_num]->args_help); nl(); }
771
772 if (!nullstr(cmd[com_num]->def_help))
773 { sf(ps,"default%s: %s\n",maybe_s(num),cmd[com_num]->args_help); pr(); }
774
775 if (cmd[com_num]->help_key!=HELPLESS)
776 { sf(ps,"Type 'help %s' for details.\n",com); pr(); }
777
778 abort_command();
779 }
780
781
input_error(val,def)782 void input_error(val,def)
783 char *val, *def;
784 {
785 print("error: you have given an invalid response\n");
786 if (!nullstr(val))
787 { print("expected: "); print(val); nl(); }
788 if (!nullstr(def))
789 { print("default: "); print(def); nl(); }
790
791 if (cmd[com_num]->help_key!=HELPLESS)
792 { sf(ps,"try typing 'help %s' for details\n",com); pr(); }
793
794 abort_command();
795 }
796
797
798 #define EXTRA_INPUT "too many values given in input line"
799
expect_nomore_input(str,mode,num)800 void expect_nomore_input(str,mode,num)
801 char *str;
802 int mode, num;
803 { if (nullstr(str)) return;
804 else if (num>0)
805 sf(ps,"error: %s\n %s %d value%s were expected\n",EXTRA_INPUT,
806 (mode==EXACTLY ? "only" : "up to"),
807 num,maybe_s(num));
808 else sf(ps,"%s\n",EXTRA_INPUT);
809 pr(); print("try 'help "); print(com); print("' for details\n");
810 abort_command();
811 }
812
813
expect_more_input(str,mode,num)814 void expect_more_input(str,mode,num)
815 char *str;
816 int mode, num;
817 { if (!nullstr(str)) return;
818 else if (num>0)
819 sf(ps,"error: missing input\n%s %d value%s expected\n",
820 (mode==EXACTLY ? "":"at least"),num,(num>1 ? "s were":" was"));
821 else sf(ps,"error: missing input\n");
822 pr(); print("try 'help "); print(com); print("' for details\n");
823 abort_command();
824 }
825
826
split_arglist(rest,divider)827 bool split_arglist(rest,divider)
828 char **rest;
829 char divider; /* character that arglist should be split on */
830 {
831 int i, j;
832
833 *rest=NULL; i=0;
834 for (i=0; args[i]!='\0'; i++)
835 if (args[i]==divider) {
836 j=i+1; while (white(args[j])) j++; *rest=args+j;
837 i--; while (white(args[i]) && i>0) i--; args[i+1]='\0';
838 return(TRUE);
839 }
840 return(FALSE);
841 }
842
843
split_uncrunched_args(rest,divider)844 bool split_uncrunched_args(rest,divider)
845 char **rest;
846 char divider; /* character that arglist should be split on */
847 {
848 int i, j;
849
850 *rest=NULL; i=0;
851 for (i=0; uncrunched_args[i]!='\0'; i++)
852 if (uncrunched_args[i]==divider) {
853 j=i+1; while (white(uncrunched_args[j])) j++;
854 *rest=uncrunched_args+j;
855 i--; while (white(uncrunched_args[i]) && i>0) i--;
856 uncrunched_args[i+1]='\0';
857 return(TRUE);
858 }
859 return(FALSE);
860 }
861
862
maybe_ok(str)863 void maybe_ok(str)
864 char *str;
865 {
866 if (update_top()) {
867 if (photo!=NULL) lib_puts(photo,str);
868 lib_puts(out,"ok");
869 } else print(str);
870 nl();
871 }
872
873
keep_user_amused(thing,iter,max_iter)874 void keep_user_amused(thing,iter,max_iter)
875 char *thing; /* nullstr(str) means we are done, len<<TOKLEN, like one word */
876 int iter, max_iter;
877 {
878 char simple[TOKLEN+1], fancy[TOKLEN+1];
879
880 if (iter==0) {
881 usertime(TRUE);
882 /* only need to do the simples the first time */
883 if (max_iter==0) sf(simple,"computing %s%s",thing,"s");
884 else sf(simple,"computing %d %s%s",max_iter,thing,maybe_s(max_iter));
885 } else strcpy(simple,"foo");
886
887 if (max_iter==0) sf(fancy,"%s %d",thing,iter);
888 else sf(fancy,"%s %d of %d",thing,iter,max_iter);
889
890 if (iter==0 || usertime(FALSE)>1.0)
891 { temp_print(simple,fancy); usertime(TRUE); }
892 }
893
894
895 /**************************** Some Basic Commands ****************************/
896
show_cmd_history()897 command show_cmd_history()
898 {
899 int i, num_to_print, first_to_print, printed_any;
900 char *cmd_str;
901
902 get_one_arg(itoken,1000,&num_to_print);
903 if (num_to_print<1) usage_error(1);
904
905 printed_any=FALSE;
906 first_to_print= imaxf(cmd_history_num-num_to_print,0);
907 for(Te=cmd_history->list; Te!=NULL; Te=Te->next) {
908 i=Te->id.num; cmd_str=Te->string;
909 if (i>=first_to_print && i<cmd_history_num) {
910 if(!printed_any){print("Previous commands:\n"); printed_any=TRUE;}
911 sf(ps,"%3d ",i+1); pr(); print(cmd_str); nl();
912 }
913 }
914 if (!printed_any) print("No commands have yet been entered.\n");
915 }
916
917
quit()918 command quit()
919 {
920 char token[TOKLEN+1];
921
922 if (interactive && !redirecting_input) {
923 /* test auto_save && data_loaded */
924 if (quit_save_hook==NULL || !(*quit_save_hook)(FALSE)) send(QUIT);
925 run getln("save data before quitting? [yes] ");
926 except {
927 when ENDOINPUT: ln[0]='y'; break;
928 when INTERRUPT: abort_command();
929 default: relay;
930 }
931 if (!stoken(&ln,sREQUIRED,token) || !matches(token,"no")) {
932 if (!((*quit_save_hook)(TRUE))) return; /* if saving fails */
933 else send(QUIT);
934 } else send(QUIT);
935 } else {
936 if (quit_save_hook!=NULL && !((*quit_save_hook)(TRUE))) return;
937 send(QUIT);
938 }
939 }
940
941
really_quit()942 command really_quit()
943 { send(QUIT); }
944
945
run_from_file()946 command run_from_file()
947 {
948 char file_name[PATH_LENGTH+1];
949
950 use_uncrunched_args();
951 get_one_arg(stoken,sREQUIRED,file_name);
952
953 if (!make_filename(file_name,DEFAULT_EXTENSION,WRS("in")))
954 error("bad input file name");
955 else redirect_input(file_name,TRUE); /* verbose -> messages */
956 }
957
958
do_photo()959 command do_photo()
960 {
961 char file_name[TOKLEN+1];
962
963 use_uncrunched_args();
964 get_one_arg(stoken,"",file_name);
965
966 if (streq(file_name,"off")) {
967 photo_to_file("","");
968 sf(ps,"'%s' is off.\n",com); pr();
969 return;
970
971 } else if (streq(file_name,"on")) {
972 usage_error(1);
973
974 } else if (!nullstr(file_name)) {
975 if (!make_filename(file_name,DEFAULT_EXTENSION,WRS("out")))
976 sf(ps,"error: Bad photo file name");
977 if (!photo_to_file(file_name,"a"))
978 sf(ps,"error: Unable to open photo file '%s'\n",file_name);
979 else {
980 photo_banner();
981 if (photo_update_top_hook && update_top()) sf(ps,"ok\n");
982 else sf(ps,"'%s' is on: file is '%s'\n",com,photo_file);
983 }
984 pr(); return;
985
986 } else { /* no args */
987 if (log_open) {
988 fflush(photo);
989 sf(ps,"'%s' is on: file is '%s'\n",com,photo_file);
990 } else sf(ps,"'%s' is off\n",com);
991 pr(); return;
992 }
993 }
994
995
set_more()996 command set_more() { maybe_set_bool(&more); }
set_wizard()997 command set_wizard() { maybe_set_bool(&wizard_mode); }
set_verbose_mem()998 command set_verbose_mem() { maybe_set_bool(&verbose_mem); }
999
1000
1001 #define s_or_space_colon (cmd[i]->num_args==1 ? ": ":"s:")
1002 #define NOT_A_TOPIC "There is no help topic number %d.\n"
1003 #define MOREHELP0 \
1004 "Type 'help' for a list of commands and topics.\n"
1005 #define MOREHELP1 \
1006 "Type 'help <topic-number>' for more information about a particular topic.\n"
1007 #define MOREHELP2 \
1008 "Type 'help <command-name>' for more help with a particular command.\n"
1009 #define HELP_LEFT 26
1010 #define HELP_FORMAT "%-26s.%-52s\n"
1011 #define HELP_DIVIDER \
1012 "=============================================================================\
1013 \n"
1014
help()1015 command help()
1016 {
1017 int j, i, k, n;
1018 char *name=get_temp_string(), *rest=NULL, *str=get_temp_string();
1019 bool got_any, got_it;
1020
1021 crunch(args);
1022 maybe_clear_screen();
1023
1024 hold (more_mode) {
1025
1026 if (nullstr(args)) { /**** Print Help Table ****/
1027 print(HELP_DIVIDER);
1028 sf(ps,"%s Commands and Options:\n",the_program); pr();
1029 for (j=1; j<MAX_COM_TOPICS; j++) { /* NOT j<=MAX_COM_TOPICS! */
1030 if (nullstr(topic_name[j]) || wizard_only(topic_code[j]))
1031 continue;
1032 strcpy(name,topic_name[j]); uppercase(name);
1033 sf(ps,"\n(%d) %s:\n",j,name); pr();
1034 for (n=0; n<help_entries; n++) {
1035 for (i=0, got_it=FALSE; i<command_num; i++) {
1036 if (cmd[i]->help_entry!=n) continue;
1037 if (cmd[i]->topic!=j) break;
1038 if (!allowed_cmd(i,TRUE)) break;
1039 got_it=TRUE; break;
1040 }
1041 if (!got_it) continue; /* for n */
1042
1043 if (!nullstr(cmd[i]->abbreviation))
1044 sf(str,"%s (%s)",cmd[i]->name,cmd[i]->abbreviation);
1045 else sf(str,"%s",cmd[i]->name,"");
1046 if (!nullstr(cmd[i]->cmd_help)) {
1047 for (k=len(str); k<HELP_LEFT; k++) str[k]='.';
1048 str[HELP_LEFT]='\0';
1049 sf(ps,HELP_FORMAT,str,cmd[i]->cmd_help); pr();
1050 } else { /* no cmd_help */
1051 print(str); nl();
1052 }
1053 }
1054 }
1055 for (i=0, got_any=FALSE; i<command_num; i++) {
1056 if (!allowed_cmd(i,TRUE)) continue;
1057 if (cmd[i]->help_entry==UNHELPFUL) {
1058 if (!got_any)
1059 { print("\nOTHER COMMANDS:\n"); got_any=TRUE; }
1060 if (!nullstr(cmd[i]->abbreviation))
1061 sf(ps,"%s (%s)\n",cmd[i]->name,cmd[i]->abbreviation);
1062 else sf(ps,"%s\n",cmd[i]->name,"");
1063 pr();
1064 }
1065 }
1066 nl();
1067 print(MOREHELP1); print(MOREHELP2);
1068 if (help_file==NULL) { print("\nNote: "); print(NO_HELP_FILE); }
1069 print(HELP_DIVIDER);
1070
1071 } else if (itoken(&args,iREQUIRED,&i)) { /**** a topic num ****/
1072 if (i<=0 || i>MAX_COM_TOPICS || nullstr(topic_name[i])) {
1073 unhold(); sf(ps,NOT_A_TOPIC,i); pr();
1074 print(MOREHELP0); print(MOREHELP1); print(MOREHELP2);
1075 } else {
1076 print(HELP_DIVIDER);
1077 strcpy(name,topic_name[i]); uppercase(name);
1078 sf(ps,"(%d) %s\n\n",i,name); pr();
1079 if (help_file==NULL) print(NO_HELP_FILE);
1080 else if (topic_help_key[i]==HELPLESS) print(NO_HELP_KEY);
1081 else {
1082 fgoto_line(help_file,topic_help_key[i]);
1083 fgetln_(help_file);
1084 got_any=FALSE;
1085 while (ln[0]!='@') {
1086 got_any=TRUE;
1087 print(ln); nl();
1088 fgetln_(help_file);
1089 }
1090 if (!got_any) print(NO_HELP_KEY);
1091 }
1092 print(HELP_DIVIDER);
1093 }
1094
1095 } else { /**** a command name? ****/
1096 if ((i=parser(args,&rest,TRUE))<0) {
1097 unhold();
1098 print_parser_results(rest,TRUE); /* not a command */
1099 print(MOREHELP0); print(MOREHELP1); print(MOREHELP2);
1100 } else {
1101 /* print out short help */
1102 print(HELP_DIVIDER);
1103 nstrcpy(name,cmd[i]->name,MAX_COM_NAME_LEN); uppercase(name);
1104 if (isa_option(cmd[i]->code)) strcpy(str,"Option");
1105 else if (isa_parameter(cmd[i]->code)) strcpy(str,"Parameter");
1106 else if (!help_only(cmd[i]->code)) strcpy(str,"Command");
1107 else strcpy(str,"Information");
1108 if (!nullstr(cmd[i]->abbreviation))
1109 sf(ps,"%s %s (abbreviation: '%s')\n",name,str,
1110 cmd[i]->abbreviation);
1111 else sf(ps,"%s %s\n",name,str);
1112 pr(); nl();
1113
1114 if (!nullstr(cmd[i]->cmd_help))
1115 { sf(ps,"Description: %s\n",cmd[i]->cmd_help); pr(); }
1116 if (cmd[i]->topic!=0)
1117 { sf(ps,"Help Topic: (%d) %s\n",cmd[i]->topic,
1118 topic_name[cmd[i]->topic]); pr(); }
1119 if (cmd[i]->num_args==0) print("No Arguments\n");
1120 else if (!nullstr(cmd[i]->args_help)) {
1121 sf(ps,"Argument%s %s\n",s_or_space_colon,
1122 cmd[i]->args_help); pr();
1123 if (!nullstr(cmd[i]->def_help)) {
1124 sf(ps,"Default%s %s\n",s_or_space_colon,
1125 cmd[i]->def_help); pr();
1126 }
1127 }
1128
1129 nl();
1130 if (help_file==NULL) print(NO_HELP_FILE);
1131 else if (cmd[i]->help_key==HELPLESS) print(NO_HELP_KEY);
1132 else {
1133 fgoto_line(help_file,cmd[i]->help_key);
1134 fgetln_(help_file);
1135 got_any=FALSE;
1136 while (ln[0]!='@') {
1137 got_any=TRUE;
1138 print(ln); nl();
1139 fgetln_(help_file);
1140 }
1141 if (!got_any) print(NO_HELP_KEY);
1142 }
1143 print(HELP_DIVIDER);
1144 }
1145 }
1146 } /* hold */
1147 }
1148
1149
about()1150 command about()
1151 {
1152 int n;
1153
1154 print(HELP_DIVIDER);
1155 sf(ps,"%s version %s, Copyright %s Whitehead Institute\n",
1156 the_program,the_version,the_copyright); pr();
1157 if (gnu_copyright(ps)) { pr(); nl(); }
1158 print("All rights reserved.\n"); nl();
1159
1160 n=cmd[com_num]->help_key;
1161 if (help_file==NULL)
1162 { print(NO_HELP_FILE); nl(); print(SURROGATE_ABOUT); }
1163 else if (n==HELPLESS)
1164 { print(NO_HELP_KEY); nl(); print(SURROGATE_ABOUT); }
1165 else {
1166 fgoto_line(help_file,n);
1167 fgetln_(help_file);
1168 while (ln[0]!='@') {
1169 print(ln); nl();
1170 fgetln_(help_file);
1171 }
1172 }
1173 nl(); print(MOREHELP0); print(MOREHELP1); print(MOREHELP2);
1174 print(HELP_DIVIDER);
1175 }
1176
1177
review_output()1178 command review_output() { review_memory(); print("ok\n"); }
1179
show_time()1180 command show_time()
1181 {
1182 char *str;
1183
1184 if (!nullstr(str=time_string())) {
1185 print(str); nl();
1186 } else print("Time to go home...\n");
1187 }
1188
1189
cd_command()1190 command cd_command()
1191 {
1192 char dir_name[PATH_LENGTH+1];
1193
1194 use_uncrunched_args();
1195 get_one_arg(stoken,"",dir_name);
1196
1197 if (nullstr(dir_name)) {
1198 if (get_directory(dir_name))
1199 { sf(ps,"The current directory is '%s'\n",dir_name); pr(); }
1200 else error("Can't get current directory name");
1201
1202 } else { /* !nullstr(dir_name) */
1203 if (change_directory(dir_name)) {
1204 if (get_directory(dir_name)) {
1205 sf(ps,"The current directory is now '%s'\n",dir_name); pr();
1206 } else print("ok\n");
1207 } else {
1208 sf(ps,"Can't change to directory '%s'",dir_name);
1209 error(ps);
1210 }
1211 }
1212 }
1213
1214
system_command()1215 command system_command()
1216 {
1217 if (!nullstr(args)) { /* run shell command */
1218 if (shell_command(args)) {
1219 if (curses)
1220 print("\n ...System Output Omitted...\n");
1221 else if (logging)
1222 lib_puts(photo,"\n ...System Output Omitted...\n");
1223 print("\nOK\n");
1224 } else {
1225 sf(ps,"Unable to run command '%s'",truncstr(args,40));
1226 error(ps);
1227 }
1228
1229 } else { /* run a subshell */
1230 print(SHELL_MESSAGE); nl(); flush(); /* for curses */
1231 if (subshell()) {
1232 if (curses)
1233 print(" ...System Interaction Omitted...\n");
1234 else if (logging)
1235 lib_puts(photo," ...System Interaction Omitted...\n");
1236 lib_puts(out,"\n"); /* C shell does not print one on ctrl-D */
1237 print("\nBack in "); print(the_program); print("...\n");
1238 } else error("Unable to run subshell\n");
1239 }
1240 }
1241
1242
1243 #define GIMME_A_COMMENT \
1244 "Enter your comment. End it with a period ('.') on a line by itself.\n"
1245
comment()1246 command comment()
1247 {
1248 if (nullstr(args)) {
1249 print(GIMME_A_COMMENT);
1250 do { getln(""); despace(ln); } while(!streq(ln,"."));
1251 } else print("ok\n");
1252 }
1253
1254
1255
1256
1257 /********** WIMP (Windows, Icons, Mouse, and Pointers) Support **********/
1258
1259 #ifdef NOT_PUBLIC
wimp_start()1260 void wimp_start() /* Call from main() */
1261 {
1262 #ifdef HAVE_WIMP
1263 if (wimp) do_wimp_start(menus,num_menus);
1264 #endif
1265 }
1266
1267
mkwimp(name,menu_entry,menu_num,wimp_function,status_function,wimp_help_function,shortcut)1268 void mkwimp(name,menu_entry,menu_num,wimp_function,status_function,
1269 wimp_help_function,shortcut)
1270 char *name, *menu_entry;
1271 int menu_num;
1272 void (*wimp_function)(), (*status_function)(), (*wimp_help_function)();
1273 char shortcut;
1274 {
1275 int i;
1276
1277 if ((i=parser(name,(char**)NULL,TRUE))<0) send(CRASH);
1278
1279 cmd[i]->menu_entry= mkstrcpy(menu_entry);
1280 cmd[i]->wimp_menu_num= menu_num;
1281 cmd[i]->wimp_procedure= wimp_function;
1282 cmd[i]->status_function= status_function;
1283 cmd[i]->wimp_help= wimp_help_function;
1284 cmd[i]->wimp_shortcut= shortcut;
1285
1286 if (menu_num>=MAX_NUM_MENUS || nullstr(menu[menu_num]->title) ||
1287 menu[menu_num]->num_entries>=MAX_MENU_ENTRIES) send(CRASH);
1288 menu[menu_num]->entry[(menu[menu_num]->num_entries)++]= cmd[i];
1289 }
1290
1291
mkmenu(num,title)1292 void mkmenu(num,title)
1293 int num;
1294 char *title;
1295 {
1296 if (num>=MAX_NUM_MENUS) send(CRASH);
1297 menu[num]->title=mkstrcpy(title);
1298 }
1299
1300
1301 COMMAND the_divider;
1302
mkdivider(menu_num)1303 void mkdivider(menu_num)
1304 int menu_num;
1305 {
1306 if (menu_num>=MAX_NUM_MENUS || nullstr(menu[menu_num]->title) ||
1307 menu[menu_num]->num_entries>=MAX_MENU_ENTRIES) send(CRASH);
1308 menu[menu_num]->entry[(menu[menu_num]->num_entries)++]= &the_divider;
1309 }
1310 #endif
1311