1 /* ------------------------------------------------------------------------- */
2 /* "inform" : The top level of Inform: switches, pathnames, filenaming */
3 /* conventions, ICL (Inform Command Line) files, main */
5 /* Part of Inform 6.40 */
6 /* copyright (c) Graham Nelson 1993 - 2022 */
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 MAX_LOCAL_VARIABLES = 16; /* including "sp" */
128 if (INDIV_PROP_START != 64) {
129 INDIV_PROP_START = 64;
130 fatalerror("You cannot change INDIV_PROP_START in Z-code");
132 if (DICT_WORD_SIZE != 6) {
134 fatalerror("You cannot change DICT_WORD_SIZE in Z-code");
136 if (DICT_CHAR_SIZE != 1) {
138 fatalerror("You cannot change DICT_CHAR_SIZE in Z-code");
140 if (NUM_ATTR_BYTES != 6) {
142 fatalerror("You cannot change NUM_ATTR_BYTES in Z-code");
148 MAXINTWORD = 0x7FFFFFFF;
149 scale_factor = 0; /* It should never even get used in Glulx */
151 /* This could really be 120, since the practical limit is the size
152 of local_variables.keywords. But historically it's been 119. */
153 MAX_LOCAL_VARIABLES = 119; /* including "sp" */
155 if (INDIV_PROP_START < 256) {
156 INDIV_PROP_START = 256;
157 warning_numbered("INDIV_PROP_START should be at least 256 in Glulx. Setting to", INDIV_PROP_START);
160 if (NUM_ATTR_BYTES % 4 != 3) {
161 NUM_ATTR_BYTES += (3 - (NUM_ATTR_BYTES % 4));
162 warning_numbered("NUM_ATTR_BYTES must be a multiple of four, plus three. Increasing to", NUM_ATTR_BYTES);
165 if (DICT_CHAR_SIZE != 1 && DICT_CHAR_SIZE != 4) {
167 warning_numbered("DICT_CHAR_SIZE must be either 1 or 4. Setting to", DICT_CHAR_SIZE);
171 if (MAX_LOCAL_VARIABLES > MAX_KEYWORD_GROUP_SIZE) {
172 compiler_error("MAX_LOCAL_VARIABLES cannot exceed MAX_KEYWORD_GROUP_SIZE");
173 MAX_LOCAL_VARIABLES = MAX_KEYWORD_GROUP_SIZE;
176 if (DICT_WORD_SIZE > MAX_DICT_WORD_SIZE) {
177 DICT_WORD_SIZE = MAX_DICT_WORD_SIZE;
179 "DICT_WORD_SIZE cannot exceed MAX_DICT_WORD_SIZE; resetting",
181 /* MAX_DICT_WORD_SIZE can be increased in header.h without fear. */
183 if (NUM_ATTR_BYTES > MAX_NUM_ATTR_BYTES) {
184 NUM_ATTR_BYTES = MAX_NUM_ATTR_BYTES;
186 "NUM_ATTR_BYTES cannot exceed MAX_NUM_ATTR_BYTES; resetting",
188 /* MAX_NUM_ATTR_BYTES can be increased in header.h without fear. */
191 /* Set up a few more variables that depend on the above values */
195 DICT_WORD_BYTES = DICT_WORD_SIZE;
196 OBJECT_BYTE_LENGTH = 0;
197 DICT_ENTRY_BYTE_LENGTH = ((version_number==3)?7:9) - (ZCODE_LESS_DICT_DATA?1:0);
198 DICT_ENTRY_FLAG_POS = 0;
202 OBJECT_BYTE_LENGTH = (1 + (NUM_ATTR_BYTES) + 6*4 + (GLULX_OBJECT_EXT_BYTES));
203 DICT_WORD_BYTES = DICT_WORD_SIZE*DICT_CHAR_SIZE;
204 if (DICT_CHAR_SIZE == 1) {
205 DICT_ENTRY_BYTE_LENGTH = (7+DICT_WORD_BYTES);
206 DICT_ENTRY_FLAG_POS = (1+DICT_WORD_BYTES);
209 DICT_ENTRY_BYTE_LENGTH = (12+DICT_WORD_BYTES);
210 DICT_ENTRY_FLAG_POS = (4+DICT_WORD_BYTES);
216 /* The Z-machine's 96 abbreviations are used for these two purposes.
217 Make sure they are set consistently. If exactly one has been
218 set non-default, set the other to match. */
219 if (MAX_DYNAMIC_STRINGS == 32 && MAX_ABBREVS != 64) {
220 MAX_DYNAMIC_STRINGS = 96 - MAX_ABBREVS;
222 if (MAX_ABBREVS == 64 && MAX_DYNAMIC_STRINGS != 32) {
223 MAX_ABBREVS = 96 - MAX_DYNAMIC_STRINGS;
225 if (MAX_ABBREVS + MAX_DYNAMIC_STRINGS != 96
227 || MAX_DYNAMIC_STRINGS < 0) {
228 warning("MAX_ABBREVS plus MAX_DYNAMIC_STRINGS must be 96 in Z-code; resetting both");
229 MAX_DYNAMIC_STRINGS = 32;
235 /* ------------------------------------------------------------------------- */
236 /* Tracery: output control variables */
237 /* (These are initially set to foo_trace_setting, but the Trace directive */
238 /* can change them on the fly) */
239 /* ------------------------------------------------------------------------- */
241 int asm_trace_level, /* trace assembly: 0 for off, 1 for assembly
242 only, 2 for full assembly tracing with hex dumps,
243 3 for branch shortening info, 4 for verbose
245 expr_trace_level, /* expression tracing: 0 off, 1 on, 2/3 more */
246 linker_trace_level, /* linker tracing: 0 to 4 levels */
247 tokens_trace_level; /* lexer output tracing: 0 off, 1 on, 2/3 more */
249 /* ------------------------------------------------------------------------- */
250 /* On/off switch variables (by default all FALSE); other switch settings */
251 /* (Some of these have become numerical settings now) */
252 /* ------------------------------------------------------------------------- */
254 int concise_switch, /* -c */
255 economy_switch, /* -e */
256 frequencies_setting, /* $!FREQ, -f */
257 ignore_switches_switch, /* -i */
258 debugfile_switch, /* -k */
259 memout_switch, /* $!MEM */
260 printprops_switch, /* $!PROPS */
261 printactions_switch, /* $!ACTIONS */
262 obsolete_switch, /* -q */
263 transcript_switch, /* -r */
264 statistics_switch, /* $!STATS, -s */
265 optimise_switch, /* -u */
266 version_set_switch, /* -v */
267 nowarnings_switch, /* -w */
268 hash_switch, /* -x */
269 memory_map_setting, /* $!MAP, -z */
270 oddeven_packing_switch, /* -B */
271 define_DEBUG_switch, /* -D */
272 module_switch, /* -M */
273 runtime_error_checking_switch, /* -S */
274 define_USE_MODULES_switch, /* -U */
275 define_INFIX_switch; /* -X */
277 int throwback_switch; /* -T */
280 int riscos_file_type_format; /* set by -R */
282 int compression_switch; /* set by -H */
283 int character_set_setting, /* set by -C0 through -C9 */
284 character_set_unicode, /* set by -Cu */
285 error_format, /* set by -E */
286 asm_trace_setting, /* $!ASM, -a: initial value of
288 bpatch_trace_setting, /* $!BPATCH */
289 symdef_trace_setting, /* $!SYMDEF */
290 expr_trace_setting, /* $!EXPR: initial value of
292 tokens_trace_setting, /* $!TOKENS: initial value of
293 tokens_trace_level */
294 optabbrevs_trace_setting, /* $!FINDABBREVS */
295 double_space_setting, /* set by -d: 0, 1 or 2 */
296 trace_fns_setting, /* $!RUNTIME, -g: 0, 1, 2, or 3 */
297 files_trace_setting, /* $!FILES */
298 linker_trace_setting, /* $!LINKER: initial value of
299 linker_trace_level */
300 list_verbs_setting, /* $!VERBS */
301 list_dict_setting, /* $!DICT */
302 list_objects_setting, /* $!OBJECTS */
303 list_symbols_setting, /* $!SYMBOLS */
304 store_the_text; /* when set, record game text to a chunk
305 of memory (used by -u) */
306 static int r_e_c_s_set; /* has -S been explicitly set? */
308 int glulx_mode; /* -G */
310 static void reset_switch_settings(void)
311 { asm_trace_setting = 0;
312 linker_trace_setting = 0;
313 tokens_trace_setting = 0;
314 expr_trace_setting = 0;
315 bpatch_trace_setting = 0;
316 symdef_trace_setting = 0;
317 list_verbs_setting = 0;
318 list_dict_setting = 0;
319 list_objects_setting = 0;
320 list_symbols_setting = 0;
322 store_the_text = FALSE;
324 concise_switch = FALSE;
325 double_space_setting = 0;
326 economy_switch = FALSE;
327 files_trace_setting = 0;
328 frequencies_setting = 0;
329 trace_fns_setting = 0;
330 ignore_switches_switch = FALSE;
331 debugfile_switch = FALSE;
333 printprops_switch = 0;
334 printactions_switch = 0;
335 obsolete_switch = FALSE;
336 transcript_switch = FALSE;
337 statistics_switch = FALSE;
338 optimise_switch = FALSE;
339 optabbrevs_trace_setting = 0;
340 version_set_switch = FALSE;
341 nowarnings_switch = FALSE;
343 memory_map_setting = 0;
344 oddeven_packing_switch = FALSE;
345 define_DEBUG_switch = FALSE;
346 define_USE_MODULES_switch = FALSE;
347 module_switch = FALSE;
349 throwback_switch = FALSE;
351 runtime_error_checking_switch = TRUE;
353 define_INFIX_switch = FALSE;
355 riscos_file_type_format = 0;
357 error_format=DEFAULT_ERROR_FORMAT;
359 character_set_setting = 1; /* Default is ISO Latin-1 */
360 character_set_unicode = FALSE;
362 compression_switch = TRUE;
364 requested_glulx_version = 0;
366 /* These aren't switches, but for clarity we reset them too. */
368 expr_trace_level = 0;
369 linker_trace_level = 0;
370 tokens_trace_level = 0;
373 /* ------------------------------------------------------------------------- */
374 /* Number of files given as command line parameters (0, 1 or 2) */
375 /* ------------------------------------------------------------------------- */
377 static int cli_files_specified,
378 convert_filename_flag;
380 char Source_Name[PATHLEN]; /* Processed name of first input file */
381 char Code_Name[PATHLEN]; /* Processed name of output file */
383 static char *cli_file1, *cli_file2; /* Unprocessed (and unsafe to alter) */
385 /* ========================================================================= */
386 /* Data structure management routines */
387 /* ------------------------------------------------------------------------- */
389 static void init_vars(void)
397 init_expressc_vars();
398 init_expressp_vars();
413 static void begin_pass(void)
419 directs_begin_pass();
421 expressc_begin_pass();
422 expressp_begin_pass();
425 endofpass_flag = FALSE;
426 expr_trace_level = expr_trace_setting;
427 asm_trace_level = asm_trace_setting;
428 tokens_trace_level = tokens_trace_setting;
429 linker_trace_level = linker_trace_setting;
434 objects_begin_pass();
436 symbols_begin_pass();
445 /* Compile a Main__ routine (see "veneer.c") */
447 compile_initial_routine();
449 /* Make the four metaclasses: Class must be object number 1, so
450 it must come first */
455 make_class("Object");
456 make_class("Routine");
457 make_class("String");
463 extern void allocate_arrays(void)
465 arrays_allocate_arrays();
466 asm_allocate_arrays();
467 bpatch_allocate_arrays();
468 chars_allocate_arrays();
469 directs_allocate_arrays();
470 errors_allocate_arrays();
471 expressc_allocate_arrays();
472 expressp_allocate_arrays();
473 files_allocate_arrays();
475 lexer_allocate_arrays();
476 linker_allocate_arrays();
477 memory_allocate_arrays();
478 objects_allocate_arrays();
479 states_allocate_arrays();
480 symbols_allocate_arrays();
481 syntax_allocate_arrays();
482 tables_allocate_arrays();
483 text_allocate_arrays();
484 veneer_allocate_arrays();
485 verbs_allocate_arrays();
488 extern void free_arrays(void)
490 /* One array may survive this routine, all_the_text (used to hold
491 game text until the abbreviations optimiser begins work on it): this
492 array (if it was ever allocated) is freed at the top level. */
494 arrays_free_arrays();
496 bpatch_free_arrays();
498 directs_free_arrays();
499 errors_free_arrays();
500 expressc_free_arrays();
501 expressp_free_arrays();
505 linker_free_arrays();
506 memory_free_arrays();
507 objects_free_arrays();
508 states_free_arrays();
509 symbols_free_arrays();
510 syntax_free_arrays();
511 tables_free_arrays();
513 veneer_free_arrays();
517 /* ------------------------------------------------------------------------- */
518 /* Name translation code for filenames */
519 /* ------------------------------------------------------------------------- */
521 static char Source_Path[PATHLEN];
522 static char Include_Path[PATHLEN];
523 static char Code_Path[PATHLEN];
524 static char Module_Path[PATHLEN];
525 static char current_source_path[PATHLEN];
526 char Debugging_Name[PATHLEN];
527 char Transcript_Name[PATHLEN];
528 char Language_Name[PATHLEN];
529 char Charset_Map[PATHLEN];
530 static char ICL_Path[PATHLEN];
532 /* Set one of the above Path buffers to the given location, or list of
533 locations. (A list is comma-separated, and only accepted for Source_Path,
534 Include_Path, ICL_Path, Module_Path.)
536 static void set_path_value(char *path, char *value)
541 if (i >= PATHLEN-1) {
542 printf("A specified path is longer than %d characters.\n",
546 if ((value[j] == FN_ALT) || (value[j] == 0))
547 { if ((value[j] == FN_ALT)
548 && (path != Source_Path) && (path != Include_Path)
549 && (path != ICL_Path) && (path != Module_Path))
550 { printf("The character '%c' is used to divide entries in a list \
551 of possible locations, and can only be used in the Include_Path, Source_Path, \
552 Module_Path or ICL_Path variables. Other paths are for output only.\n", FN_ALT);
555 if ((path != Debugging_Name) && (path != Transcript_Name)
556 && (path != Language_Name) && (path != Charset_Map)
557 && (i>0) && (isalnum(path[i-1]))) path[i++] = FN_SEP;
558 path[i++] = value[j++];
559 if (value[j-1] == 0) return;
561 else path[i++] = value[j++];
565 /* Prepend the given location or list of locations to one of the above
566 Path buffers. This is only permitted for Source_Path, Include_Path,
567 ICL_Path, Module_Path.
569 An empty field (in the comma-separated list) means the current
570 directory. If the Path buffer is entirely empty, we assume that
571 we want to search both value and the current directory, so
572 the result will be "value,".
574 static void prepend_path_value(char *path, char *value)
577 int oldlen = strlen(path);
579 char new_path[PATHLEN];
581 if ((path != Source_Path) && (path != Include_Path)
582 && (path != ICL_Path) && (path != Module_Path))
583 { printf("The character '+' is used to add to a list \
584 of possible locations, and can only be used in the Include_Path, Source_Path, \
585 Module_Path or ICL_Path variables. Other paths are for output only.\n");
591 if (i >= PATHLEN-1) {
592 printf("A specified path is longer than %d characters.\n",
596 if ((value[j] == FN_ALT) || (value[j] == 0))
597 { if ((path != Debugging_Name) && (path != Transcript_Name)
598 && (path != Language_Name) && (path != Charset_Map)
599 && (i>0) && (isalnum(new_path[i-1]))) new_path[i++] = FN_SEP;
600 new_path[i++] = value[j++];
601 if (value[j-1] == 0) {
606 else new_path[i++] = value[j++];
609 if (newlen+1+oldlen >= PATHLEN-1) {
610 printf("A specified path is longer than %d characters.\n",
616 new_path[i++] = FN_ALT;
618 new_path[i++] = path[j++];
621 strcpy(path, new_path);
624 static void set_default_paths(void)
626 set_path_value(Source_Path, Source_Directory);
627 set_path_value(Include_Path, Include_Directory);
628 set_path_value(Code_Path, Code_Directory);
629 set_path_value(Module_Path, Module_Directory);
630 set_path_value(ICL_Path, ICL_Directory);
631 set_path_value(Debugging_Name, Debugging_File);
632 set_path_value(Transcript_Name, Transcript_File);
633 set_path_value(Language_Name, Default_Language);
634 set_path_value(Charset_Map, "");
637 /* Parse a path option which looks like "dir", "+dir", "pathname=dir",
638 or "+pathname=dir". If there is no "=", we assume "include_path=...".
639 If the option begins with a "+" the directory is prepended to the
640 existing path instead of replacing it.
642 static void set_path_command(char *command)
643 { int i, j; char *path_to_set = NULL;
646 if (command[0] == '+') {
651 for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;
653 path_to_set=Include_Path;
655 if (command[i] == '=') {
656 char pathname[PATHLEN];
657 if (i>=PATHLEN) i=PATHLEN-1;
659 char ch = command[j];
660 if (isupper(ch)) ch=tolower(ch);
664 command = command+i+1;
667 if (strcmp(pathname, "source_path")==0) path_to_set=Source_Path;
668 if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
669 if (strcmp(pathname, "code_path")==0) path_to_set=Code_Path;
670 if (strcmp(pathname, "module_path")==0) path_to_set=Module_Path;
671 if (strcmp(pathname, "icl_path")==0) path_to_set=ICL_Path;
672 if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
673 if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;
674 if (strcmp(pathname, "language_name")==0) path_to_set=Language_Name;
675 if (strcmp(pathname, "charset_map")==0) path_to_set=Charset_Map;
677 if (path_to_set == NULL)
678 { printf("No such path setting as \"%s\"\n", pathname);
684 set_path_value(path_to_set, command);
686 prepend_path_value(path_to_set, command);
689 static int contains_separator(char *name)
691 for (i=0; name[i]!=0; i++)
692 if (name[i] == FN_SEP) return 1;
696 static int write_translated_name(char *new_name, char *old_name,
697 char *prefix_path, int start_pos,
700 if (strlen(old_name)+strlen(extension) >= PATHLEN) {
701 printf("One of your filenames is longer than %d characters.\n", PATHLEN);
704 if (prefix_path == NULL)
705 { sprintf(new_name,"%s%s", old_name, extension);
708 strcpy(new_name, prefix_path + start_pos);
709 for (x=0; (new_name[x]!=0) && (new_name[x]!=FN_ALT); x++) ;
710 if (new_name[x] == 0) start_pos = 0; else start_pos += x+1;
711 if (x+strlen(old_name)+strlen(extension) >= PATHLEN) {
712 printf("One of your pathnames is longer than %d characters.\n", PATHLEN);
715 sprintf(new_name + x, "%s%s", old_name, extension);
719 #ifdef FILE_EXTENSIONS
720 static char *check_extension(char *name, char *extension)
723 /* If a filename ends in '.', remove the dot and add no file extension: */
725 if (name[i] == '.') { name[i]=0; return ""; }
727 /* Remove the new extension if it's already got one: */
729 for (; (i>=0) && (name[i]!=FN_SEP); i--)
730 if (name[i] == '.') return "";
735 /* ------------------------------------------------------------------------- */
736 /* Three translation routines have to deal with path variables which may */
737 /* contain alternative locations separated by the FN_ALT character. */
738 /* These have the protocol: */
740 /* int translate_*_filename(int last_value, ...) */
742 /* and should first be called with last_value equal to 0. If the */
743 /* translated filename works, fine. Otherwise, if the returned integer */
744 /* was zero, the caller knows that no filename works and can issue an */
745 /* error message. If it was non-zero, the caller should pass it on as */
746 /* the last_value again. */
748 /* As implemented below, last_value is the position in the path variable */
749 /* string at which the next directory name to try begins. */
750 /* ------------------------------------------------------------------------- */
752 extern int translate_in_filename(int last_value,
753 char *new_name, char *old_name,
754 int same_directory_flag, int command_line_flag)
755 { char *prefix_path = NULL;
757 int add_path_flag = 1;
760 if ((same_directory_flag==0)
761 && (contains_separator(old_name)==1)) add_path_flag=0;
763 if (add_path_flag==1)
764 { if (command_line_flag == 0)
765 { /* File is opened as a result of an Include directive */
767 if (same_directory_flag==1)
768 prefix_path = current_source_path;
770 if (Include_Path[0]!=0) prefix_path = Include_Path;
772 /* Main file being opened from the command line */
774 else if (Source_Path[0]!=0) prefix_path = Source_Path;
777 #ifdef FILE_EXTENSIONS
778 /* Which file extension is expected? */
780 if ((command_line_flag==1)||(same_directory_flag==1))
781 extension = Source_Extension;
783 extension = Include_Extension;
785 extension = check_extension(old_name, extension);
790 last_value = write_translated_name(new_name, old_name,
791 prefix_path, last_value, extension);
793 /* Set the "current source path" (for use of Include ">...") */
795 if (command_line_flag==1)
796 { strcpy(current_source_path, new_name);
797 for (i=strlen(current_source_path)-1;
798 ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
800 if (i!=0) current_source_path[i+1] = 0; /* Current file in subdir */
801 else current_source_path[0] = 0; /* Current file at root dir */
807 extern int translate_link_filename(int last_value,
808 char *new_name, char *old_name)
809 { char *prefix_path = NULL;
812 if (contains_separator(old_name)==0)
813 if (Module_Path[0]!=0)
814 prefix_path = Module_Path;
816 #ifdef FILE_EXTENSIONS
817 extension = check_extension(old_name, Module_Extension);
822 return write_translated_name(new_name, old_name,
823 prefix_path, last_value, extension);
826 static int translate_icl_filename(int last_value,
827 char *new_name, char *old_name)
828 { char *prefix_path = NULL;
829 char *extension = "";
831 if (contains_separator(old_name)==0)
833 prefix_path = ICL_Path;
835 #ifdef FILE_EXTENSIONS
836 extension = check_extension(old_name, ICL_Extension);
839 return write_translated_name(new_name, old_name,
840 prefix_path, last_value, extension);
843 extern void translate_out_filename(char *new_name, char *old_name)
845 char *extension = "";
848 /* If !convert_filename_flag, then the old_name is just the <file2>
849 parameter on the Inform command line, which we leave alone. */
851 if (!convert_filename_flag)
852 { strcpy(new_name, old_name); return;
855 /* Remove any pathname or extension in <file1>. */
857 if (contains_separator(old_name)==1)
858 { for (i=strlen(old_name)-1; (i>0)&&(old_name[i]!=FN_SEP) ;i--) { };
859 if (old_name[i]==FN_SEP) i++;
862 #ifdef FILE_EXTENSIONS
863 for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
864 if (old_name[i] == '.') old_name[i] = 0;
869 { extension = Module_Extension;
870 if (Module_Path[0]!=0) prefix_path = Module_Path;
875 switch(version_number)
876 { case 3: extension = Code_Extension; break;
877 case 4: extension = V4Code_Extension; break;
878 case 5: extension = V5Code_Extension; break;
879 case 6: extension = V6Code_Extension; break;
880 case 7: extension = V7Code_Extension; break;
881 case 8: extension = V8Code_Extension; break;
885 extension = GlulxCode_Extension;
887 if (Code_Path[0]!=0) prefix_path = Code_Path;
890 #ifdef FILE_EXTENSIONS
891 extension = check_extension(old_name, extension);
894 write_translated_name(new_name, old_name, prefix_path, 0, extension);
897 static char *name_or_unset(char *p)
898 { if (p[0]==0) return "(unset)";
902 static void help_on_filenames(void)
903 { char old_name[PATHLEN];
904 char new_name[PATHLEN];
905 int save_mm = module_switch, x;
907 module_switch = FALSE;
909 printf("Help information on filenames:\n\n");
912 "The command line can take one of two forms:\n\n\
913 inform [commands...] <file1>\n\
914 inform [commands...] <file1> <file2>\n\n\
915 Inform translates <file1> into a source file name (see below) for its input.\n\
916 <file2> is usually omitted: if so, the output filename is made from <file1>\n\
917 by cutting out the name part and translating that (see below).\n\
918 If <file2> is given, however, the output filename is set to just <file2>\n\
919 (not altered in any way).\n\n");
922 "Filenames given in the game source (with commands like Include \"name\" and\n\
923 Link \"name\") are also translated by the rules below.\n\n");
926 "Rules of translation:\n\n\
927 Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
928 (such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
929 1. If the name contains a '%c' character (so it's already a pathname), it\n\
930 isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);
933 " [Exception: when the name is given in an Include command using the >\n\
934 form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
935 of the file doing the inclusion");
936 #ifdef FILE_EXTENSIONS
937 printf(" and a suitable file extension is added");
942 " Filenames must never contain double-quotation marks \". To use filenames\n\
943 which contain spaces, write them in double-quotes: for instance,\n\n\
944 \"inform +code_path=\"Jigsaw Final Version\" jigsaw\".\n\n");
947 "2. The file is looked for at a particular \"path\" (the filename of a\n\
948 directory), depending on what kind of file it is.\n\n\
949 File type Name Current setting\n\n\
950 Source code (in) source_path %s\n\
951 Include file (in) include_path %s\n\
952 Story file (out) code_path %s\n",
953 name_or_unset(Source_Path), name_or_unset(Include_Path),
954 name_or_unset(Code_Path));
957 " ICL command file (in) icl_path %s\n\
958 Module (in & out) module_path %s\n\n",
959 name_or_unset(ICL_Path), name_or_unset(Module_Path));
962 " If the path is unset, then the current working directory is used (so\n\
963 the filename doesn't change): if, for instance, include_path is set to\n\
964 \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
965 \"backup%coldlib%cparser\".\n\n\
966 The paths can be set or unset on the Inform command line by, eg,\n\
967 \"inform +code_path=finished jigsaw\" or\n\
968 \"inform +include_path= balances\" (which unsets include_path).\n\n",
969 FN_SEP, FN_SEP, FN_SEP);
972 " The four input path variables can be set to lists of alternative paths\n\
973 separated by '%c' characters: these alternatives are always tried in\n\
974 the order they are specified in, that is, left to right through the text\n\
975 in the path variable.\n\n",
978 " If two '+' signs are used (\"inform ++include_path=dir jigsaw\") then\n\
979 the path or paths are added to the existing list.\n\n");
981 " (Modules are written to the first alternative in the module_path list;\n\
982 it is an error to give alternatives at all for purely output paths.)\n\n");
984 #ifdef FILE_EXTENSIONS
985 printf("3. The following file extensions are added:\n\n\
988 Story files: %s (Version 3), %s (v4), %s (v5, the default),\n\
989 %s (v6), %s (v7), %s (v8), %s (Glulx)\n\
991 Source_Extension, Include_Extension,
992 Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
993 V7Code_Extension, V8Code_Extension, GlulxCode_Extension,
996 except that any extension you give (on the command line or in a filename\n\
997 used in a program) will override these. If you give the null extension\n\
998 \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
1001 printf("Names of four individual files can also be set using the same\n\
1002 + command notation (though they aren't really pathnames). These are:\n\n\
1003 transcript_name (text written by -r switch): now \"%s\"\n\
1004 debugging_name (data written by -k switch): now \"%s\"\n\
1005 language_name (library file defining natural language of game):\n\
1007 charset_map (file for character set mapping): now \"%s\"\n\n",
1008 Transcript_Name, Debugging_Name, Language_Name, Charset_Map);
1010 translate_in_filename(0, new_name, "rezrov", 0, 1);
1011 printf("Examples: 1. \"inform rezrov\"\n\
1012 the source code is read from \"%s\"\n",
1014 convert_filename_flag = TRUE;
1015 translate_out_filename(new_name, "rezrov");
1016 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1018 translate_in_filename(0, new_name, "frotz", 0, 1);
1019 printf("2. \"inform -M frotz\"\n\
1020 the source code is read from \"%s\"\n",
1022 module_switch = TRUE;
1023 convert_filename_flag = TRUE;
1024 translate_out_filename(new_name, "frotz");
1025 printf(" and a module is compiled to \"%s\".\n\n", new_name);
1027 module_switch = FALSE;
1029 sprintf(old_name, "demos%cplugh", FN_SEP);
1030 printf("3. \"inform %s\"\n", old_name);
1031 translate_in_filename(0, new_name, old_name, 0, 1);
1032 printf(" the source code is read from \"%s\"\n", new_name);
1033 sprintf(old_name, "demos%cplugh", FN_SEP);
1034 convert_filename_flag = TRUE;
1035 translate_out_filename(new_name, old_name);
1036 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1038 printf("4. \"inform plover my_demo\"\n");
1039 translate_in_filename(0, new_name, "plover", 0, 1);
1040 printf(" the source code is read from \"%s\"\n", new_name);
1041 convert_filename_flag = FALSE;
1042 translate_out_filename(new_name, "my_demo");
1043 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
1045 strcpy(old_name, Source_Path);
1046 sprintf(new_name, "%cnew%cold%crecent%cold%cancient",
1047 FN_ALT, FN_ALT, FN_SEP, FN_ALT, FN_SEP);
1048 printf("5. \"inform +source_path=%s zooge\"\n", new_name);
1050 " Note that four alternative paths are given, the first being the empty\n\
1051 path-name (meaning: where you are now). Inform looks for the source code\n\
1052 by trying these four places in turn, stopping when it finds anything:\n\n");
1054 set_path_value(Source_Path, new_name);
1057 { x = translate_in_filename(x, new_name, "zooge", 0, 1);
1058 printf(" \"%s\"\n", new_name);
1060 strcpy(Source_Path, old_name);
1061 module_switch = save_mm;
1065 static char riscos_ft_buffer[4];
1067 extern char *riscos_file_type(void)
1069 if (riscos_file_type_format == 1)
1070 { if (module_switch) return("data");
1074 if (module_switch) return("075");
1076 sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
1077 return(riscos_ft_buffer);
1081 /* ------------------------------------------------------------------------- */
1082 /* The compilation pass */
1083 /* ------------------------------------------------------------------------- */
1085 static void run_pass(void)
1087 lexer_begin_prepass();
1088 files_begin_prepass();
1089 load_sourcefile(Source_Name, 0);
1093 parse_program(NULL);
1096 issue_unused_warnings();
1100 if (module_switch) linker_endpass();
1102 issue_debug_symbol_warnings();
1105 if (hash_switch && hash_printed_since_newline) printf("\n");
1108 if (track_unused_routines)
1109 locate_dead_functions();
1110 construct_storyfile();
1113 int output_has_occurred;
1115 static void rennab(float time_taken)
1116 { /* rennab = reverse of banner */
1117 int t = no_warnings + no_suppressed_warnings;
1119 if (memout_switch) print_memory_usage();
1121 if ((no_errors + t)!=0)
1122 { printf("Compiled with ");
1124 { printf("%d error%s", no_errors,(no_errors==1)?"":"s");
1125 if (t > 0) printf(" and ");
1127 if (no_warnings > 0)
1128 printf("%d warning%s", t, (t==1)?"":"s");
1129 if (no_suppressed_warnings > 0)
1130 { if (no_warnings > 0)
1131 printf(" (%d suppressed)", no_suppressed_warnings);
1133 printf("%d suppressed warning%s", no_suppressed_warnings,
1134 (no_suppressed_warnings==1)?"":"s");
1136 if (output_has_occurred == FALSE) printf(" (no output)");
1140 if (no_compiler_errors > 0) print_sorry_message();
1142 if (statistics_switch)
1144 /* Print the duration to a sensible number of decimal places.
1145 (We aim for three significant figures.) */
1146 if (time_taken >= 10.0)
1147 printf("Completed in %.1f seconds\n", time_taken);
1148 else if (time_taken >= 1.0)
1149 printf("Completed in %.2f seconds\n", time_taken);
1151 printf("Completed in %.3f seconds\n", time_taken);
1155 /* ------------------------------------------------------------------------- */
1156 /* The compiler abstracted to a routine. */
1157 /* ------------------------------------------------------------------------- */
1159 static int execute_icl_header(char *file1);
1161 static int compile(int number_of_files_specified, char *file1, char *file2)
1163 TIMEVALUE time_start, time_end;
1166 if (execute_icl_header(file1))
1169 select_target(glulx_mode);
1171 if (define_INFIX_switch && glulx_mode) {
1172 printf("Infix (-X) facilities are not available in Glulx: \
1173 disabling -X switch\n");
1174 define_INFIX_switch = FALSE;
1177 if (module_switch && glulx_mode) {
1178 printf("Modules are not available in Glulx: \
1179 disabling -M switch\n");
1180 module_switch = FALSE;
1183 if (define_INFIX_switch && module_switch)
1184 { printf("Infix (-X) facilities are not available when compiling \
1185 modules: disabling -X switch\n");
1186 define_INFIX_switch = FALSE;
1188 if (runtime_error_checking_switch && module_switch)
1189 { printf("Strict checking (-S) facilities are not available when \
1190 compiling modules: disabling -S switch\n");
1191 runtime_error_checking_switch = FALSE;
1194 TIMEVALUE_NOW(&time_start);
1198 strcpy(Source_Name, file1); convert_filename_flag = TRUE;
1199 strcpy(Code_Name, file1);
1200 if (number_of_files_specified == 2)
1201 { strcpy(Code_Name, file2); convert_filename_flag = FALSE;
1206 if (debugfile_switch) begin_debug_file();
1210 if (transcript_switch) open_transcript_file(Source_Name);
1214 if (transcript_switch)
1215 { write_dictionary_to_transcript();
1216 close_transcript_file();
1219 if (no_errors==0) { output_file(); output_has_occurred = TRUE; }
1220 else { output_has_occurred = FALSE; }
1222 if (debugfile_switch)
1226 if (optimise_switch) {
1227 /* Pull out all_text so that it will not be freed. */
1233 TIMEVALUE_NOW(&time_end);
1234 duration = TIMEVALUE_DIFFERENCE(&time_start, &time_end);
1238 if (optimise_switch) {
1239 optimise_abbreviations();
1243 return (no_errors==0)?0:1;
1246 /* ------------------------------------------------------------------------- */
1247 /* The command line interpreter */
1248 /* ------------------------------------------------------------------------- */
1250 static void cli_print_help(int help_level)
1253 "\nThis program is a compiler of Infocom format (also called \"Z-machine\")\n\
1254 story files, as well as \"Glulx\" story files:\n\
1255 Copyright (c) Graham Nelson 1993 - 2022.\n\n");
1257 /* For people typing just "inform", a summary only: */
1262 #ifndef PROMPT_INPUT
1263 printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
1265 printf("When run, Inform prompts you for commands (and switches),\n\
1266 which are optional, then an input <file1> and an (optional) output\n\
1271 "<file1> is the Inform source file of the game to be compiled. <file2>,\n\
1272 if given, overrides the filename Inform would normally use for the\n\
1273 compiled output. Try \"inform -h1\" for file-naming conventions.\n\n\
1274 One or more words can be supplied as \"commands\". These may be:\n\n\
1275 -switches a list of compiler switches, 1 or 2 letter\n\
1276 (see \"inform -h2\" for the full range)\n\n\
1277 +dir set Include_Path to this directory\n\
1278 ++dir add this directory to Include_Path\n\
1279 +PATH=dir change the PATH to this directory\n\
1280 ++PATH=dir add this directory to the PATH\n\n\
1281 $... one of the following configuration commands:\n");
1284 " $list list current settings\n\
1285 $?SETTING explain briefly what SETTING is for\n\
1286 $SETTING=number change SETTING to given number\n\
1287 $!TRACEOPT set trace option TRACEOPT\n\
1288 (or $!TRACEOPT=2, 3, etc for more tracing;\n\
1289 $! by itself to list all trace options)\n\
1290 $#SYMBOL=number define SYMBOL as a constant in the story\n\n");
1293 " (filename) read in a list of commands (in the format above)\n\
1294 from this \"setup file\"\n\n");
1296 printf("Alternate command-line formats for the above:\n\
1297 --help (this page)\n\
1298 --path PATH=dir (set path)\n\
1299 --addpath PATH=dir (add to path)\n\
1300 --list (list current settings)\n\
1301 --helpopt SETTING (explain setting)\n\
1302 --opt SETTING=number (change setting)\n\
1303 --helptrace (list all trace options)\n\
1304 --trace TRACEOPT (set trace option)\n\
1305 --trace TRACEOPT=num (more tracing)\n\
1306 --define SYMBOL=number (define constant)\n\
1307 --config filename (read setup file)\n\n");
1309 #ifndef PROMPT_INPUT
1310 printf("For example: \"inform -dexs curses\".\n");
1316 /* The -h1 (filenaming) help information: */
1318 if (help_level == 1) { help_on_filenames(); return; }
1320 /* The -h2 (switches) help information: */
1322 printf("Help on the full list of legal switch commands:\n\n\
1323 a trace assembly-language\n\
1324 a2 trace assembly with hex dumps\n\
1325 c more concise error messages\n\
1326 d contract double spaces after full stops in text\n\
1327 d2 contract double spaces after exclamation and question marks, too\n\
1328 e economy mode (slower): make use of declared abbreviations\n");
1331 f frequencies mode: show how useful abbreviations are\n\
1332 g traces calls to all game functions\n\
1333 g2 traces calls to all game and library functions\n\
1334 g3 traces calls to all functions (including veneer)\n\
1335 h print general help information\n\
1336 h1 print help information on filenames and path options\n\
1337 h2 print help information on switches (this page)\n");
1340 i ignore default switches set within the file\n\
1341 k output debugging information to \"%s\"\n",
1344 q keep quiet about obsolete usages\n\
1345 r record all the text to \"%s\"\n\
1346 s give statistics\n",
1350 u work out most useful abbreviations (very very slowly)\n\
1351 v3 compile to version-3 (\"Standard\"/\"ZIP\") story file\n\
1352 v4 compile to version-4 (\"Plus\"/\"EZIP\") story file\n\
1353 v5 compile to version-5 (\"Advanced\"/\"XZIP\") story file: the default\n\
1354 v6 compile to version-6 (graphical/\"YZIP\") story file\n\
1355 v7 compile to version-7 (expanded \"Advanced\") story file\n\
1356 v8 compile to version-8 (expanded \"Advanced\") story file\n\
1357 w disable warning messages\n\
1358 x print # for every 100 lines compiled\n\
1359 z print memory map of the virtual machine\n\n");
1362 B use big memory model (for large V6/V7 files)\n\
1363 C0 text character set is plain ASCII only\n\
1364 Cu text character set is UTF-8\n\
1365 Cn text character set is ISO 8859-n (n = 1 to 9)\n\
1366 (1 to 4, Latin1 to Latin4; 5, Cyrillic; 6, Arabic;\n\
1367 7, Greek; 8, Hebrew; 9, Latin5. Default is -C1.)\n");
1368 printf(" D insert \"Constant DEBUG;\" automatically\n");
1369 printf(" E0 Archimedes-style error messages%s\n",
1370 (error_format==0)?" (current setting)":"");
1371 printf(" E1 Microsoft-style error messages%s\n",
1372 (error_format==1)?" (current setting)":"");
1373 printf(" E2 Macintosh MPW-style error messages%s\n",
1374 (error_format==2)?" (current setting)":"");
1375 printf(" G compile a Glulx game file\n");
1376 printf(" H use Huffman encoding to compress Glulx strings\n");
1377 printf(" M compile as a Module for future linking\n");
1381 R0 use filetype 060 + version number for games (default)\n\
1382 R1 use official Acorn filetype 11A for all games\n");
1384 printf(" S compile strict error-checking at run-time (on by default)\n");
1385 #ifdef ARC_THROWBACK
1386 printf(" T enable throwback of errors in the DDE\n");
1388 printf(" U insert \"Constant USE_MODULES;\" automatically\n");
1389 printf(" V print the version and date of this program\n");
1390 printf(" Wn header extension table is at least n words (n = 3 to 99)\n");
1391 printf(" X compile with INFIX debugging facilities present\n");
1395 extern void switches(char *p, int cmode)
1396 { int i, s=1, state;
1397 /* Here cmode is 0 if switches list is from a "Switches" directive
1398 and 1 if from a "-switches" command-line or ICL list */
1403 "Ignoring second word which should be a -list of switches.\n");
1407 for (i=cmode; p[i]!=0; i+=s, s=1)
1415 case 'a': switch(p[i+1])
1416 { case '1': asm_trace_setting=1; s=2; break;
1417 case '2': asm_trace_setting=2; s=2; break;
1418 case '3': asm_trace_setting=3; s=2; break;
1419 case '4': asm_trace_setting=4; s=2; break;
1420 default: asm_trace_setting=1; break;
1423 case 'c': concise_switch = state; break;
1424 case 'd': switch(p[i+1])
1425 { case '1': double_space_setting=1; s=2; break;
1426 case '2': double_space_setting=2; s=2; break;
1427 default: double_space_setting=1; break;
1430 case 'e': economy_switch = state; break;
1431 case 'f': frequencies_setting = (state?1:0); break;
1432 case 'g': switch(p[i+1])
1433 { case '1': trace_fns_setting=1; s=2; break;
1434 case '2': trace_fns_setting=2; s=2; break;
1435 case '3': trace_fns_setting=3; s=2; break;
1436 default: trace_fns_setting=1; break;
1439 case 'h': switch(p[i+1])
1440 { case '1': cli_print_help(1); s=2; break;
1441 case '2': cli_print_help(2); s=2; break;
1443 default: cli_print_help(0); break;
1446 case 'i': ignore_switches_switch = state; break;
1447 case 'k': if (cmode == 0)
1448 error("The switch '-k' can't be set with 'Switches'");
1450 debugfile_switch = state;
1452 case 'q': obsolete_switch = state; break;
1453 case 'r': if (cmode == 0)
1454 error("The switch '-r' can't be set with 'Switches'");
1456 transcript_switch = state; break;
1457 case 's': statistics_switch = state; break;
1458 case 'u': if (cmode == 0) {
1459 error("The switch '-u' can't be set with 'Switches'");
1462 optimise_switch = state; break;
1463 case 'v': if (glulx_mode) { s = select_glulx_version(p+i+1)+1; break; }
1464 if ((cmode==0) && (version_set_switch)) { s=2; break; }
1465 version_set_switch = TRUE; s=2;
1467 { case '3': select_version(3); break;
1468 case '4': select_version(4); break;
1469 case '5': select_version(5); break;
1470 case '6': select_version(6); break;
1471 case '7': select_version(7); break;
1472 case '8': select_version(8); break;
1473 default: printf("-v must be followed by 3 to 8\n");
1474 version_set_switch=0; s=1;
1477 if ((version_number < 5) && (r_e_c_s_set == FALSE))
1478 runtime_error_checking_switch = FALSE;
1480 case 'w': nowarnings_switch = state; break;
1481 case 'x': hash_switch = state; break;
1482 case 'z': memory_map_setting = (state ? 1 : 0); break;
1483 case 'B': oddeven_packing_switch = state; break;
1485 if (p[i+1] == 'u') {
1486 character_set_unicode = TRUE;
1487 /* Leave the set_setting on Latin-1, because that
1488 matches the first block of Unicode. */
1489 character_set_setting = 1;
1492 { character_set_setting=p[i+1]-'0';
1493 if ((character_set_setting < 0)
1494 || (character_set_setting > 9))
1495 { printf("-C must be followed by 'u' or 0 to 9. Defaulting to ISO-8859-1.\n");
1496 character_set_unicode = FALSE;
1497 character_set_setting = 1;
1500 if (cmode == 0) change_character_set();
1502 case 'D': define_DEBUG_switch = state; break;
1503 case 'E': switch(p[i+1])
1504 { case '0': s=2; error_format=0; break;
1505 case '1': s=2; error_format=1; break;
1506 case '2': s=2; error_format=2; break;
1507 default: error_format=1; break;
1510 case 'M': module_switch = state;
1511 if (state && (r_e_c_s_set == FALSE))
1512 runtime_error_checking_switch = FALSE;
1515 case 'R': switch(p[i+1])
1516 { case '0': s=2; riscos_file_type_format=0; break;
1517 case '1': s=2; riscos_file_type_format=1; break;
1518 default: riscos_file_type_format=1; break;
1522 #ifdef ARC_THROWBACK
1523 case 'T': throwback_switch = state; break;
1525 case 'S': runtime_error_checking_switch = state;
1526 r_e_c_s_set = TRUE; break;
1527 case 'G': if (cmode == 0)
1528 error("The switch '-G' can't be set with 'Switches'");
1529 else if (version_set_switch)
1530 error("The '-G' switch cannot follow the '-v' switch");
1532 { glulx_mode = state;
1533 adjust_memory_sizes();
1536 case 'H': compression_switch = state; break;
1537 case 'U': define_USE_MODULES_switch = state; break;
1538 case 'V': exit(0); break;
1539 case 'W': if ((p[i+1]>='0') && (p[i+1]<='9'))
1540 { s=2; ZCODE_HEADER_EXT_WORDS = p[i+1]-'0';
1541 if ((p[i+2]>='0') && (p[i+2]<='9'))
1542 { s=3; ZCODE_HEADER_EXT_WORDS *= 10;
1543 ZCODE_HEADER_EXT_WORDS += p[i+2]-'0';
1547 case 'X': define_INFIX_switch = state; break;
1549 printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
1555 if (optimise_switch)
1557 /* store_the_text is equivalent to optimise_switch; -u sets both.
1558 We could simplify this. */
1559 store_the_text=TRUE;
1563 /* Check whether the string looks like an ICL command. */
1564 static int icl_command(char *p)
1565 { if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
1566 || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
1570 static void icl_error(char *filename, int line)
1571 { printf("Error in ICL file '%s', line %d:\n", filename, line);
1574 static void icl_header_error(char *filename, int line)
1575 { printf("Error in ICL header of file '%s', line %d:\n", filename, line);
1578 static int copy_icl_word(char *from, char *to, int max)
1580 /* Copies one token from 'from' to 'to', null-terminated:
1581 returns the number of chars in 'from' read past (possibly 0). */
1583 int i, j, quoted_mode, truncated;
1585 i = 0; truncated = 0;
1586 while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
1587 || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;
1590 { while (from[i] != 0) i++;
1591 to[0] = 0; return i;
1594 for (quoted_mode = FALSE, j=0;;)
1595 { if (from[i] == 0) break;
1596 if (from[i] == 10) break;
1597 if (from[i] == 13) break;
1598 if (from[i] == TAB_CHARACTER) break;
1599 if ((from[i] == ' ') && (!quoted_mode)) break;
1600 if (from[i] == '\"') { quoted_mode = !quoted_mode; i++; }
1601 else to[j++] = from[i++];
1609 printf("The following parameter has been truncated:\n%s\n", to);
1613 /* Copy a string, converting to uppercase. The to array should be
1614 (at least) max characters. Result will be null-terminated, so
1615 at most max-1 characters will be copied.
1617 static int strcpyupper(char *to, char *from, int max)
1620 for (ix=0; ix<max-1; ix++) {
1622 if (islower(ch)) ch = toupper(ch);
1629 static void execute_icl_command(char *p);
1630 static int execute_dashdash_command(char *p, char *p2);
1632 static int execute_icl_header(char *argname)
1635 char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1639 char filename[PATHLEN];
1643 { x = translate_in_filename(x, filename, argname, 0, 1);
1644 command_file = fopen(filename,"r");
1645 } while ((command_file == NULL) && (x != 0));
1646 if (!command_file) {
1647 /* Fail silently. The regular compiler will try to open the file
1648 again, and report the problem. */
1652 while (feof(command_file)==0) {
1653 if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1655 if (!(cli_buff[0] == '!' && cli_buff[1] == '%'))
1657 i = copy_icl_word(cli_buff+2, fw, CMD_BUF_SIZE);
1658 if (icl_command(fw)) {
1659 execute_icl_command(fw);
1660 copy_icl_word(cli_buff+2 + i, fw, CMD_BUF_SIZE);
1661 if ((fw[0] != 0) && (fw[0] != '!')) {
1662 icl_header_error(filename, line);
1664 printf("expected comment or nothing but found '%s'\n", fw);
1669 icl_header_error(filename, line);
1671 printf("Expected command or comment but found '%s'\n", fw);
1675 fclose(command_file);
1677 return (errcount==0)?0:1;
1681 static void run_icl_file(char *filename, FILE *command_file)
1682 { char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1684 printf("[Running ICL file '%s']\n", filename);
1686 while (feof(command_file)==0)
1687 { if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1689 i = copy_icl_word(cli_buff, fw, CMD_BUF_SIZE);
1690 if (icl_command(fw))
1691 { execute_icl_command(fw);
1692 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1693 if ((fw[0] != 0) && (fw[0] != '!'))
1694 { icl_error(filename, line);
1695 printf("expected comment or nothing but found '%s'\n", fw);
1699 { if (strcmp(fw, "compile")==0)
1700 { char story_name[PATHLEN], code_name[PATHLEN];
1701 i += copy_icl_word(cli_buff + i, story_name, PATHLEN);
1702 i += copy_icl_word(cli_buff + i, code_name, PATHLEN);
1704 if (code_name[0] != 0) x=2;
1705 else if (story_name[0] != 0) x=1;
1709 { case 0: icl_error(filename, line);
1710 printf("No filename given to 'compile'\n");
1712 case 1: printf("[Compiling <%s>]\n", story_name);
1713 compile(x, story_name, code_name);
1715 case 2: printf("[Compiling <%s> to <%s>]\n",
1716 story_name, code_name);
1717 compile(x, story_name, code_name);
1718 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1720 { icl_error(filename, line);
1721 printf("Expected comment or nothing but found '%s'\n",
1729 { icl_error(filename, line);
1730 printf("Expected command or comment but found '%s'\n", fw);
1736 /* This should only be called if the argument has been verified to be
1737 an ICL command, e.g. by checking icl_command().
1739 static void execute_icl_command(char *p)
1740 { char filename[PATHLEN], cli_buff[CMD_BUF_SIZE];
1745 { case '+': set_path_command(p+1); break;
1746 case '-': switches(p,1); break;
1747 case '$': memory_command(p+1); break;
1748 case '(': len = strlen(p);
1749 if (p[len-1] != ')') {
1750 printf("Error in ICL: (command) missing closing paren\n");
1753 len -= 2; /* omit parens */
1754 if (len > CMD_BUF_SIZE-1) len = CMD_BUF_SIZE-1;
1755 strncpy(cli_buff, p+1, len);
1759 { x = translate_icl_filename(x, filename, cli_buff);
1760 command_file = fopen(filename,"r");
1761 } while ((command_file == NULL) && (x != 0));
1763 if (command_file == NULL) {
1764 printf("Error in ICL: Couldn't open command file '%s'\n",
1768 run_icl_file(filename, command_file);
1769 fclose(command_file);
1774 /* Convert a --command into the equivalent ICL command and call
1775 execute_icl_command(). The dashes have already been stripped.
1777 The second argument is the following command-line argument
1778 (or NULL if there was none). This may or may not be consumed.
1779 Returns TRUE if it was.
1781 static int execute_dashdash_command(char *p, char *p2)
1783 char cli_buff[CMD_BUF_SIZE];
1784 int consumed2 = FALSE;
1786 if (!strcmp(p, "help")) {
1787 strcpy(cli_buff, "-h");
1789 else if (!strcmp(p, "list")) {
1790 strcpy(cli_buff, "$LIST");
1792 else if (!strcmp(p, "size")) {
1794 /* We accept these arguments even though they've been withdrawn. */
1795 if (!(p2 && (!strcmpcis(p2, "HUGE") || !strcmpcis(p2, "LARGE") || !strcmpcis(p2, "SMALL")))) {
1796 printf("--size must be followed by \"huge\", \"large\", or \"small\"\n");
1799 strcpy(cli_buff, "$");
1800 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1802 else if (!strcmp(p, "opt")) {
1804 if (!p2 || !strchr(p2, '=')) {
1805 printf("--opt must be followed by \"setting=number\"\n");
1808 strcpy(cli_buff, "$");
1809 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1811 else if (!strcmp(p, "helpopt")) {
1814 printf("--helpopt must be followed by \"setting\"\n");
1817 strcpy(cli_buff, "$?");
1818 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1820 else if (!strcmp(p, "define")) {
1823 printf("--define must be followed by \"symbol=number\"\n");
1826 strcpy(cli_buff, "$#");
1827 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1829 else if (!strcmp(p, "path")) {
1831 if (!p2 || !strchr(p2, '=')) {
1832 printf("--path must be followed by \"name=path\"\n");
1835 snprintf(cli_buff, CMD_BUF_SIZE, "+%s", p2);
1837 else if (!strcmp(p, "addpath")) {
1839 if (!p2 || !strchr(p2, '=')) {
1840 printf("--addpath must be followed by \"name=path\"\n");
1843 snprintf(cli_buff, CMD_BUF_SIZE, "++%s", p2);
1845 else if (!strcmp(p, "config")) {
1848 printf("--config must be followed by \"file.icl\"\n");
1851 snprintf(cli_buff, CMD_BUF_SIZE, "(%s)", p2);
1853 else if (!strcmp(p, "trace")) {
1856 printf("--trace must be followed by \"traceopt\" or \"traceopt=N\"\n");
1859 snprintf(cli_buff, CMD_BUF_SIZE, "$!%s", p2);
1861 else if (!strcmp(p, "helptrace")) {
1862 strcpy(cli_buff, "$!");
1865 printf("Option \"--%s\" unknown (try \"inform -h\")\n", p);
1869 execute_icl_command(cli_buff);
1873 /* ------------------------------------------------------------------------- */
1874 /* Opening and closing banners */
1875 /* ------------------------------------------------------------------------- */
1877 char banner_line[CMD_BUF_SIZE];
1879 /* We store the banner text for use elsewhere (see files.c).
1881 static void banner(void)
1884 snprintf(banner_line, CMD_BUF_SIZE, "Inform %d.%d%d",
1885 (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10);
1886 #ifdef RELEASE_SUFFIX
1887 len = strlen(banner_line);
1888 snprintf(banner_line+len, CMD_BUF_SIZE-len, "%s", RELEASE_SUFFIX);
1890 #ifdef MACHINE_STRING
1891 len = strlen(banner_line);
1892 snprintf(banner_line+len, CMD_BUF_SIZE-len, " for %s", MACHINE_STRING);
1894 len = strlen(banner_line);
1895 snprintf(banner_line+len, CMD_BUF_SIZE-len, " (%s)", RELEASE_DATE);
1897 printf("%s\n", banner_line);
1900 /* ------------------------------------------------------------------------- */
1901 /* Input from the outside world */
1902 /* ------------------------------------------------------------------------- */
1905 static void read_command_line(int argc, char **argv)
1907 char buffer1[PATHLEN], buffer2[PATHLEN], buffer3[PATHLEN];
1909 printf("Source filename?\n> ");
1910 while (gets(buffer1)==NULL); cli_file1=buffer1;
1911 printf("Output filename (RETURN for the same)?\n> ");
1912 while (gets(buffer2)==NULL); cli_file2=buffer2;
1913 cli_files_specified=1;
1914 if (buffer2[0]!=0) cli_files_specified=2;
1916 { printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
1917 while (gets(buffer3)==NULL); execute_icl_command(buffer3);
1918 } while (buffer3[0]!=0);
1921 static void read_command_line(int argc, char **argv)
1923 if (argc==1) switches("-h",1);
1925 for (i=1, cli_files_specified=0; i<argc; i++)
1926 if (argv[i][0] == '-' && argv[i][1] == '-') {
1927 char *nextarg = NULL;
1929 if (i+1 < argc) nextarg = argv[i+1];
1930 consumed2 = execute_dashdash_command(argv[i]+2, nextarg);
1931 if (consumed2 && i+1 < argc) {
1935 else if (icl_command(argv[i])) {
1936 execute_icl_command(argv[i]);
1939 switch(++cli_files_specified)
1940 { case 1: cli_file1 = argv[i]; break;
1941 case 2: cli_file2 = argv[i]; break;
1943 printf("Command line error: unknown parameter '%s'\n",
1950 /* ------------------------------------------------------------------------- */
1951 /* M A I N : An outer shell for machine-specific quirks */
1952 /* Omitted altogether if EXTERNAL_SHELL is defined, as for instance is */
1953 /* needed for the Macintosh front end. */
1954 /* ------------------------------------------------------------------------- */
1956 #ifdef EXTERNAL_SHELL
1957 extern int sub_main(int argc, char **argv);
1960 static int sub_main(int argc, char **argv);
1962 int main(int argc, char **argv, char *envp[])
1964 int main(int argc, char **argv)
1968 InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
1970 rcode = sub_main(argc, argv);
1971 #ifdef ARC_THROWBACK
1979 /* ------------------------------------------------------------------------- */
1980 /* M A I N II: Starting up ICL with the command line */
1981 /* ------------------------------------------------------------------------- */
1983 #ifdef EXTERNAL_SHELL
1984 extern int sub_main(int argc, char **argv)
1986 static int sub_main(int argc, char **argv)
1991 ProcessEvents (&g_proc);
1994 if (store_the_text) my_free(&all_text,"transcription text");
1995 longjmp (g_fallback, 1);
2001 set_memory_sizes(); set_default_paths();
2002 reset_switch_settings(); select_version(5);
2004 cli_files_specified = 0; no_compilations = 0;
2005 cli_file1 = "source"; cli_file2 = "output";
2007 read_command_line(argc, argv);
2009 if (cli_files_specified > 0)
2010 { return_code = compile(cli_files_specified, cli_file1, cli_file2);
2012 if (return_code != 0) return(return_code);
2015 if (no_compilations == 0)
2016 printf("\n[No compilation requested]\n");
2017 if (no_compilations > 1)
2018 printf("[%d compilations completed]\n", no_compilations);
2023 /* ========================================================================= */