1 /* ------------------------------------------------------------------------- */
2 /* "inform" : The top level of Inform: switches, pathnames, filenaming */
3 /* conventions, ICL (Inform Command Line) files, main */
5 /* Part of Inform 6.35 */
6 /* copyright (c) Graham Nelson 1993 - 2021 */
8 /* Inform is free software: you can redistribute it and/or modify */
9 /* it under the terms of the GNU General Public License as published by */
10 /* the Free Software Foundation, either version 3 of the License, or */
11 /* (at your option) any later version. */
13 /* Inform is distributed in the hope that it will be useful, */
14 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
15 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
16 /* GNU General Public License for more details. */
18 /* You should have received a copy of the GNU General Public License */
19 /* along with Inform. If not, see https://gnu.org/licenses/ *
21 /* ------------------------------------------------------------------------- */
23 #define MAIN_INFORM_FILE
26 #define CMD_BUF_SIZE (256)
28 /* ------------------------------------------------------------------------- */
29 /* Compiler progress */
30 /* ------------------------------------------------------------------------- */
32 static int no_compilations;
34 int endofpass_flag; /* set to TRUE when an "end" directive is reached
35 (the inputs routines insert one into the stream
38 /* ------------------------------------------------------------------------- */
40 /* ------------------------------------------------------------------------- */
42 int version_number, /* 3 to 8 (Z-code) */
43 instruction_set_number,
44 /* 3 to 6: versions 7 and 8 use instruction set of
46 extend_memory_map; /* extend using function- and string-offsets */
47 int32 scale_factor, /* packed address multiplier */
48 length_scale_factor; /* length-in-header multiplier */
50 int32 requested_glulx_version;
52 extern void select_version(int vn)
53 { version_number = vn;
54 extend_memory_map = FALSE;
55 if ((version_number==6)||(version_number==7)) extend_memory_map = TRUE;
58 if (version_number==3) scale_factor = 2;
59 if (version_number==8) scale_factor = 8;
61 length_scale_factor = scale_factor;
62 if ((version_number==6)||(version_number==7)) length_scale_factor = 8;
64 instruction_set_number = version_number;
65 if ((version_number==7)||(version_number==8)) instruction_set_number = 5;
68 static int select_glulx_version(char *str)
70 /* Parse an "X.Y.Z" style version number, and store it for later use. */
72 int major=0, minor=0, patch=0;
75 major = major*10 + ((*cx++)-'0');
79 minor = minor*10 + ((*cx++)-'0');
83 patch = patch*10 + ((*cx++)-'0');
87 requested_glulx_version = ((major & 0x7FFF) << 16)
88 + ((minor & 0xFF) << 8)
93 /* ------------------------------------------------------------------------- */
94 /* Target: variables which vary between the Z-machine and Glulx */
95 /* ------------------------------------------------------------------------- */
97 int WORDSIZE; /* Size of a machine word: 2 or 4 */
98 int32 MAXINTWORD; /* 0x7FFF or 0x7FFFFFFF */
100 /* The first property number which is an individual property. The
101 eight class-system i-props (create, recreate, ... print_to_array)
102 are numbered from INDIV_PROP_START to INDIV_PROP_START+7.
104 int INDIV_PROP_START;
106 /* The length of an object, as written in tables.c. It's easier to define
107 it here than to repeat the same expression all over the source code.
110 int OBJECT_BYTE_LENGTH;
111 /* The total length of a dict entry, in bytes. Not used in Z-code.
113 int DICT_ENTRY_BYTE_LENGTH;
114 /* The position in a dict entry that the flag values begin.
117 int DICT_ENTRY_FLAG_POS;
119 static void select_target(int targ)
126 if (INDIV_PROP_START != 64) {
127 INDIV_PROP_START = 64;
128 fatalerror("You cannot change INDIV_PROP_START in Z-code");
130 if (DICT_WORD_SIZE != 6) {
132 fatalerror("You cannot change DICT_WORD_SIZE in Z-code");
134 if (DICT_CHAR_SIZE != 1) {
136 fatalerror("You cannot change DICT_CHAR_SIZE in Z-code");
138 if (NUM_ATTR_BYTES != 6) {
140 fatalerror("You cannot change NUM_ATTR_BYTES in Z-code");
142 if (MAX_LOCAL_VARIABLES != 16) {
143 MAX_LOCAL_VARIABLES = 16;
144 fatalerror("You cannot change MAX_LOCAL_VARIABLES in Z-code");
146 if (MAX_GLOBAL_VARIABLES != 240) {
147 MAX_GLOBAL_VARIABLES = 240;
148 fatalerror("You cannot change MAX_GLOBAL_VARIABLES in Z-code");
150 if (MAX_VERBS > 255) {
152 fatalerror("MAX_VERBS can only go above 255 when Glulx is used");
158 MAXINTWORD = 0x7FFFFFFF;
159 scale_factor = 0; /* It should never even get used in Glulx */
161 if (INDIV_PROP_START < 256) {
162 INDIV_PROP_START = 256;
163 warning_numbered("INDIV_PROP_START should be at least 256 in Glulx. Setting to", INDIV_PROP_START);
166 if (NUM_ATTR_BYTES % 4 != 3) {
167 NUM_ATTR_BYTES += (3 - (NUM_ATTR_BYTES % 4));
168 warning_numbered("NUM_ATTR_BYTES must be a multiple of four, plus three. Increasing to", NUM_ATTR_BYTES);
171 if (DICT_CHAR_SIZE != 1 && DICT_CHAR_SIZE != 4) {
173 warning_numbered("DICT_CHAR_SIZE must be either 1 or 4. Setting to", DICT_CHAR_SIZE);
177 if (MAX_LOCAL_VARIABLES >= 120) {
178 MAX_LOCAL_VARIABLES = 119;
179 warning("MAX_LOCAL_VARIABLES cannot exceed 119; resetting to 119");
180 /* This is because the keyword table in the lexer only has 120
183 if (DICT_WORD_SIZE > MAX_DICT_WORD_SIZE) {
184 DICT_WORD_SIZE = MAX_DICT_WORD_SIZE;
186 "DICT_WORD_SIZE cannot exceed MAX_DICT_WORD_SIZE; resetting",
188 /* MAX_DICT_WORD_SIZE can be increased in header.h without fear. */
190 if (NUM_ATTR_BYTES > MAX_NUM_ATTR_BYTES) {
191 NUM_ATTR_BYTES = MAX_NUM_ATTR_BYTES;
193 "NUM_ATTR_BYTES cannot exceed MAX_NUM_ATTR_BYTES; resetting",
195 /* MAX_NUM_ATTR_BYTES can be increased in header.h without fear. */
198 if (MAX_ADJECTIVES > 255) {
199 MAX_ADJECTIVES = 255;
200 warning("MAX_ADJECTIVES cannot exceed 255; resetting to 255");
201 /* Only used under Grammar__Version 1, which is obsolete. */
204 /* Set up a few more variables that depend on the above values */
208 DICT_WORD_BYTES = DICT_WORD_SIZE;
209 /* The Z-code generator doesn't use the following variables, although
210 it would be a little cleaner if it did. */
211 OBJECT_BYTE_LENGTH = 0;
212 DICT_ENTRY_BYTE_LENGTH = (version_number==3)?7:9;
213 DICT_ENTRY_FLAG_POS = 0;
217 OBJECT_BYTE_LENGTH = (1 + (NUM_ATTR_BYTES) + 6*4 + (GLULX_OBJECT_EXT_BYTES));
218 DICT_WORD_BYTES = DICT_WORD_SIZE*DICT_CHAR_SIZE;
219 if (DICT_CHAR_SIZE == 1) {
220 DICT_ENTRY_BYTE_LENGTH = (7+DICT_WORD_BYTES);
221 DICT_ENTRY_FLAG_POS = (1+DICT_WORD_BYTES);
224 DICT_ENTRY_BYTE_LENGTH = (12+DICT_WORD_BYTES);
225 DICT_ENTRY_FLAG_POS = (4+DICT_WORD_BYTES);
231 /* The Z-machine's 96 abbreviations are used for these two purposes.
232 Make sure they are set consistently. If exactly one has been
233 set non-default, set the other to match. */
234 if (MAX_DYNAMIC_STRINGS == 32 && MAX_ABBREVS != 64) {
235 MAX_DYNAMIC_STRINGS = 96 - MAX_ABBREVS;
237 if (MAX_ABBREVS == 64 && MAX_DYNAMIC_STRINGS != 32) {
238 MAX_ABBREVS = 96 - MAX_DYNAMIC_STRINGS;
240 if (MAX_ABBREVS + MAX_DYNAMIC_STRINGS != 96
242 || MAX_DYNAMIC_STRINGS < 0) {
243 warning("MAX_ABBREVS plus MAX_DYNAMIC_STRINGS must be 96 in Z-code; resetting both");
244 MAX_DYNAMIC_STRINGS = 32;
249 if (MAX_DYNAMIC_STRINGS > 100) {
250 MAX_DYNAMIC_STRINGS = 100;
251 warning("MAX_DYNAMIC_STRINGS cannot exceed 100; resetting to 100");
252 /* This is because they are specified in text literals like "@00",
258 /* ------------------------------------------------------------------------- */
259 /* Tracery: output control variables */
260 /* ------------------------------------------------------------------------- */
262 int asm_trace_level, /* trace assembly: 0 for off, 1 for assembly
263 only, 2 for full assembly tracing with hex dumps */
264 line_trace_level, /* line tracing: 0 off, 1 on */
265 expr_trace_level, /* expression tracing: 0 off, 1 full, 2 brief */
266 linker_trace_level, /* set by -y: 0 to 4 levels of tracing */
267 tokens_trace_level; /* lexer output tracing: 0 off, 1 on */
269 /* ------------------------------------------------------------------------- */
270 /* On/off switch variables (by default all FALSE); other switch settings */
271 /* ------------------------------------------------------------------------- */
273 int bothpasses_switch, /* -b */
274 concise_switch, /* -c */
275 economy_switch, /* -e */
276 frequencies_switch, /* -f */
277 ignore_switches_switch, /* -i */
278 listobjects_switch, /* -j */
279 debugfile_switch, /* -k */
280 listing_switch, /* -l */
281 memout_switch, /* -m */
282 printprops_switch, /* -n */
283 offsets_switch, /* -o */
284 percentages_switch, /* -p */
285 obsolete_switch, /* -q */
286 transcript_switch, /* -r */
287 statistics_switch, /* -s */
288 optimise_switch, /* -u */
289 version_set_switch, /* -v */
290 nowarnings_switch, /* -w */
291 hash_switch, /* -x */
292 memory_map_switch, /* -z */
293 oddeven_packing_switch, /* -B */
294 define_DEBUG_switch, /* -D */
295 temporary_files_switch, /* -F */
296 module_switch, /* -M */
297 runtime_error_checking_switch, /* -S */
298 define_USE_MODULES_switch, /* -U */
299 define_INFIX_switch; /* -X */
301 int throwback_switch; /* -T */
304 int riscos_file_type_format; /* set by -R */
306 int compression_switch; /* set by -H */
307 int character_set_setting, /* set by -C0 through -C9 */
308 character_set_unicode, /* set by -Cu */
309 error_format, /* set by -E */
310 asm_trace_setting, /* set by -a and -t: value of
311 asm_trace_level to use when tracing */
312 double_space_setting, /* set by -d: 0, 1 or 2 */
313 trace_fns_setting, /* set by -g: 0, 1 or 2 */
314 linker_trace_setting, /* set by -y: ditto for linker_... */
315 store_the_text; /* when set, record game text to a chunk
316 of memory (used by both -r & -k) */
317 static int r_e_c_s_set; /* has -S been explicitly set? */
319 int glulx_mode; /* -G */
321 static void reset_switch_settings(void)
322 { asm_trace_setting=0;
323 linker_trace_level=0;
324 tokens_trace_level=0;
326 store_the_text = FALSE;
328 bothpasses_switch = FALSE;
329 concise_switch = FALSE;
330 double_space_setting = 0;
331 economy_switch = FALSE;
332 frequencies_switch = FALSE;
333 trace_fns_setting = 0;
334 ignore_switches_switch = FALSE;
335 listobjects_switch = FALSE;
336 debugfile_switch = FALSE;
337 listing_switch = FALSE;
338 memout_switch = FALSE;
339 printprops_switch = FALSE;
340 offsets_switch = FALSE;
341 percentages_switch = FALSE;
342 obsolete_switch = FALSE;
343 transcript_switch = FALSE;
344 statistics_switch = FALSE;
345 optimise_switch = FALSE;
346 version_set_switch = FALSE;
347 nowarnings_switch = FALSE;
349 memory_map_switch = FALSE;
350 oddeven_packing_switch = FALSE;
351 define_DEBUG_switch = FALSE;
352 #ifdef USE_TEMPORARY_FILES
353 temporary_files_switch = TRUE;
355 temporary_files_switch = FALSE;
357 define_USE_MODULES_switch = FALSE;
358 module_switch = FALSE;
360 throwback_switch = FALSE;
362 runtime_error_checking_switch = TRUE;
364 define_INFIX_switch = FALSE;
366 riscos_file_type_format = 0;
368 error_format=DEFAULT_ERROR_FORMAT;
370 character_set_setting = 1; /* Default is ISO Latin-1 */
371 character_set_unicode = FALSE;
373 compression_switch = TRUE;
375 requested_glulx_version = 0;
378 /* ------------------------------------------------------------------------- */
379 /* Number of files given as command line parameters (0, 1 or 2) */
380 /* ------------------------------------------------------------------------- */
382 static int cli_files_specified,
383 convert_filename_flag;
385 char Source_Name[PATHLEN]; /* Processed name of first input file */
386 char Code_Name[PATHLEN]; /* Processed name of output file */
388 static char *cli_file1, *cli_file2; /* Unprocessed (and unsafe to alter) */
390 /* ========================================================================= */
391 /* Data structure management routines */
392 /* ------------------------------------------------------------------------- */
394 static void init_vars(void)
402 init_expressc_vars();
403 init_expressp_vars();
418 static void begin_pass(void)
424 directs_begin_pass();
426 expressc_begin_pass();
427 expressp_begin_pass();
430 endofpass_flag = FALSE;
431 line_trace_level = 0; expr_trace_level = 0;
432 asm_trace_level = asm_trace_setting;
433 linker_trace_level = linker_trace_setting;
434 if (listing_switch) line_trace_level=1;
439 objects_begin_pass();
441 symbols_begin_pass();
450 /* Compile a Main__ routine (see "veneer.c") */
452 compile_initial_routine();
454 /* Make the four metaclasses: Class must be object number 1, so
455 it must come first */
460 make_class("Object");
461 make_class("Routine");
462 make_class("String");
468 extern void allocate_arrays(void)
470 arrays_allocate_arrays();
471 asm_allocate_arrays();
472 bpatch_allocate_arrays();
473 chars_allocate_arrays();
474 directs_allocate_arrays();
475 errors_allocate_arrays();
476 expressc_allocate_arrays();
477 expressp_allocate_arrays();
478 files_allocate_arrays();
480 lexer_allocate_arrays();
481 linker_allocate_arrays();
482 memory_allocate_arrays();
483 objects_allocate_arrays();
484 states_allocate_arrays();
485 symbols_allocate_arrays();
486 syntax_allocate_arrays();
487 tables_allocate_arrays();
488 text_allocate_arrays();
489 veneer_allocate_arrays();
490 verbs_allocate_arrays();
493 extern void free_arrays(void)
495 /* One array may survive this routine, all_the_text (used to hold
496 game text until the abbreviations optimiser begins work on it): this
497 array (if it was ever allocated) is freed at the top level. */
499 arrays_free_arrays();
501 bpatch_free_arrays();
503 directs_free_arrays();
504 errors_free_arrays();
505 expressc_free_arrays();
506 expressp_free_arrays();
510 linker_free_arrays();
511 memory_free_arrays();
512 objects_free_arrays();
513 states_free_arrays();
514 symbols_free_arrays();
515 syntax_free_arrays();
516 tables_free_arrays();
518 veneer_free_arrays();
522 /* ------------------------------------------------------------------------- */
523 /* Name translation code for filenames */
524 /* ------------------------------------------------------------------------- */
526 static char Source_Path[PATHLEN];
527 static char Include_Path[PATHLEN];
528 static char Code_Path[PATHLEN];
529 static char Module_Path[PATHLEN];
530 static char Temporary_Path[PATHLEN];
531 static char current_source_path[PATHLEN];
532 char Debugging_Name[PATHLEN];
533 char Transcript_Name[PATHLEN];
534 char Language_Name[PATHLEN];
535 char Charset_Map[PATHLEN];
536 static char ICL_Path[PATHLEN];
538 /* Set one of the above Path buffers to the given location, or list of
539 locations. (A list is comma-separated, and only accepted for Source_Path,
540 Include_Path, ICL_Path, Module_Path.)
542 static void set_path_value(char *path, char *value)
547 if (i >= PATHLEN-1) {
548 printf("A specified path is longer than %d characters.\n",
552 if ((value[j] == FN_ALT) || (value[j] == 0))
553 { if ((value[j] == FN_ALT)
554 && (path != Source_Path) && (path != Include_Path)
555 && (path != ICL_Path) && (path != Module_Path))
556 { printf("The character '%c' is used to divide entries in 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", FN_ALT);
561 if ((path != Debugging_Name) && (path != Transcript_Name)
562 && (path != Language_Name) && (path != Charset_Map)
563 && (i>0) && (isalnum(path[i-1]))) path[i++] = FN_SEP;
564 path[i++] = value[j++];
565 if (value[j-1] == 0) return;
567 else path[i++] = value[j++];
571 /* Prepend the given location or list of locations to one of the above
572 Path buffers. This is only permitted for Source_Path, Include_Path,
573 ICL_Path, Module_Path.
575 An empty field (in the comma-separated list) means the current
576 directory. If the Path buffer is entirely empty, we assume that
577 we want to search both value and the current directory, so
578 the result will be "value,".
580 static void prepend_path_value(char *path, char *value)
583 int oldlen = strlen(path);
585 char new_path[PATHLEN];
587 if ((path != Source_Path) && (path != Include_Path)
588 && (path != ICL_Path) && (path != Module_Path))
589 { printf("The character '+' is used to add to a list \
590 of possible locations, and can only be used in the Include_Path, Source_Path, \
591 Module_Path or ICL_Path variables. Other paths are for output only.\n");
597 if (i >= PATHLEN-1) {
598 printf("A specified path is longer than %d characters.\n",
602 if ((value[j] == FN_ALT) || (value[j] == 0))
603 { if ((path != Debugging_Name) && (path != Transcript_Name)
604 && (path != Language_Name) && (path != Charset_Map)
605 && (i>0) && (isalnum(new_path[i-1]))) new_path[i++] = FN_SEP;
606 new_path[i++] = value[j++];
607 if (value[j-1] == 0) {
612 else new_path[i++] = value[j++];
615 if (newlen+1+oldlen >= PATHLEN-1) {
616 printf("A specified path is longer than %d characters.\n",
622 new_path[i++] = FN_ALT;
624 new_path[i++] = path[j++];
627 strcpy(path, new_path);
630 static void set_default_paths(void)
632 set_path_value(Source_Path, Source_Directory);
633 set_path_value(Include_Path, Include_Directory);
634 set_path_value(Code_Path, Code_Directory);
635 set_path_value(Module_Path, Module_Directory);
636 set_path_value(ICL_Path, ICL_Directory);
637 set_path_value(Temporary_Path, Temporary_Directory);
638 set_path_value(Debugging_Name, Debugging_File);
639 set_path_value(Transcript_Name, Transcript_File);
640 set_path_value(Language_Name, Default_Language);
641 set_path_value(Charset_Map, "");
644 /* Parse a path option which looks like "dir", "+dir", "pathname=dir",
645 or "+pathname=dir". If there is no "=", we assume "include_path=...".
646 If the option begins with a "+" the directory is prepended to the
647 existing path instead of replacing it.
649 static void set_path_command(char *command)
650 { int i, j; char *path_to_set = NULL;
653 if (command[0] == '+') {
658 for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;
660 path_to_set=Include_Path;
662 if (command[i] == '=') {
663 char pathname[PATHLEN];
664 if (i>=PATHLEN) i=PATHLEN-1;
666 char ch = command[j];
667 if (isupper(ch)) ch=tolower(ch);
671 command = command+i+1;
674 if (strcmp(pathname, "source_path")==0) path_to_set=Source_Path;
675 if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
676 if (strcmp(pathname, "code_path")==0) path_to_set=Code_Path;
677 if (strcmp(pathname, "module_path")==0) path_to_set=Module_Path;
678 if (strcmp(pathname, "icl_path")==0) path_to_set=ICL_Path;
679 if (strcmp(pathname, "temporary_path")==0) path_to_set=Temporary_Path;
680 if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
681 if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;
682 if (strcmp(pathname, "language_name")==0) path_to_set=Language_Name;
683 if (strcmp(pathname, "charset_map")==0) path_to_set=Charset_Map;
685 if (path_to_set == NULL)
686 { printf("No such path setting as \"%s\"\n", pathname);
692 set_path_value(path_to_set, command);
694 prepend_path_value(path_to_set, command);
697 static int contains_separator(char *name)
699 for (i=0; name[i]!=0; i++)
700 if (name[i] == FN_SEP) return 1;
704 static int write_translated_name(char *new_name, char *old_name,
705 char *prefix_path, int start_pos,
708 if (strlen(old_name)+strlen(extension) >= PATHLEN) {
709 printf("One of your filenames is longer than %d characters.\n", PATHLEN);
712 if (prefix_path == NULL)
713 { sprintf(new_name,"%s%s", old_name, extension);
716 strcpy(new_name, prefix_path + start_pos);
717 for (x=0; (new_name[x]!=0) && (new_name[x]!=FN_ALT); x++) ;
718 if (new_name[x] == 0) start_pos = 0; else start_pos += x+1;
719 if (x+strlen(old_name)+strlen(extension) >= PATHLEN) {
720 printf("One of your pathnames is longer than %d characters.\n", PATHLEN);
723 sprintf(new_name + x, "%s%s", old_name, extension);
727 #ifdef FILE_EXTENSIONS
728 static char *check_extension(char *name, char *extension)
731 /* If a filename ends in '.', remove the dot and add no file extension: */
733 if (name[i] == '.') { name[i]=0; return ""; }
735 /* Remove the new extension if it's already got one: */
737 for (; (i>=0) && (name[i]!=FN_SEP); i--)
738 if (name[i] == '.') return "";
743 /* ------------------------------------------------------------------------- */
744 /* Three translation routines have to deal with path variables which may */
745 /* contain alternative locations separated by the FN_ALT character. */
746 /* These have the protocol: */
748 /* int translate_*_filename(int last_value, ...) */
750 /* and should first be called with last_value equal to 0. If the */
751 /* translated filename works, fine. Otherwise, if the returned integer */
752 /* was zero, the caller knows that no filename works and can issue an */
753 /* error message. If it was non-zero, the caller should pass it on as */
754 /* the last_value again. */
756 /* As implemented below, last_value is the position in the path variable */
757 /* string at which the next directory name to try begins. */
758 /* ------------------------------------------------------------------------- */
760 extern int translate_in_filename(int last_value,
761 char *new_name, char *old_name,
762 int same_directory_flag, int command_line_flag)
763 { char *prefix_path = NULL;
765 int add_path_flag = 1;
768 if ((same_directory_flag==0)
769 && (contains_separator(old_name)==1)) add_path_flag=0;
771 if (add_path_flag==1)
772 { if (command_line_flag == 0)
773 { /* File is opened as a result of an Include directive */
775 if (same_directory_flag==1)
776 prefix_path = current_source_path;
778 if (Include_Path[0]!=0) prefix_path = Include_Path;
780 /* Main file being opened from the command line */
782 else if (Source_Path[0]!=0) prefix_path = Source_Path;
785 #ifdef FILE_EXTENSIONS
786 /* Which file extension is expected? */
788 if ((command_line_flag==1)||(same_directory_flag==1))
789 extension = Source_Extension;
791 extension = Include_Extension;
793 extension = check_extension(old_name, extension);
798 last_value = write_translated_name(new_name, old_name,
799 prefix_path, last_value, extension);
801 /* Set the "current source path" (for use of Include ">...") */
803 if (command_line_flag==1)
804 { strcpy(current_source_path, new_name);
805 for (i=strlen(current_source_path)-1;
806 ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
808 if (i!=0) current_source_path[i+1] = 0; /* Current file in subdir */
809 else current_source_path[0] = 0; /* Current file at root dir */
815 extern int translate_link_filename(int last_value,
816 char *new_name, char *old_name)
817 { char *prefix_path = NULL;
820 if (contains_separator(old_name)==0)
821 if (Module_Path[0]!=0)
822 prefix_path = Module_Path;
824 #ifdef FILE_EXTENSIONS
825 extension = check_extension(old_name, Module_Extension);
830 return write_translated_name(new_name, old_name,
831 prefix_path, last_value, extension);
834 static int translate_icl_filename(int last_value,
835 char *new_name, char *old_name)
836 { char *prefix_path = NULL;
837 char *extension = "";
839 if (contains_separator(old_name)==0)
841 prefix_path = ICL_Path;
843 #ifdef FILE_EXTENSIONS
844 extension = check_extension(old_name, ICL_Extension);
847 return write_translated_name(new_name, old_name,
848 prefix_path, last_value, extension);
851 extern void translate_out_filename(char *new_name, char *old_name)
853 char *extension = "";
856 /* If !convert_filename_flag, then the old_name is just the <file2>
857 parameter on the Inform command line, which we leave alone. */
859 if (!convert_filename_flag)
860 { strcpy(new_name, old_name); return;
863 /* Remove any pathname or extension in <file1>. */
865 if (contains_separator(old_name)==1)
866 { for (i=strlen(old_name)-1; (i>0)&&(old_name[i]!=FN_SEP) ;i--) { };
867 if (old_name[i]==FN_SEP) i++;
870 #ifdef FILE_EXTENSIONS
871 for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
872 if (old_name[i] == '.') old_name[i] = 0;
877 { extension = Module_Extension;
878 if (Module_Path[0]!=0) prefix_path = Module_Path;
883 switch(version_number)
884 { case 3: extension = Code_Extension; break;
885 case 4: extension = V4Code_Extension; break;
886 case 5: extension = V5Code_Extension; break;
887 case 6: extension = V6Code_Extension; break;
888 case 7: extension = V7Code_Extension; break;
889 case 8: extension = V8Code_Extension; break;
893 extension = GlulxCode_Extension;
895 if (Code_Path[0]!=0) prefix_path = Code_Path;
898 #ifdef FILE_EXTENSIONS
899 extension = check_extension(old_name, extension);
902 write_translated_name(new_name, old_name, prefix_path, 0, extension);
905 static char *name_or_unset(char *p)
906 { if (p[0]==0) return "(unset)";
910 static void help_on_filenames(void)
911 { char old_name[PATHLEN];
912 char new_name[PATHLEN];
913 int save_mm = module_switch, x;
915 module_switch = FALSE;
917 printf("Help information on filenames:\n\n");
920 "The command line can take one of two forms:\n\n\
921 inform [commands...] <file1>\n\
922 inform [commands...] <file1> <file2>\n\n\
923 Inform translates <file1> into a source file name (see below) for its input.\n\
924 <file2> is usually omitted: if so, the output filename is made from <file1>\n\
925 by cutting out the name part and translating that (see below).\n\
926 If <file2> is given, however, the output filename is set to just <file2>\n\
927 (not altered in any way).\n\n");
930 "Filenames given in the game source (with commands like Include \"name\" and\n\
931 Link \"name\") are also translated by the rules below.\n\n");
934 "Rules of translation:\n\n\
935 Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
936 (such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
937 1. If the name contains a '%c' character (so it's already a pathname), it\n\
938 isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);
941 " [Exception: when the name is given in an Include command using the >\n\
942 form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
943 of the file doing the inclusion");
944 #ifdef FILE_EXTENSIONS
945 printf(" and a suitable file extension is added");
950 " Filenames must never contain double-quotation marks \". To use filenames\n\
951 which contain spaces, write them in double-quotes: for instance,\n\n\
952 \"inform +code_path=\"Jigsaw Final Version\" jigsaw\".\n\n");
955 "2. The file is looked for at a particular \"path\" (the filename of a\n\
956 directory), depending on what kind of file it is.\n\n\
957 File type Name Current setting\n\n\
958 Source code (in) source_path %s\n\
959 Include file (in) include_path %s\n\
960 Story file (out) code_path %s\n",
961 name_or_unset(Source_Path), name_or_unset(Include_Path),
962 name_or_unset(Code_Path));
965 " Temporary file (out) temporary_path %s\n\
966 ICL command file (in) icl_path %s\n\
967 Module (in & out) module_path %s\n\n",
968 name_or_unset(Temporary_Path),
969 name_or_unset(ICL_Path), name_or_unset(Module_Path));
972 " If the path is unset, then the current working directory is used (so\n\
973 the filename doesn't change): if, for instance, include_path is set to\n\
974 \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
975 \"backup%coldlib%cparser\".\n\n\
976 The paths can be set or unset on the Inform command line by, eg,\n\
977 \"inform +code_path=finished jigsaw\" or\n\
978 \"inform +include_path= balances\" (which unsets include_path).\n\n",
979 FN_SEP, FN_SEP, FN_SEP);
982 " The four input path variables can be set to lists of alternative paths\n\
983 separated by '%c' characters: these alternatives are always tried in\n\
984 the order they are specified in, that is, left to right through the text\n\
985 in the path variable.\n\n",
988 " If two '+' signs are used (\"inform ++include_path=dir jigsaw\") then\n\
989 the path or paths are added to the existing list.\n\n");
991 " (Modules are written to the first alternative in the module_path list;\n\
992 it is an error to give alternatives at all for purely output paths.)\n\n");
994 #ifdef FILE_EXTENSIONS
995 printf("3. The following file extensions are added:\n\n\
998 Story files: %s (Version 3), %s (v4), %s (v5, the default),\n\
999 %s (v6), %s (v7), %s (v8), %s (Glulx)\n\
1000 Temporary files: .tmp\n\
1002 Source_Extension, Include_Extension,
1003 Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
1004 V7Code_Extension, V8Code_Extension, GlulxCode_Extension,
1007 except that any extension you give (on the command line or in a filename\n\
1008 used in a program) will override these. If you give the null extension\n\
1009 \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
1012 printf("Names of four individual files can also be set using the same\n\
1013 + command notation (though they aren't really pathnames). These are:\n\n\
1014 transcript_name (text written by -r switch): now \"%s\"\n\
1015 debugging_name (data written by -k switch): now \"%s\"\n\
1016 language_name (library file defining natural language of game):\n\
1018 charset_map (file for character set mapping): now \"%s\"\n\n",
1019 Transcript_Name, Debugging_Name, Language_Name, Charset_Map);
1021 translate_in_filename(0, new_name, "rezrov", 0, 1);
1022 printf("Examples: 1. \"inform rezrov\"\n\
1023 the source code is read from \"%s\"\n",
1025 convert_filename_flag = TRUE;
1026 translate_out_filename(new_name, "rezrov");
1027 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1029 translate_in_filename(0, new_name, "frotz", 0, 1);
1030 printf("2. \"inform -M frotz\"\n\
1031 the source code is read from \"%s\"\n",
1033 module_switch = TRUE;
1034 convert_filename_flag = TRUE;
1035 translate_out_filename(new_name, "frotz");
1036 printf(" and a module is compiled to \"%s\".\n\n", new_name);
1038 module_switch = FALSE;
1040 sprintf(old_name, "demos%cplugh", FN_SEP);
1041 printf("3. \"inform %s\"\n", old_name);
1042 translate_in_filename(0, new_name, old_name, 0, 1);
1043 printf(" the source code is read from \"%s\"\n", new_name);
1044 sprintf(old_name, "demos%cplugh", FN_SEP);
1045 convert_filename_flag = TRUE;
1046 translate_out_filename(new_name, old_name);
1047 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1049 printf("4. \"inform plover my_demo\"\n");
1050 translate_in_filename(0, new_name, "plover", 0, 1);
1051 printf(" the source code is read from \"%s\"\n", new_name);
1052 convert_filename_flag = FALSE;
1053 translate_out_filename(new_name, "my_demo");
1054 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1056 strcpy(old_name, Source_Path);
1057 sprintf(new_name, "%cnew%cold%crecent%cold%cancient",
1058 FN_ALT, FN_ALT, FN_SEP, FN_ALT, FN_SEP);
1059 printf("5. \"inform +source_path=%s zooge\"\n", new_name);
1061 " Note that four alternative paths are given, the first being the empty\n\
1062 path-name (meaning: where you are now). Inform looks for the source code\n\
1063 by trying these four places in turn, stopping when it finds anything:\n\n");
1065 set_path_value(Source_Path, new_name);
1068 { x = translate_in_filename(x, new_name, "zooge", 0, 1);
1069 printf(" \"%s\"\n", new_name);
1071 strcpy(Source_Path, old_name);
1072 module_switch = save_mm;
1075 /* ------------------------------------------------------------------------- */
1076 /* Naming temporary files */
1077 /* (Arguably temporary files should be made using "tmpfile" in */
1078 /* the ANSI C library, but many supposed ANSI libraries lack it.) */
1079 /* ------------------------------------------------------------------------- */
1081 extern void translate_temp_filename(int i)
1084 { case 1: p=Temp1_Name; break;
1085 case 2: p=Temp2_Name; break;
1086 case 3: p=Temp3_Name; break;
1089 if (strlen(Temporary_Path)+strlen(Temporary_File)+6 >= PATHLEN) {
1090 printf ("Temporary_Path is too long.\n");
1093 sprintf(p,"%s%s%d", Temporary_Path, Temporary_File, i);
1094 #ifdef INCLUDE_TASK_ID
1095 sprintf(p+strlen(p), "_proc%08lx", (long int) unique_task_id());
1097 #ifdef FILE_EXTENSIONS
1098 sprintf(p+strlen(p), ".tmp");
1103 static char riscos_ft_buffer[4];
1105 extern char *riscos_file_type(void)
1107 if (riscos_file_type_format == 1)
1108 { if (module_switch) return("data");
1112 if (module_switch) return("075");
1114 sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
1115 return(riscos_ft_buffer);
1119 /* ------------------------------------------------------------------------- */
1120 /* The compilation pass */
1121 /* ------------------------------------------------------------------------- */
1123 static void run_pass(void)
1125 lexer_begin_prepass();
1126 files_begin_prepass();
1127 load_sourcefile(Source_Name, 0);
1131 parse_program(NULL);
1134 issue_unused_warnings();
1138 if (module_switch) linker_endpass();
1141 if (hash_switch && hash_printed_since_newline) printf("\n");
1143 if (temporary_files_switch)
1144 { if (module_switch) flush_link_data();
1148 if (track_unused_routines)
1149 locate_dead_functions();
1150 construct_storyfile();
1153 int output_has_occurred;
1155 static void rennab(int32 time_taken)
1156 { /* rennab = reverse of banner */
1158 int t = no_warnings + no_suppressed_warnings;
1160 if (memout_switch) print_memory_usage();
1162 if ((no_errors + t)!=0)
1163 { printf("Compiled with ");
1165 { printf("%d error%s", no_errors,(no_errors==1)?"":"s");
1166 if (t > 0) printf(" and ");
1168 if (no_warnings > 0)
1169 printf("%d warning%s", t, (t==1)?"":"s");
1170 if (no_suppressed_warnings > 0)
1171 { if (no_warnings > 0)
1172 printf(" (%d suppressed)", no_suppressed_warnings);
1174 printf("%d suppressed warning%s", no_suppressed_warnings,
1175 (no_suppressed_warnings==1)?"":"s");
1177 if (output_has_occurred == FALSE) printf(" (no output)");
1181 if (no_compiler_errors > 0) print_sorry_message();
1183 if (statistics_switch)
1184 printf("Completed in %ld seconds\n", (long int) time_taken);
1187 /* ------------------------------------------------------------------------- */
1188 /* The compiler abstracted to a routine. */
1189 /* ------------------------------------------------------------------------- */
1191 static int execute_icl_header(char *file1);
1193 static int compile(int number_of_files_specified, char *file1, char *file2)
1196 if (execute_icl_header(file1))
1199 select_target(glulx_mode);
1201 if (define_INFIX_switch && glulx_mode) {
1202 printf("Infix (-X) facilities are not available in Glulx: \
1203 disabling -X switch\n");
1204 define_INFIX_switch = FALSE;
1207 if (module_switch && glulx_mode) {
1208 printf("Modules are not available in Glulx: \
1209 disabling -M switch\n");
1210 module_switch = FALSE;
1213 if (define_INFIX_switch && module_switch)
1214 { printf("Infix (-X) facilities are not available when compiling \
1215 modules: disabling -X switch\n");
1216 define_INFIX_switch = FALSE;
1218 if (runtime_error_checking_switch && module_switch)
1219 { printf("Strict checking (-S) facilities are not available when \
1220 compiling modules: disabling -S switch\n");
1221 runtime_error_checking_switch = FALSE;
1224 time_start=time(0); no_compilations++;
1226 strcpy(Source_Name, file1); convert_filename_flag = TRUE;
1227 strcpy(Code_Name, file1);
1228 if (number_of_files_specified == 2)
1229 { strcpy(Code_Name, file2); convert_filename_flag = FALSE;
1234 if (debugfile_switch) begin_debug_file();
1238 if (transcript_switch) open_transcript_file(Source_Name);
1242 if (transcript_switch)
1243 { write_dictionary_to_transcript();
1244 close_transcript_file();
1247 if (no_errors==0) { output_file(); output_has_occurred = TRUE; }
1248 else { output_has_occurred = FALSE; }
1250 if (debugfile_switch)
1254 if (temporary_files_switch && (no_errors>0)) remove_temp_files();
1258 rennab((int32) (time(0)-time_start));
1260 if (optimise_switch) optimise_abbreviations();
1262 if (store_the_text) my_free(&all_text,"transcription text");
1264 return (no_errors==0)?0:1;
1267 /* ------------------------------------------------------------------------- */
1268 /* The command line interpreter */
1269 /* ------------------------------------------------------------------------- */
1271 static void cli_print_help(int help_level)
1274 "\nThis program is a compiler of Infocom format (also called \"Z-machine\")\n\
1275 story files, as well as \"Glulx\" story files:\n\
1276 Copyright (c) Graham Nelson 1993 - 2021.\n\n");
1278 /* For people typing just "inform", a summary only: */
1283 #ifndef PROMPT_INPUT
1284 printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
1286 printf("When run, Inform prompts you for commands (and switches),\n\
1287 which are optional, then an input <file1> and an (optional) output\n\
1292 "<file1> is the Inform source file of the game to be compiled. <file2>,\n\
1293 if given, overrides the filename Inform would normally use for the\n\
1294 compiled output. Try \"inform -h1\" for file-naming conventions.\n\n\
1295 One or more words can be supplied as \"commands\". These may be:\n\n\
1296 -switches a list of compiler switches, 1 or 2 letter\n\
1297 (see \"inform -h2\" for the full range)\n\n\
1298 +dir set Include_Path to this directory\n\
1299 ++dir add this directory to Include_Path\n\
1300 +PATH=dir change the PATH to this directory\n\
1301 ++PATH=dir add this directory to the PATH\n\n\
1302 $... one of the following configuration commands:\n");
1305 " $list list current settings\n\
1306 $huge make standard \"huge game\" settings %s\n\
1307 $large make standard \"large game\" settings %s\n\
1308 $small make standard \"small game\" settings %s\n\
1309 $?SETTING explain briefly what SETTING is for\n\
1310 $SETTING=number change SETTING to given number\n\
1311 $#SYMBOL=number define SYMBOL as a constant in the story\n\n",
1312 (DEFAULT_MEMORY_SIZE==HUGE_SIZE)?"(default)":"",
1313 (DEFAULT_MEMORY_SIZE==LARGE_SIZE)?"(default)":"",
1314 (DEFAULT_MEMORY_SIZE==SMALL_SIZE)?"(default)":"");
1317 " (filename) read in a list of commands (in the format above)\n\
1318 from this \"setup file\"\n\n");
1320 printf("Alternate command-line formats for the above:\n\
1321 --help (this page)\n\
1323 --addpath PATH=dir\n\
1325 --size huge, --size large, --size small\n\
1326 --helpopt SETTING\n\
1327 --opt SETTING=number\n\
1328 --define SETTING=number\n\
1329 --config filename (setup file)\n\n");
1331 #ifndef PROMPT_INPUT
1332 printf("For example: \"inform -dexs $huge curses\".\n");
1338 /* The -h1 (filenaming) help information: */
1340 if (help_level == 1) { help_on_filenames(); return; }
1342 /* The -h2 (switches) help information: */
1344 printf("Help on the full list of legal switch commands:\n\n\
1345 a trace assembly-language (without hex dumps; see -t)\n\
1346 c more concise error messages\n\
1347 d contract double spaces after full stops in text\n\
1348 d2 contract double spaces after exclamation and question marks, too\n\
1349 e economy mode (slower): make use of declared abbreviations\n");
1352 f frequencies mode: show how useful abbreviations are\n\
1353 g traces calls to all game functions\n\
1354 g2 traces calls to all game and library functions\n\
1355 g3 traces calls to all functions (including veneer)\n\
1356 h print general help information\n\
1357 h1 print help information on filenames and path options\n\
1358 h2 print help information on switches (this page)\n");
1361 i ignore default switches set within the file\n\
1362 j list objects as constructed\n\
1363 k output debugging information to \"%s\"\n\
1364 l list every statement run through Inform (not implemented)\n\
1365 m say how much memory has been allocated\n\
1366 n print numbers of properties, attributes and actions\n",
1369 o print offset addresses\n\
1370 p give percentage breakdown of story file\n\
1371 q keep quiet about obsolete usages\n\
1372 r record all the text to \"%s\"\n\
1373 s give statistics\n\
1374 t trace assembly-language (with full hex dumps; see -a)\n",
1378 u work out most useful abbreviations (very very slowly)\n\
1379 v3 compile to version-3 (\"Standard\"/\"ZIP\") story file\n\
1380 v4 compile to version-4 (\"Plus\"/\"EZIP\") story file\n\
1381 v5 compile to version-5 (\"Advanced\"/\"XZIP\") story file: the default\n\
1382 v6 compile to version-6 (graphical/\"YZIP\") story file\n\
1383 v7 compile to version-7 (expanded \"Advanced\") story file\n\
1384 v8 compile to version-8 (expanded \"Advanced\") story file\n\
1385 w disable warning messages\n\
1386 x print # for every 100 lines compiled\n\
1387 y trace linking system\n\
1388 z print memory map of the virtual machine\n\n");
1391 B use big memory model (for large V6/V7 files)\n\
1392 C0 text character set is plain ASCII only\n\
1393 Cu text character set is UTF-8\n\
1394 Cn text character set is ISO 8859-n (n = 1 to 9)\n\
1395 (1 to 4, Latin1 to Latin4; 5, Cyrillic; 6, Arabic;\n\
1396 7, Greek; 8, Hebrew; 9, Latin5. Default is -C1.)\n");
1397 printf(" D insert \"Constant DEBUG;\" automatically\n");
1398 printf(" E0 Archimedes-style error messages%s\n",
1399 (error_format==0)?" (current setting)":"");
1400 printf(" E1 Microsoft-style error messages%s\n",
1401 (error_format==1)?" (current setting)":"");
1402 printf(" E2 Macintosh MPW-style error messages%s\n",
1403 (error_format==2)?" (current setting)":"");
1404 #ifdef USE_TEMPORARY_FILES
1405 printf(" F0 use extra memory rather than temporary files\n");
1407 printf(" F1 use temporary files to reduce memory consumption\n");
1409 printf(" G compile a Glulx game file\n");
1410 printf(" H use Huffman encoding to compress Glulx strings\n");
1411 printf(" M compile as a Module for future linking\n");
1415 R0 use filetype 060 + version number for games (default)\n\
1416 R1 use official Acorn filetype 11A for all games\n");
1418 printf(" S compile strict error-checking at run-time (on by default)\n");
1419 #ifdef ARC_THROWBACK
1420 printf(" T enable throwback of errors in the DDE\n");
1422 printf(" U insert \"Constant USE_MODULES;\" automatically\n");
1423 printf(" V print the version and date of this program\n");
1424 printf(" Wn header extension table is at least n words (n = 3 to 99)\n");
1425 printf(" X compile with INFIX debugging facilities present\n");
1429 extern void switches(char *p, int cmode)
1430 { int i, s=1, state;
1431 /* Here cmode is 0 if switches list is from a "Switches" directive
1432 and 1 if from a "-switches" command-line or ICL list */
1437 "Ignoring second word which should be a -list of switches.\n");
1441 for (i=cmode; p[i]!=0; i+=s, s=1)
1449 case 'a': asm_trace_setting = 1; break;
1450 case 'b': bothpasses_switch = state; break;
1451 case 'c': concise_switch = state; break;
1452 case 'd': switch(p[i+1])
1453 { case '1': double_space_setting=1; s=2; break;
1454 case '2': double_space_setting=2; s=2; break;
1455 default: double_space_setting=1; break;
1458 case 'e': economy_switch = state; break;
1459 case 'f': frequencies_switch = state; break;
1460 case 'g': switch(p[i+1])
1461 { case '1': trace_fns_setting=1; s=2; break;
1462 case '2': trace_fns_setting=2; s=2; break;
1463 case '3': trace_fns_setting=3; s=2; break;
1464 default: trace_fns_setting=1; break;
1467 case 'h': switch(p[i+1])
1468 { case '1': cli_print_help(1); s=2; break;
1469 case '2': cli_print_help(2); s=2; break;
1471 default: cli_print_help(0); break;
1474 case 'i': ignore_switches_switch = state; break;
1475 case 'j': listobjects_switch = state; break;
1476 case 'k': if (cmode == 0)
1477 error("The switch '-k' can't be set with 'Switches'");
1479 debugfile_switch = state;
1481 case 'l': listing_switch = state; break;
1482 case 'm': memout_switch = state; break;
1483 case 'n': printprops_switch = state; break;
1484 case 'o': offsets_switch = state; break;
1485 case 'p': percentages_switch = state; break;
1486 case 'q': obsolete_switch = state; break;
1487 case 'r': if (cmode == 0)
1488 error("The switch '-r' can't be set with 'Switches'");
1490 transcript_switch = state; break;
1491 case 's': statistics_switch = state; break;
1492 case 't': asm_trace_setting=2; break;
1493 case 'u': if (cmode == 0) {
1494 error("The switch '-u' can't be set with 'Switches'");
1497 optimise_switch = state; break;
1498 case 'v': if (glulx_mode) { s = select_glulx_version(p+i+1)+1; break; }
1499 if ((cmode==0) && (version_set_switch)) { s=2; break; }
1500 version_set_switch = TRUE; s=2;
1502 { case '3': select_version(3); break;
1503 case '4': select_version(4); break;
1504 case '5': select_version(5); break;
1505 case '6': select_version(6); break;
1506 case '7': select_version(7); break;
1507 case '8': select_version(8); break;
1508 default: printf("-v must be followed by 3 to 8\n");
1509 version_set_switch=0; s=1;
1512 if ((version_number < 5) && (r_e_c_s_set == FALSE))
1513 runtime_error_checking_switch = FALSE;
1515 case 'w': nowarnings_switch = state; break;
1516 case 'x': hash_switch = state; break;
1517 case 'y': s=2; linker_trace_setting=p[i+1]-'0'; break;
1518 case 'z': memory_map_switch = state; break;
1519 case 'B': oddeven_packing_switch = state; break;
1521 if (p[i+1] == 'u') {
1522 character_set_unicode = TRUE;
1523 /* Leave the set_setting on Latin-1, because that
1524 matches the first block of Unicode. */
1525 character_set_setting = 1;
1528 { character_set_setting=p[i+1]-'0';
1529 if ((character_set_setting < 0)
1530 || (character_set_setting > 9))
1531 { printf("-C must be followed by 'u' or 0 to 9. Defaulting to ISO-8859-1.\n");
1532 character_set_unicode = FALSE;
1533 character_set_setting = 1;
1536 if (cmode == 0) change_character_set();
1538 case 'D': define_DEBUG_switch = state; break;
1539 case 'E': switch(p[i+1])
1540 { case '0': s=2; error_format=0; break;
1541 case '1': s=2; error_format=1; break;
1542 case '2': s=2; error_format=2; break;
1543 default: error_format=1; break;
1546 case 'F': if (cmode == 0) {
1547 error("The switch '-F' can't be set with 'Switches'");
1551 { case '0': s=2; temporary_files_switch = FALSE; break;
1552 case '1': s=2; temporary_files_switch = TRUE; break;
1553 default: temporary_files_switch = state; break;
1556 case 'M': module_switch = state;
1557 if (state && (r_e_c_s_set == FALSE))
1558 runtime_error_checking_switch = FALSE;
1561 case 'R': switch(p[i+1])
1562 { case '0': s=2; riscos_file_type_format=0; break;
1563 case '1': s=2; riscos_file_type_format=1; break;
1564 default: riscos_file_type_format=1; break;
1568 #ifdef ARC_THROWBACK
1569 case 'T': throwback_switch = state; break;
1571 case 'S': runtime_error_checking_switch = state;
1572 r_e_c_s_set = TRUE; break;
1573 case 'G': if (cmode == 0)
1574 error("The switch '-G' can't be set with 'Switches'");
1575 else if (version_set_switch)
1576 error("The '-G' switch cannot follow the '-v' switch");
1578 { glulx_mode = state;
1579 adjust_memory_sizes();
1582 case 'H': compression_switch = state; break;
1583 case 'U': define_USE_MODULES_switch = state; break;
1584 case 'V': exit(0); break;
1585 case 'W': if ((p[i+1]>='0') && (p[i+1]<='9'))
1586 { s=2; ZCODE_HEADER_EXT_WORDS = p[i+1]-'0';
1587 if ((p[i+2]>='0') && (p[i+2]<='9'))
1588 { s=3; ZCODE_HEADER_EXT_WORDS *= 10;
1589 ZCODE_HEADER_EXT_WORDS += p[i+2]-'0';
1593 case 'X': define_INFIX_switch = state; break;
1595 printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
1601 if (optimise_switch && (!store_the_text))
1602 { store_the_text=TRUE;
1605 printf("Allocation %ld bytes for transcription text\n",
1606 (long) MAX_TRANSCRIPT_SIZE);
1607 all_text = halloc(MAX_TRANSCRIPT_SIZE,1);
1608 malloced_bytes += MAX_TRANSCRIPT_SIZE;
1610 fatalerror("Can't hallocate memory for transcription text. Darn.");
1612 all_text=my_malloc(MAX_TRANSCRIPT_SIZE,"transcription text");
1617 static int icl_command(char *p)
1618 { if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
1619 || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
1623 static void icl_error(char *filename, int line)
1624 { printf("Error in ICL file '%s', line %d:\n", filename, line);
1627 static void icl_header_error(char *filename, int line)
1628 { printf("Error in ICL header of file '%s', line %d:\n", filename, line);
1631 static int copy_icl_word(char *from, char *to, int max)
1633 /* Copies one token from 'from' to 'to', null-terminated:
1634 returns the number of chars in 'from' read past (possibly 0). */
1636 int i, j, quoted_mode, truncated;
1638 i = 0; truncated = 0;
1639 while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
1640 || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;
1643 { while (from[i] != 0) i++;
1644 to[0] = 0; return i;
1647 for (quoted_mode = FALSE, j=0;;)
1648 { if (from[i] == 0) break;
1649 if (from[i] == 10) break;
1650 if (from[i] == 13) break;
1651 if (from[i] == TAB_CHARACTER) break;
1652 if ((from[i] == ' ') && (!quoted_mode)) break;
1653 if (from[i] == '\"') { quoted_mode = !quoted_mode; i++; }
1654 else to[j++] = from[i++];
1662 printf("The following parameter has been truncated:\n%s\n", to);
1666 /* Copy a string, converting to uppercase. The to array should be
1667 (at least) max characters. Result will be null-terminated, so
1668 at most max-1 characters will be copied.
1670 static int strcpyupper(char *to, char *from, int max)
1673 for (ix=0; ix<max-1; ix++) {
1675 if (islower(ch)) ch = toupper(ch);
1682 static void execute_icl_command(char *p);
1683 static int execute_dashdash_command(char *p, char *p2);
1685 static int execute_icl_header(char *argname)
1688 char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1692 char filename[PATHLEN];
1696 { x = translate_in_filename(x, filename, argname, 0, 1);
1697 command_file = fopen(filename,"r");
1698 } while ((command_file == NULL) && (x != 0));
1699 if (!command_file) {
1700 /* Fail silently. The regular compiler will try to open the file
1701 again, and report the problem. */
1705 while (feof(command_file)==0) {
1706 if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1708 if (!(cli_buff[0] == '!' && cli_buff[1] == '%'))
1710 i = copy_icl_word(cli_buff+2, fw, CMD_BUF_SIZE);
1711 if (icl_command(fw)) {
1712 execute_icl_command(fw);
1713 copy_icl_word(cli_buff+2 + i, fw, CMD_BUF_SIZE);
1714 if ((fw[0] != 0) && (fw[0] != '!')) {
1715 icl_header_error(filename, line);
1717 printf("expected comment or nothing but found '%s'\n", fw);
1722 icl_header_error(filename, line);
1724 printf("Expected command or comment but found '%s'\n", fw);
1728 fclose(command_file);
1730 return (errcount==0)?0:1;
1734 static void run_icl_file(char *filename, FILE *command_file)
1735 { char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1737 printf("[Running ICL file '%s']\n", filename);
1739 while (feof(command_file)==0)
1740 { if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1742 i = copy_icl_word(cli_buff, fw, CMD_BUF_SIZE);
1743 if (icl_command(fw))
1744 { execute_icl_command(fw);
1745 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1746 if ((fw[0] != 0) && (fw[0] != '!'))
1747 { icl_error(filename, line);
1748 printf("expected comment or nothing but found '%s'\n", fw);
1752 { if (strcmp(fw, "compile")==0)
1753 { char story_name[PATHLEN], code_name[PATHLEN];
1754 i += copy_icl_word(cli_buff + i, story_name, PATHLEN);
1755 i += copy_icl_word(cli_buff + i, code_name, PATHLEN);
1757 if (code_name[0] != 0) x=2;
1758 else if (story_name[0] != 0) x=1;
1762 { case 0: icl_error(filename, line);
1763 printf("No filename given to 'compile'\n");
1765 case 1: printf("[Compiling <%s>]\n", story_name);
1766 compile(x, story_name, code_name);
1768 case 2: printf("[Compiling <%s> to <%s>]\n",
1769 story_name, code_name);
1770 compile(x, story_name, code_name);
1771 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1773 { icl_error(filename, line);
1774 printf("Expected comment or nothing but found '%s'\n",
1782 { icl_error(filename, line);
1783 printf("Expected command or comment but found '%s'\n", fw);
1789 /* This should only be called if the argument has been verified to be
1790 an ICL command, e.g. by checking icl_command().
1792 static void execute_icl_command(char *p)
1793 { char filename[PATHLEN], cli_buff[CMD_BUF_SIZE];
1798 { case '+': set_path_command(p+1); break;
1799 case '-': switches(p,1); break;
1800 case '$': memory_command(p+1); break;
1801 case '(': len = strlen(p);
1802 if (p[len-1] != ')') {
1803 printf("Error in ICL: (command) missing closing paren\n");
1806 len -= 2; /* omit parens */
1807 if (len > CMD_BUF_SIZE-1) len = CMD_BUF_SIZE-1;
1808 strncpy(cli_buff, p+1, len);
1812 { x = translate_icl_filename(x, filename, cli_buff);
1813 command_file = fopen(filename,"r");
1814 } while ((command_file == NULL) && (x != 0));
1816 if (command_file == NULL) {
1817 printf("Error in ICL: Couldn't open command file '%s'\n",
1821 run_icl_file(filename, command_file);
1822 fclose(command_file);
1827 /* Convert a --command into the equivalent ICL command and call
1828 execute_icl_command(). The dashes have already been stripped.
1830 The second argument is the following command-line argument
1831 (or NULL if there was none). This may or may not be consumed.
1832 Returns TRUE if it was.
1834 static int execute_dashdash_command(char *p, char *p2)
1836 char cli_buff[CMD_BUF_SIZE];
1837 int consumed2 = FALSE;
1839 if (!strcmp(p, "help")) {
1840 strcpy(cli_buff, "-h");
1842 else if (!strcmp(p, "list")) {
1843 strcpy(cli_buff, "$LIST");
1845 else if (!strcmp(p, "size")) {
1847 if (!(p2 && (!strcmpcis(p2, "HUGE") || !strcmpcis(p2, "LARGE") || !strcmpcis(p2, "SMALL")))) {
1848 printf("--size must be followed by \"huge\", \"large\", or \"small\"\n");
1851 strcpy(cli_buff, "$");
1852 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1854 else if (!strcmp(p, "opt")) {
1856 if (!p2 || !strchr(p2, '=')) {
1857 printf("--opt must be followed by \"setting=number\"\n");
1860 strcpy(cli_buff, "$");
1861 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1863 else if (!strcmp(p, "helpopt")) {
1866 printf("--helpopt must be followed by \"setting\"\n");
1869 strcpy(cli_buff, "$?");
1870 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1872 else if (!strcmp(p, "define")) {
1875 printf("--define must be followed by \"symbol=number\"\n");
1878 strcpy(cli_buff, "$#");
1879 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1881 else if (!strcmp(p, "path")) {
1883 if (!p2 || !strchr(p2, '=')) {
1884 printf("--path must be followed by \"name=path\"\n");
1887 snprintf(cli_buff, CMD_BUF_SIZE, "+%s", p2);
1889 else if (!strcmp(p, "addpath")) {
1891 if (!p2 || !strchr(p2, '=')) {
1892 printf("--addpath must be followed by \"name=path\"\n");
1895 snprintf(cli_buff, CMD_BUF_SIZE, "++%s", p2);
1897 else if (!strcmp(p, "config")) {
1900 printf("--config must be followed by \"file.icl\"\n");
1903 snprintf(cli_buff, CMD_BUF_SIZE, "(%s)", p2);
1906 printf("Option \"--%s\" unknown (try \"inform -h\")\n", p);
1910 execute_icl_command(cli_buff);
1914 /* ------------------------------------------------------------------------- */
1915 /* Opening and closing banners */
1916 /* ------------------------------------------------------------------------- */
1918 char banner_line[CMD_BUF_SIZE];
1920 /* We store the banner text for use elsewhere (see files.c).
1922 static void banner(void)
1925 snprintf(banner_line, CMD_BUF_SIZE, "Inform %d.%d%d",
1926 (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10);
1927 #ifdef RELEASE_SUFFIX
1928 len = strlen(banner_line);
1929 snprintf(banner_line+len, CMD_BUF_SIZE-len, "%s", RELEASE_SUFFIX);
1931 #ifdef MACHINE_STRING
1932 len = strlen(banner_line);
1933 snprintf(banner_line+len, CMD_BUF_SIZE-len, " for %s", MACHINE_STRING);
1935 len = strlen(banner_line);
1936 snprintf(banner_line+len, CMD_BUF_SIZE-len, " (%s)", RELEASE_DATE);
1938 printf("%s\n", banner_line);
1941 /* ------------------------------------------------------------------------- */
1942 /* Input from the outside world */
1943 /* ------------------------------------------------------------------------- */
1946 static void read_command_line(int argc, char **argv)
1948 char buffer1[PATHLEN], buffer2[PATHLEN], buffer3[PATHLEN];
1950 printf("Source filename?\n> ");
1951 while (gets(buffer1)==NULL); cli_file1=buffer1;
1952 printf("Output filename (RETURN for the same)?\n> ");
1953 while (gets(buffer2)==NULL); cli_file2=buffer2;
1954 cli_files_specified=1;
1955 if (buffer2[0]!=0) cli_files_specified=2;
1957 { printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
1958 while (gets(buffer3)==NULL); execute_icl_command(buffer3);
1959 } while (buffer3[0]!=0);
1962 static void read_command_line(int argc, char **argv)
1964 if (argc==1) switches("-h",1);
1966 for (i=1, cli_files_specified=0; i<argc; i++)
1967 if (argv[i][0] == '-' && argv[i][1] == '-') {
1968 char *nextarg = NULL;
1970 if (i+1 < argc) nextarg = argv[i+1];
1971 consumed2 = execute_dashdash_command(argv[i]+2, nextarg);
1972 if (consumed2 && i+1 < argc) {
1976 else if (icl_command(argv[i])) {
1977 execute_icl_command(argv[i]);
1980 switch(++cli_files_specified)
1981 { case 1: cli_file1 = argv[i]; break;
1982 case 2: cli_file2 = argv[i]; break;
1984 printf("Command line error: unknown parameter '%s'\n",
1991 /* ------------------------------------------------------------------------- */
1992 /* M A I N : An outer shell for machine-specific quirks */
1993 /* Omitted altogether if EXTERNAL_SHELL is defined, as for instance is */
1994 /* needed for the Macintosh front end. */
1995 /* ------------------------------------------------------------------------- */
1997 #ifdef EXTERNAL_SHELL
1998 extern int sub_main(int argc, char **argv);
2001 static int sub_main(int argc, char **argv);
2003 int main(int argc, char **argv, char *envp[])
2005 int main(int argc, char **argv)
2009 InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
2011 rcode = sub_main(argc, argv);
2012 #ifdef ARC_THROWBACK
2020 /* ------------------------------------------------------------------------- */
2021 /* M A I N II: Starting up ICL with the command line */
2022 /* ------------------------------------------------------------------------- */
2024 #ifdef EXTERNAL_SHELL
2025 extern int sub_main(int argc, char **argv)
2027 static int sub_main(int argc, char **argv)
2032 ProcessEvents (&g_proc);
2035 if (store_the_text) my_free(&all_text,"transcription text");
2036 longjmp (g_fallback, 1);
2042 set_memory_sizes(DEFAULT_MEMORY_SIZE); set_default_paths();
2043 reset_switch_settings(); select_version(5);
2045 cli_files_specified = 0; no_compilations = 0;
2046 cli_file1 = "source"; cli_file2 = "output";
2048 read_command_line(argc, argv);
2050 if (cli_files_specified > 0)
2051 { return_code = compile(cli_files_specified, cli_file1, cli_file2);
2053 if (return_code != 0) return(return_code);
2056 if (no_compilations == 0)
2057 printf("\n[No compilation requested]\n");
2058 if (no_compilations > 1)
2059 printf("[%d compilations completed]\n", no_compilations);
2064 /* ========================================================================= */