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