1 /* ------------------------------------------------------------------------- */
2 /* "inform" : The top level of Inform: switches, pathnames, filenaming */
3 /* conventions, ICL (Inform Command Line) files, main */
5 /* Copyright (c) Graham Nelson 1993 - 2020 */
7 /* This file is part of Inform. */
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. */
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. */
19 /* You should have received a copy of the GNU General Public License */
20 /* along with Inform. If not, see https://gnu.org/licenses/ */
22 /* ------------------------------------------------------------------------- */
24 #define MAIN_INFORM_FILE
27 #define CMD_BUF_SIZE (256)
29 /* ------------------------------------------------------------------------- */
30 /* Compiler progress */
31 /* ------------------------------------------------------------------------- */
33 static int no_compilations;
35 int endofpass_flag; /* set to TRUE when an "end" directive is reached
36 (the inputs routines insert one into the stream
39 /* ------------------------------------------------------------------------- */
41 /* ------------------------------------------------------------------------- */
43 int version_number, /* 3 to 8 (Z-code) */
44 instruction_set_number,
45 /* 3 to 6: versions 7 and 8 use instruction set of
47 extend_memory_map; /* extend using function- and string-offsets */
48 int32 scale_factor, /* packed address multiplier */
49 length_scale_factor; /* length-in-header multiplier */
51 int32 requested_glulx_version;
53 extern void select_version(int vn)
54 { version_number = vn;
55 extend_memory_map = FALSE;
56 if ((version_number==6)||(version_number==7)) extend_memory_map = TRUE;
59 if (version_number==3) scale_factor = 2;
60 if (version_number==8) scale_factor = 8;
62 length_scale_factor = scale_factor;
63 if ((version_number==6)||(version_number==7)) length_scale_factor = 8;
65 instruction_set_number = version_number;
66 if ((version_number==7)||(version_number==8)) instruction_set_number = 5;
69 static int select_glulx_version(char *str)
71 /* Parse an "X.Y.Z" style version number, and store it for later use. */
73 int major=0, minor=0, patch=0;
76 major = major*10 + ((*cx++)-'0');
80 minor = minor*10 + ((*cx++)-'0');
84 patch = patch*10 + ((*cx++)-'0');
88 requested_glulx_version = ((major & 0x7FFF) << 16)
89 + ((minor & 0xFF) << 8)
94 /* ------------------------------------------------------------------------- */
95 /* Target: variables which vary between the Z-machine and Glulx */
96 /* ------------------------------------------------------------------------- */
98 int WORDSIZE; /* Size of a machine word: 2 or 4 */
99 int32 MAXINTWORD; /* 0x7FFF or 0x7FFFFFFF */
101 /* The first property number which is an individual property. The
102 eight class-system i-props (create, recreate, ... print_to_array)
103 are numbered from INDIV_PROP_START to INDIV_PROP_START+7.
105 int INDIV_PROP_START;
107 /* The length of an object, as written in tables.c. It's easier to define
108 it here than to repeat the same expression all over the source code.
111 int OBJECT_BYTE_LENGTH;
112 /* The total length of a dict entry, in bytes. Not used in Z-code.
114 int DICT_ENTRY_BYTE_LENGTH;
115 /* The position in a dict entry that the flag values begin.
118 int DICT_ENTRY_FLAG_POS;
120 static void select_target(int targ)
127 if (INDIV_PROP_START != 64) {
128 INDIV_PROP_START = 64;
129 fatalerror("You cannot change INDIV_PROP_START in Z-code");
131 if (DICT_WORD_SIZE != 6) {
133 fatalerror("You cannot change DICT_WORD_SIZE in Z-code");
135 if (DICT_CHAR_SIZE != 1) {
137 fatalerror("You cannot change DICT_CHAR_SIZE in Z-code");
139 if (NUM_ATTR_BYTES != 6) {
141 fatalerror("You cannot change NUM_ATTR_BYTES in Z-code");
143 if (MAX_LOCAL_VARIABLES != 16) {
144 MAX_LOCAL_VARIABLES = 16;
145 fatalerror("You cannot change MAX_LOCAL_VARIABLES in Z-code");
147 if (MAX_GLOBAL_VARIABLES != 240) {
148 MAX_GLOBAL_VARIABLES = 240;
149 fatalerror("You cannot change MAX_GLOBAL_VARIABLES in Z-code");
151 if (MAX_VERBS > 255) {
153 fatalerror("MAX_VERBS can only go above 255 when Glulx is used");
159 MAXINTWORD = 0x7FFFFFFF;
160 scale_factor = 0; /* It should never even get used in Glulx */
162 if (INDIV_PROP_START < 256) {
163 INDIV_PROP_START = 256;
164 warning_numbered("INDIV_PROP_START should be at least 256 in Glulx. Setting to", INDIV_PROP_START);
167 if (NUM_ATTR_BYTES % 4 != 3) {
168 NUM_ATTR_BYTES += (3 - (NUM_ATTR_BYTES % 4));
169 warning_numbered("NUM_ATTR_BYTES must be a multiple of four, plus three. Increasing to", NUM_ATTR_BYTES);
172 if (DICT_CHAR_SIZE != 1 && DICT_CHAR_SIZE != 4) {
174 warning_numbered("DICT_CHAR_SIZE must be either 1 or 4. Setting to", DICT_CHAR_SIZE);
178 if (MAX_LOCAL_VARIABLES >= 120) {
179 MAX_LOCAL_VARIABLES = 119;
180 warning("MAX_LOCAL_VARIABLES cannot exceed 119; resetting to 119");
181 /* This is because the keyword table in the lexer only has 120
184 if (DICT_WORD_SIZE > MAX_DICT_WORD_SIZE) {
185 DICT_WORD_SIZE = MAX_DICT_WORD_SIZE;
187 "DICT_WORD_SIZE cannot exceed MAX_DICT_WORD_SIZE; resetting",
189 /* MAX_DICT_WORD_SIZE can be increased in header.h without fear. */
191 if (NUM_ATTR_BYTES > MAX_NUM_ATTR_BYTES) {
192 NUM_ATTR_BYTES = MAX_NUM_ATTR_BYTES;
194 "NUM_ATTR_BYTES cannot exceed MAX_NUM_ATTR_BYTES; resetting",
196 /* MAX_NUM_ATTR_BYTES can be increased in header.h without fear. */
199 /* Set up a few more variables that depend on the above values */
203 DICT_WORD_BYTES = DICT_WORD_SIZE;
204 /* The Z-code generator doesn't use the following variables, although
205 it would be a little cleaner if it did. */
206 OBJECT_BYTE_LENGTH = 0;
207 DICT_ENTRY_BYTE_LENGTH = (version_number==3)?7:9;
208 DICT_ENTRY_FLAG_POS = 0;
212 OBJECT_BYTE_LENGTH = (1 + (NUM_ATTR_BYTES) + 6*4 + (GLULX_OBJECT_EXT_BYTES));
213 DICT_WORD_BYTES = DICT_WORD_SIZE*DICT_CHAR_SIZE;
214 if (DICT_CHAR_SIZE == 1) {
215 DICT_ENTRY_BYTE_LENGTH = (7+DICT_WORD_BYTES);
216 DICT_ENTRY_FLAG_POS = (1+DICT_WORD_BYTES);
219 DICT_ENTRY_BYTE_LENGTH = (12+DICT_WORD_BYTES);
220 DICT_ENTRY_FLAG_POS = (4+DICT_WORD_BYTES);
225 /* ------------------------------------------------------------------------- */
226 /* Tracery: output control variables */
227 /* ------------------------------------------------------------------------- */
229 int asm_trace_level, /* trace assembly: 0 for off, 1 for assembly
230 only, 2 for full assembly tracing with hex dumps */
231 line_trace_level, /* line tracing: 0 off, 1 on */
232 expr_trace_level, /* expression tracing: 0 off, 1 full, 2 brief */
233 linker_trace_level, /* set by -y: 0 to 4 levels of tracing */
234 tokens_trace_level; /* lexer output tracing: 0 off, 1 on */
236 /* ------------------------------------------------------------------------- */
237 /* On/off switch variables (by default all FALSE); other switch settings */
238 /* ------------------------------------------------------------------------- */
240 int bothpasses_switch, /* -b */
241 concise_switch, /* -c */
242 economy_switch, /* -e */
243 frequencies_switch, /* -f */
244 ignore_switches_switch, /* -i */
245 listobjects_switch, /* -j */
246 debugfile_switch, /* -k */
247 listing_switch, /* -l */
248 memout_switch, /* -m */
249 printprops_switch, /* -n */
250 offsets_switch, /* -o */
251 percentages_switch, /* -p */
252 obsolete_switch, /* -q */
253 transcript_switch, /* -r */
254 statistics_switch, /* -s */
255 optimise_switch, /* -u */
256 version_set_switch, /* -v */
257 nowarnings_switch, /* -w */
258 hash_switch, /* -x */
259 memory_map_switch, /* -z */
260 oddeven_packing_switch, /* -B */
261 define_DEBUG_switch, /* -D */
262 temporary_files_switch, /* -F */
263 module_switch, /* -M */
264 runtime_error_checking_switch, /* -S */
265 define_USE_MODULES_switch, /* -U */
266 define_INFIX_switch; /* -X */
268 int throwback_switch; /* -T */
271 int riscos_file_type_format; /* set by -R */
273 int compression_switch; /* set by -H */
274 int character_set_setting, /* set by -C0 through -C9 */
275 character_set_unicode, /* set by -Cu */
276 error_format, /* set by -E */
277 asm_trace_setting, /* set by -a and -t: value of
278 asm_trace_level to use when tracing */
279 double_space_setting, /* set by -d: 0, 1 or 2 */
280 trace_fns_setting, /* set by -g: 0, 1 or 2 */
281 linker_trace_setting, /* set by -y: ditto for linker_... */
282 store_the_text; /* when set, record game text to a chunk
283 of memory (used by both -r & -k) */
284 static int r_e_c_s_set; /* has -S been explicitly set? */
286 int glulx_mode; /* -G */
288 static void reset_switch_settings(void)
289 { asm_trace_setting=0;
290 linker_trace_level=0;
291 tokens_trace_level=0;
293 store_the_text = FALSE;
295 bothpasses_switch = FALSE;
296 concise_switch = FALSE;
297 double_space_setting = 0;
298 economy_switch = FALSE;
299 frequencies_switch = FALSE;
300 trace_fns_setting = 0;
301 ignore_switches_switch = FALSE;
302 listobjects_switch = FALSE;
303 debugfile_switch = FALSE;
304 listing_switch = FALSE;
305 memout_switch = FALSE;
306 printprops_switch = FALSE;
307 offsets_switch = FALSE;
308 percentages_switch = FALSE;
309 obsolete_switch = FALSE;
310 transcript_switch = FALSE;
311 statistics_switch = FALSE;
312 optimise_switch = FALSE;
313 version_set_switch = FALSE;
314 nowarnings_switch = FALSE;
316 memory_map_switch = FALSE;
317 oddeven_packing_switch = FALSE;
318 define_DEBUG_switch = FALSE;
319 #ifdef USE_TEMPORARY_FILES
320 temporary_files_switch = TRUE;
322 temporary_files_switch = FALSE;
324 define_USE_MODULES_switch = FALSE;
325 module_switch = FALSE;
327 throwback_switch = FALSE;
329 runtime_error_checking_switch = TRUE;
331 define_INFIX_switch = FALSE;
333 riscos_file_type_format = 0;
335 error_format=DEFAULT_ERROR_FORMAT;
337 character_set_setting = 1; /* Default is ISO Latin-1 */
338 character_set_unicode = FALSE;
340 compression_switch = TRUE;
342 requested_glulx_version = 0;
345 /* ------------------------------------------------------------------------- */
346 /* Number of files given as command line parameters (0, 1 or 2) */
347 /* ------------------------------------------------------------------------- */
349 static int cli_files_specified,
350 convert_filename_flag;
352 char Source_Name[PATHLEN]; /* Processed name of first input file */
353 char Code_Name[PATHLEN]; /* Processed name of output file */
355 static char *cli_file1, *cli_file2; /* Unprocessed (and unsafe to alter) */
357 /* ========================================================================= */
358 /* Data structure management routines */
359 /* ------------------------------------------------------------------------- */
361 static void init_vars(void)
369 init_expressc_vars();
370 init_expressp_vars();
385 static void begin_pass(void)
391 directs_begin_pass();
393 expressc_begin_pass();
394 expressp_begin_pass();
397 endofpass_flag = FALSE;
398 line_trace_level = 0; expr_trace_level = 0;
399 asm_trace_level = asm_trace_setting;
400 linker_trace_level = linker_trace_setting;
401 if (listing_switch) line_trace_level=1;
406 objects_begin_pass();
408 symbols_begin_pass();
417 /* Compile a Main__ routine (see "veneer.c") */
419 compile_initial_routine();
421 /* Make the four metaclasses: Class must be object number 1, so
422 it must come first */
427 make_class("Object");
428 make_class("Routine");
429 make_class("String");
435 extern void allocate_arrays(void)
437 arrays_allocate_arrays();
438 asm_allocate_arrays();
439 bpatch_allocate_arrays();
440 chars_allocate_arrays();
441 directs_allocate_arrays();
442 errors_allocate_arrays();
443 expressc_allocate_arrays();
444 expressp_allocate_arrays();
445 files_allocate_arrays();
447 lexer_allocate_arrays();
448 linker_allocate_arrays();
449 memory_allocate_arrays();
450 objects_allocate_arrays();
451 states_allocate_arrays();
452 symbols_allocate_arrays();
453 syntax_allocate_arrays();
454 tables_allocate_arrays();
455 text_allocate_arrays();
456 veneer_allocate_arrays();
457 verbs_allocate_arrays();
460 extern void free_arrays(void)
462 /* One array may survive this routine, all_the_text (used to hold
463 game text until the abbreviations optimiser begins work on it): this
464 array (if it was ever allocated) is freed at the top level. */
466 arrays_free_arrays();
468 bpatch_free_arrays();
470 directs_free_arrays();
471 errors_free_arrays();
472 expressc_free_arrays();
473 expressp_free_arrays();
477 linker_free_arrays();
478 memory_free_arrays();
479 objects_free_arrays();
480 states_free_arrays();
481 symbols_free_arrays();
482 syntax_free_arrays();
483 tables_free_arrays();
485 veneer_free_arrays();
489 /* ------------------------------------------------------------------------- */
490 /* Name translation code for filenames */
491 /* ------------------------------------------------------------------------- */
493 static char Source_Path[PATHLEN];
494 static char Include_Path[PATHLEN];
495 static char Code_Path[PATHLEN];
496 static char Module_Path[PATHLEN];
497 static char Temporary_Path[PATHLEN];
498 static char current_source_path[PATHLEN];
499 char Debugging_Name[PATHLEN];
500 char Transcript_Name[PATHLEN];
501 char Language_Name[PATHLEN];
502 char Charset_Map[PATHLEN];
503 static char ICL_Path[PATHLEN];
505 /* Set one of the above Path buffers to the given location, or list of
506 locations. (A list is comma-separated, and only accepted for Source_Path,
507 Include_Path, ICL_Path, Module_Path.)
509 static void set_path_value(char *path, char *value)
514 if (i >= PATHLEN-1) {
515 printf("A specified path is longer than %d characters.\n",
519 if ((value[j] == FN_ALT) || (value[j] == 0))
520 { if ((value[j] == FN_ALT)
521 && (path != Source_Path) && (path != Include_Path)
522 && (path != ICL_Path) && (path != Module_Path))
523 { printf("The character '%c' is used to divide entries in a list \
524 of possible locations, and can only be used in the Include_Path, Source_Path, \
525 Module_Path or ICL_Path variables. Other paths are for output only.\n", FN_ALT);
528 if ((path != Debugging_Name) && (path != Transcript_Name)
529 && (path != Language_Name) && (path != Charset_Map)
530 && (i>0) && (isalnum(path[i-1]))) path[i++] = FN_SEP;
531 path[i++] = value[j++];
532 if (value[j-1] == 0) return;
534 else path[i++] = value[j++];
538 /* Prepend the given location or list of locations to one of the above
539 Path buffers. This is only permitted for Source_Path, Include_Path,
540 ICL_Path, Module_Path.
542 An empty field (in the comma-separated list) means the current
543 directory. If the Path buffer is entirely empty, we assume that
544 we want to search both value and the current directory, so
545 the result will be "value,".
547 static void prepend_path_value(char *path, char *value)
550 int oldlen = strlen(path);
552 char new_path[PATHLEN];
554 if ((path != Source_Path) && (path != Include_Path)
555 && (path != ICL_Path) && (path != Module_Path))
556 { printf("The character '+' is used to add to a list \
557 of possible locations, and can only be used in the Include_Path, Source_Path, \
558 Module_Path or ICL_Path variables. Other paths are for output only.\n");
564 if (i >= PATHLEN-1) {
565 printf("A specified path is longer than %d characters.\n",
569 if ((value[j] == FN_ALT) || (value[j] == 0))
570 { if ((path != Debugging_Name) && (path != Transcript_Name)
571 && (path != Language_Name) && (path != Charset_Map)
572 && (i>0) && (isalnum(new_path[i-1]))) new_path[i++] = FN_SEP;
573 new_path[i++] = value[j++];
574 if (value[j-1] == 0) {
579 else new_path[i++] = value[j++];
582 if (newlen+1+oldlen >= PATHLEN-1) {
583 printf("A specified path is longer than %d characters.\n",
589 new_path[i++] = FN_ALT;
591 new_path[i++] = path[j++];
594 strcpy(path, new_path);
597 static void set_default_paths(void)
599 set_path_value(Source_Path, Source_Directory);
600 set_path_value(Include_Path, Include_Directory);
601 set_path_value(Code_Path, Code_Directory);
602 set_path_value(Module_Path, Module_Directory);
603 set_path_value(ICL_Path, ICL_Directory);
604 set_path_value(Temporary_Path, Temporary_Directory);
605 set_path_value(Debugging_Name, Debugging_File);
606 set_path_value(Transcript_Name, Transcript_File);
607 set_path_value(Language_Name, Default_Language);
608 set_path_value(Charset_Map, "");
611 /* Parse a path option which looks like "dir", "+dir", "pathname=dir",
612 or "+pathname=dir". If there is no "=", we assume "include_path=...".
613 If the option begins with a "+" the directory is prepended to the
614 existing path instead of replacing it.
616 static void set_path_command(char *command)
617 { int i, j; char *path_to_set = NULL;
620 if (command[0] == '+') {
625 for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;
627 path_to_set=Include_Path;
629 if (command[i] == '=') {
630 char pathname[PATHLEN];
631 if (i>=PATHLEN) i=PATHLEN-1;
633 char ch = command[j];
634 if (isupper(ch)) ch=tolower(ch);
638 command = command+i+1;
641 if (strcmp(pathname, "source_path")==0) path_to_set=Source_Path;
642 if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
643 if (strcmp(pathname, "code_path")==0) path_to_set=Code_Path;
644 if (strcmp(pathname, "module_path")==0) path_to_set=Module_Path;
645 if (strcmp(pathname, "icl_path")==0) path_to_set=ICL_Path;
646 if (strcmp(pathname, "temporary_path")==0) path_to_set=Temporary_Path;
647 if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
648 if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;
649 if (strcmp(pathname, "language_name")==0) path_to_set=Language_Name;
650 if (strcmp(pathname, "charset_map")==0) path_to_set=Charset_Map;
652 if (path_to_set == NULL)
653 { printf("No such path setting as \"%s\"\n", pathname);
659 set_path_value(path_to_set, command);
661 prepend_path_value(path_to_set, command);
664 static int contains_separator(char *name)
666 for (i=0; name[i]!=0; i++)
667 if (name[i] == FN_SEP) return 1;
671 static int write_translated_name(char *new_name, char *old_name,
672 char *prefix_path, int start_pos,
675 if (strlen(old_name)+strlen(extension) >= PATHLEN) {
676 printf("One of your filenames is longer than %d characters.\n", PATHLEN);
679 if (prefix_path == NULL)
680 { sprintf(new_name,"%s%s", old_name, extension);
683 strcpy(new_name, prefix_path + start_pos);
684 for (x=0; (new_name[x]!=0) && (new_name[x]!=FN_ALT); x++) ;
685 if (new_name[x] == 0) start_pos = 0; else start_pos += x+1;
686 if (x+strlen(old_name)+strlen(extension) >= PATHLEN) {
687 printf("One of your pathnames is longer than %d characters.\n", PATHLEN);
690 sprintf(new_name + x, "%s%s", old_name, extension);
694 #ifdef FILE_EXTENSIONS
695 static char *check_extension(char *name, char *extension)
698 /* If a filename ends in '.', remove the dot and add no file extension: */
700 if (name[i] == '.') { name[i]=0; return ""; }
702 /* Remove the new extension if it's already got one: */
704 for (; (i>=0) && (name[i]!=FN_SEP); i--)
705 if (name[i] == '.') return "";
710 /* ------------------------------------------------------------------------- */
711 /* Three translation routines have to deal with path variables which may */
712 /* contain alternative locations separated by the FN_ALT character. */
713 /* These have the protocol: */
715 /* int translate_*_filename(int last_value, ...) */
717 /* and should first be called with last_value equal to 0. If the */
718 /* translated filename works, fine. Otherwise, if the returned integer */
719 /* was zero, the caller knows that no filename works and can issue an */
720 /* error message. If it was non-zero, the caller should pass it on as */
721 /* the last_value again. */
723 /* As implemented below, last_value is the position in the path variable */
724 /* string at which the next directory name to try begins. */
725 /* ------------------------------------------------------------------------- */
727 extern int translate_in_filename(int last_value,
728 char *new_name, char *old_name,
729 int same_directory_flag, int command_line_flag)
730 { char *prefix_path = NULL;
732 int add_path_flag = 1;
735 if ((same_directory_flag==0)
736 && (contains_separator(old_name)==1)) add_path_flag=0;
738 if (add_path_flag==1)
739 { if (command_line_flag == 0)
740 { /* File is opened as a result of an Include directive */
742 if (same_directory_flag==1)
743 prefix_path = current_source_path;
745 if (Include_Path[0]!=0) prefix_path = Include_Path;
747 /* Main file being opened from the command line */
749 else if (Source_Path[0]!=0) prefix_path = Source_Path;
752 #ifdef FILE_EXTENSIONS
753 /* Which file extension is expected? */
755 if ((command_line_flag==1)||(same_directory_flag==1))
756 extension = Source_Extension;
758 extension = Include_Extension;
760 extension = check_extension(old_name, extension);
765 last_value = write_translated_name(new_name, old_name,
766 prefix_path, last_value, extension);
768 /* Set the "current source path" (for use of Include ">...") */
770 if (command_line_flag==1)
771 { strcpy(current_source_path, new_name);
772 for (i=strlen(current_source_path)-1;
773 ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
775 if (i!=0) current_source_path[i+1] = 0; /* Current file in subdir */
776 else current_source_path[0] = 0; /* Current file at root dir */
782 extern int translate_link_filename(int last_value,
783 char *new_name, char *old_name)
784 { char *prefix_path = NULL;
787 if (contains_separator(old_name)==0)
788 if (Module_Path[0]!=0)
789 prefix_path = Module_Path;
791 #ifdef FILE_EXTENSIONS
792 extension = check_extension(old_name, Module_Extension);
797 return write_translated_name(new_name, old_name,
798 prefix_path, last_value, extension);
801 static int translate_icl_filename(int last_value,
802 char *new_name, char *old_name)
803 { char *prefix_path = NULL;
804 char *extension = "";
806 if (contains_separator(old_name)==0)
808 prefix_path = ICL_Path;
810 #ifdef FILE_EXTENSIONS
811 extension = check_extension(old_name, ICL_Extension);
814 return write_translated_name(new_name, old_name,
815 prefix_path, last_value, extension);
818 extern void translate_out_filename(char *new_name, char *old_name)
820 char *extension = "";
823 /* If !convert_filename_flag, then the old_name is just the <file2>
824 parameter on the Inform command line, which we leave alone. */
826 if (!convert_filename_flag)
827 { strcpy(new_name, old_name); return;
830 /* Remove any pathname or extension in <file1>. */
832 if (contains_separator(old_name)==1)
833 { for (i=strlen(old_name)-1; (i>0)&&(old_name[i]!=FN_SEP) ;i--) { };
834 if (old_name[i]==FN_SEP) i++;
837 #ifdef FILE_EXTENSIONS
838 for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
839 if (old_name[i] == '.') old_name[i] = 0;
844 { extension = Module_Extension;
845 if (Module_Path[0]!=0) prefix_path = Module_Path;
850 switch(version_number)
851 { case 3: extension = Code_Extension; break;
852 case 4: extension = V4Code_Extension; break;
853 case 5: extension = V5Code_Extension; break;
854 case 6: extension = V6Code_Extension; break;
855 case 7: extension = V7Code_Extension; break;
856 case 8: extension = V8Code_Extension; break;
860 extension = GlulxCode_Extension;
862 if (Code_Path[0]!=0) prefix_path = Code_Path;
865 #ifdef FILE_EXTENSIONS
866 extension = check_extension(old_name, extension);
869 write_translated_name(new_name, old_name, prefix_path, 0, extension);
872 static char *name_or_unset(char *p)
873 { if (p[0]==0) return "(unset)";
877 static void help_on_filenames(void)
878 { char old_name[PATHLEN];
879 char new_name[PATHLEN];
880 int save_mm = module_switch, x;
882 module_switch = FALSE;
884 printf("Help information on filenames:\n\n");
887 "The command line can take one of two forms:\n\n\
888 inform [commands...] <file1>\n\
889 inform [commands...] <file1> <file2>\n\n\
890 Inform translates <file1> into a source file name (see below) for its input.\n\
891 <file2> is usually omitted: if so, the output filename is made from <file1>\n\
892 by cutting out the name part and translating that (see below).\n\
893 If <file2> is given, however, the output filename is set to just <file2>\n\
894 (not altered in any way).\n\n");
897 "Filenames given in the game source (with commands like Include \"name\" and\n\
898 Link \"name\") are also translated by the rules below.\n\n");
901 "Rules of translation:\n\n\
902 Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
903 (such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
904 1. If the name contains a '%c' character (so it's already a pathname), it\n\
905 isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);
908 " [Exception: when the name is given in an Include command using the >\n\
909 form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
910 of the file doing the inclusion");
911 #ifdef FILE_EXTENSIONS
912 printf(" and a suitable file extension is added");
917 " Filenames must never contain double-quotation marks \". To use filenames\n\
918 which contain spaces, write them in double-quotes: for instance,\n\n\
919 \"inform +code_path=\"Jigsaw Final Version\" jigsaw\".\n\n");
922 "2. The file is looked for at a particular \"path\" (the filename of a\n\
923 directory), depending on what kind of file it is.\n\n\
924 File type Name Current setting\n\n\
925 Source code (in) source_path %s\n\
926 Include file (in) include_path %s\n\
927 Story file (out) code_path %s\n",
928 name_or_unset(Source_Path), name_or_unset(Include_Path),
929 name_or_unset(Code_Path));
932 " Temporary file (out) temporary_path %s\n\
933 ICL command file (in) icl_path %s\n\
934 Module (in & out) module_path %s\n\n",
935 name_or_unset(Temporary_Path),
936 name_or_unset(ICL_Path), name_or_unset(Module_Path));
939 " If the path is unset, then the current working directory is used (so\n\
940 the filename doesn't change): if, for instance, include_path is set to\n\
941 \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
942 \"backup%coldlib%cparser\".\n\n\
943 The paths can be set or unset on the Inform command line by, eg,\n\
944 \"inform +code_path=finished jigsaw\" or\n\
945 \"inform +include_path= balances\" (which unsets include_path).\n\n",
946 FN_SEP, FN_SEP, FN_SEP);
949 " The four input path variables can be set to lists of alternative paths\n\
950 separated by '%c' characters: these alternatives are always tried in\n\
951 the order they are specified in, that is, left to right through the text\n\
952 in the path variable.\n\n",
955 " If two '+' signs are used (\"inform ++include_path=dir jigsaw\") then\n\
956 the path or paths are added to the existing list.\n\n");
958 " (Modules are written to the first alternative in the module_path list;\n\
959 it is an error to give alternatives at all for purely output paths.)\n\n");
961 #ifdef FILE_EXTENSIONS
962 printf("3. The following file extensions are added:\n\n\
965 Story files: %s (Version 3), %s (v4), %s (v5, the default),\n\
966 %s (v6), %s (v7), %s (v8), %s (Glulx)\n\
967 Temporary files: .tmp\n\
969 Source_Extension, Include_Extension,
970 Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
971 V7Code_Extension, V8Code_Extension, GlulxCode_Extension,
974 except that any extension you give (on the command line or in a filename\n\
975 used in a program) will override these. If you give the null extension\n\
976 \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
979 printf("Names of four individual files can also be set using the same\n\
980 + command notation (though they aren't really pathnames). These are:\n\n\
981 transcript_name (text written by -r switch): now \"%s\"\n\
982 debugging_name (data written by -k switch): now \"%s\"\n\
983 language_name (library file defining natural language of game):\n\
985 charset_map (file for character set mapping): now \"%s\"\n\n",
986 Transcript_Name, Debugging_Name, Language_Name, Charset_Map);
988 translate_in_filename(0, new_name, "rezrov", 0, 1);
989 printf("Examples: 1. \"inform rezrov\"\n\
990 the source code is read from \"%s\"\n",
992 convert_filename_flag = TRUE;
993 translate_out_filename(new_name, "rezrov");
994 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
996 translate_in_filename(0, new_name, "frotz", 0, 1);
997 printf("2. \"inform -M frotz\"\n\
998 the source code is read from \"%s\"\n",
1000 module_switch = TRUE;
1001 convert_filename_flag = TRUE;
1002 translate_out_filename(new_name, "frotz");
1003 printf(" and a module is compiled to \"%s\".\n\n", new_name);
1005 module_switch = FALSE;
1007 sprintf(old_name, "demos%cplugh", FN_SEP);
1008 printf("3. \"inform %s\"\n", old_name);
1009 translate_in_filename(0, new_name, old_name, 0, 1);
1010 printf(" the source code is read from \"%s\"\n", new_name);
1011 sprintf(old_name, "demos%cplugh", FN_SEP);
1012 convert_filename_flag = TRUE;
1013 translate_out_filename(new_name, old_name);
1014 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1016 printf("4. \"inform plover my_demo\"\n");
1017 translate_in_filename(0, new_name, "plover", 0, 1);
1018 printf(" the source code is read from \"%s\"\n", new_name);
1019 convert_filename_flag = FALSE;
1020 translate_out_filename(new_name, "my_demo");
1021 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1023 strcpy(old_name, Source_Path);
1024 sprintf(new_name, "%cnew%cold%crecent%cold%cancient",
1025 FN_ALT, FN_ALT, FN_SEP, FN_ALT, FN_SEP);
1026 printf("5. \"inform +source_path=%s zooge\"\n", new_name);
1028 " Note that four alternative paths are given, the first being the empty\n\
1029 path-name (meaning: where you are now). Inform looks for the source code\n\
1030 by trying these four places in turn, stopping when it finds anything:\n\n");
1032 set_path_value(Source_Path, new_name);
1035 { x = translate_in_filename(x, new_name, "zooge", 0, 1);
1036 printf(" \"%s\"\n", new_name);
1038 strcpy(Source_Path, old_name);
1039 module_switch = save_mm;
1042 /* ------------------------------------------------------------------------- */
1043 /* Naming temporary files */
1044 /* (Arguably temporary files should be made using "tmpfile" in */
1045 /* the ANSI C library, but many supposed ANSI libraries lack it.) */
1046 /* ------------------------------------------------------------------------- */
1048 extern void translate_temp_filename(int i)
1051 { case 1: p=Temp1_Name; break;
1052 case 2: p=Temp2_Name; break;
1053 case 3: p=Temp3_Name; break;
1055 if (strlen(Temporary_Path)+strlen(Temporary_File)+6 >= PATHLEN) {
1056 printf ("Temporary_Path is too long.\n");
1059 sprintf(p,"%s%s%d", Temporary_Path, Temporary_File, i);
1060 #ifdef INCLUDE_TASK_ID
1061 sprintf(p+strlen(p), "_proc%08lx", (long int) unique_task_id());
1063 #ifdef FILE_EXTENSIONS
1064 sprintf(p+strlen(p), ".tmp");
1069 static char riscos_ft_buffer[4];
1071 extern char *riscos_file_type(void)
1073 if (riscos_file_type_format == 1)
1074 { if (module_switch) return("data");
1078 if (module_switch) return("075");
1080 sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
1081 return(riscos_ft_buffer);
1085 /* ------------------------------------------------------------------------- */
1086 /* The compilation pass */
1087 /* ------------------------------------------------------------------------- */
1089 static void run_pass(void)
1091 lexer_begin_prepass();
1092 files_begin_prepass();
1093 load_sourcefile(Source_Name, 0);
1097 parse_program(NULL);
1100 issue_unused_warnings();
1104 if (module_switch) linker_endpass();
1107 if (hash_switch && hash_printed_since_newline) printf("\n");
1109 if (temporary_files_switch)
1110 { if (module_switch) flush_link_data();
1114 if (track_unused_routines)
1115 locate_dead_functions();
1116 construct_storyfile();
1119 int output_has_occurred;
1121 static void rennab(int32 time_taken)
1122 { /* rennab = reverse of banner */
1124 int t = no_warnings + no_suppressed_warnings;
1126 if (memout_switch) print_memory_usage();
1128 if ((no_errors + t)!=0)
1129 { printf("Compiled with ");
1131 { printf("%d error%s", no_errors,(no_errors==1)?"":"s");
1132 if (t > 0) printf(" and ");
1134 if (no_warnings > 0)
1135 printf("%d warning%s", t, (t==1)?"":"s");
1136 if (no_suppressed_warnings > 0)
1137 { if (no_warnings > 0)
1138 printf(" (%d suppressed)", no_suppressed_warnings);
1140 printf("%d suppressed warning%s", no_suppressed_warnings,
1141 (no_suppressed_warnings==1)?"":"s");
1143 if (output_has_occurred == FALSE) printf(" (no output)");
1147 if (no_compiler_errors > 0) print_sorry_message();
1149 if (statistics_switch)
1150 printf("Completed in %ld seconds\n", (long int) time_taken);
1153 /* ------------------------------------------------------------------------- */
1154 /* The compiler abstracted to a routine. */
1155 /* ------------------------------------------------------------------------- */
1157 static int execute_icl_header(char *file1);
1159 static int compile(int number_of_files_specified, char *file1, char *file2)
1162 if (execute_icl_header(file1))
1165 select_target(glulx_mode);
1167 if (define_INFIX_switch && glulx_mode) {
1168 printf("Infix (-X) facilities are not available in Glulx: \
1169 disabling -X switch\n");
1170 define_INFIX_switch = FALSE;
1173 if (module_switch && glulx_mode) {
1174 printf("Modules are not available in Glulx: \
1175 disabling -M switch\n");
1176 module_switch = FALSE;
1179 if (define_INFIX_switch && module_switch)
1180 { printf("Infix (-X) facilities are not available when compiling \
1181 modules: disabling -X switch\n");
1182 define_INFIX_switch = FALSE;
1184 if (runtime_error_checking_switch && module_switch)
1185 { printf("Strict checking (-S) facilities are not available when \
1186 compiling modules: disabling -S switch\n");
1187 runtime_error_checking_switch = FALSE;
1190 time_start=time(0); no_compilations++;
1192 strcpy(Source_Name, file1); convert_filename_flag = TRUE;
1193 strcpy(Code_Name, file1);
1194 if (number_of_files_specified == 2)
1195 { strcpy(Code_Name, file2); convert_filename_flag = FALSE;
1200 if (debugfile_switch) begin_debug_file();
1204 if (transcript_switch) open_transcript_file(Source_Name);
1208 if (transcript_switch)
1209 { write_dictionary_to_transcript();
1210 close_transcript_file();
1213 if (no_errors==0) { output_file(); output_has_occurred = TRUE; }
1214 else { output_has_occurred = FALSE; }
1216 if (debugfile_switch)
1220 if (temporary_files_switch && (no_errors>0)) remove_temp_files();
1224 rennab((int32) (time(0)-time_start));
1226 if (optimise_switch) optimise_abbreviations();
1228 if (store_the_text) my_free(&all_text,"transcription text");
1230 return (no_errors==0)?0:1;
1233 /* ------------------------------------------------------------------------- */
1234 /* The command line interpreter */
1235 /* ------------------------------------------------------------------------- */
1237 static void cli_print_help(int help_level)
1240 "\nThis program is a compiler of Infocom format (also called \"Z-machine\")\n\
1241 story files, as well as \"Glulx\" story files:\n\
1242 Copyright (c) Graham Nelson 1993 - 2020.\n\n");
1244 /* For people typing just "inform", a summary only: */
1249 #ifndef PROMPT_INPUT
1250 printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
1252 printf("When run, Inform prompts you for commands (and switches),\n\
1253 which are optional, then an input <file1> and an (optional) output\n\
1258 "<file1> is the Inform source file of the game to be compiled. <file2>,\n\
1259 if given, overrides the filename Inform would normally use for the\n\
1260 compiled output. Try \"inform -h1\" for file-naming conventions.\n\n\
1261 One or more words can be supplied as \"commands\". These may be:\n\n\
1262 -switches a list of compiler switches, 1 or 2 letter\n\
1263 (see \"inform -h2\" for the full range)\n\n\
1264 +dir set Include_Path to this directory\n\
1265 ++dir add this directory to Include_Path\n\
1266 +PATH=dir change the PATH to this directory\n\
1267 ++PATH=dir add this directory to the PATH\n\n\
1268 $... one of the following memory commands:\n");
1271 " $list list current memory allocation settings\n\
1272 $huge make standard \"huge game\" settings %s\n\
1273 $large make standard \"large game\" settings %s\n\
1274 $small make standard \"small game\" settings %s\n\
1275 $?SETTING explain briefly what SETTING is for\n\
1276 $SETTING=number change SETTING to given number\n\n",
1277 (DEFAULT_MEMORY_SIZE==HUGE_SIZE)?"(default)":"",
1278 (DEFAULT_MEMORY_SIZE==LARGE_SIZE)?"(default)":"",
1279 (DEFAULT_MEMORY_SIZE==SMALL_SIZE)?"(default)":"");
1282 " (filename) read in a list of commands (in the format above)\n\
1283 from this \"setup file\"\n\n");
1285 printf("Alternate command-line formats for the above:\n\
1286 --help (this page)\n\
1288 --addpath PATH=dir\n\
1290 --size huge, --size large, --size small\n\
1291 --helpopt SETTING\n\
1292 --opt SETTING=number\n\
1293 --config filename (setup file)\n\n");
1295 #ifndef PROMPT_INPUT
1296 printf("For example: \"inform -dexs $huge curses\".\n\n");
1302 /* The -h1 (filenaming) help information: */
1304 if (help_level == 1) { help_on_filenames(); return; }
1306 /* The -h2 (switches) help information: */
1308 printf("Help on the full list of legal switch commands:\n\n\
1309 a trace assembly-language (without hex dumps; see -t)\n\
1310 c more concise error messages\n\
1311 d contract double spaces after full stops in text\n\
1312 d2 contract double spaces after exclamation and question marks, too\n\
1313 e economy mode (slower): make use of declared abbreviations\n");
1316 f frequencies mode: show how useful abbreviations are\n\
1317 g traces calls to functions (except in the library)\n\
1318 g2 traces calls to all functions\n\
1319 h print this information\n");
1322 i ignore default switches set within the file\n\
1323 j list objects as constructed\n\
1324 k output Infix debugging information to \"%s\" (and switch -D on)\n\
1325 l list every statement run through Inform\n\
1326 m say how much memory has been allocated\n\
1327 n print numbers of properties, attributes and actions\n",
1330 o print offset addresses\n\
1331 p give percentage breakdown of story file\n\
1332 q keep quiet about obsolete usages\n\
1333 r record all the text to \"%s\"\n\
1334 s give statistics\n\
1335 t trace assembly-language (with full hex dumps; see -a)\n",
1339 u work out most useful abbreviations (very very slowly)\n\
1340 v3 compile to version-3 (\"Standard\") story file\n\
1341 v4 compile to version-4 (\"Plus\") story file\n\
1342 v5 compile to version-5 (\"Advanced\") story file: the default\n\
1343 v6 compile to version-6 (graphical) story file\n\
1344 v8 compile to version-8 (expanded \"Advanced\") story file\n\
1345 w disable warning messages\n\
1346 x print # for every 100 lines compiled\n\
1347 y trace linking system\n\
1348 z print memory map of the virtual machine\n\n");
1351 B use big memory model (for large V6/V7 files)\n\
1352 C0 text character set is plain ASCII only\n\
1353 Cu text character set is UTF-8\n\
1354 Cn text character set is ISO 8859-n (n = 1 to 9)\n\
1355 (1 to 4, Latin1 to Latin4; 5, Cyrillic; 6, Arabic;\n\
1356 7, Greek; 8, Hebrew; 9, Latin5. Default is -C1.)\n");
1357 printf(" D insert \"Constant DEBUG;\" automatically\n");
1358 printf(" E0 Archimedes-style error messages%s\n",
1359 (error_format==0)?" (current setting)":"");
1360 printf(" E1 Microsoft-style error messages%s\n",
1361 (error_format==1)?" (current setting)":"");
1362 printf(" E2 Macintosh MPW-style error messages%s\n",
1363 (error_format==2)?" (current setting)":"");
1364 #ifdef USE_TEMPORARY_FILES
1365 printf(" F0 use extra memory rather than temporary files\n");
1367 printf(" F1 use temporary files to reduce memory consumption\n");
1369 printf(" G compile a Glulx game file\n");
1370 printf(" H use Huffman encoding to compress Glulx strings\n");
1371 printf(" M compile as a Module for future linking\n");
1375 R0 use filetype 060 + version number for games (default)\n\
1376 R1 use official Acorn filetype 11A for all games\n");
1378 printf(" S compile strict error-checking at run-time (on by default)\n");
1379 #ifdef ARC_THROWBACK
1380 printf(" T enable throwback of errors in the DDE\n");
1382 printf(" U insert \"Constant USE_MODULES;\" automatically\n");
1383 printf(" V print the version and date of this program\n");
1384 printf(" Wn header extension table is at least n words (n = 3 to 99)\n");
1385 printf(" X compile with INFIX debugging facilities present\n");
1389 extern void switches(char *p, int cmode)
1390 { int i, s=1, state;
1391 /* Here cmode is 0 if switches list is from a "Switches" directive
1392 and 1 if from a "-switches" command-line or ICL list */
1397 "Ignoring second word which should be a -list of switches.\n");
1401 for (i=cmode; p[i]!=0; i+=s, s=1)
1409 case 'a': asm_trace_setting = 1; break;
1410 case 'b': bothpasses_switch = state; break;
1411 case 'c': concise_switch = state; break;
1412 case 'd': switch(p[i+1])
1413 { case '1': double_space_setting=1; s=2; break;
1414 case '2': double_space_setting=2; s=2; break;
1415 default: double_space_setting=1; break;
1418 case 'e': economy_switch = state; break;
1419 case 'f': frequencies_switch = state; break;
1420 case 'g': switch(p[i+1])
1421 { case '1': trace_fns_setting=1; s=2; break;
1422 case '2': trace_fns_setting=2; s=2; break;
1423 default: trace_fns_setting=1; break;
1426 case 'h': switch(p[i+1])
1427 { case '1': cli_print_help(1); s=2; break;
1428 case '2': cli_print_help(2); s=2; break;
1430 default: cli_print_help(0); break;
1433 case 'i': ignore_switches_switch = state; break;
1434 case 'j': listobjects_switch = state; break;
1435 case 'k': if (cmode == 0)
1436 error("The switch '-k' can't be set with 'Switches'");
1438 { debugfile_switch = state;
1439 if (state) define_DEBUG_switch = TRUE;
1442 case 'l': listing_switch = state; break;
1443 case 'm': memout_switch = state; break;
1444 case 'n': printprops_switch = state; break;
1445 case 'o': offsets_switch = state; break;
1446 case 'p': percentages_switch = state; break;
1447 case 'q': obsolete_switch = state; break;
1448 case 'r': if (cmode == 0)
1449 error("The switch '-r' can't be set with 'Switches'");
1451 transcript_switch = state; break;
1452 case 's': statistics_switch = state; break;
1453 case 't': asm_trace_setting=2; break;
1454 case 'u': if (cmode == 0) {
1455 error("The switch '-u' can't be set with 'Switches'");
1458 optimise_switch = state; break;
1459 case 'v': if (glulx_mode) { s = select_glulx_version(p+i+1)+1; break; }
1460 if ((cmode==0) && (version_set_switch)) { s=2; break; }
1461 version_set_switch = TRUE; s=2;
1463 { case '3': select_version(3); break;
1464 case '4': select_version(4); break;
1465 case '5': select_version(5); break;
1466 case '6': select_version(6); break;
1467 case '7': select_version(7); break;
1468 case '8': select_version(8); break;
1469 default: printf("-v must be followed by 3 to 8\n");
1470 version_set_switch=0; s=1;
1473 if ((version_number < 5) && (r_e_c_s_set == FALSE))
1474 runtime_error_checking_switch = FALSE;
1476 case 'w': nowarnings_switch = state; break;
1477 case 'x': hash_switch = state; break;
1478 case 'y': s=2; linker_trace_setting=p[i+1]-'0'; break;
1479 case 'z': memory_map_switch = state; break;
1480 case 'B': oddeven_packing_switch = state; break;
1482 if (p[i+1] == 'u') {
1483 character_set_unicode = TRUE;
1484 /* Leave the set_setting on Latin-1, because that
1485 matches the first block of Unicode. */
1486 character_set_setting = 1;
1489 { character_set_setting=p[i+1]-'0';
1490 if ((character_set_setting < 0)
1491 || (character_set_setting > 9))
1492 { printf("-C must be followed by 'u' or 0 to 9. Defaulting to ISO-8859-1.\n");
1493 character_set_unicode = FALSE;
1494 character_set_setting = 1;
1497 if (cmode == 0) change_character_set();
1499 case 'D': define_DEBUG_switch = state; break;
1500 case 'E': switch(p[i+1])
1501 { case '0': s=2; error_format=0; break;
1502 case '1': s=2; error_format=1; break;
1503 case '2': s=2; error_format=2; break;
1504 default: error_format=1; break;
1507 case 'F': if (cmode == 0) {
1508 error("The switch '-F' can't be set with 'Switches'");
1512 { case '0': s=2; temporary_files_switch = FALSE; break;
1513 case '1': s=2; temporary_files_switch = TRUE; break;
1514 default: temporary_files_switch = state; break;
1517 case 'M': module_switch = state;
1518 if (state && (r_e_c_s_set == FALSE))
1519 runtime_error_checking_switch = FALSE;
1522 case 'R': switch(p[i+1])
1523 { case '0': s=2; riscos_file_type_format=0; break;
1524 case '1': s=2; riscos_file_type_format=1; break;
1525 default: riscos_file_type_format=1; break;
1529 #ifdef ARC_THROWBACK
1530 case 'T': throwback_switch = state; break;
1532 case 'S': runtime_error_checking_switch = state;
1533 r_e_c_s_set = TRUE; break;
1534 case 'G': if (cmode == 0)
1535 error("The switch '-G' can't be set with 'Switches'");
1536 else if (version_set_switch)
1537 error("The '-G' switch cannot follow the '-v' switch");
1539 { glulx_mode = state;
1540 adjust_memory_sizes();
1543 case 'H': compression_switch = state; break;
1544 case 'U': define_USE_MODULES_switch = state; break;
1545 case 'V': exit(0); break;
1546 case 'W': if ((p[i+1]>='0') && (p[i+1]<='9'))
1547 { s=2; ZCODE_HEADER_EXT_WORDS = p[i+1]-'0';
1548 if ((p[i+2]>='0') && (p[i+2]<='9'))
1549 { s=3; ZCODE_HEADER_EXT_WORDS *= 10;
1550 ZCODE_HEADER_EXT_WORDS += p[i+2]-'0';
1554 case 'X': define_INFIX_switch = state; break;
1556 printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
1562 if (optimise_switch && (!store_the_text))
1563 { store_the_text=TRUE;
1566 printf("Allocation %ld bytes for transcription text\n",
1567 (long) MAX_TRANSCRIPT_SIZE);
1568 all_text = halloc(MAX_TRANSCRIPT_SIZE,1);
1569 malloced_bytes += MAX_TRANSCRIPT_SIZE;
1571 fatalerror("Can't hallocate memory for transcription text. Darn.");
1573 all_text=my_malloc(MAX_TRANSCRIPT_SIZE,"transcription text");
1578 static int icl_command(char *p)
1579 { if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
1580 || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
1584 static void icl_error(char *filename, int line)
1585 { printf("Error in ICL file '%s', line %d:\n", filename, line);
1588 static void icl_header_error(char *filename, int line)
1589 { printf("Error in ICL header of file '%s', line %d:\n", filename, line);
1592 static int copy_icl_word(char *from, char *to, int max)
1594 /* Copies one token from 'from' to 'to', null-terminated:
1595 returns the number of chars in 'from' read past (possibly 0). */
1597 int i, j, quoted_mode, truncated;
1599 i = 0; truncated = 0;
1600 while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
1601 || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;
1604 { while (from[i] != 0) i++;
1605 to[0] = 0; return i;
1608 for (quoted_mode = FALSE, j=0;;)
1609 { if (from[i] == 0) break;
1610 if (from[i] == 10) break;
1611 if (from[i] == 13) break;
1612 if (from[i] == TAB_CHARACTER) break;
1613 if ((from[i] == ' ') && (!quoted_mode)) break;
1614 if (from[i] == '\"') { quoted_mode = !quoted_mode; i++; }
1615 else to[j++] = from[i++];
1623 printf("The following parameter has been truncated:\n%s\n", to);
1627 /* Copy a string, converting to uppercase. The to array should be
1628 (at least) max characters. Result will be null-terminated, so
1629 at most max-1 characters will be copied.
1631 static int strcpyupper(char *to, char *from, int max)
1634 for (ix=0; ix<max-1; ix++) {
1636 if (islower(ch)) ch = toupper(ch);
1643 static void execute_icl_command(char *p);
1644 static int execute_dashdash_command(char *p, char *p2);
1646 static int execute_icl_header(char *argname)
1649 char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1653 char filename[PATHLEN];
1657 { x = translate_in_filename(x, filename, argname, 0, 1);
1658 command_file = fopen(filename,"r");
1659 } while ((command_file == NULL) && (x != 0));
1660 if (!command_file) {
1661 /* Fail silently. The regular compiler will try to open the file
1662 again, and report the problem. */
1666 while (feof(command_file)==0) {
1667 if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1669 if (!(cli_buff[0] == '!' && cli_buff[1] == '%'))
1671 i = copy_icl_word(cli_buff+2, fw, CMD_BUF_SIZE);
1672 if (icl_command(fw)) {
1673 execute_icl_command(fw);
1674 copy_icl_word(cli_buff+2 + i, fw, CMD_BUF_SIZE);
1675 if ((fw[0] != 0) && (fw[0] != '!')) {
1676 icl_header_error(filename, line);
1678 printf("expected comment or nothing but found '%s'\n", fw);
1683 icl_header_error(filename, line);
1685 printf("Expected command or comment but found '%s'\n", fw);
1689 fclose(command_file);
1691 return (errcount==0)?0:1;
1695 static void run_icl_file(char *filename, FILE *command_file)
1696 { char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1698 printf("[Running ICL file '%s']\n", filename);
1700 while (feof(command_file)==0)
1701 { if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1703 i = copy_icl_word(cli_buff, fw, CMD_BUF_SIZE);
1704 if (icl_command(fw))
1705 { execute_icl_command(fw);
1706 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1707 if ((fw[0] != 0) && (fw[0] != '!'))
1708 { icl_error(filename, line);
1709 printf("expected comment or nothing but found '%s'\n", fw);
1713 { if (strcmp(fw, "compile")==0)
1714 { char story_name[PATHLEN], code_name[PATHLEN];
1715 i += copy_icl_word(cli_buff + i, story_name, PATHLEN);
1716 i += copy_icl_word(cli_buff + i, code_name, PATHLEN);
1718 if (code_name[0] != 0) x=2;
1719 else if (story_name[0] != 0) x=1;
1723 { case 0: icl_error(filename, line);
1724 printf("No filename given to 'compile'\n");
1726 case 1: printf("[Compiling <%s>]\n", story_name);
1727 compile(x, story_name, code_name);
1729 case 2: printf("[Compiling <%s> to <%s>]\n",
1730 story_name, code_name);
1731 compile(x, story_name, code_name);
1732 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1734 { icl_error(filename, line);
1735 printf("Expected comment or nothing but found '%s'\n",
1743 { icl_error(filename, line);
1744 printf("Expected command or comment but found '%s'\n", fw);
1750 /* This should only be called if the argument has been verified to be
1751 an ICL command, e.g. by checking icl_command().
1753 static void execute_icl_command(char *p)
1754 { char filename[PATHLEN], cli_buff[CMD_BUF_SIZE];
1759 { case '+': set_path_command(p+1); break;
1760 case '-': switches(p,1); break;
1761 case '$': memory_command(p+1); break;
1762 case '(': len = strlen(p);
1763 if (p[len-1] != ')') {
1764 printf("Error in ICL: (command) missing closing paren\n");
1767 len -= 2; /* omit parens */
1768 if (len > CMD_BUF_SIZE-1) len = CMD_BUF_SIZE-1;
1769 strncpy(cli_buff, p+1, len);
1773 { x = translate_icl_filename(x, filename, cli_buff);
1774 command_file = fopen(filename,"r");
1775 } while ((command_file == NULL) && (x != 0));
1777 if (command_file == NULL) {
1778 printf("Error in ICL: Couldn't open command file '%s'\n",
1782 run_icl_file(filename, command_file);
1783 fclose(command_file);
1788 /* Convert a --command into the equivalent ICL command and call
1789 execute_icl_command(). The dashes have already been stripped.
1791 The second argument is the following command-line argument
1792 (or NULL if there was none). This may or may not be consumed.
1793 Returns TRUE if it was.
1795 static int execute_dashdash_command(char *p, char *p2)
1797 char cli_buff[CMD_BUF_SIZE];
1798 int consumed2 = FALSE;
1800 if (!strcmp(p, "help")) {
1801 strcpy(cli_buff, "-h");
1803 else if (!strcmp(p, "list")) {
1804 strcpy(cli_buff, "$LIST");
1806 else if (!strcmp(p, "size")) {
1808 if (!(p2 && (!strcmpcis(p2, "HUGE") || !strcmpcis(p2, "LARGE") || !strcmpcis(p2, "SMALL")))) {
1809 printf("--size must be followed by \"huge\", \"large\", or \"small\"\n");
1812 strcpy(cli_buff, "$");
1813 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1815 else if (!strcmp(p, "opt")) {
1817 if (!p2 || !strchr(p2, '=')) {
1818 printf("--opt must be followed by \"setting=number\"\n");
1821 strcpy(cli_buff, "$");
1822 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1824 else if (!strcmp(p, "helpopt")) {
1827 printf("--helpopt must be followed by \"setting\"\n");
1830 strcpy(cli_buff, "$?");
1831 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1833 else if (!strcmp(p, "path")) {
1835 if (!p2 || !strchr(p2, '=')) {
1836 printf("--path must be followed by \"name=path\"\n");
1839 snprintf(cli_buff, CMD_BUF_SIZE, "+%s", p2);
1841 else if (!strcmp(p, "addpath")) {
1843 if (!p2 || !strchr(p2, '=')) {
1844 printf("--addpath must be followed by \"name=path\"\n");
1847 snprintf(cli_buff, CMD_BUF_SIZE, "++%s", p2);
1849 else if (!strcmp(p, "config")) {
1852 printf("--config must be followed by \"file.icl\"\n");
1855 snprintf(cli_buff, CMD_BUF_SIZE, "(%s)", p2);
1858 printf("Option \"--%s\" unknown (try \"inform -h\")\n", p);
1862 execute_icl_command(cli_buff);
1866 /* ------------------------------------------------------------------------- */
1867 /* Opening and closing banners */
1868 /* ------------------------------------------------------------------------- */
1870 char banner_line[CMD_BUF_SIZE];
1872 /* We store the banner text for use elsewhere (see files.c).
1874 static void banner(void)
1877 snprintf(banner_line, CMD_BUF_SIZE, "Inform %d.%d%d",
1878 (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10);
1879 #ifdef RELEASE_SUFFIX
1880 len = strlen(banner_line);
1881 snprintf(banner_line+len, CMD_BUF_SIZE-len, "%s", RELEASE_SUFFIX);
1883 #ifdef MACHINE_STRING
1884 len = strlen(banner_line);
1885 snprintf(banner_line+len, CMD_BUF_SIZE-len, " for %s", MACHINE_STRING);
1887 len = strlen(banner_line);
1888 snprintf(banner_line+len, CMD_BUF_SIZE-len, " (%s)", RELEASE_DATE);
1890 printf("%s\n", banner_line);
1893 /* ------------------------------------------------------------------------- */
1894 /* Input from the outside world */
1895 /* ------------------------------------------------------------------------- */
1898 static void read_command_line(int argc, char **argv)
1900 char buffer1[PATHLEN], buffer2[PATHLEN], buffer3[PATHLEN];
1902 printf("Source filename?\n> ");
1903 while (gets(buffer1)==NULL); cli_file1=buffer1;
1904 printf("Output filename (RETURN for the same)?\n> ");
1905 while (gets(buffer2)==NULL); cli_file2=buffer2;
1906 cli_files_specified=1;
1907 if (buffer2[0]!=0) cli_files_specified=2;
1909 { printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
1910 while (gets(buffer3)==NULL); execute_icl_command(buffer3);
1911 } while (buffer3[0]!=0);
1914 static void read_command_line(int argc, char **argv)
1916 if (argc==1) switches("-h",1);
1918 for (i=1, cli_files_specified=0; i<argc; i++)
1919 if (argv[i][0] == '-' && argv[i][1] == '-') {
1920 char *nextarg = NULL;
1921 if (i+1 < argc) nextarg = argv[i+1];
1922 int consumed2 = execute_dashdash_command(argv[i]+2, nextarg);
1923 if (consumed2 && i+1 < argc) {
1927 else if (icl_command(argv[i])) {
1928 execute_icl_command(argv[i]);
1931 switch(++cli_files_specified)
1932 { case 1: cli_file1 = argv[i]; break;
1933 case 2: cli_file2 = argv[i]; break;
1935 printf("Command line error: unknown parameter '%s'\n",
1942 /* ------------------------------------------------------------------------- */
1943 /* M A I N : An outer shell for machine-specific quirks */
1944 /* Omitted altogether if EXTERNAL_SHELL is defined, as for instance is */
1945 /* needed for the Macintosh front end. */
1946 /* ------------------------------------------------------------------------- */
1948 #ifdef EXTERNAL_SHELL
1949 extern int sub_main(int argc, char **argv);
1952 static int sub_main(int argc, char **argv);
1954 int main(int argc, char **argv, char *envp[])
1956 int main(int argc, char **argv)
1960 InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
1962 rcode = sub_main(argc, argv);
1963 #ifdef ARC_THROWBACK
1971 /* ------------------------------------------------------------------------- */
1972 /* M A I N II: Starting up ICL with the command line */
1973 /* ------------------------------------------------------------------------- */
1975 #ifdef EXTERNAL_SHELL
1976 extern int sub_main(int argc, char **argv)
1978 static int sub_main(int argc, char **argv)
1983 ProcessEvents (&g_proc);
1986 if (store_the_text) my_free(&all_text,"transcription text");
1987 longjmp (g_fallback, 1);
1993 set_memory_sizes(DEFAULT_MEMORY_SIZE); set_default_paths();
1994 reset_switch_settings(); select_version(5);
1996 cli_files_specified = 0; no_compilations = 0;
1997 cli_file1 = "source"; cli_file2 = "output";
1999 read_command_line(argc, argv);
2001 if (cli_files_specified > 0)
2002 { return_code = compile(cli_files_specified, cli_file1, cli_file2);
2004 if (return_code != 0) return(return_code);
2007 if (no_compilations == 0)
2008 printf("\n[No compilation requested]\n");
2009 if (no_compilations > 1)
2010 printf("[%d compilations completed]\n", no_compilations);
2015 /* ========================================================================= */