5c02b6bfe123020f4c05cafc09f55e55993f8b98
[inform.git] / src / inform.c
1 /* ------------------------------------------------------------------------- */
2 /*   "inform" :  The top level of Inform: switches, pathnames, filenaming    */
3 /*               conventions, ICL (Inform Command Line) files, main          */
4 /*                                                                           */
5 /*   Part of Inform 6.40                                                     */
6 /*   copyright (c) Graham Nelson 1993 - 2022                                 */
7 /*                                                                           */
8 /* Inform is free software: you can redistribute it and/or modify            */
9 /* it under the terms of the GNU General Public License as published by      */
10 /* the Free Software Foundation, either version 3 of the License, or         */
11 /* (at your option) any later version.                                       */
12 /*                                                                           */
13 /* Inform is distributed in the hope that it will be useful,                 */
14 /* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
15 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
16 /* GNU General Public License for more details.                              */
17 /*                                                                           */
18 /* You should have received a copy of the GNU General Public License         */
19 /* along with Inform. If not, see https://gnu.org/licenses/                  */
20 /*                                                                           */
21 /* ------------------------------------------------------------------------- */
22
23 #define MAIN_INFORM_FILE
24 #include "header.h"
25
26 #define CMD_BUF_SIZE (256)
27
28 /* ------------------------------------------------------------------------- */
29 /*   Compiler progress                                                       */
30 /* ------------------------------------------------------------------------- */
31
32 static int no_compilations;
33
34 int endofpass_flag;      /* set to TRUE when an "end" directive is reached
35                             (the inputs routines insert one into the stream
36                             if necessary)                                    */
37
38 /* ------------------------------------------------------------------------- */
39 /*   Version control                                                         */
40 /* ------------------------------------------------------------------------- */
41
42 int version_number,      /* 3 to 8 (Z-code)                                  */
43     instruction_set_number,
44                          /* 3 to 6: versions 7 and 8 use instruction set of
45                             version 5                                        */
46     extend_memory_map;   /* extend using function- and string-offsets        */
47 int32 scale_factor,      /* packed address multiplier                        */
48     length_scale_factor; /* length-in-header multiplier                      */
49
50 int32 requested_glulx_version;
51
52 extern void select_version(int vn)
53 {   version_number = vn;
54     extend_memory_map = FALSE;
55     if ((version_number==6)||(version_number==7)) extend_memory_map = TRUE;
56
57     scale_factor = 4;
58     if (version_number==3) scale_factor = 2;
59     if (version_number==8) scale_factor = 8;
60
61     length_scale_factor = scale_factor;
62     if ((version_number==6)||(version_number==7)) length_scale_factor = 8;
63
64     instruction_set_number = version_number;
65     if ((version_number==7)||(version_number==8)) instruction_set_number = 5;
66 }
67
68 static int select_glulx_version(char *str)
69 {
70   /* Parse an "X.Y.Z" style version number, and store it for later use. */
71   char *cx = str;
72   int major=0, minor=0, patch=0;
73
74   while (isdigit(*cx))
75     major = major*10 + ((*cx++)-'0');
76   if (*cx == '.') {
77     cx++;
78     while (isdigit(*cx))
79       minor = minor*10 + ((*cx++)-'0');
80     if (*cx == '.') {
81       cx++;
82       while (isdigit(*cx))
83         patch = patch*10 + ((*cx++)-'0');
84     }
85   }
86
87   requested_glulx_version = ((major & 0x7FFF) << 16) 
88     + ((minor & 0xFF) << 8) 
89     + (patch & 0xFF);
90   return (cx - str);
91 }
92
93 /* ------------------------------------------------------------------------- */
94 /*   Target: variables which vary between the Z-machine and Glulx            */
95 /* ------------------------------------------------------------------------- */
96
97 int   WORDSIZE;            /* Size of a machine word: 2 or 4 */
98 int32 MAXINTWORD;          /* 0x7FFF or 0x7FFFFFFF */
99
100 /* The first property number which is an individual property. The
101    eight class-system i-props (create, recreate, ... print_to_array)
102    are numbered from INDIV_PROP_START to INDIV_PROP_START+7.
103 */
104 int INDIV_PROP_START;
105
106 /* The length of an object, as written in tables.c. It's easier to define
107    it here than to repeat the same expression all over the source code.
108    Not used in Z-code. 
109 */
110 int OBJECT_BYTE_LENGTH;
111 /* The total length of a dict entry, in bytes. Not used in Z-code. 
112 */
113 int DICT_ENTRY_BYTE_LENGTH;
114 /* The position in a dict entry that the flag values begin.
115    Not used in Z-code. 
116 */
117 int DICT_ENTRY_FLAG_POS;
118
119 static void select_target(int targ)
120 {
121   if (!targ) {
122     /* Z-machine */
123     WORDSIZE = 2;
124     MAXINTWORD = 0x7FFF;
125
126     MAX_LOCAL_VARIABLES = 16; /* including "sp" */
127
128     if (INDIV_PROP_START != 64) {
129         INDIV_PROP_START = 64;
130         fatalerror("You cannot change INDIV_PROP_START in Z-code");
131     }
132     if (DICT_WORD_SIZE != 6) {
133       DICT_WORD_SIZE = 6;
134       fatalerror("You cannot change DICT_WORD_SIZE in Z-code");
135     }
136     if (DICT_CHAR_SIZE != 1) {
137       DICT_CHAR_SIZE = 1;
138       fatalerror("You cannot change DICT_CHAR_SIZE in Z-code");
139     }
140     if (NUM_ATTR_BYTES != 6) {
141       NUM_ATTR_BYTES = 6;
142       fatalerror("You cannot change NUM_ATTR_BYTES in Z-code");
143     }
144   }
145   else {
146     /* Glulx */
147     WORDSIZE = 4;
148     MAXINTWORD = 0x7FFFFFFF;
149     scale_factor = 0; /* It should never even get used in Glulx */
150
151     /* This could really be 120, since the practical limit is the size
152        of local_variables.keywords. But historically it's been 119. */
153     MAX_LOCAL_VARIABLES = 119; /* including "sp" */
154
155     if (INDIV_PROP_START < 256) {
156         INDIV_PROP_START = 256;
157         warning_numbered("INDIV_PROP_START should be at least 256 in Glulx. Setting to", INDIV_PROP_START);
158     }
159
160     if (NUM_ATTR_BYTES % 4 != 3) {
161       NUM_ATTR_BYTES += (3 - (NUM_ATTR_BYTES % 4)); 
162       warning_numbered("NUM_ATTR_BYTES must be a multiple of four, plus three. Increasing to", NUM_ATTR_BYTES);
163     }
164
165     if (DICT_CHAR_SIZE != 1 && DICT_CHAR_SIZE != 4) {
166       DICT_CHAR_SIZE = 4;
167       warning_numbered("DICT_CHAR_SIZE must be either 1 or 4. Setting to", DICT_CHAR_SIZE);
168     }
169   }
170
171   if (MAX_LOCAL_VARIABLES > MAX_KEYWORD_GROUP_SIZE) {
172     compiler_error("MAX_LOCAL_VARIABLES cannot exceed MAX_KEYWORD_GROUP_SIZE");
173     MAX_LOCAL_VARIABLES = MAX_KEYWORD_GROUP_SIZE;
174   }
175
176   if (DICT_WORD_SIZE > MAX_DICT_WORD_SIZE) {
177     DICT_WORD_SIZE = MAX_DICT_WORD_SIZE;
178     warning_numbered(
179       "DICT_WORD_SIZE cannot exceed MAX_DICT_WORD_SIZE; resetting", 
180       MAX_DICT_WORD_SIZE);
181     /* MAX_DICT_WORD_SIZE can be increased in header.h without fear. */
182   }
183   if (NUM_ATTR_BYTES > MAX_NUM_ATTR_BYTES) {
184     NUM_ATTR_BYTES = MAX_NUM_ATTR_BYTES;
185     warning_numbered(
186       "NUM_ATTR_BYTES cannot exceed MAX_NUM_ATTR_BYTES; resetting",
187       MAX_NUM_ATTR_BYTES);
188     /* MAX_NUM_ATTR_BYTES can be increased in header.h without fear. */
189   }
190
191   /* Set up a few more variables that depend on the above values */
192
193   if (!targ) {
194     /* Z-machine */
195     DICT_WORD_BYTES = DICT_WORD_SIZE;
196     OBJECT_BYTE_LENGTH = 0;
197     DICT_ENTRY_BYTE_LENGTH = ((version_number==3)?7:9) - (ZCODE_LESS_DICT_DATA?1:0);
198     DICT_ENTRY_FLAG_POS = 0;
199   }
200   else {
201     /* Glulx */
202     OBJECT_BYTE_LENGTH = (1 + (NUM_ATTR_BYTES) + 6*4 + (GLULX_OBJECT_EXT_BYTES));
203     DICT_WORD_BYTES = DICT_WORD_SIZE*DICT_CHAR_SIZE;
204     if (DICT_CHAR_SIZE == 1) {
205       DICT_ENTRY_BYTE_LENGTH = (7+DICT_WORD_BYTES);
206       DICT_ENTRY_FLAG_POS = (1+DICT_WORD_BYTES);
207     }
208     else {
209       DICT_ENTRY_BYTE_LENGTH = (12+DICT_WORD_BYTES);
210       DICT_ENTRY_FLAG_POS = (4+DICT_WORD_BYTES);
211     }
212   }
213
214   if (!targ) {
215     /* Z-machine */
216     /* The Z-machine's 96 abbreviations are used for these two purposes.
217        Make sure they are set consistently. If exactly one has been
218        set non-default, set the other to match. */
219     if (MAX_DYNAMIC_STRINGS == 32 && MAX_ABBREVS != 64) {
220         MAX_DYNAMIC_STRINGS = 96 - MAX_ABBREVS;
221     }
222     if (MAX_ABBREVS == 64 && MAX_DYNAMIC_STRINGS != 32) {
223         MAX_ABBREVS = 96 - MAX_DYNAMIC_STRINGS;
224     }
225     if (MAX_ABBREVS + MAX_DYNAMIC_STRINGS != 96
226         || MAX_ABBREVS < 0
227         || MAX_DYNAMIC_STRINGS < 0) {
228       warning("MAX_ABBREVS plus MAX_DYNAMIC_STRINGS must be 96 in Z-code; resetting both");
229       MAX_DYNAMIC_STRINGS = 32;
230       MAX_ABBREVS = 64;
231     }
232   }
233 }
234
235 /* ------------------------------------------------------------------------- */
236 /*   Tracery: output control variables                                       */
237 /*   (These are initially set to foo_trace_setting, but the Trace directive  */
238 /*   can change them on the fly)                                             */
239 /* ------------------------------------------------------------------------- */
240
241 int asm_trace_level,     /* trace assembly: 0 for off, 1 for assembly
242                             only, 2 for full assembly tracing with hex dumps,
243                             3 for branch shortening info, 4 for verbose
244                             branch info                                      */
245     expr_trace_level,    /* expression tracing: 0 off, 1 on, 2/3 more        */
246     linker_trace_level,  /* linker tracing: 0 to 4 levels                    */
247     tokens_trace_level;  /* lexer output tracing: 0 off, 1 on, 2/3 more      */
248
249 /* ------------------------------------------------------------------------- */
250 /*   On/off switch variables (by default all FALSE); other switch settings   */
251 /*   (Some of these have become numerical settings now)                      */
252 /* ------------------------------------------------------------------------- */
253
254 int concise_switch,                 /* -c */
255     economy_switch,                 /* -e */
256     frequencies_setting,            /* $!FREQ, -f */
257     ignore_switches_switch,         /* -i */
258     debugfile_switch,               /* -k */
259     memout_switch,                  /* $!MEM */
260     printprops_switch,              /* $!PROPS */
261     printactions_switch,            /* $!ACTIONS */
262     obsolete_switch,                /* -q */
263     transcript_switch,              /* -r */
264     statistics_switch,              /* $!STATS, -s */
265     optimise_switch,                /* -u */
266     version_set_switch,             /* -v */
267     nowarnings_switch,              /* -w */
268     hash_switch,                    /* -x */
269     memory_map_setting,             /* $!MAP, -z */
270     oddeven_packing_switch,         /* -B */
271     define_DEBUG_switch,            /* -D */
272     module_switch,                  /* -M */
273     runtime_error_checking_switch,  /* -S */
274     define_USE_MODULES_switch,      /* -U */
275     define_INFIX_switch;            /* -X */
276 #ifdef ARC_THROWBACK
277 int throwback_switch;               /* -T */
278 #endif
279 #ifdef ARCHIMEDES
280 int riscos_file_type_format;        /* set by -R */
281 #endif
282 int compression_switch;             /* set by -H */
283 int character_set_setting,          /* set by -C0 through -C9 */
284     character_set_unicode,          /* set by -Cu */
285     error_format,                   /* set by -E */
286     asm_trace_setting,              /* $!ASM, -a: initial value of
287                                        asm_trace_level */
288     bpatch_trace_setting,           /* $!BPATCH */
289     symdef_trace_setting,           /* $!SYMDEF */
290     expr_trace_setting,             /* $!EXPR: initial value of
291                                        expr_trace_level */
292     tokens_trace_setting,           /* $!TOKENS: initial value of
293                                        tokens_trace_level */
294     optabbrevs_trace_setting,       /* $!FINDABBREVS */
295     double_space_setting,           /* set by -d: 0, 1 or 2 */
296     trace_fns_setting,              /* $!RUNTIME, -g: 0, 1, 2, or 3 */
297     files_trace_setting,            /* $!FILES */
298     linker_trace_setting,           /* $!LINKER: initial value of
299                                        linker_trace_level */
300     list_verbs_setting,             /* $!VERBS */
301     list_dict_setting,              /* $!DICT */
302     list_objects_setting,           /* $!OBJECTS */
303     list_symbols_setting,           /* $!SYMBOLS */
304     store_the_text;                 /* when set, record game text to a chunk
305                                        of memory (used by -u) */
306 static int r_e_c_s_set;             /* has -S been explicitly set? */
307
308 int glulx_mode;                     /* -G */
309
310 static void reset_switch_settings(void)
311 {   asm_trace_setting = 0;
312     linker_trace_setting = 0;
313     tokens_trace_setting = 0;
314     expr_trace_setting = 0;
315     bpatch_trace_setting = 0;
316     symdef_trace_setting = 0;
317     list_verbs_setting = 0;
318     list_dict_setting = 0;
319     list_objects_setting = 0;
320     list_symbols_setting = 0;
321
322     store_the_text = FALSE;
323
324     concise_switch = FALSE;
325     double_space_setting = 0;
326     economy_switch = FALSE;
327     files_trace_setting = 0;
328     frequencies_setting = 0;
329     trace_fns_setting = 0;
330     ignore_switches_switch = FALSE;
331     debugfile_switch = FALSE;
332     memout_switch = 0;
333     printprops_switch = 0;
334     printactions_switch = 0;
335     obsolete_switch = FALSE;
336     transcript_switch = FALSE;
337     statistics_switch = FALSE;
338     optimise_switch = FALSE;
339     optabbrevs_trace_setting = 0;
340     version_set_switch = FALSE;
341     nowarnings_switch = FALSE;
342     hash_switch = FALSE;
343     memory_map_setting = 0;
344     oddeven_packing_switch = FALSE;
345     define_DEBUG_switch = FALSE;
346     define_USE_MODULES_switch = FALSE;
347     module_switch = FALSE;
348 #ifdef ARC_THROWBACK
349     throwback_switch = FALSE;
350 #endif
351     runtime_error_checking_switch = TRUE;
352     r_e_c_s_set = FALSE;
353     define_INFIX_switch = FALSE;
354 #ifdef ARCHIMEDES
355     riscos_file_type_format = 0;
356 #endif
357     error_format=DEFAULT_ERROR_FORMAT;
358
359     character_set_setting = 1;         /* Default is ISO Latin-1 */
360     character_set_unicode = FALSE;
361
362     compression_switch = TRUE;
363     glulx_mode = FALSE;
364     requested_glulx_version = 0;
365
366     /* These aren't switches, but for clarity we reset them too. */
367     asm_trace_level = 0;
368     expr_trace_level = 0;
369     linker_trace_level = 0;
370     tokens_trace_level = 0;
371 }
372
373 /* ------------------------------------------------------------------------- */
374 /*   Number of files given as command line parameters (0, 1 or 2)            */
375 /* ------------------------------------------------------------------------- */
376
377 static int cli_files_specified,
378            convert_filename_flag;
379
380 char Source_Name[PATHLEN];             /* Processed name of first input file */
381 char Code_Name[PATHLEN];               /* Processed name of output file      */
382
383 static char *cli_file1, *cli_file2;    /* Unprocessed (and unsafe to alter)  */
384
385 /* ========================================================================= */
386 /*   Data structure management routines                                      */
387 /* ------------------------------------------------------------------------- */
388
389 static void init_vars(void)
390 {
391     init_arrays_vars();
392     init_asm_vars();
393     init_bpatch_vars();
394     init_chars_vars();
395     init_directs_vars();
396     init_errors_vars();
397     init_expressc_vars();
398     init_expressp_vars();
399     init_files_vars();
400     init_lexer_vars();
401     init_linker_vars();
402     init_memory_vars();
403     init_objects_vars();
404     init_states_vars();
405     init_symbols_vars();
406     init_syntax_vars();
407     init_tables_vars();
408     init_text_vars();
409     init_veneer_vars();
410     init_verbs_vars();
411 }
412
413 static void begin_pass(void)
414 {
415     arrays_begin_pass();
416     asm_begin_pass();
417     bpatch_begin_pass();
418     chars_begin_pass();
419     directs_begin_pass();
420     errors_begin_pass();
421     expressc_begin_pass();
422     expressp_begin_pass();
423     files_begin_pass();
424
425     endofpass_flag = FALSE;
426     expr_trace_level = expr_trace_setting;
427     asm_trace_level = asm_trace_setting;
428     tokens_trace_level = tokens_trace_setting;
429     linker_trace_level = linker_trace_setting;
430
431     lexer_begin_pass();
432     linker_begin_pass();
433     memory_begin_pass();
434     objects_begin_pass();
435     states_begin_pass();
436     symbols_begin_pass();
437     syntax_begin_pass();
438     tables_begin_pass();
439     text_begin_pass();
440     veneer_begin_pass();
441     verbs_begin_pass();
442
443     if (!module_switch)
444     {
445         /*  Compile a Main__ routine (see "veneer.c")  */
446
447         compile_initial_routine();
448
449         /*  Make the four metaclasses: Class must be object number 1, so
450             it must come first  */
451
452         veneer_mode = TRUE;
453
454         make_class("Class");
455         make_class("Object");
456         make_class("Routine");
457         make_class("String");
458
459         veneer_mode = FALSE;
460     }
461 }
462
463 extern void allocate_arrays(void)
464 {
465     arrays_allocate_arrays();
466     asm_allocate_arrays();
467     bpatch_allocate_arrays();
468     chars_allocate_arrays();
469     directs_allocate_arrays();
470     errors_allocate_arrays();
471     expressc_allocate_arrays();
472     expressp_allocate_arrays();
473     files_allocate_arrays();
474
475     lexer_allocate_arrays();
476     linker_allocate_arrays();
477     memory_allocate_arrays();
478     objects_allocate_arrays();
479     states_allocate_arrays();
480     symbols_allocate_arrays();
481     syntax_allocate_arrays();
482     tables_allocate_arrays();
483     text_allocate_arrays();
484     veneer_allocate_arrays();
485     verbs_allocate_arrays();
486 }
487
488 extern void free_arrays(void)
489 {
490     /*  One array may survive this routine, all_the_text (used to hold
491         game text until the abbreviations optimiser begins work on it): this
492         array (if it was ever allocated) is freed at the top level.          */
493
494     arrays_free_arrays();
495     asm_free_arrays();
496     bpatch_free_arrays();
497     chars_free_arrays();
498     directs_free_arrays();
499     errors_free_arrays();
500     expressc_free_arrays();
501     expressp_free_arrays();
502     files_free_arrays();
503
504     lexer_free_arrays();
505     linker_free_arrays();
506     memory_free_arrays();
507     objects_free_arrays();
508     states_free_arrays();
509     symbols_free_arrays();
510     syntax_free_arrays();
511     tables_free_arrays();
512     text_free_arrays();
513     veneer_free_arrays();
514     verbs_free_arrays();
515 }
516
517 /* ------------------------------------------------------------------------- */
518 /*    Name translation code for filenames                                    */
519 /* ------------------------------------------------------------------------- */
520
521 static char Source_Path[PATHLEN];
522 static char Include_Path[PATHLEN];
523 static char Code_Path[PATHLEN];
524 static char Module_Path[PATHLEN];
525 static char current_source_path[PATHLEN];
526        char Debugging_Name[PATHLEN];
527        char Transcript_Name[PATHLEN];
528        char Language_Name[PATHLEN];
529        char Charset_Map[PATHLEN];
530 static char ICL_Path[PATHLEN];
531
532 /* Set one of the above Path buffers to the given location, or list of
533    locations. (A list is comma-separated, and only accepted for Source_Path,
534    Include_Path, ICL_Path, Module_Path.)
535 */
536 static void set_path_value(char *path, char *value)
537 {   int i, j;
538
539     for (i=0, j=0;;)
540     {
541         if (i >= PATHLEN-1) {
542             printf("A specified path is longer than %d characters.\n",
543                 PATHLEN-1);
544             exit(1);
545         }
546         if ((value[j] == FN_ALT) || (value[j] == 0))
547         {   if ((value[j] == FN_ALT)
548                 && (path != Source_Path) && (path != Include_Path)
549                 && (path != ICL_Path) && (path != Module_Path))
550             {   printf("The character '%c' is used to divide entries in a list \
551 of possible locations, and can only be used in the Include_Path, Source_Path, \
552 Module_Path or ICL_Path variables. Other paths are for output only.\n", FN_ALT);
553                 exit(1);
554             }
555             if ((path != Debugging_Name) && (path != Transcript_Name)
556                  && (path != Language_Name) && (path != Charset_Map)
557                  && (i>0) && (isalnum(path[i-1]))) path[i++] = FN_SEP;
558             path[i++] = value[j++];
559             if (value[j-1] == 0) return;
560         }
561         else path[i++] = value[j++];
562     }
563 }
564
565 /* Prepend the given location or list of locations to one of the above
566    Path buffers. This is only permitted for Source_Path, Include_Path, 
567    ICL_Path, Module_Path.
568
569    An empty field (in the comma-separated list) means the current
570    directory. If the Path buffer is entirely empty, we assume that
571    we want to search both value and the current directory, so
572    the result will be "value,".
573 */
574 static void prepend_path_value(char *path, char *value)
575 {
576     int i, j;
577     int oldlen = strlen(path);
578     int newlen;
579     char new_path[PATHLEN];
580
581     if ((path != Source_Path) && (path != Include_Path)
582         && (path != ICL_Path) && (path != Module_Path))
583     {   printf("The character '+' is used to add to a list \
584 of possible locations, and can only be used in the Include_Path, Source_Path, \
585 Module_Path or ICL_Path variables. Other paths are for output only.\n");
586         exit(1);
587     }
588
589     for (i=0, j=0;;)
590     {
591         if (i >= PATHLEN-1) {
592             printf("A specified path is longer than %d characters.\n",
593                 PATHLEN-1);
594             exit(1);
595         }
596         if ((value[j] == FN_ALT) || (value[j] == 0))
597         {   if ((path != Debugging_Name) && (path != Transcript_Name)
598                  && (path != Language_Name) && (path != Charset_Map)
599                  && (i>0) && (isalnum(new_path[i-1]))) new_path[i++] = FN_SEP;
600             new_path[i++] = value[j++];
601             if (value[j-1] == 0) {
602                 newlen = i-1;
603                 break;
604             }
605         }
606         else new_path[i++] = value[j++];
607     }
608
609     if (newlen+1+oldlen >= PATHLEN-1) {
610         printf("A specified path is longer than %d characters.\n",
611             PATHLEN-1);
612         exit(1);
613     }
614
615     i = newlen;
616     new_path[i++] = FN_ALT;
617     for (j=0; j<oldlen;)
618         new_path[i++] = path[j++];
619     new_path[i] = 0;
620     
621     strcpy(path, new_path);
622 }
623
624 static void set_default_paths(void)
625 {
626     set_path_value(Source_Path,     Source_Directory);
627     set_path_value(Include_Path,    Include_Directory);
628     set_path_value(Code_Path,       Code_Directory);
629     set_path_value(Module_Path,     Module_Directory);
630     set_path_value(ICL_Path,        ICL_Directory);
631     set_path_value(Debugging_Name,  Debugging_File);
632     set_path_value(Transcript_Name, Transcript_File);
633     set_path_value(Language_Name,   Default_Language);
634     set_path_value(Charset_Map,     "");
635 }
636
637 /* Parse a path option which looks like "dir", "+dir", "pathname=dir",
638    or "+pathname=dir". If there is no "=", we assume "include_path=...".
639    If the option begins with a "+" the directory is prepended to the
640    existing path instead of replacing it.
641 */
642 static void set_path_command(char *command)
643 {   int i, j; char *path_to_set = NULL;
644     int prepend = 0;
645
646     if (command[0] == '+') {
647         prepend = 1;
648         command++;
649     }
650
651     for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;
652
653     path_to_set=Include_Path; 
654
655     if (command[i] == '=') { 
656         char pathname[PATHLEN];
657         if (i>=PATHLEN) i=PATHLEN-1;
658         for (j=0;j<i;j++) {
659             char ch = command[j];
660             if (isupper(ch)) ch=tolower(ch);
661             pathname[j]=ch;
662         }
663         pathname[j]=0;
664         command = command+i+1;
665
666         path_to_set = NULL;
667         if (strcmp(pathname, "source_path")==0)  path_to_set=Source_Path;
668         if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
669         if (strcmp(pathname, "code_path")==0)    path_to_set=Code_Path;
670         if (strcmp(pathname, "module_path")==0)  path_to_set=Module_Path;
671         if (strcmp(pathname, "icl_path")==0)     path_to_set=ICL_Path;
672         if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
673         if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;
674         if (strcmp(pathname, "language_name")==0) path_to_set=Language_Name;
675         if (strcmp(pathname, "charset_map")==0) path_to_set=Charset_Map;
676
677         if (path_to_set == NULL)
678         {   printf("No such path setting as \"%s\"\n", pathname);
679             exit(1);
680         }
681     }
682
683     if (!prepend)
684         set_path_value(path_to_set, command);
685     else
686         prepend_path_value(path_to_set, command);
687 }
688
689 static int contains_separator(char *name)
690 {   int i;
691     for (i=0; name[i]!=0; i++)
692         if (name[i] == FN_SEP) return 1;
693     return 0;
694 }
695
696 static int write_translated_name(char *new_name, char *old_name,
697                                  char *prefix_path, int start_pos,
698                                  char *extension)
699 {   int x;
700     if (strlen(old_name)+strlen(extension) >= PATHLEN) {
701         printf("One of your filenames is longer than %d characters.\n", PATHLEN);
702         exit(1);
703     }
704     if (prefix_path == NULL)
705     {   sprintf(new_name,"%s%s", old_name, extension);
706         return 0;
707     }
708     strcpy(new_name, prefix_path + start_pos);
709     for (x=0; (new_name[x]!=0) && (new_name[x]!=FN_ALT); x++) ;
710     if (new_name[x] == 0) start_pos = 0; else start_pos += x+1;
711     if (x+strlen(old_name)+strlen(extension) >= PATHLEN) {
712         printf("One of your pathnames is longer than %d characters.\n", PATHLEN);
713         exit(1);
714     }
715     sprintf(new_name + x, "%s%s", old_name, extension);
716     return start_pos;
717 }
718
719 #ifdef FILE_EXTENSIONS
720 static char *check_extension(char *name, char *extension)
721 {   int i;
722
723     /* If a filename ends in '.', remove the dot and add no file extension: */
724     i = strlen(name)-1;
725     if (name[i] == '.') { name[i]=0; return ""; }
726
727     /* Remove the new extension if it's already got one: */
728
729     for (; (i>=0) && (name[i]!=FN_SEP); i--)
730         if (name[i] == '.') return "";
731     return extension;
732 }
733 #endif
734
735 /* ------------------------------------------------------------------------- */
736 /*    Three translation routines have to deal with path variables which may  */
737 /*    contain alternative locations separated by the FN_ALT character.       */
738 /*    These have the protocol:                                               */
739 /*                                                                           */
740 /*        int translate_*_filename(int last_value, ...)                      */
741 /*                                                                           */
742 /*    and should first be called with last_value equal to 0.  If the         */
743 /*    translated filename works, fine.  Otherwise, if the returned integer   */
744 /*    was zero, the caller knows that no filename works and can issue an     */
745 /*    error message.  If it was non-zero, the caller should pass it on as    */
746 /*    the last_value again.                                                  */
747 /*                                                                           */
748 /*    As implemented below, last_value is the position in the path variable  */
749 /*    string at which the next directory name to try begins.                 */
750 /* ------------------------------------------------------------------------- */
751
752 extern int translate_in_filename(int last_value,
753     char *new_name, char *old_name,
754     int same_directory_flag, int command_line_flag)
755 {   char *prefix_path = NULL;
756     char *extension;
757     int add_path_flag = 1;
758     int i;
759
760     if ((same_directory_flag==0)
761         && (contains_separator(old_name)==1)) add_path_flag=0;
762
763     if (add_path_flag==1)
764     {   if (command_line_flag == 0)
765         {   /* File is opened as a result of an Include directive */
766
767             if (same_directory_flag==1)
768                 prefix_path = current_source_path;
769             else
770                 if (Include_Path[0]!=0) prefix_path = Include_Path;
771         }
772         /* Main file being opened from the command line */
773
774         else if (Source_Path[0]!=0) prefix_path = Source_Path;
775     }
776
777 #ifdef FILE_EXTENSIONS
778     /* Which file extension is expected? */
779
780     if ((command_line_flag==1)||(same_directory_flag==1))
781         extension = Source_Extension;
782     else
783         extension = Include_Extension;
784
785     extension = check_extension(old_name, extension);
786 #else
787     extension = "";
788 #endif
789
790     last_value = write_translated_name(new_name, old_name,
791                      prefix_path, last_value, extension);
792
793     /* Set the "current source path" (for use of Include ">...") */
794
795     if (command_line_flag==1)
796     {   strcpy(current_source_path, new_name);
797         for (i=strlen(current_source_path)-1;
798              ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
799
800         if (i!=0) current_source_path[i+1] = 0; /* Current file in subdir   */
801         else current_source_path[0] = 0;        /* Current file at root dir */
802     }
803
804     return last_value;
805 }
806
807 extern int translate_link_filename(int last_value,
808     char *new_name, char *old_name)
809 {   char *prefix_path = NULL;
810     char *extension;
811
812     if (contains_separator(old_name)==0)
813         if (Module_Path[0]!=0)
814             prefix_path = Module_Path;
815
816 #ifdef FILE_EXTENSIONS
817     extension = check_extension(old_name, Module_Extension);
818 #else
819     extension = "";
820 #endif
821
822     return write_translated_name(new_name, old_name,
823                prefix_path, last_value, extension);
824 }
825
826 static int translate_icl_filename(int last_value,
827     char *new_name, char *old_name)
828 {   char *prefix_path = NULL;
829     char *extension = "";
830
831     if (contains_separator(old_name)==0)
832         if (ICL_Path[0]!=0)
833             prefix_path = ICL_Path;
834
835 #ifdef FILE_EXTENSIONS
836     extension = check_extension(old_name, ICL_Extension);
837 #endif
838
839     return write_translated_name(new_name, old_name,
840                prefix_path, last_value, extension);
841 }
842
843 extern void translate_out_filename(char *new_name, char *old_name)
844 {   char *prefix_path;
845     char *extension = "";
846     int i;
847
848     /* If !convert_filename_flag, then the old_name is just the <file2>
849        parameter on the Inform command line, which we leave alone. */
850
851     if (!convert_filename_flag)
852     {   strcpy(new_name, old_name); return;
853     }
854
855     /* Remove any pathname or extension in <file1>. */
856
857     if (contains_separator(old_name)==1)
858     {   for (i=strlen(old_name)-1; (i>0)&&(old_name[i]!=FN_SEP) ;i--) { };
859         if (old_name[i]==FN_SEP) i++;
860         old_name += i;
861     }
862 #ifdef FILE_EXTENSIONS
863     for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
864     if (old_name[i] == '.') old_name[i] = 0;
865 #endif
866
867     prefix_path = NULL;
868     if (module_switch)
869     {   extension = Module_Extension;
870         if (Module_Path[0]!=0) prefix_path = Module_Path;
871     }
872     else
873     {
874         if (!glulx_mode) {
875             switch(version_number)
876             {   case 3: extension = Code_Extension;   break;
877                 case 4: extension = V4Code_Extension; break;
878                 case 5: extension = V5Code_Extension; break;
879                 case 6: extension = V6Code_Extension; break;
880                 case 7: extension = V7Code_Extension; break;
881                 case 8: extension = V8Code_Extension; break;
882             }
883         }
884         else {
885             extension = GlulxCode_Extension;
886         }
887         if (Code_Path[0]!=0) prefix_path = Code_Path;
888     }
889
890 #ifdef FILE_EXTENSIONS
891     extension = check_extension(old_name, extension);
892 #endif
893
894     write_translated_name(new_name, old_name, prefix_path, 0, extension);
895 }
896
897 static char *name_or_unset(char *p)
898 {   if (p[0]==0) return "(unset)";
899     return p;
900 }
901
902 static void help_on_filenames(void)
903 {   char old_name[PATHLEN];
904     char new_name[PATHLEN];
905     int save_mm = module_switch, x;
906
907     module_switch = FALSE;
908
909     printf("Help information on filenames:\n\n");
910
911     printf(
912 "The command line can take one of two forms:\n\n\
913   inform [commands...] <file1>\n\
914   inform [commands...] <file1> <file2>\n\n\
915 Inform translates <file1> into a source file name (see below) for its input.\n\
916 <file2> is usually omitted: if so, the output filename is made from <file1>\n\
917 by cutting out the name part and translating that (see below).\n\
918 If <file2> is given, however, the output filename is set to just <file2>\n\
919 (not altered in any way).\n\n");
920
921     printf(
922 "Filenames given in the game source (with commands like Include \"name\" and\n\
923 Link \"name\") are also translated by the rules below.\n\n");
924
925     printf(
926 "Rules of translation:\n\n\
927 Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
928 (such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
929 1. If the name contains a '%c' character (so it's already a pathname), it\n\
930    isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);
931
932     printf(
933 "   [Exception: when the name is given in an Include command using the >\n\
934    form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
935    of the file doing the inclusion");
936 #ifdef FILE_EXTENSIONS
937                           printf(" and a suitable file extension is added");
938 #endif
939     printf(".]\n\n");
940
941     printf(
942 "   Filenames must never contain double-quotation marks \".  To use filenames\n\
943    which contain spaces, write them in double-quotes: for instance,\n\n\
944    \"inform +code_path=\"Jigsaw Final Version\" jigsaw\".\n\n");
945
946     printf(
947 "2. The file is looked for at a particular \"path\" (the filename of a\n\
948    directory), depending on what kind of file it is.\n\n\
949        File type              Name                Current setting\n\n\
950        Source code (in)       source_path         %s\n\
951        Include file (in)      include_path        %s\n\
952        Story file (out)       code_path           %s\n",
953    name_or_unset(Source_Path), name_or_unset(Include_Path),
954    name_or_unset(Code_Path));
955
956     printf(
957 "       ICL command file (in)  icl_path            %s\n\
958        Module (in & out)      module_path         %s\n\n",
959    name_or_unset(ICL_Path), name_or_unset(Module_Path));
960
961     printf(
962 "   If the path is unset, then the current working directory is used (so\n\
963    the filename doesn't change): if, for instance, include_path is set to\n\
964    \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
965    \"backup%coldlib%cparser\".\n\n\
966    The paths can be set or unset on the Inform command line by, eg,\n\
967    \"inform +code_path=finished jigsaw\" or\n\
968    \"inform +include_path= balances\" (which unsets include_path).\n\n",
969         FN_SEP, FN_SEP, FN_SEP);
970
971     printf(
972 "   The four input path variables can be set to lists of alternative paths\n\
973    separated by '%c' characters: these alternatives are always tried in\n\
974    the order they are specified in, that is, left to right through the text\n\
975    in the path variable.\n\n",
976    FN_ALT);
977     printf(
978 "   If two '+' signs are used (\"inform ++include_path=dir jigsaw\") then\n\
979    the path or paths are added to the existing list.\n\n");
980     printf(
981 "   (Modules are written to the first alternative in the module_path list;\n\
982    it is an error to give alternatives at all for purely output paths.)\n\n");
983
984 #ifdef FILE_EXTENSIONS
985     printf("3. The following file extensions are added:\n\n\
986       Source code:     %s\n\
987       Include files:   %s\n\
988       Story files:     %s (Version 3), %s (v4), %s (v5, the default),\n\
989                        %s (v6), %s (v7), %s (v8), %s (Glulx)\n\
990       Modules:         %s\n\n",
991       Source_Extension, Include_Extension,
992       Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
993       V7Code_Extension, V8Code_Extension, GlulxCode_Extension, 
994       Module_Extension);
995     printf("\
996    except that any extension you give (on the command line or in a filename\n\
997    used in a program) will override these.  If you give the null extension\n\
998    \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
999 #endif
1000
1001     printf("Names of four individual files can also be set using the same\n\
1002   + command notation (though they aren't really pathnames).  These are:\n\n\
1003       transcript_name  (text written by -r switch): now \"%s\"\n\
1004       debugging_name   (data written by -k switch): now \"%s\"\n\
1005       language_name    (library file defining natural language of game):\n\
1006                        now \"%s\"\n\
1007       charset_map      (file for character set mapping): now \"%s\"\n\n",
1008     Transcript_Name, Debugging_Name, Language_Name, Charset_Map);
1009
1010     translate_in_filename(0, new_name, "rezrov", 0, 1);
1011     printf("Examples: 1. \"inform rezrov\"\n\
1012   the source code is read from \"%s\"\n",
1013         new_name);
1014     convert_filename_flag = TRUE;
1015     translate_out_filename(new_name, "rezrov");
1016     printf("  and a story file is compiled to \"%s\".\n\n", new_name);
1017
1018     translate_in_filename(0, new_name, "frotz", 0, 1);
1019     printf("2. \"inform -M frotz\"\n\
1020   the source code is read from \"%s\"\n",
1021         new_name);
1022     module_switch = TRUE;
1023     convert_filename_flag = TRUE;
1024     translate_out_filename(new_name, "frotz");
1025     printf("  and a module is compiled to \"%s\".\n\n", new_name);
1026
1027     module_switch = FALSE;
1028
1029     sprintf(old_name, "demos%cplugh", FN_SEP);
1030     printf("3. \"inform %s\"\n", old_name);
1031     translate_in_filename(0, new_name, old_name, 0, 1);
1032     printf("  the source code is read from \"%s\"\n", new_name);
1033     sprintf(old_name, "demos%cplugh", FN_SEP);
1034     convert_filename_flag = TRUE;
1035     translate_out_filename(new_name, old_name);
1036     printf("  and a story file is compiled to \"%s\".\n\n", new_name);
1037
1038     printf("4. \"inform plover my_demo\"\n");
1039     translate_in_filename(0, new_name, "plover", 0, 1);
1040     printf("  the source code is read from \"%s\"\n", new_name);
1041     convert_filename_flag = FALSE;
1042     translate_out_filename(new_name, "my_demo");
1043     printf("  and a story file is compiled to \"%s\".\n\n", new_name);
1044
1045     strcpy(old_name, Source_Path);
1046     sprintf(new_name, "%cnew%cold%crecent%cold%cancient",
1047         FN_ALT, FN_ALT, FN_SEP, FN_ALT, FN_SEP);
1048     printf("5. \"inform +source_path=%s zooge\"\n", new_name);
1049     printf(
1050 "   Note that four alternative paths are given, the first being the empty\n\
1051    path-name (meaning: where you are now).  Inform looks for the source code\n\
1052    by trying these four places in turn, stopping when it finds anything:\n\n");
1053
1054     set_path_value(Source_Path, new_name);
1055     x = 0;
1056     do
1057     {   x = translate_in_filename(x, new_name, "zooge", 0, 1);
1058         printf("     \"%s\"\n", new_name);
1059     } while (x != 0);
1060     strcpy(Source_Path, old_name);
1061     module_switch = save_mm;
1062 }
1063
1064 #ifdef ARCHIMEDES
1065 static char riscos_ft_buffer[4];
1066
1067 extern char *riscos_file_type(void)
1068 {
1069     if (riscos_file_type_format == 1)
1070     {   if (module_switch) return("data");
1071         return("11A");
1072     }
1073
1074     if (module_switch) return("075");
1075
1076     sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
1077     return(riscos_ft_buffer);
1078 }
1079 #endif
1080
1081 /* ------------------------------------------------------------------------- */
1082 /*   The compilation pass                                                    */
1083 /* ------------------------------------------------------------------------- */
1084
1085 static void run_pass(void)
1086 {
1087     lexer_begin_prepass();
1088     files_begin_prepass();
1089     load_sourcefile(Source_Name, 0);
1090
1091     begin_pass();
1092
1093     parse_program(NULL);
1094
1095     find_the_actions();
1096     issue_unused_warnings();
1097     compile_veneer();
1098
1099     lexer_endpass();
1100     if (module_switch) linker_endpass();
1101
1102     issue_debug_symbol_warnings();
1103     
1104     close_all_source();
1105     if (hash_switch && hash_printed_since_newline) printf("\n");
1106
1107     sort_dictionary();
1108     if (track_unused_routines)
1109         locate_dead_functions();
1110     construct_storyfile();
1111 }
1112
1113 int output_has_occurred;
1114
1115 static void rennab(float time_taken)
1116 {   /*  rennab = reverse of banner  */
1117     int t = no_warnings + no_suppressed_warnings;
1118
1119     if (memout_switch) print_memory_usage();
1120
1121     if ((no_errors + t)!=0)
1122     {   printf("Compiled with ");
1123         if (no_errors > 0)
1124         {   printf("%d error%s", no_errors,(no_errors==1)?"":"s");
1125             if (t > 0) printf(" and ");
1126         }
1127         if (no_warnings > 0)
1128             printf("%d warning%s", t, (t==1)?"":"s");
1129         if (no_suppressed_warnings > 0)
1130         {   if (no_warnings > 0)
1131                 printf(" (%d suppressed)", no_suppressed_warnings);
1132             else
1133             printf("%d suppressed warning%s", no_suppressed_warnings,
1134                 (no_suppressed_warnings==1)?"":"s");
1135         }
1136         if (output_has_occurred == FALSE) printf(" (no output)");
1137         printf("\n");
1138     }
1139
1140     if (no_compiler_errors > 0) print_sorry_message();
1141
1142     if (statistics_switch)
1143     {
1144         /* Print the duration to a sensible number of decimal places.
1145            (We aim for three significant figures.) */
1146         if (time_taken >= 10.0)
1147             printf("Completed in %.1f seconds\n", time_taken);
1148         else if (time_taken >= 1.0)
1149             printf("Completed in %.2f seconds\n", time_taken);
1150         else
1151             printf("Completed in %.3f seconds\n", time_taken);
1152     }
1153 }
1154
1155 /* ------------------------------------------------------------------------- */
1156 /*   The compiler abstracted to a routine.                                   */
1157 /* ------------------------------------------------------------------------- */
1158
1159 static int execute_icl_header(char *file1);
1160
1161 static int compile(int number_of_files_specified, char *file1, char *file2)
1162 {
1163     TIMEVALUE time_start, time_end;
1164     float duration;
1165
1166     if (execute_icl_header(file1))
1167       return 1;
1168
1169     select_target(glulx_mode);
1170
1171     if (define_INFIX_switch && glulx_mode) {
1172         printf("Infix (-X) facilities are not available in Glulx: \
1173 disabling -X switch\n");
1174         define_INFIX_switch = FALSE;
1175     }
1176
1177     if (module_switch && glulx_mode) {
1178         printf("Modules are not available in Glulx: \
1179 disabling -M switch\n");
1180         module_switch = FALSE;
1181     }
1182
1183     if (define_INFIX_switch && module_switch)
1184     {   printf("Infix (-X) facilities are not available when compiling \
1185 modules: disabling -X switch\n");
1186         define_INFIX_switch = FALSE;
1187     }
1188     if (runtime_error_checking_switch && module_switch)
1189     {   printf("Strict checking (-S) facilities are not available when \
1190 compiling modules: disabling -S switch\n");
1191         runtime_error_checking_switch = FALSE;
1192     }
1193
1194     TIMEVALUE_NOW(&time_start);
1195     
1196     no_compilations++;
1197
1198     strcpy(Source_Name, file1); convert_filename_flag = TRUE;
1199     strcpy(Code_Name, file1);
1200     if (number_of_files_specified == 2)
1201     {   strcpy(Code_Name, file2); convert_filename_flag = FALSE;
1202     }
1203
1204     init_vars();
1205
1206     if (debugfile_switch) begin_debug_file();
1207
1208     allocate_arrays();
1209
1210     if (transcript_switch) open_transcript_file(Source_Name);
1211
1212     run_pass();
1213
1214     if (transcript_switch)
1215     {   write_dictionary_to_transcript();
1216         close_transcript_file();
1217     }
1218
1219     if (no_errors==0) { output_file(); output_has_occurred = TRUE; }
1220     else { output_has_occurred = FALSE; }
1221
1222     if (debugfile_switch)
1223     {   end_debug_file();
1224     }
1225
1226     if (optimise_switch) {
1227         /* Pull out all_text so that it will not be freed. */
1228         extract_all_text();
1229     }
1230
1231     free_arrays();
1232
1233     TIMEVALUE_NOW(&time_end);
1234     duration = TIMEVALUE_DIFFERENCE(&time_start, &time_end);
1235     
1236     rennab(duration);
1237
1238     if (optimise_switch) {
1239         optimise_abbreviations();
1240         ao_free_arrays();
1241     }
1242
1243     return (no_errors==0)?0:1;
1244 }
1245
1246 /* ------------------------------------------------------------------------- */
1247 /*   The command line interpreter                                            */
1248 /* ------------------------------------------------------------------------- */
1249
1250 static void cli_print_help(int help_level)
1251 {
1252     printf(
1253 "\nThis program is a compiler of Infocom format (also called \"Z-machine\")\n\
1254 story files, as well as \"Glulx\" story files:\n\
1255 Copyright (c) Graham Nelson 1993 - 2022.\n\n");
1256
1257    /* For people typing just "inform", a summary only: */
1258
1259    if (help_level==0)
1260    {
1261
1262 #ifndef PROMPT_INPUT
1263   printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
1264 #else
1265   printf("When run, Inform prompts you for commands (and switches),\n\
1266 which are optional, then an input <file1> and an (optional) output\n\
1267 <file2>.\n\n");
1268 #endif
1269
1270   printf(
1271 "<file1> is the Inform source file of the game to be compiled. <file2>,\n\
1272 if given, overrides the filename Inform would normally use for the\n\
1273 compiled output.  Try \"inform -h1\" for file-naming conventions.\n\n\
1274 One or more words can be supplied as \"commands\". These may be:\n\n\
1275   -switches     a list of compiler switches, 1 or 2 letter\n\
1276                 (see \"inform -h2\" for the full range)\n\n\
1277   +dir          set Include_Path to this directory\n\
1278   ++dir         add this directory to Include_Path\n\
1279   +PATH=dir     change the PATH to this directory\n\
1280   ++PATH=dir    add this directory to the PATH\n\n\
1281   $...          one of the following configuration commands:\n");
1282   
1283   printf(
1284 "     $list            list current settings\n\
1285      $?SETTING        explain briefly what SETTING is for\n\
1286      $SETTING=number  change SETTING to given number\n\
1287      $!TRACEOPT       set trace option TRACEOPT\n\
1288                       (or $!TRACEOPT=2, 3, etc for more tracing;\n\
1289                       $! by itself to list all trace options)\n\
1290      $#SYMBOL=number  define SYMBOL as a constant in the story\n\n");
1291
1292   printf(
1293 "  (filename)    read in a list of commands (in the format above)\n\
1294                 from this \"setup file\"\n\n");
1295
1296   printf("Alternate command-line formats for the above:\n\
1297   --help                 (this page)\n\
1298   --path PATH=dir        (set path)\n\
1299   --addpath PATH=dir     (add to path)\n\
1300   --list                 (list current settings)\n\
1301   --helpopt SETTING      (explain setting)\n\
1302   --opt SETTING=number   (change setting)\n\
1303   --helptrace            (list all trace options)\n\
1304   --trace TRACEOPT       (set trace option)\n\
1305   --trace TRACEOPT=num   (more tracing)\n\
1306   --define SYMBOL=number (define constant)\n\
1307   --config filename      (read setup file)\n\n");
1308
1309 #ifndef PROMPT_INPUT
1310     printf("For example: \"inform -dexs curses\".\n");
1311 #endif
1312
1313        return;
1314    }
1315
1316    /* The -h1 (filenaming) help information: */
1317
1318    if (help_level == 1) { help_on_filenames(); return; }
1319
1320    /* The -h2 (switches) help information: */
1321
1322    printf("Help on the full list of legal switch commands:\n\n\
1323   a   trace assembly-language\n\
1324   a2  trace assembly with hex dumps\n\
1325   c   more concise error messages\n\
1326   d   contract double spaces after full stops in text\n\
1327   d2  contract double spaces after exclamation and question marks, too\n\
1328   e   economy mode (slower): make use of declared abbreviations\n");
1329
1330    printf("\
1331   f   frequencies mode: show how useful abbreviations are\n\
1332   g   traces calls to all game functions\n\
1333   g2  traces calls to all game and library functions\n\
1334   g3  traces calls to all functions (including veneer)\n\
1335   h   print general help information\n\
1336   h1  print help information on filenames and path options\n\
1337   h2  print help information on switches (this page)\n");
1338
1339    printf("\
1340   i   ignore default switches set within the file\n\
1341   k   output debugging information to \"%s\"\n",
1342           Debugging_Name);
1343    printf("\
1344   q   keep quiet about obsolete usages\n\
1345   r   record all the text to \"%s\"\n\
1346   s   give statistics\n",
1347       Transcript_Name);
1348
1349    printf("\
1350   u   work out most useful abbreviations (very very slowly)\n\
1351   v3  compile to version-3 (\"Standard\"/\"ZIP\") story file\n\
1352   v4  compile to version-4 (\"Plus\"/\"EZIP\") story file\n\
1353   v5  compile to version-5 (\"Advanced\"/\"XZIP\") story file: the default\n\
1354   v6  compile to version-6 (graphical/\"YZIP\") story file\n\
1355   v7  compile to version-7 (expanded \"Advanced\") story file\n\
1356   v8  compile to version-8 (expanded \"Advanced\") story file\n\
1357   w   disable warning messages\n\
1358   x   print # for every 100 lines compiled\n\
1359   z   print memory map of the virtual machine\n\n");
1360
1361 printf("\
1362   B   use big memory model (for large V6/V7 files)\n\
1363   C0  text character set is plain ASCII only\n\
1364   Cu  text character set is UTF-8\n\
1365   Cn  text character set is ISO 8859-n (n = 1 to 9)\n\
1366       (1 to 4, Latin1 to Latin4; 5, Cyrillic; 6, Arabic;\n\
1367        7, Greek; 8, Hebrew; 9, Latin5.  Default is -C1.)\n");
1368 printf("  D   insert \"Constant DEBUG;\" automatically\n");
1369 printf("  E0  Archimedes-style error messages%s\n",
1370       (error_format==0)?" (current setting)":"");
1371 printf("  E1  Microsoft-style error messages%s\n",
1372       (error_format==1)?" (current setting)":"");
1373 printf("  E2  Macintosh MPW-style error messages%s\n",
1374       (error_format==2)?" (current setting)":"");
1375 printf("  G   compile a Glulx game file\n");
1376 printf("  H   use Huffman encoding to compress Glulx strings\n");
1377 printf("  M   compile as a Module for future linking\n");
1378
1379 #ifdef ARCHIMEDES
1380 printf("\
1381   R0  use filetype 060 + version number for games (default)\n\
1382   R1  use official Acorn filetype 11A for all games\n");
1383 #endif
1384 printf("  S   compile strict error-checking at run-time (on by default)\n");
1385 #ifdef ARC_THROWBACK
1386 printf("  T   enable throwback of errors in the DDE\n");
1387 #endif
1388 printf("  U   insert \"Constant USE_MODULES;\" automatically\n");
1389 printf("  V   print the version and date of this program\n");
1390 printf("  Wn  header extension table is at least n words (n = 3 to 99)\n");
1391 printf("  X   compile with INFIX debugging facilities present\n");
1392   printf("\n");
1393 }
1394
1395 extern void switches(char *p, int cmode)
1396 {   int i, s=1, state;
1397     /* Here cmode is 0 if switches list is from a "Switches" directive
1398        and 1 if from a "-switches" command-line or ICL list */
1399
1400     if (cmode==1)
1401     {   if (p[0]!='-')
1402         {   printf(
1403                 "Ignoring second word which should be a -list of switches.\n");
1404             return;
1405         }
1406     }
1407     for (i=cmode; p[i]!=0; i+=s, s=1)
1408     {   state = TRUE;
1409         if (p[i] == '~')
1410         {   state = FALSE;
1411             i++;
1412         }
1413         switch(p[i])
1414         {
1415         case 'a': switch(p[i+1])
1416                   {   case '1': asm_trace_setting=1; s=2; break;
1417                       case '2': asm_trace_setting=2; s=2; break;
1418                       case '3': asm_trace_setting=3; s=2; break;
1419                       case '4': asm_trace_setting=4; s=2; break;
1420                       default: asm_trace_setting=1; break;
1421                   }
1422                   break;
1423         case 'c': concise_switch = state; break;
1424         case 'd': switch(p[i+1])
1425                   {   case '1': double_space_setting=1; s=2; break;
1426                       case '2': double_space_setting=2; s=2; break;
1427                       default: double_space_setting=1; break;
1428                   }
1429                   break;
1430         case 'e': economy_switch = state; break;
1431         case 'f': frequencies_setting = (state?1:0); break;
1432         case 'g': switch(p[i+1])
1433                   {   case '1': trace_fns_setting=1; s=2; break;
1434                       case '2': trace_fns_setting=2; s=2; break;
1435                       case '3': trace_fns_setting=3; s=2; break;
1436                       default: trace_fns_setting=1; break;
1437                   }
1438                   break;
1439         case 'h': switch(p[i+1])
1440                   {   case '1': cli_print_help(1); s=2; break;
1441                       case '2': cli_print_help(2); s=2; break;
1442                       case '0': s=2;
1443                       default:  cli_print_help(0); break;
1444                   }
1445                   break;
1446         case 'i': ignore_switches_switch = state; break;
1447         case 'k': if (cmode == 0)
1448                       error("The switch '-k' can't be set with 'Switches'");
1449                   else
1450                       debugfile_switch = state;
1451                   break;
1452         case 'q': obsolete_switch = state; break;
1453         case 'r': if (cmode == 0)
1454                       error("The switch '-r' can't be set with 'Switches'");
1455                   else
1456                       transcript_switch = state; break;
1457         case 's': statistics_switch = state; break;
1458         case 'u': if (cmode == 0) {
1459                       error("The switch '-u' can't be set with 'Switches'");
1460                       break;
1461                   }
1462                   optimise_switch = state; break;
1463         case 'v': if (glulx_mode) { s = select_glulx_version(p+i+1)+1; break; }
1464                   if ((cmode==0) && (version_set_switch)) { s=2; break; }
1465                   version_set_switch = TRUE; s=2;
1466                   switch(p[i+1])
1467                   {   case '3': select_version(3); break;
1468                       case '4': select_version(4); break;
1469                       case '5': select_version(5); break;
1470                       case '6': select_version(6); break;
1471                       case '7': select_version(7); break;
1472                       case '8': select_version(8); break;
1473                       default:  printf("-v must be followed by 3 to 8\n");
1474                                 version_set_switch=0; s=1;
1475                                 break;
1476                   }
1477                   if ((version_number < 5) && (r_e_c_s_set == FALSE))
1478                       runtime_error_checking_switch = FALSE;
1479                   break;
1480         case 'w': nowarnings_switch = state; break;
1481         case 'x': hash_switch = state; break;
1482         case 'z': memory_map_setting = (state ? 1 : 0); break;
1483         case 'B': oddeven_packing_switch = state; break;
1484         case 'C': s=2;
1485                   if (p[i+1] == 'u') {
1486                       character_set_unicode = TRUE;
1487                       /* Leave the set_setting on Latin-1, because that 
1488                          matches the first block of Unicode. */
1489                       character_set_setting = 1;
1490                   }
1491                   else 
1492                   {   character_set_setting=p[i+1]-'0';
1493                       if ((character_set_setting < 0)
1494                           || (character_set_setting > 9))
1495                       {   printf("-C must be followed by 'u' or 0 to 9. Defaulting to ISO-8859-1.\n");
1496                           character_set_unicode = FALSE;
1497                           character_set_setting = 1;
1498                       }
1499                   }
1500                   if (cmode == 0) change_character_set();
1501                   break;
1502         case 'D': define_DEBUG_switch = state; break;
1503         case 'E': switch(p[i+1])
1504                   {   case '0': s=2; error_format=0; break;
1505                       case '1': s=2; error_format=1; break;
1506                       case '2': s=2; error_format=2; break;
1507                       default:  error_format=1; break;
1508                   }
1509                   break;
1510         case 'M': module_switch = state;
1511                   if (state && (r_e_c_s_set == FALSE))
1512                       runtime_error_checking_switch = FALSE;
1513                   break;
1514 #ifdef ARCHIMEDES
1515         case 'R': switch(p[i+1])
1516                   {   case '0': s=2; riscos_file_type_format=0; break;
1517                       case '1': s=2; riscos_file_type_format=1; break;
1518                       default:  riscos_file_type_format=1; break;
1519                   }
1520                   break;
1521 #endif
1522 #ifdef ARC_THROWBACK
1523         case 'T': throwback_switch = state; break;
1524 #endif
1525         case 'S': runtime_error_checking_switch = state;
1526                   r_e_c_s_set = TRUE; break;
1527         case 'G': if (cmode == 0)
1528                       error("The switch '-G' can't be set with 'Switches'");
1529                   else if (version_set_switch)
1530                       error("The '-G' switch cannot follow the '-v' switch");
1531                   else
1532                   {   glulx_mode = state;
1533                       adjust_memory_sizes();
1534                   }
1535                   break;
1536         case 'H': compression_switch = state; break;
1537         case 'U': define_USE_MODULES_switch = state; break;
1538         case 'V': exit(0); break;
1539         case 'W': if ((p[i+1]>='0') && (p[i+1]<='9'))
1540                   {   s=2; ZCODE_HEADER_EXT_WORDS = p[i+1]-'0';
1541                       if ((p[i+2]>='0') && (p[i+2]<='9'))
1542                       {   s=3; ZCODE_HEADER_EXT_WORDS *= 10;
1543                           ZCODE_HEADER_EXT_WORDS += p[i+2]-'0';
1544                       }
1545                   }
1546                   break;
1547         case 'X': define_INFIX_switch = state; break;
1548         default:
1549           printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
1550               p[i]);
1551           break;
1552         }
1553     }
1554
1555     if (optimise_switch)
1556     {
1557         /* store_the_text is equivalent to optimise_switch; -u sets both.
1558            We could simplify this. */
1559         store_the_text=TRUE;
1560     }
1561 }
1562
1563 /* Check whether the string looks like an ICL command. */
1564 static int icl_command(char *p)
1565 {   if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
1566         || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
1567     return FALSE;
1568 }
1569
1570 static void icl_error(char *filename, int line)
1571 {   printf("Error in ICL file '%s', line %d:\n", filename, line);
1572 }
1573
1574 static void icl_header_error(char *filename, int line)
1575 {   printf("Error in ICL header of file '%s', line %d:\n", filename, line);
1576 }
1577
1578 static int copy_icl_word(char *from, char *to, int max)
1579 {
1580     /*  Copies one token from 'from' to 'to', null-terminated:
1581         returns the number of chars in 'from' read past (possibly 0).  */
1582
1583     int i, j, quoted_mode, truncated;
1584
1585     i = 0; truncated = 0;
1586     while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
1587            || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;
1588
1589     if (from[i] == '!')
1590     {   while (from[i] != 0) i++;
1591         to[0] = 0; return i;
1592     }
1593
1594     for (quoted_mode = FALSE, j=0;;)
1595     {   if (from[i] == 0) break;
1596         if (from[i] == 10) break;
1597         if (from[i] == 13) break;
1598         if (from[i] == TAB_CHARACTER) break;
1599         if ((from[i] == ' ') && (!quoted_mode)) break;
1600         if (from[i] == '\"') { quoted_mode = !quoted_mode; i++; }
1601         else to[j++] = from[i++];
1602         if (j == max) {
1603             j--;
1604             truncated = 1;
1605         }
1606     }
1607     to[j] = 0;
1608     if (truncated == 1)
1609         printf("The following parameter has been truncated:\n%s\n", to);
1610     return i;
1611 }
1612
1613 /* Copy a string, converting to uppercase. The to array should be
1614    (at least) max characters. Result will be null-terminated, so
1615    at most max-1 characters will be copied. 
1616 */
1617 static int strcpyupper(char *to, char *from, int max)
1618 {
1619     int ix;
1620     for (ix=0; ix<max-1; ix++) {
1621         char ch = from[ix];
1622         if (islower(ch)) ch = toupper(ch);
1623         to[ix] = ch;
1624     }
1625     to[ix] = 0;
1626     return ix;
1627 }
1628
1629 static void execute_icl_command(char *p);
1630 static int execute_dashdash_command(char *p, char *p2);
1631
1632 static int execute_icl_header(char *argname)
1633 {
1634   FILE *command_file;
1635   char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1636   int line = 0;
1637   int errcount = 0;
1638   int i;
1639   char filename[PATHLEN]; 
1640   int x = 0;
1641
1642   do
1643     {   x = translate_in_filename(x, filename, argname, 0, 1);
1644         command_file = fopen(filename,"r");
1645     } while ((command_file == NULL) && (x != 0));
1646   if (!command_file) {
1647     /* Fail silently. The regular compiler will try to open the file
1648        again, and report the problem. */
1649     return 0;
1650   }
1651
1652   while (feof(command_file)==0) {
1653     if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1654     line++;
1655     if (!(cli_buff[0] == '!' && cli_buff[1] == '%'))
1656       break;
1657     i = copy_icl_word(cli_buff+2, fw, CMD_BUF_SIZE);
1658     if (icl_command(fw)) {
1659       execute_icl_command(fw);
1660       copy_icl_word(cli_buff+2 + i, fw, CMD_BUF_SIZE);
1661       if ((fw[0] != 0) && (fw[0] != '!')) {
1662         icl_header_error(filename, line);
1663         errcount++;
1664         printf("expected comment or nothing but found '%s'\n", fw);
1665       }
1666     }
1667     else {
1668       if (fw[0]!=0) {
1669         icl_header_error(filename, line);
1670         errcount++;
1671         printf("Expected command or comment but found '%s'\n", fw);
1672       }
1673     }
1674   }
1675   fclose(command_file);
1676
1677   return (errcount==0)?0:1;
1678 }
1679
1680
1681 static void run_icl_file(char *filename, FILE *command_file)
1682 {   char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1683     int i, x, line = 0;
1684     printf("[Running ICL file '%s']\n", filename);
1685
1686     while (feof(command_file)==0)
1687     {   if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1688         line++;
1689         i = copy_icl_word(cli_buff, fw, CMD_BUF_SIZE);
1690         if (icl_command(fw))
1691         {   execute_icl_command(fw);
1692             copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1693             if ((fw[0] != 0) && (fw[0] != '!'))
1694             {   icl_error(filename, line);
1695                 printf("expected comment or nothing but found '%s'\n", fw);
1696             }
1697         }
1698         else
1699         {   if (strcmp(fw, "compile")==0)
1700             {   char story_name[PATHLEN], code_name[PATHLEN];
1701                 i += copy_icl_word(cli_buff + i, story_name, PATHLEN);
1702                 i += copy_icl_word(cli_buff + i, code_name, PATHLEN);
1703
1704                 if (code_name[0] != 0) x=2;
1705                 else if (story_name[0] != 0) x=1;
1706                 else x=0;
1707
1708                 switch(x)
1709                 {   case 0: icl_error(filename, line);
1710                             printf("No filename given to 'compile'\n");
1711                             break;
1712                     case 1: printf("[Compiling <%s>]\n", story_name);
1713                             compile(x, story_name, code_name);
1714                             break;
1715                     case 2: printf("[Compiling <%s> to <%s>]\n",
1716                                 story_name, code_name);
1717                             compile(x, story_name, code_name);
1718                             copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1719                             if (fw[0]!=0)
1720                             {   icl_error(filename, line);
1721                         printf("Expected comment or nothing but found '%s'\n",
1722                                 fw);
1723                             }
1724                             break;
1725                 }
1726             }
1727             else
1728             if (fw[0]!=0)
1729             {   icl_error(filename, line);
1730                 printf("Expected command or comment but found '%s'\n", fw);
1731             }
1732         }
1733     }
1734 }
1735
1736 /* This should only be called if the argument has been verified to be
1737    an ICL command, e.g. by checking icl_command().
1738 */
1739 static void execute_icl_command(char *p)
1740 {   char filename[PATHLEN], cli_buff[CMD_BUF_SIZE];
1741     FILE *command_file;
1742     int len;
1743     
1744     switch(p[0])
1745     {   case '+': set_path_command(p+1); break;
1746         case '-': switches(p,1); break;
1747         case '$': memory_command(p+1); break;
1748         case '(': len = strlen(p);
1749                   if (p[len-1] != ')') {
1750                       printf("Error in ICL: (command) missing closing paren\n");
1751                       break;
1752                   }
1753                   len -= 2; /* omit parens */
1754                   if (len > CMD_BUF_SIZE-1) len = CMD_BUF_SIZE-1;
1755                   strncpy(cli_buff, p+1, len);
1756                   cli_buff[len]=0;
1757                   {   int x = 0;
1758                       do
1759                       {   x = translate_icl_filename(x, filename, cli_buff);
1760                           command_file = fopen(filename,"r");
1761                       } while ((command_file == NULL) && (x != 0));
1762                   }
1763                   if (command_file == NULL) {
1764                       printf("Error in ICL: Couldn't open command file '%s'\n",
1765                           filename);
1766                       break;
1767                   }
1768                   run_icl_file(filename, command_file);
1769                   fclose(command_file);
1770                   break;
1771     }
1772 }
1773
1774 /* Convert a --command into the equivalent ICL command and call 
1775    execute_icl_command(). The dashes have already been stripped.
1776
1777    The second argument is the following command-line argument 
1778    (or NULL if there was none). This may or may not be consumed.
1779    Returns TRUE if it was.
1780 */
1781 static int execute_dashdash_command(char *p, char *p2)
1782 {
1783     char cli_buff[CMD_BUF_SIZE];
1784     int consumed2 = FALSE;
1785     
1786     if (!strcmp(p, "help")) {
1787         strcpy(cli_buff, "-h");
1788     }
1789     else if (!strcmp(p, "list")) {
1790         strcpy(cli_buff, "$LIST");
1791     }
1792     else if (!strcmp(p, "size")) {
1793         consumed2 = TRUE;
1794         /* We accept these arguments even though they've been withdrawn. */
1795         if (!(p2 && (!strcmpcis(p2, "HUGE") || !strcmpcis(p2, "LARGE") || !strcmpcis(p2, "SMALL")))) {
1796             printf("--size must be followed by \"huge\", \"large\", or \"small\"\n");
1797             return consumed2;
1798         }
1799         strcpy(cli_buff, "$");
1800         strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1801     }
1802     else if (!strcmp(p, "opt")) {
1803         consumed2 = TRUE;
1804         if (!p2 || !strchr(p2, '=')) {
1805             printf("--opt must be followed by \"setting=number\"\n");
1806             return consumed2;
1807         }
1808         strcpy(cli_buff, "$");
1809         strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1810     }
1811     else if (!strcmp(p, "helpopt")) {
1812         consumed2 = TRUE;
1813         if (!p2) {
1814             printf("--helpopt must be followed by \"setting\"\n");
1815             return consumed2;
1816         }
1817         strcpy(cli_buff, "$?");
1818         strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1819     }
1820     else if (!strcmp(p, "define")) {
1821         consumed2 = TRUE;
1822         if (!p2) {
1823             printf("--define must be followed by \"symbol=number\"\n");
1824             return consumed2;
1825         }
1826         strcpy(cli_buff, "$#");
1827         strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1828     }
1829     else if (!strcmp(p, "path")) {
1830         consumed2 = TRUE;
1831         if (!p2 || !strchr(p2, '=')) {
1832             printf("--path must be followed by \"name=path\"\n");
1833             return consumed2;
1834         }
1835         snprintf(cli_buff, CMD_BUF_SIZE, "+%s", p2);
1836     }
1837     else if (!strcmp(p, "addpath")) {
1838         consumed2 = TRUE;
1839         if (!p2 || !strchr(p2, '=')) {
1840             printf("--addpath must be followed by \"name=path\"\n");
1841             return consumed2;
1842         }
1843         snprintf(cli_buff, CMD_BUF_SIZE, "++%s", p2);
1844     }
1845     else if (!strcmp(p, "config")) {
1846         consumed2 = TRUE;
1847         if (!p2) {
1848             printf("--config must be followed by \"file.icl\"\n");
1849             return consumed2;
1850         }
1851         snprintf(cli_buff, CMD_BUF_SIZE, "(%s)", p2);
1852     }
1853     else if (!strcmp(p, "trace")) {
1854         consumed2 = TRUE;
1855         if (!p2) {
1856             printf("--trace must be followed by \"traceopt\" or \"traceopt=N\"\n");
1857             return consumed2;
1858         }
1859         snprintf(cli_buff, CMD_BUF_SIZE, "$!%s", p2);
1860     }
1861     else if (!strcmp(p, "helptrace")) {
1862         strcpy(cli_buff, "$!");
1863     }
1864     else {
1865         printf("Option \"--%s\" unknown (try \"inform -h\")\n", p);
1866         return FALSE;
1867     }
1868
1869     execute_icl_command(cli_buff);
1870     return consumed2;
1871 }
1872
1873 /* ------------------------------------------------------------------------- */
1874 /*   Opening and closing banners                                             */
1875 /* ------------------------------------------------------------------------- */
1876
1877 char banner_line[CMD_BUF_SIZE];
1878
1879 /* We store the banner text for use elsewhere (see files.c).
1880 */
1881 static void banner(void)
1882 {
1883     int len;
1884     snprintf(banner_line, CMD_BUF_SIZE, "Inform %d.%d%d",
1885         (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10);
1886 #ifdef RELEASE_SUFFIX
1887     len = strlen(banner_line);
1888     snprintf(banner_line+len, CMD_BUF_SIZE-len, "%s", RELEASE_SUFFIX);
1889 #endif
1890 #ifdef MACHINE_STRING
1891     len = strlen(banner_line);
1892     snprintf(banner_line+len, CMD_BUF_SIZE-len, " for %s", MACHINE_STRING);
1893 #endif
1894     len = strlen(banner_line);
1895     snprintf(banner_line+len, CMD_BUF_SIZE-len, " (%s)", RELEASE_DATE);
1896     
1897     printf("%s\n", banner_line);
1898 }
1899
1900 /* ------------------------------------------------------------------------- */
1901 /*   Input from the outside world                                            */
1902 /* ------------------------------------------------------------------------- */
1903
1904 #ifdef PROMPT_INPUT
1905 static void read_command_line(int argc, char **argv)
1906 {   int i;
1907     char buffer1[PATHLEN], buffer2[PATHLEN], buffer3[PATHLEN];
1908     i=0;
1909     printf("Source filename?\n> ");
1910     while (gets(buffer1)==NULL); cli_file1=buffer1;
1911     printf("Output filename (RETURN for the same)?\n> ");
1912     while (gets(buffer2)==NULL); cli_file2=buffer2;
1913     cli_files_specified=1;
1914     if (buffer2[0]!=0) cli_files_specified=2;
1915     do
1916     {   printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
1917         while (gets(buffer3)==NULL); execute_icl_command(buffer3);
1918     } while (buffer3[0]!=0);
1919 }
1920 #else
1921 static void read_command_line(int argc, char **argv)
1922 {   int i;
1923     if (argc==1) switches("-h",1);
1924
1925     for (i=1, cli_files_specified=0; i<argc; i++)
1926         if (argv[i][0] == '-' && argv[i][1] == '-') {
1927             char *nextarg = NULL;
1928             int consumed2;
1929             if (i+1 < argc) nextarg = argv[i+1];
1930             consumed2 = execute_dashdash_command(argv[i]+2, nextarg);
1931             if (consumed2 && i+1 < argc) {
1932                 i++;
1933             }
1934         }
1935         else if (icl_command(argv[i])) {
1936             execute_icl_command(argv[i]);
1937         }
1938         else {
1939             switch(++cli_files_specified)
1940             {   case 1: cli_file1 = argv[i]; break;
1941                 case 2: cli_file2 = argv[i]; break;
1942                 default:
1943                     printf("Command line error: unknown parameter '%s'\n",
1944                         argv[i]); return;
1945             }
1946         }
1947 }
1948 #endif
1949
1950 /* ------------------------------------------------------------------------- */
1951 /*   M A I N : An outer shell for machine-specific quirks                    */
1952 /*   Omitted altogether if EXTERNAL_SHELL is defined, as for instance is     */
1953 /*   needed for the Macintosh front end.                                     */
1954 /* ------------------------------------------------------------------------- */
1955
1956 #ifdef EXTERNAL_SHELL
1957 extern int sub_main(int argc, char **argv);
1958 #else
1959
1960 static int sub_main(int argc, char **argv);
1961 #ifdef MAC_MPW
1962 int main(int argc, char **argv, char *envp[])
1963 #else
1964 int main(int argc, char **argv)
1965 #endif
1966 {   int rcode;
1967 #ifdef MAC_MPW
1968     InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
1969 #endif
1970     rcode = sub_main(argc, argv);
1971 #ifdef ARC_THROWBACK
1972     throwback_end();
1973 #endif
1974     return rcode;
1975 }
1976
1977 #endif
1978
1979 /* ------------------------------------------------------------------------- */
1980 /*   M A I N  II:  Starting up ICL with the command line                     */
1981 /* ------------------------------------------------------------------------- */
1982
1983 #ifdef EXTERNAL_SHELL
1984 extern int sub_main(int argc, char **argv)
1985 #else
1986 static int sub_main(int argc, char **argv)
1987 #endif
1988 {   int return_code;
1989
1990 #ifdef MAC_FACE
1991     ProcessEvents (&g_proc);
1992     if (g_proc != true)
1993     {   free_arrays();
1994         if (store_the_text) my_free(&all_text,"transcription text");
1995         longjmp (g_fallback, 1);
1996     }
1997 #endif
1998
1999     banner();
2000
2001     set_memory_sizes(); set_default_paths();
2002     reset_switch_settings(); select_version(5);
2003
2004     cli_files_specified = 0; no_compilations = 0;
2005     cli_file1 = "source"; cli_file2 = "output";
2006
2007     read_command_line(argc, argv);
2008
2009     if (cli_files_specified > 0)
2010     {   return_code = compile(cli_files_specified, cli_file1, cli_file2);
2011
2012         if (return_code != 0) return(return_code);
2013     }
2014
2015     if (no_compilations == 0)
2016         printf("\n[No compilation requested]\n");
2017     if (no_compilations > 1)
2018         printf("[%d compilations completed]\n", no_compilations);
2019
2020     return(0);
2021 }
2022
2023 /* ========================================================================= */